001package org.eclipse.aether.internal.impl.collect;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 * 
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 * 
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.util.Arrays;
023import java.util.Collections;
024import java.util.List;
025
026import org.eclipse.aether.artifact.Artifact;
027import org.eclipse.aether.graph.Dependency;
028import org.eclipse.aether.graph.DependencyCycle;
029import org.eclipse.aether.graph.DependencyNode;
030import org.eclipse.aether.util.artifact.ArtifactIdUtils;
031
032/**
033 * Default implementation of {@link DependencyCycle}.
034 * Internal helper class for collector implementations.
035 */
036public final class DefaultDependencyCycle
037    implements DependencyCycle
038{
039    private final List<Dependency> dependencies;
040
041    private final int cycleEntry;
042
043    public DefaultDependencyCycle( List<DependencyNode> nodes, int cycleEntry, Dependency dependency )
044    {
045        // skip root node unless it actually has a dependency or is considered the cycle entry (due to its label)
046        int offset = ( cycleEntry > 0 && nodes.get( 0 ).getDependency() == null ) ? 1 : 0;
047        Dependency[] dependencies = new Dependency[nodes.size() - offset + 1];
048        for ( int i = 0, n = dependencies.length - 1; i < n; i++ )
049        {
050            DependencyNode node = nodes.get( i + offset );
051            dependencies[i] = node.getDependency();
052            // when cycle starts at root artifact as opposed to root dependency, synthesize a dependency
053            if ( dependencies[i] == null )
054            {
055                dependencies[i] = new Dependency( node.getArtifact(), null );
056            }
057        }
058        dependencies[dependencies.length - 1] = dependency;
059        this.dependencies = Collections.unmodifiableList( Arrays.asList( dependencies ) );
060        this.cycleEntry = cycleEntry;
061    }
062
063    @Override
064    public List<Dependency> getPrecedingDependencies()
065    {
066        return dependencies.subList( 0, cycleEntry );
067    }
068
069    @Override
070    public List<Dependency> getCyclicDependencies()
071    {
072        return dependencies.subList( cycleEntry, dependencies.size() );
073    }
074
075    /**
076     * Searches for a node associated with the given artifact. A version of the artifact is not considered during the
077     * search.
078     *
079     * @param nodes a list representing single path in the dependency graph. First element is the root.
080     * @param artifact to find among the parent nodes.
081     * @return the index of the node furthest from the root and associated with the given artifact, or {@literal -1} if
082     * there is no such node.
083     */
084    public static int find( List<DependencyNode> nodes, Artifact artifact )
085    {
086
087        for ( int i = nodes.size() - 1; i >= 0; i-- )
088        {
089            DependencyNode node = nodes.get( i );
090
091            Artifact a = node.getArtifact();
092            if ( a == null )
093            {
094                break;
095            }
096
097            if ( !a.getArtifactId().equals( artifact.getArtifactId() ) )
098            {
099                continue;
100            }
101            if ( !a.getGroupId().equals( artifact.getGroupId() ) )
102            {
103                continue;
104            }
105            if ( !a.getExtension().equals( artifact.getExtension() ) )
106            {
107                continue;
108            }
109            if ( !a.getClassifier().equals( artifact.getClassifier() ) )
110            {
111                continue;
112            }
113            /*
114             * NOTE: While a:1 and a:2 are technically different artifacts, we want to consider the path a:2 -> b:2 ->
115             * a:1 a cycle in the current context. The artifacts themselves might not form a cycle but their producing
116             * projects surely do. Furthermore, conflict resolution will always have to consider a:1 a loser (otherwise
117             * its ancestor a:2 would get pruned and so would a:1) so there is no point in building the sub graph of
118             * a:1.
119             */
120
121            return i;
122        }
123
124        return -1;
125    }
126
127    @Override
128    public String toString()
129    {
130        StringBuilder buffer = new StringBuilder( 256 );
131        int i = 0;
132        for ( Dependency dependency : dependencies )
133        {
134            if ( i++ > 0 )
135            {
136                buffer.append( " -> " );
137            }
138            buffer.append( ArtifactIdUtils.toVersionlessId( dependency.getArtifact() ) );
139        }
140        return buffer.toString();
141    }
142
143}