Skip to content

Commit 80e0d60

Browse files
cstamasgnodet
andauthored
Make levelOrder the default visitor (apache#1593)
Apply same strategy to classpath collection as we apply to dependency collection ("nearest wins"). Co-authored-by: Guillaume Nodet <[email protected]>
1 parent 71c9124 commit 80e0d60

File tree

8 files changed

+149
-31
lines changed

8 files changed

+149
-31
lines changed

maven-resolver-api/src/main/java/org/eclipse/aether/ConfigurationProperties.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -515,16 +515,17 @@ public final class ConfigurationProperties {
515515
public static final String HTTPS_SECURITY_MODE_INSECURE = "insecure";
516516

517517
/**
518-
* A flag indicating which visitor should be used to "flatten" the dependency graph into list. Default is
519-
* same as in older resolver versions "preOrder", while it can accept values like "postOrder" and "levelOrder".
518+
* A flag indicating which visitor should be used to "flatten" the dependency graph into list. In Maven 4
519+
* the default is new "levelOrder", while Maven 3 used "preOrder". This property accepts values
520+
* "preOrder", "postOrder" and "levelOrder".
520521
*
521522
* @see #REPOSITORY_SYSTEM_DEPENDENCY_VISITOR_PREORDER
522523
* @see #REPOSITORY_SYSTEM_DEPENDENCY_VISITOR_POSTORDER
523524
* @see #REPOSITORY_SYSTEM_DEPENDENCY_VISITOR_LEVELORDER
524525
* @since 2.0.0
525526
* @configurationSource {@link RepositorySystemSession#getConfigProperties()}
526527
* @configurationType {@link java.lang.String}
527-
* @configurationDefaultValue {@link #REPOSITORY_SYSTEM_DEPENDENCY_VISITOR_PREORDER}
528+
* @configurationDefaultValue {@link #DEFAULT_REPOSITORY_SYSTEM_DEPENDENCY_VISITOR}
528529
* @configurationRepoIdSuffix No
529530
*/
530531
public static final String REPOSITORY_SYSTEM_DEPENDENCY_VISITOR = PREFIX_SYSTEM + "dependencyVisitor";
@@ -551,6 +552,14 @@ public final class ConfigurationProperties {
551552
*/
552553
public static final String REPOSITORY_SYSTEM_DEPENDENCY_VISITOR_LEVELORDER = "levelOrder";
553554

555+
/**
556+
* The default visitor strategy.
557+
*
558+
* @since 2.0.12
559+
*/
560+
public static final String DEFAULT_REPOSITORY_SYSTEM_DEPENDENCY_VISITOR =
561+
REPOSITORY_SYSTEM_DEPENDENCY_VISITOR_LEVELORDER;
562+
554563
/**
555564
* A flag indicating whether version scheme cache statistics should be printed on JVM shutdown.
556565
* This is useful for analyzing cache performance and effectiveness in development and testing scenarios.

maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@
9292
import org.eclipse.aether.spi.artifact.decorator.ArtifactDecoratorFactory;
9393
import org.eclipse.aether.spi.synccontext.SyncContextFactory;
9494
import org.eclipse.aether.util.ConfigUtils;
95-
import org.eclipse.aether.util.graph.visitor.FilteringDependencyVisitor;
9695
import org.eclipse.aether.util.graph.visitor.LevelOrderDependencyNodeConsumerVisitor;
9796
import org.eclipse.aether.util.graph.visitor.PostorderDependencyNodeConsumerVisitor;
9897
import org.eclipse.aether.util.graph.visitor.PreorderDependencyNodeConsumerVisitor;
@@ -328,27 +327,24 @@ private List<DependencyNode> doFlattenDependencyNodes(
328327
RepositorySystemSession session, DependencyNode root, DependencyFilter dependencyFilter) {
329328
final ArrayList<DependencyNode> dependencyNodes = new ArrayList<>();
330329
if (root != null) {
331-
DependencyVisitor builder = getDependencyVisitor(session, dependencyNodes::add);
332-
DependencyVisitor visitor =
333-
(dependencyFilter != null) ? new FilteringDependencyVisitor(builder, dependencyFilter) : builder;
334-
root.accept(visitor);
330+
root.accept(getDependencyVisitor(session, dependencyNodes::add, dependencyFilter));
335331
}
336332
return dependencyNodes;
337333
}
338334

339335
private DependencyVisitor getDependencyVisitor(
340-
RepositorySystemSession session, Consumer<DependencyNode> nodeConsumer) {
336+
RepositorySystemSession session, Consumer<DependencyNode> nodeConsumer, DependencyFilter dependencyFilter) {
341337
String strategy = ConfigUtils.getString(
342338
session,
343-
ConfigurationProperties.REPOSITORY_SYSTEM_DEPENDENCY_VISITOR_PREORDER,
339+
ConfigurationProperties.DEFAULT_REPOSITORY_SYSTEM_DEPENDENCY_VISITOR,
344340
ConfigurationProperties.REPOSITORY_SYSTEM_DEPENDENCY_VISITOR);
345341
switch (strategy) {
346342
case PreorderDependencyNodeConsumerVisitor.NAME:
347-
return new PreorderDependencyNodeConsumerVisitor(nodeConsumer);
343+
return new PreorderDependencyNodeConsumerVisitor(nodeConsumer, dependencyFilter);
348344
case PostorderDependencyNodeConsumerVisitor.NAME:
349-
return new PostorderDependencyNodeConsumerVisitor(nodeConsumer);
345+
return new PostorderDependencyNodeConsumerVisitor(nodeConsumer, dependencyFilter);
350346
case LevelOrderDependencyNodeConsumerVisitor.NAME:
351-
return new LevelOrderDependencyNodeConsumerVisitor(nodeConsumer);
347+
return new LevelOrderDependencyNodeConsumerVisitor(nodeConsumer, dependencyFilter);
352348
default:
353349
throw new IllegalArgumentException("Invalid dependency visitor strategy: " + strategy);
354350
}

maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/visitor/AbstractDependencyNodeConsumerVisitor.java

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,35 @@
2222
import java.util.Map;
2323
import java.util.function.Consumer;
2424

25+
import org.eclipse.aether.graph.DependencyFilter;
2526
import org.eclipse.aether.graph.DependencyNode;
2627
import org.eclipse.aether.graph.DependencyVisitor;
2728

2829
import static java.util.Objects.requireNonNull;
2930

3031
/**
3132
* Abstract base class for dependency tree traverses that feed {@link Consumer<DependencyNode>}.
33+
* <p>
34+
* <strong>Implementations derived from this class cannot be embedded into {@link FilteringDependencyVisitor}</strong>,
35+
* this is why these classes accept {@link DependencyFilter} in constructor instead.
3236
*
3337
* @since 2.0.0
3438
*/
3539
abstract class AbstractDependencyNodeConsumerVisitor implements DependencyVisitor {
36-
protected final Consumer<DependencyNode> nodeConsumer;
40+
private static final DependencyFilter ACCEPT_ALL = (d, p) -> true;
41+
42+
private final Consumer<DependencyNode> nodeConsumer;
43+
44+
private final DependencyFilter filter;
45+
46+
private final Stack<DependencyNode> path;
3747

3848
private final Map<DependencyNode, Object> visitedNodes;
3949

40-
protected AbstractDependencyNodeConsumerVisitor(Consumer<DependencyNode> nodeConsumer) {
50+
protected AbstractDependencyNodeConsumerVisitor(Consumer<DependencyNode> nodeConsumer, DependencyFilter filter) {
4151
this.nodeConsumer = requireNonNull(nodeConsumer);
52+
this.filter = filter == null ? ACCEPT_ALL : filter;
53+
this.path = new Stack<>();
4254
this.visitedNodes = new IdentityHashMap<>(512);
4355
}
4456

@@ -53,8 +65,26 @@ protected boolean setVisited(DependencyNode node) {
5365
}
5466

5567
@Override
56-
public abstract boolean visitEnter(DependencyNode node);
68+
public final boolean visitEnter(DependencyNode node) {
69+
path.push(node);
70+
return doVisitEnter(node);
71+
}
72+
73+
protected abstract boolean doVisitEnter(DependencyNode node);
5774

5875
@Override
59-
public abstract boolean visitLeave(DependencyNode node);
76+
public final boolean visitLeave(DependencyNode node) {
77+
path.pop();
78+
return doVisitLeave(node);
79+
}
80+
81+
protected abstract boolean doVisitLeave(DependencyNode node);
82+
83+
protected boolean acceptNode(DependencyNode node) {
84+
return filter.accept(node, path);
85+
}
86+
87+
protected void consumeNode(DependencyNode node) {
88+
nodeConsumer.accept(node);
89+
}
6090
}

maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/visitor/LevelOrderDependencyNodeConsumerVisitor.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,20 @@
2020

2121
import java.util.ArrayList;
2222
import java.util.HashMap;
23+
import java.util.List;
2324
import java.util.function.Consumer;
2425

2526
import org.eclipse.aether.ConfigurationProperties;
27+
import org.eclipse.aether.graph.DependencyFilter;
2628
import org.eclipse.aether.graph.DependencyNode;
2729

2830
/**
2931
* Processes dependency graph by traversing the graph in level order. This visitor visits each node exactly once
3032
* regardless how many paths within the dependency graph lead to the node such that the resulting node sequence is
3133
* free of duplicates.
34+
* <p>
35+
* <strong>Instances of this class cannot be embedded into {@link FilteringDependencyVisitor}</strong>, pass in the
36+
* filter {@link DependencyFilter} into constructor instead.
3237
*
3338
* @see NodeListGenerator
3439
* @since 2.0.0
@@ -45,30 +50,42 @@ public final class LevelOrderDependencyNodeConsumerVisitor extends AbstractDepen
4550
* Creates a new level order list generator.
4651
*/
4752
public LevelOrderDependencyNodeConsumerVisitor(Consumer<DependencyNode> nodeConsumer) {
48-
super(nodeConsumer);
53+
this(nodeConsumer, null);
54+
}
55+
56+
/**
57+
* Creates a new level order list generator with filter.
58+
*
59+
* @since 2.0.12
60+
*/
61+
public LevelOrderDependencyNodeConsumerVisitor(Consumer<DependencyNode> nodeConsumer, DependencyFilter filter) {
62+
super(nodeConsumer, filter);
4963
nodesPerLevel = new HashMap<>(16);
5064
visits = new Stack<>();
5165
}
5266

5367
@Override
54-
public boolean visitEnter(DependencyNode node) {
68+
protected boolean doVisitEnter(DependencyNode node) {
5569
boolean visited = !setVisited(node);
5670
visits.push(visited);
5771
if (!visited) {
58-
nodesPerLevel.computeIfAbsent(visits.size(), k -> new ArrayList<>()).add(node);
72+
List<DependencyNode> nodesOnLevel = nodesPerLevel.computeIfAbsent(visits.size(), k -> new ArrayList<>());
73+
if (acceptNode(node)) {
74+
nodesOnLevel.add(node);
75+
}
5976
}
6077
return !visited;
6178
}
6279

6380
@Override
64-
public boolean visitLeave(DependencyNode node) {
81+
protected boolean doVisitLeave(DependencyNode node) {
6582
Boolean visited = visits.pop();
6683
if (visited) {
6784
return true;
6885
}
6986
if (visits.isEmpty()) {
7087
for (int l = 1; nodesPerLevel.containsKey(l); l++) {
71-
nodesPerLevel.get(l).forEach(nodeConsumer);
88+
nodesPerLevel.get(l).forEach(this::consumeNode);
7289
}
7390
}
7491
return true;

maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/visitor/PostorderDependencyNodeConsumerVisitor.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,16 @@
2121
import java.util.function.Consumer;
2222

2323
import org.eclipse.aether.ConfigurationProperties;
24+
import org.eclipse.aether.graph.DependencyFilter;
2425
import org.eclipse.aether.graph.DependencyNode;
2526

2627
/**
2728
* Processes dependency graph by traversing the graph in postorder. This visitor visits each node exactly once
2829
* regardless how many paths within the dependency graph lead to the node such that the resulting node sequence is
2930
* free of duplicates.
31+
* <p>
32+
* <strong>Instances of this class cannot be embedded into {@link FilteringDependencyVisitor}</strong>, pass in the
33+
* filter {@link DependencyFilter} into constructor instead.
3034
*
3135
* @see NodeListGenerator
3236
* @since 2.0.0
@@ -41,24 +45,35 @@ public final class PostorderDependencyNodeConsumerVisitor extends AbstractDepend
4145
* Creates a new postorder list generator.
4246
*/
4347
public PostorderDependencyNodeConsumerVisitor(Consumer<DependencyNode> nodeConsumer) {
44-
super(nodeConsumer);
48+
this(nodeConsumer, null);
49+
}
50+
51+
/**
52+
* Creates a new postorder list generator.
53+
*
54+
* @since 2.0.12
55+
*/
56+
public PostorderDependencyNodeConsumerVisitor(Consumer<DependencyNode> nodeConsumer, DependencyFilter filter) {
57+
super(nodeConsumer, filter);
4558
visits = new Stack<>();
4659
}
4760

4861
@Override
49-
public boolean visitEnter(DependencyNode node) {
62+
protected boolean doVisitEnter(DependencyNode node) {
5063
boolean visited = !setVisited(node);
5164
visits.push(visited);
5265
return !visited;
5366
}
5467

5568
@Override
56-
public boolean visitLeave(DependencyNode node) {
69+
protected boolean doVisitLeave(DependencyNode node) {
5770
Boolean visited = visits.pop();
5871
if (visited) {
5972
return true;
6073
}
61-
nodeConsumer.accept(node);
74+
if (acceptNode(node)) {
75+
consumeNode(node);
76+
}
6277
return true;
6378
}
6479
}

maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/visitor/PreorderDependencyNodeConsumerVisitor.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,16 @@
2121
import java.util.function.Consumer;
2222

2323
import org.eclipse.aether.ConfigurationProperties;
24+
import org.eclipse.aether.graph.DependencyFilter;
2425
import org.eclipse.aether.graph.DependencyNode;
2526

2627
/**
2728
* Processes dependency graph by traversing the graph in preorder. This visitor visits each node exactly once
2829
* regardless how many paths within the dependency graph lead to the node such that the resulting node sequence is
2930
* free of duplicates.
31+
* <p>
32+
* <strong>Instances of this class cannot be embedded into {@link FilteringDependencyVisitor}</strong>, pass in the
33+
* filter {@link DependencyFilter} into constructor instead.
3034
*
3135
* @see NodeListGenerator
3236
* @since 2.0.0
@@ -39,20 +43,31 @@ public final class PreorderDependencyNodeConsumerVisitor extends AbstractDepende
3943
* Creates a new preorder list generator.
4044
*/
4145
public PreorderDependencyNodeConsumerVisitor(Consumer<DependencyNode> nodeConsumer) {
42-
super(nodeConsumer);
46+
this(nodeConsumer, null);
47+
}
48+
49+
/**
50+
* Creates a new preorder list generator.
51+
*
52+
* @since 2.0.12
53+
*/
54+
public PreorderDependencyNodeConsumerVisitor(Consumer<DependencyNode> nodeConsumer, DependencyFilter filter) {
55+
super(nodeConsumer, filter);
4356
}
4457

4558
@Override
46-
public boolean visitEnter(DependencyNode node) {
59+
protected boolean doVisitEnter(DependencyNode node) {
4760
if (!setVisited(node)) {
4861
return false;
4962
}
50-
nodeConsumer.accept(node);
63+
if (acceptNode(node)) {
64+
consumeNode(node);
65+
}
5166
return true;
5267
}
5368

5469
@Override
55-
public boolean visitLeave(DependencyNode node) {
70+
protected boolean doVisitLeave(DependencyNode node) {
5671
return true;
5772
}
5873
}

maven-resolver-util/src/test/java/org/eclipse/aether/util/graph/visitor/NodeListGeneratorTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,4 +218,40 @@ public boolean visitLeave(DependencyNode node) {
218218
assertEquals(3, nodeListGenerator.getFiles().size());
219219
assertEquals(fileNames, classPathNames);
220220
}
221+
222+
@Test
223+
void testPreOrderDuplicateSuppressionWithFilteringSimple() throws Exception {
224+
DependencyNode root = parse("simple.txt");
225+
226+
NodeListGenerator nodeListGenerator = new NodeListGenerator();
227+
PreorderDependencyNodeConsumerVisitor visitor = new PreorderDependencyNodeConsumerVisitor(
228+
nodeListGenerator, (d, p) -> !"a".equals(d.getArtifact().getArtifactId()));
229+
root.accept(visitor);
230+
231+
assertSequence(nodeListGenerator.getNodes(), "b", "c", "d", "e");
232+
}
233+
234+
@Test
235+
void testPostOrderDuplicateSuppressionWithFilteringSimple() throws Exception {
236+
DependencyNode root = parse("simple.txt");
237+
238+
NodeListGenerator nodeListGenerator = new NodeListGenerator();
239+
PostorderDependencyNodeConsumerVisitor visitor = new PostorderDependencyNodeConsumerVisitor(
240+
nodeListGenerator, (d, p) -> !"a".equals(d.getArtifact().getArtifactId()));
241+
root.accept(visitor);
242+
243+
assertSequence(nodeListGenerator.getNodes(), "c", "b", "e", "d");
244+
}
245+
246+
@Test
247+
void testLevelOrderDuplicateSuppressionWithFilteringSimple() throws Exception {
248+
DependencyNode root = parse("simple.txt");
249+
250+
NodeListGenerator nodeListGenerator = new NodeListGenerator();
251+
LevelOrderDependencyNodeConsumerVisitor visitor = new LevelOrderDependencyNodeConsumerVisitor(
252+
nodeListGenerator, (d, p) -> !"a".equals(d.getArtifact().getArtifactId()));
253+
root.accept(visitor);
254+
255+
assertSequence(nodeListGenerator.getNodes(), "b", "d", "c", "e");
256+
}
221257
}

src/site/markdown/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ To modify this file, edit the template and regenerate.
116116
| `"aether.syncContext.named.retry.wait"` | `Long` | The amount of milliseconds to wait between retries on time-out. | `200l` | 1.7.0 | No | Session Configuration |
117117
| `"aether.syncContext.named.time"` | `Long` | The maximum of time amount to be blocked to obtain lock. | `30l` | 1.7.0 | No | Session Configuration |
118118
| `"aether.syncContext.named.time.unit"` | `String` | The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names. | `"SECONDS"` | 1.7.0 | No | Session Configuration |
119-
| `"aether.system.dependencyVisitor"` | `String` | A flag indicating which visitor should be used to "flatten" the dependency graph into list. Default is same as in older resolver versions "preOrder", while it can accept values like "postOrder" and "levelOrder". | `"preOrder"` | 2.0.0 | No | Session Configuration |
119+
| `"aether.system.dependencyVisitor"` | `String` | A flag indicating which visitor should be used to "flatten" the dependency graph into list. In Maven 4 the default is new "levelOrder", while Maven 3 used "preOrder". This property accepts values "preOrder", "postOrder" and "levelOrder". | `"levelOrder"` | 2.0.0 | No | Session Configuration |
120120
| `"aether.transport.apache.followRedirects"` | `Boolean` | If enabled, Apache HttpClient will follow HTTP redirects. | `true` | 2.0.2 | Yes | Session Configuration |
121121
| `"aether.transport.apache.https.cipherSuites"` | `String` | Comma-separated list of <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#ciphersuites">Cipher Suites</a> which are enabled for HTTPS connections. | - | 2.0.0 | No | Session Configuration |
122122
| `"aether.transport.apache.https.protocols"` | `String` | Comma-separated list of <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#jssenames">Protocols </a> which are enabled for HTTPS connections. | - | 2.0.0 | No | Session Configuration |

0 commit comments

Comments
 (0)