1 package org.eclipse.aether.internal.impl.collect;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import java.util.Arrays;
23 import java.util.Collections;
24 import java.util.List;
25
26 import org.eclipse.aether.artifact.Artifact;
27 import org.eclipse.aether.graph.Dependency;
28 import org.eclipse.aether.graph.DependencyCycle;
29 import org.eclipse.aether.graph.DependencyNode;
30 import org.eclipse.aether.util.artifact.ArtifactIdUtils;
31
32 /**
33 * Default implementation of {@link DependencyCycle}.
34 * Internal helper class for collector implementations.
35 */
36 public final class DefaultDependencyCycle
37 implements DependencyCycle
38 {
39 private final List<Dependency> dependencies;
40
41 private final int cycleEntry;
42
43 public DefaultDependencyCycle( List<DependencyNode> nodes, int cycleEntry, Dependency dependency )
44 {
45 // skip root node unless it actually has a dependency or is considered the cycle entry (due to its label)
46 int offset = ( cycleEntry > 0 && nodes.get( 0 ).getDependency() == null ) ? 1 : 0;
47 Dependency[] dependencies = new Dependency[nodes.size() - offset + 1];
48 for ( int i = 0, n = dependencies.length - 1; i < n; i++ )
49 {
50 DependencyNode node = nodes.get( i + offset );
51 dependencies[i] = node.getDependency();
52 // when cycle starts at root artifact as opposed to root dependency, synthesize a dependency
53 if ( dependencies[i] == null )
54 {
55 dependencies[i] = new Dependency( node.getArtifact(), null );
56 }
57 }
58 dependencies[dependencies.length - 1] = dependency;
59 this.dependencies = Collections.unmodifiableList( Arrays.asList( dependencies ) );
60 this.cycleEntry = cycleEntry;
61 }
62
63 @Override
64 public List<Dependency> getPrecedingDependencies()
65 {
66 return dependencies.subList( 0, cycleEntry );
67 }
68
69 @Override
70 public List<Dependency> getCyclicDependencies()
71 {
72 return dependencies.subList( cycleEntry, dependencies.size() );
73 }
74
75 /**
76 * Searches for a node associated with the given artifact. A version of the artifact is not considered during the
77 * search.
78 *
79 * @param nodes a list representing single path in the dependency graph. First element is the root.
80 * @param artifact to find among the parent nodes.
81 * @return the index of the node furthest from the root and associated with the given artifact, or {@literal -1} if
82 * there is no such node.
83 */
84 public static int find( List<DependencyNode> nodes, Artifact artifact )
85 {
86
87 for ( int i = nodes.size() - 1; i >= 0; i-- )
88 {
89 DependencyNode node = nodes.get( i );
90
91 Artifact a = node.getArtifact();
92 if ( a == null )
93 {
94 break;
95 }
96
97 if ( !a.getArtifactId().equals( artifact.getArtifactId() ) )
98 {
99 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 }