Add functionality to collect raw dependencies in Maven 3+
- Added functionality to collect the node tree of project dependencies.
- Adapted some Eclipse Aether classes to work with Sonatype Aether
classes.
- Implemented a collector plugin using the new functionality to run ITs.
diff --git a/pom.xml b/pom.xml
index 748c00e..1b789d5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -141,6 +141,12 @@
</dependency>
<dependency>
+ <groupId>org.apache.maven.shared</groupId>
+ <artifactId>maven-dependency-tree</artifactId>
+ <version>3.0.1</version>
+ </dependency>
+
+ <dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-component-annotations</artifactId>
</dependency>
diff --git a/src/it/maven-dependency-collector-plugin/pom.xml b/src/it/maven-dependency-collector-plugin/pom.xml
new file mode 100644
index 0000000..78ad7e1
--- /dev/null
+++ b/src/it/maven-dependency-collector-plugin/pom.xml
@@ -0,0 +1,130 @@
+<?xml version='1.0' encoding='UTF-8'?>
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+>
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-plugins</artifactId>
+ <version>31</version>
+ <relativePath />
+ </parent>
+
+ <artifactId>maven-dependency-collector-plugin</artifactId>
+ <version>1.0.0</version>
+ <packaging>maven-plugin</packaging>
+
+ <name>Apache Maven Dependency Collector Plugin</name>
+ <description>The plugin is only intended as a real testing environment for parts
+ of the maven-artifact-transfer component. In this test we check the DependencyCollector</description>
+ <prerequisites>
+ <maven>${mavenVersion}</maven>
+ </prerequisites>
+
+ <properties>
+ <surefire.version>2.21.0</surefire.version>
+ <mavenVersion>3.0</mavenVersion>
+ <javaVersion>8</javaVersion>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-plugin-api</artifactId>
+ <version>${mavenVersion}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-artifact</artifactId>
+ <version>${mavenVersion}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.shared</groupId>
+ <artifactId>maven-artifact-transfer</artifactId>
+ <version>@project.version@</version>
+ </dependency>
+
+ <!-- dependencies to annotations -->
+ <dependency>
+ <groupId>org.apache.maven.plugin-tools</groupId>
+ <artifactId>maven-plugin-annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.takari.maven.plugins</groupId>
+ <artifactId>takari-plugin-integration-testing</artifactId>
+ <version>2.9.2</version>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.takari.maven.plugins</groupId>
+ <artifactId>takari-plugin-testing</artifactId>
+ <version>2.9.2</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.rat</groupId>
+ <artifactId>apache-rat-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemProperties>
+ <maven.local.repo>${maven.local.repo}</maven.local.repo>
+ <localRepositoryPath>${localRepositoryPath}</localRepositoryPath>
+ <mvnVersion>${mvnVersion}</mvnVersion>
+ </systemProperties>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>io.takari.maven.plugins</groupId>
+ <artifactId>takari-lifecycle-plugin</artifactId>
+ <version>1.13.4</version>
+ <executions>
+ <execution>
+ <?m2e ignore ?>
+ <id>testProperties</id>
+ <phase>process-test-resources</phase>
+ <goals>
+ <goal>testProperties</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+
+ </build>
+</project>
diff --git a/src/it/maven-dependency-collector-plugin/src/main/java/org/apache/maven/plugin/dependency/collector/DependencyCollectorMojo.java b/src/it/maven-dependency-collector-plugin/src/main/java/org/apache/maven/plugin/dependency/collector/DependencyCollectorMojo.java
new file mode 100644
index 0000000..45ab470
--- /dev/null
+++ b/src/it/maven-dependency-collector-plugin/src/main/java/org/apache/maven/plugin/dependency/collector/DependencyCollectorMojo.java
@@ -0,0 +1,110 @@
+package org.apache.maven.plugin.dependency.collector;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.DefaultProjectBuildingRequest;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.ProjectBuildingRequest;
+import org.apache.maven.shared.dependency.graph.DependencyNode;
+import org.apache.maven.shared.dependency.graph.traversal.SerializingDependencyNodeVisitor;
+import org.apache.maven.shared.transfer.dependencies.collect.CollectorResult;
+import org.apache.maven.shared.transfer.dependencies.collect.DependencyCollector;
+import org.apache.maven.shared.transfer.dependencies.collect.DependencyCollectorException;
+import org.apache.maven.shared.transfer.project.NoFileAssignedException;
+import org.apache.maven.shared.transfer.project.deploy.ProjectDeployer;
+import org.apache.maven.shared.transfer.project.deploy.ProjectDeployerRequest;
+import org.apache.maven.shared.transfer.project.install.ProjectInstaller;
+
+/**
+ * This mojo is implemented to test the {@link DependencyCollector} part of the maven-artifact-transfer shared component.
+ *
+ * @author Gabriel Belingueres
+ */
+@Mojo( name = "dependency-collector", defaultPhase = LifecyclePhase.VALIDATE, threadSafe = true )
+public class DependencyCollectorMojo
+ extends AbstractMojo
+{
+
+ /**
+ * Parameter to have different locations for each Maven version we are testing with.
+ */
+ @Parameter
+ private String mvnVersion;
+
+ @Parameter( defaultValue = "${session}", required = true, readonly = true )
+ protected MavenSession session;
+
+ @Parameter( defaultValue = "${project}", required = true, readonly = true )
+ protected MavenProject project;
+
+ @Component
+ private DependencyCollector dependencyCollector;
+
+ public void execute()
+ throws MojoExecutionException, MojoFailureException
+ {
+ getLog().info( "Hello from dependency-collector plugin" );
+
+ ProjectBuildingRequest buildingRequest =
+ new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() );
+ buildingRequest.setProject( project );
+
+ collectDependencies( buildingRequest );
+ getLog().info( "Bye bye from dependency-collector plugin" );
+ }
+
+ private void collectDependencies( ProjectBuildingRequest buildingRequest ) throws MojoExecutionException
+ {
+ try
+ {
+ CollectorResult result = dependencyCollector.collectDependenciesGraph( buildingRequest );
+ DependencyNode root = result.getDependencyGraphRoot();
+
+ StringWriter writer = new StringWriter();
+ SerializingDependencyNodeVisitor visitor = new SerializingDependencyNodeVisitor( writer );
+ root.accept( visitor );
+
+ getLog().info( writer.toString() );
+ }
+ catch ( DependencyCollectorException e )
+ {
+ throw new MojoExecutionException( "DependencyCollectorException", e );
+ }
+ }
+
+}
diff --git a/src/it/maven-dependency-collector-plugin/src/test/java/org/apache/maven/plugin/dependency/collector/DependencyCollectorTest.java b/src/it/maven-dependency-collector-plugin/src/test/java/org/apache/maven/plugin/dependency/collector/DependencyCollectorTest.java
new file mode 100644
index 0000000..9688de5
--- /dev/null
+++ b/src/it/maven-dependency-collector-plugin/src/test/java/org/apache/maven/plugin/dependency/collector/DependencyCollectorTest.java
@@ -0,0 +1,415 @@
+package org.apache.maven.plugin.dependency.collector;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.codehaus.plexus.util.FileUtils;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+import io.takari.maven.testing.TestResources;
+import io.takari.maven.testing.executor.MavenExecution;
+import io.takari.maven.testing.executor.MavenExecutionResult;
+import io.takari.maven.testing.executor.MavenRuntime;
+import io.takari.maven.testing.executor.MavenRuntime.MavenRuntimeBuilder;
+import io.takari.maven.testing.executor.MavenVersions;
+import io.takari.maven.testing.executor.junit.MavenJUnitTestRunner;
+
+/**
+ * This will check if the DependencyCollector works for all Maven versions 3.0.5, 3.1.1, 3.2.5, 3.3.1, 3.3.9, 3.5.0, 3.5.2,
+ * 3.5.3, 3.5.4, 3.6.0. This is done by using the test plugin <code>maven-dependency-collector-plugin</code> which uses the
+ * DependencyCollector as component. By using this way we get a real runtime environment which supports all Maven versions.
+ *
+ * @author Gabriel Belingueres
+ */
+@RunWith( MavenJUnitTestRunner.class )
+@MavenVersions( {
+ // (Maven version) <-- uses (Aether version)
+ // test ONLY with the most recent Maven versions that make use of an specific Aether version.
+ "3.0", // <-- Sonatype Aether 1.7
+ "3.0.1", // <-- Sonatype Aether 1.8
+ "3.0.2", // <-- Sonatype Aether 1.9
+ "3.0.3", // <-- Sonatype Aether 1.11
+// "3.0.4", // <-- Sonatype Aether 1.13.1
+ "3.0.5", // <-- Sonatype Aether 1.13.1
+
+// "3.1.0", // <-- Eclipse Aether 0.9.0M2
+ "3.1.1", // <-- Eclipse Aether 0.9.0M2
+
+// "3.2.1", // <-- Eclipse Aether 0.9.0M2
+// "3.2.2", // <-- Eclipse Aether 0.9.0M2
+ "3.2.3", // <-- Eclipse Aether 0.9.0M2
+ "3.2.5", // <-- Eclipse Aether 1.0.0.v20140518
+
+// "3.3.1", // <-- Eclipse Aether 1.0.2.v20150114
+// "3.3.3", // <-- Eclipse Aether 1.0.2.v20150114
+ "3.3.9", // <-- Eclipse Aether 1.0.2.v20150114
+
+ "3.5.0", // <-- Maven Resolver 1.0.3
+ "3.5.2", // <-- Maven Resolver 1.1.0
+// "3.5.3", // <-- Maven Resolver 1.1.1
+ "3.5.4", // <-- Maven Resolver 1.1.1
+ "3.6.0", // <-- Maven Resolver 1.3.1
+ "3.6.1", // <-- Maven Resolver 1.3.3
+ "3.6.2" // <-- Maven Resolver 1.4.1
+} )
+public class DependencyCollectorTest
+{
+
+ private static final String LS = System.lineSeparator();
+
+ private static boolean testDependenciesInstalled = false;
+
+ @Rule
+ public final TestResources resources = new TestResources();
+
+ /**
+ * Relates test method name with the project to test below "projects" directory.
+ */
+ @Rule
+ public final TestName testName = new TestName();
+
+ public final MavenRuntime mavenRuntime;
+
+ private File basedir;
+
+ private MavenExecution mavenExecution;
+
+ public DependencyCollectorTest( MavenRuntimeBuilder builder )
+ throws Exception
+ {
+ this.mavenRuntime = builder.build();
+ }
+
+ @Before
+ public void setUp()
+ throws Exception
+ {
+ installMockDependencies();
+
+ String testMethodName = removeMavenVersion( testName.getMethodName() );
+
+ basedir = resources.getBasedir( testMethodName );
+
+ //@formatter:off
+ mavenExecution = mavenRuntime
+ .forProject( basedir )
+ .withCliOption( "-DmvnVersion=" + mavenRuntime.getMavenVersion() ) // Might be superfluous
+ .withCliOption( "-B" )
+ .withCliOption( "-V" );
+ //@formatter:on
+ }
+
+ /**
+ * Test method cames in the form "testMethod>[version]", so remove the method name only.
+ * @param methodWithMavenVersion the JUnit test method.
+ * @return the method without the maven version.
+ */
+ private String removeMavenVersion( String methodWithMavenVersion )
+ {
+ int index = methodWithMavenVersion.indexOf( '[' );
+ return methodWithMavenVersion.substring( 0, index );
+ }
+
+ /**
+ * Install dependencies used for testing.
+ *
+ * workaround to install the dependencies used in the tests, since
+ * mavenRuntime is not static and it is required to install them and
+ * it needs to execute just once.
+ * TODO: improve this or find a solution to use mrm-maven-plugin with
+ * takari
+ *
+ * @throws Exception if anything goes wrong.
+ */
+ private void installMockDependencies()
+ throws Exception
+ {
+ if ( testDependenciesInstalled )
+ return;
+
+ File basedir = resources.getBasedir( "mockDependencies" );
+ //@formatter:off
+ MavenExecutionResult result =
+ mavenRuntime
+ .forProject( basedir )
+ .withCliOption( "-DmvnVersion=" + mavenRuntime.getMavenVersion() ) // Might be superfluous
+ .withCliOption( "-B" )
+ .withCliOption( "-V" )
+ // We use verify to prevent running maven-install-plugin.
+ .execute( "clean", "install" );
+ //@formatter:on
+
+ result.assertErrorFreeLog();
+
+ testDependenciesInstalled = true;
+ }
+
+ /**
+ * collect dependencies, not informing the test dependencies from transitive dependencies.
+ * @throws Exception if anything goes wrong.
+ */
+ @Test
+ public void noTransitiveTestDep()
+ throws Exception
+ {
+ MavenExecutionResult result = mavenExecution.execute( "clean", "validate" );
+
+ result.assertErrorFreeLog();
+
+ // Check that the current plugins has been called at least once.
+ result.assertLogText( "[INFO] --- maven-dependency-collector-plugin:1.0.0:dependency-collector (id-dependency-collector) @ maven-dependency-collector-plugin-it ---" );
+
+ File logFile = new File(basedir, "log.txt");
+ String strLog = FileUtils.fileRead( logFile );
+
+ String expected =
+ "PROJECT-DEPENDENCY-COLLECTOR:maven-dependency-collector-plugin-it:jar:1.0.0-A:" + LS +
+ " org.apache.maven.plugin.dependency.collector.its:a:jar:1.0:compile" + LS +
+ " org.apache.maven.plugin.dependency.collector.its:u:jar:1.0:test" + LS +
+ LS;
+
+ assertTrue( strLog.contains( expected ) );
+ }
+
+ /**
+ * collect dependencies, but don't inform in the tree those dependencies that are already shown in
+ * a a level closer to the root.
+ * @throws Exception if anything goes wrong.
+ */
+ @Test
+ public void noRepeatedDeps()
+ throws Exception
+ {
+ MavenExecutionResult result = mavenExecution.execute( "clean", "validate" );
+
+ result.assertErrorFreeLog();
+
+ // Check that the current plugins has been called at least once.
+ result.assertLogText( "[INFO] --- maven-dependency-collector-plugin:1.0.0:dependency-collector (id-dependency-collector) @ maven-dependency-collector-plugin-it ---" );
+
+ File logFile = new File(basedir, "log.txt");
+ String strLog = FileUtils.fileRead( logFile );
+
+ String expected =
+ "PROJECT-DEPENDENCY-COLLECTOR:maven-dependency-collector-plugin-it:jar:1.0.0-A:" + LS +
+ " org.apache.maven.plugin.dependency.collector.its:c:jar:1.0:compile" + LS +
+ " org.apache.maven.plugin.dependency.collector.its:b:jar:1.0:compile" + LS +
+ " org.apache.maven.plugin.dependency.collector.its:b:jar:1.0:compile" + LS +
+ " org.apache.maven.plugin.dependency.collector.its:a:jar:1.0:compile" + LS +
+ LS;
+
+ assertTrue( strLog.contains( expected ) );
+ }
+
+ /**
+ * collect test dependencies, and its respective compile scope dependencies are informed as "test" scope
+ * for the project.
+ * @throws Exception if anything goes wrong.
+ */
+ @Test
+ public void transitiveTestDeps()
+ throws Exception
+ {
+ MavenExecutionResult result = mavenExecution.execute( "clean", "validate" );
+
+ result.assertErrorFreeLog();
+
+ // Check that the current plugins has been called at least once.
+ result.assertLogText( "[INFO] --- maven-dependency-collector-plugin:1.0.0:dependency-collector (id-dependency-collector) @ maven-dependency-collector-plugin-it ---" );
+
+ File logFile = new File(basedir, "log.txt");
+ String strLog = FileUtils.fileRead( logFile );
+
+ String expected =
+ "PROJECT-DEPENDENCY-COLLECTOR:maven-dependency-collector-plugin-it:jar:1.0.0-A:" + LS +
+ " org.apache.maven.plugin.dependency.collector.its:t:jar:1.1:test" + LS +
+ " org.apache.maven.plugin.dependency.collector.its:u:jar:1.0:test" + LS +
+ LS;
+
+ assertTrue( strLog.contains( expected ) );
+ }
+
+ /**
+ * collect dependencies, and inform when dependencyManagement supersedes
+ * the declared (premanaged) version and scope.
+ * @throws Exception if anything goes wrong.
+ */
+ @Test
+ public void managedDepsAndScope()
+ throws Exception
+ {
+ MavenExecutionResult result = mavenExecution.execute( "clean", "validate" );
+
+ result.assertErrorFreeLog();
+
+ // Check that the current plugins has been called at least once.
+ result.assertLogText( "[INFO] --- maven-dependency-collector-plugin:1.0.0:dependency-collector (id-dependency-collector) @ maven-dependency-collector-plugin-it ---" );
+
+ File logFile = new File(basedir, "log.txt");
+ String strLog = FileUtils.fileRead( logFile );
+
+ String expected =
+ "PROJECT-DEPENDENCY-COLLECTOR:maven-dependency-collector-plugin-it:jar:1.0.0-A:" + LS +
+ " org.apache.maven.plugin.dependency.collector.its:t:jar:1.1:compile" + LS +
+ " org.apache.maven.plugin.dependency.collector.its:u:jar:1.1:test (version managed from 1.0; scope managed from compile)" + LS +
+ LS;
+
+ assertTrue( strLog.contains( expected ) );
+ }
+
+ /**
+ * collect dependencies, and inform when a dependency is optional.
+ * NOTE: Maven 3.0.x and 3.1+ behave differently.
+ * @throws Exception if anything goes wrong.
+ */
+ @Test
+ public void optionalDep()
+ throws Exception
+ {
+ MavenExecutionResult result = mavenExecution.execute( "clean", "validate" );
+
+ result.assertErrorFreeLog();
+
+ // Check that the current plugins has been called at least once.
+ result.assertLogText( "[INFO] --- maven-dependency-collector-plugin:1.0.0:dependency-collector (id-dependency-collector) @ maven-dependency-collector-plugin-it ---" );
+
+ File logFile = new File(basedir, "log.txt");
+ String strLog = FileUtils.fileRead( logFile );
+
+ String expected;
+ if ( isAtLeastMaven31() )
+ {
+ expected =
+ "PROJECT-DEPENDENCY-COLLECTOR:maven-dependency-collector-plugin-it:jar:1.0.0-A:" + LS +
+ " org.apache.maven.plugin.dependency.collector.its:c:jar:1.0:runtime (optional) " + LS +
+ " org.apache.maven.plugin.dependency.collector.its:b:jar:1.0:runtime (optional) " + LS +
+ " org.apache.maven.plugin.dependency.collector.its:a:jar:1.0:runtime (optional) " + LS +
+ LS;
+ }
+ else
+ {
+ // Maven 3.0.x
+ expected =
+ "PROJECT-DEPENDENCY-COLLECTOR:maven-dependency-collector-plugin-it:jar:1.0.0-A:" + LS +
+ " org.apache.maven.plugin.dependency.collector.its:c:jar:1.0:runtime (optional) " + LS +
+ " org.apache.maven.plugin.dependency.collector.its:b:jar:1.0:runtime" + LS +
+ " org.apache.maven.plugin.dependency.collector.its:a:jar:1.0:runtime" + LS +
+ LS;
+ }
+
+ assertTrue( strLog.contains( expected ) );
+ }
+
+ /**
+ * collect dependencies, and not inform the transitive optional dependencies.
+ * @throws Exception if anything goes wrong.
+ */
+ @Test
+ public void noTransitiveOptionalDep()
+ throws Exception
+ {
+ MavenExecutionResult result = mavenExecution.execute( "clean", "validate" );
+
+ result.assertErrorFreeLog();
+
+ // Check that the current plugins has been called at least once.
+ result.assertLogText( "[INFO] --- maven-dependency-collector-plugin:1.0.0:dependency-collector (id-dependency-collector) @ maven-dependency-collector-plugin-it ---" );
+
+ File logFile = new File(basedir, "log.txt");
+ String strLog = FileUtils.fileRead( logFile );
+
+ String expected =
+ "PROJECT-DEPENDENCY-COLLECTOR:maven-dependency-collector-plugin-it:jar:1.0.0-A:" + LS +
+ " org.apache.maven.plugin.dependency.collector.its:d:jar:1.0:compile" + LS +
+ LS;
+
+ assertTrue( strLog.contains( expected ) );
+ }
+
+ /**
+ * collect dependencies, and inform when a dependency version is selected from a range.
+ * @throws Exception if anything goes wrong.
+ */
+ @Test
+ public void versionConstraintDep()
+ throws Exception
+ {
+ MavenExecutionResult result = mavenExecution.execute( "clean", "validate" );
+
+ result.assertErrorFreeLog();
+
+ // Check that the current plugins has been called at least once.
+ result.assertLogText( "[INFO] --- maven-dependency-collector-plugin:1.0.0:dependency-collector (id-dependency-collector) @ maven-dependency-collector-plugin-it ---" );
+
+ File logFile = new File(basedir, "log.txt");
+ String strLog = FileUtils.fileRead( logFile );
+
+ String expected =
+ "PROJECT-DEPENDENCY-COLLECTOR:maven-dependency-collector-plugin-it:jar:1.0.0-A:" + LS +
+ " org.apache.maven.plugin.dependency.collector.its:t:jar:1.1:compile (version selected from constraint [1.1,))" + LS +
+ " org.apache.maven.plugin.dependency.collector.its:u:jar:1.0:compile" + LS +
+ LS;
+
+ assertTrue( strLog.contains( expected ) );
+ }
+
+ /**
+ * collect dependencies, and not inform when a dependency is excluded.
+ * @throws Exception if anything goes wrong.
+ */
+ @Test
+ public void noExcludedDep()
+ throws Exception
+ {
+ MavenExecutionResult result = mavenExecution.execute( "clean", "validate" );
+
+ result.assertErrorFreeLog();
+
+ // Check that the current plugins has been called at least once.
+ result.assertLogText( "[INFO] --- maven-dependency-collector-plugin:1.0.0:dependency-collector (id-dependency-collector) @ maven-dependency-collector-plugin-it ---" );
+
+ File logFile = new File(basedir, "log.txt");
+ String strLog = FileUtils.fileRead( logFile );
+
+ String expected =
+ "PROJECT-DEPENDENCY-COLLECTOR:maven-dependency-collector-plugin-it:jar:1.0.0-A:" + LS +
+ " org.apache.maven.plugin.dependency.collector.its:c:jar:1.0:compile" + LS +
+ " org.apache.maven.plugin.dependency.collector.its:b:jar:1.0:compile" + LS +
+ " org.apache.maven.plugin.dependency.collector.its:b:jar:1.0:compile" + LS +
+ LS;
+
+ assertTrue( strLog.contains( expected ) );
+ }
+
+ private boolean isAtLeastMaven31()
+ throws Exception
+ {
+ return "3.1".compareTo( mavenRuntime.getMavenVersion() ) <= 0;
+ }
+}
\ No newline at end of file
diff --git a/src/it/maven-dependency-collector-plugin/src/test/projects/managedDepsAndScope/pom.xml b/src/it/maven-dependency-collector-plugin/src/test/projects/managedDepsAndScope/pom.xml
new file mode 100644
index 0000000..06e17b6
--- /dev/null
+++ b/src/it/maven-dependency-collector-plugin/src/test/projects/managedDepsAndScope/pom.xml
@@ -0,0 +1,68 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>PROJECT-DEPENDENCY-COLLECTOR</groupId>
+ <artifactId>maven-dependency-collector-plugin-it</artifactId>
+ <version>1.0.0-A</version>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>u</artifactId>
+ <version>1.1</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>t</artifactId>
+ <version>1.1</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-collector-plugin</artifactId>
+ <version>${it-plugin.version}</version>
+ <configuration>
+ <localRepositoryPath>${localRepositoryPath}</localRepositoryPath>
+ <mvnVersion>${mvnVersion}</mvnVersion>
+ </configuration>
+ <executions>
+ <execution>
+ <id>id-dependency-collector</id>
+ <goals>
+ <goal>dependency-collector</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/a10/pom.xml b/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/a10/pom.xml
new file mode 100644
index 0000000..d0d62cf
--- /dev/null
+++ b/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/a10/pom.xml
@@ -0,0 +1,34 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>a</artifactId>
+ <version>1.0</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>t</artifactId>
+ <version>1.0</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
diff --git a/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/b10/pom.xml b/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/b10/pom.xml
new file mode 100644
index 0000000..5c026c5
--- /dev/null
+++ b/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/b10/pom.xml
@@ -0,0 +1,33 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>b</artifactId>
+ <version>1.0</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>a</artifactId>
+ <version>1.0</version>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
diff --git a/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/c10/pom.xml b/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/c10/pom.xml
new file mode 100644
index 0000000..9049b83
--- /dev/null
+++ b/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/c10/pom.xml
@@ -0,0 +1,33 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>c</artifactId>
+ <version>1.0</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>b</artifactId>
+ <version>1.0</version>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
diff --git a/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/d10/pom.xml b/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/d10/pom.xml
new file mode 100644
index 0000000..0b20d79
--- /dev/null
+++ b/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/d10/pom.xml
@@ -0,0 +1,34 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>d</artifactId>
+ <version>1.0</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>c</artifactId>
+ <version>1.0</version>
+ <optional>true</optional>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
diff --git a/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/pom.xml b/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/pom.xml
new file mode 100644
index 0000000..f9f6abb
--- /dev/null
+++ b/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/pom.xml
@@ -0,0 +1,37 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>all-dependencies-project</artifactId>
+ <version>1.0</version>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>u10</module>
+ <module>u11</module>
+ <module>t10</module>
+ <module>t11</module>
+ <module>a10</module>
+ <module>b10</module>
+ <module>c10</module>
+ <module>d10</module>
+ </modules>
+</project>
\ No newline at end of file
diff --git a/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/t10/pom.xml b/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/t10/pom.xml
new file mode 100644
index 0000000..46221db
--- /dev/null
+++ b/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/t10/pom.xml
@@ -0,0 +1,25 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>t</artifactId>
+ <version>1.0</version>
+</project>
\ No newline at end of file
diff --git a/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/t11/pom.xml b/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/t11/pom.xml
new file mode 100644
index 0000000..c7d8e8d
--- /dev/null
+++ b/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/t11/pom.xml
@@ -0,0 +1,33 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>t</artifactId>
+ <version>1.1</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>u</artifactId>
+ <version>1.0</version>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
diff --git a/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/u10/pom.xml b/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/u10/pom.xml
new file mode 100644
index 0000000..d43b1da
--- /dev/null
+++ b/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/u10/pom.xml
@@ -0,0 +1,25 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>u</artifactId>
+ <version>1.0</version>
+</project>
\ No newline at end of file
diff --git a/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/u11/pom.xml b/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/u11/pom.xml
new file mode 100644
index 0000000..8e08eee
--- /dev/null
+++ b/src/it/maven-dependency-collector-plugin/src/test/projects/mockDependencies/u11/pom.xml
@@ -0,0 +1,25 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>u</artifactId>
+ <version>1.1</version>
+</project>
\ No newline at end of file
diff --git a/src/it/maven-dependency-collector-plugin/src/test/projects/noExcludedDep/pom.xml b/src/it/maven-dependency-collector-plugin/src/test/projects/noExcludedDep/pom.xml
new file mode 100644
index 0000000..9d905d9
--- /dev/null
+++ b/src/it/maven-dependency-collector-plugin/src/test/projects/noExcludedDep/pom.xml
@@ -0,0 +1,68 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>PROJECT-DEPENDENCY-COLLECTOR</groupId>
+ <artifactId>maven-dependency-collector-plugin-it</artifactId>
+ <version>1.0.0-A</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>c</artifactId>
+ <version>1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>b</artifactId>
+ <version>1.0</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>a</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-collector-plugin</artifactId>
+ <version>${it-plugin.version}</version>
+ <configuration>
+ <localRepositoryPath>${localRepositoryPath}</localRepositoryPath>
+ <mvnVersion>${mvnVersion}</mvnVersion>
+ </configuration>
+ <executions>
+ <execution>
+ <id>id-dependency-collector</id>
+ <goals>
+ <goal>dependency-collector</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/src/it/maven-dependency-collector-plugin/src/test/projects/noRepeatedDeps/pom.xml b/src/it/maven-dependency-collector-plugin/src/test/projects/noRepeatedDeps/pom.xml
new file mode 100644
index 0000000..33959c1
--- /dev/null
+++ b/src/it/maven-dependency-collector-plugin/src/test/projects/noRepeatedDeps/pom.xml
@@ -0,0 +1,62 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>PROJECT-DEPENDENCY-COLLECTOR</groupId>
+ <artifactId>maven-dependency-collector-plugin-it</artifactId>
+ <version>1.0.0-A</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>c</artifactId>
+ <version>1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>b</artifactId>
+ <version>1.0</version>
+ </dependency>
+</dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-collector-plugin</artifactId>
+ <version>${it-plugin.version}</version>
+ <configuration>
+ <localRepositoryPath>${localRepositoryPath}</localRepositoryPath>
+ <mvnVersion>${mvnVersion}</mvnVersion>
+ </configuration>
+ <executions>
+ <execution>
+ <id>id-dependency-collector</id>
+ <goals>
+ <goal>dependency-collector</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/src/it/maven-dependency-collector-plugin/src/test/projects/noTransitiveOptionalDep/pom.xml b/src/it/maven-dependency-collector-plugin/src/test/projects/noTransitiveOptionalDep/pom.xml
new file mode 100644
index 0000000..003b6a3
--- /dev/null
+++ b/src/it/maven-dependency-collector-plugin/src/test/projects/noTransitiveOptionalDep/pom.xml
@@ -0,0 +1,57 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>PROJECT-DEPENDENCY-COLLECTOR</groupId>
+ <artifactId>maven-dependency-collector-plugin-it</artifactId>
+ <version>1.0.0-A</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>d</artifactId>
+ <version>1.0</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-collector-plugin</artifactId>
+ <version>${it-plugin.version}</version>
+ <configuration>
+ <localRepositoryPath>${localRepositoryPath}</localRepositoryPath>
+ <mvnVersion>${mvnVersion}</mvnVersion>
+ </configuration>
+ <executions>
+ <execution>
+ <id>id-dependency-collector</id>
+ <goals>
+ <goal>dependency-collector</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/src/it/maven-dependency-collector-plugin/src/test/projects/noTransitiveTestDep/pom.xml b/src/it/maven-dependency-collector-plugin/src/test/projects/noTransitiveTestDep/pom.xml
new file mode 100644
index 0000000..47ae154
--- /dev/null
+++ b/src/it/maven-dependency-collector-plugin/src/test/projects/noTransitiveTestDep/pom.xml
@@ -0,0 +1,63 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>PROJECT-DEPENDENCY-COLLECTOR</groupId>
+ <artifactId>maven-dependency-collector-plugin-it</artifactId>
+ <version>1.0.0-A</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>a</artifactId>
+ <version>1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>u</artifactId>
+ <version>1.0</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-collector-plugin</artifactId>
+ <version>${it-plugin.version}</version>
+ <configuration>
+ <localRepositoryPath>${localRepositoryPath}</localRepositoryPath>
+ <mvnVersion>${mvnVersion}</mvnVersion>
+ </configuration>
+ <executions>
+ <execution>
+ <id>id-dependency-collector</id>
+ <goals>
+ <goal>dependency-collector</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/src/it/maven-dependency-collector-plugin/src/test/projects/optionalDep/pom.xml b/src/it/maven-dependency-collector-plugin/src/test/projects/optionalDep/pom.xml
new file mode 100644
index 0000000..12c80e7
--- /dev/null
+++ b/src/it/maven-dependency-collector-plugin/src/test/projects/optionalDep/pom.xml
@@ -0,0 +1,59 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>PROJECT-DEPENDENCY-COLLECTOR</groupId>
+ <artifactId>maven-dependency-collector-plugin-it</artifactId>
+ <version>1.0.0-A</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>c</artifactId>
+ <version>1.0</version>
+ <scope>runtime</scope>
+ <optional>true</optional>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-collector-plugin</artifactId>
+ <version>${it-plugin.version}</version>
+ <configuration>
+ <localRepositoryPath>${localRepositoryPath}</localRepositoryPath>
+ <mvnVersion>${mvnVersion}</mvnVersion>
+ </configuration>
+ <executions>
+ <execution>
+ <id>id-dependency-collector</id>
+ <goals>
+ <goal>dependency-collector</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/src/it/maven-dependency-collector-plugin/src/test/projects/transitiveTestDeps/pom.xml b/src/it/maven-dependency-collector-plugin/src/test/projects/transitiveTestDeps/pom.xml
new file mode 100644
index 0000000..38e7e4f
--- /dev/null
+++ b/src/it/maven-dependency-collector-plugin/src/test/projects/transitiveTestDeps/pom.xml
@@ -0,0 +1,58 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>PROJECT-DEPENDENCY-COLLECTOR</groupId>
+ <artifactId>maven-dependency-collector-plugin-it</artifactId>
+ <version>1.0.0-A</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>t</artifactId>
+ <version>1.1</version>
+ <scope>test</scope>
+ </dependency>
+</dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-collector-plugin</artifactId>
+ <version>${it-plugin.version}</version>
+ <configuration>
+ <localRepositoryPath>${localRepositoryPath}</localRepositoryPath>
+ <mvnVersion>${mvnVersion}</mvnVersion>
+ </configuration>
+ <executions>
+ <execution>
+ <id>id-dependency-collector</id>
+ <goals>
+ <goal>dependency-collector</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/src/it/maven-dependency-collector-plugin/src/test/projects/versionConstraintDep/pom.xml b/src/it/maven-dependency-collector-plugin/src/test/projects/versionConstraintDep/pom.xml
new file mode 100644
index 0000000..947f236
--- /dev/null
+++ b/src/it/maven-dependency-collector-plugin/src/test/projects/versionConstraintDep/pom.xml
@@ -0,0 +1,57 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>PROJECT-DEPENDENCY-COLLECTOR</groupId>
+ <artifactId>maven-dependency-collector-plugin-it</artifactId>
+ <version>1.0.0-A</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.plugin.dependency.collector.its</groupId>
+ <artifactId>t</artifactId>
+ <version>[1.1,)</version>
+ </dependency>
+</dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-collector-plugin</artifactId>
+ <version>${it-plugin.version}</version>
+ <configuration>
+ <localRepositoryPath>${localRepositoryPath}</localRepositoryPath>
+ <mvnVersion>${mvnVersion}</mvnVersion>
+ </configuration>
+ <executions>
+ <execution>
+ <id>id-dependency-collector</id>
+ <goals>
+ <goal>dependency-collector</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/CollectorResult.java b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/CollectorResult.java
index f7b8809..8f393a5 100644
--- a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/CollectorResult.java
+++ b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/CollectorResult.java
@@ -22,6 +22,7 @@
import java.util.List;
import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.shared.dependency.graph.DependencyNode;
/**
*
@@ -34,4 +35,9 @@
* @return {@link ArtifactRepository}
*/
List<ArtifactRepository> getRemoteRepositories();
+
+ /***
+ * @return the root of the dependency graph
+ */
+ DependencyNode getDependencyGraphRoot();
}
diff --git a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/DependencyCollector.java b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/DependencyCollector.java
index 2134776..700d66c 100644
--- a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/DependencyCollector.java
+++ b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/DependencyCollector.java
@@ -70,4 +70,14 @@
CollectorResult collectDependencies( ProjectBuildingRequest buildingRequest, Model root )
throws DependencyCollectorException;
+ /**
+ * @param buildingRequest {@link ProjectBuildingRequest}.
+ * @return {@link CollectorResult}
+ * @throws DependencyCollectorException in case of an error which can be a component lookup error or an error while
+ * trying to collect the dependencies.
+ * @throws IllegalArgumentException in case of parameter <code>buildingRequest</code> is <code>null</code>
+ */
+ CollectorResult collectDependenciesGraph( ProjectBuildingRequest buildingRequest )
+ throws DependencyCollectorException;
+
}
diff --git a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/DefaultDependencyCollector.java b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/DefaultDependencyCollector.java
index 9904731..90548c3 100644
--- a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/DefaultDependencyCollector.java
+++ b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/DefaultDependencyCollector.java
@@ -104,6 +104,21 @@
}
}
+ @Override
+ public CollectorResult collectDependenciesGraph( ProjectBuildingRequest buildingRequest )
+ throws DependencyCollectorException
+ {
+ validateBuildingRequest( buildingRequest );
+ try
+ {
+ return getMavenDependencyCollector( buildingRequest ).collectDependenciesGraph( buildingRequest );
+ }
+ catch ( ComponentLookupException e )
+ {
+ throw new DependencyCollectorException( e.getMessage(), e );
+ }
+ }
+
private void validateParameters( ProjectBuildingRequest buildingRequest, DependableCoordinate root )
{
validateBuildingRequest( buildingRequest );
@@ -167,6 +182,7 @@
* @param context Plexus context to inject.
* @throws ContextException if the PlexusContainer could not be located.
*/
+ @Override
public void contextualize( Context context )
throws ContextException
{
diff --git a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30CollectorResult.java b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30CollectorResult.java
index f07c10f..bebfe96 100644
--- a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30CollectorResult.java
+++ b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30CollectorResult.java
@@ -20,16 +20,24 @@
*/
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
+import org.apache.maven.shared.dependency.graph.internal.DefaultDependencyNode;
import org.apache.maven.shared.transfer.dependencies.collect.CollectorResult;
+import org.apache.maven.shared.transfer.dependencies.collect.DependencyCollectorException;
+import org.sonatype.aether.artifact.Artifact;
import org.sonatype.aether.collection.CollectResult;
+import org.sonatype.aether.graph.Dependency;
import org.sonatype.aether.graph.DependencyNode;
import org.sonatype.aether.graph.DependencyVisitor;
import org.sonatype.aether.repository.RemoteRepository;
+import org.sonatype.aether.version.VersionConstraint;
/**
* CollectorResult wrapper around {@link CollectResult}
@@ -82,4 +90,81 @@
return mavenRepositories;
}
+ @Override
+ public org.apache.maven.shared.dependency.graph.DependencyNode getDependencyGraphRoot()
+ {
+ DependencyNode root = collectResult.getRoot();
+ org.apache.maven.artifact.Artifact rootArtifact = getDependencyArtifact( root.getDependency() );
+
+ return buildDependencyNode( null, root, rootArtifact, null );
+ }
+
+ // CHECKSTYLE_OFF: LineLength
+ private org.apache.maven.shared.dependency.graph.DependencyNode buildDependencyNode( org.apache.maven.shared.dependency.graph.DependencyNode parent,
+ DependencyNode node,
+ org.apache.maven.artifact.Artifact artifact,
+ ArtifactFilter filter )
+ // CHECKSTYLE_ON: LineLength
+ {
+ String premanagedVersion = node.getPremanagedVersion();
+ String premanagedScope = node.getPremanagedScope();
+
+ Boolean optional = null;
+ if ( node.getDependency() != null )
+ {
+ optional = node.getDependency().isOptional();
+ }
+
+ DefaultDependencyNode current =
+ new DefaultDependencyNode( parent, artifact, premanagedVersion, premanagedScope,
+ getVersionSelectedFromRange( node.getVersionConstraint() ), optional );
+
+ List<org.apache.maven.shared.dependency.graph.DependencyNode> nodes =
+ new ArrayList<org.apache.maven.shared.dependency.graph.DependencyNode>( node.getChildren().size() );
+ for ( DependencyNode child : node.getChildren() )
+ {
+ org.apache.maven.artifact.Artifact childArtifact = getDependencyArtifact( child.getDependency() );
+
+ if ( ( filter == null ) || filter.include( childArtifact ) )
+ {
+ nodes.add( buildDependencyNode( current, child, childArtifact, filter ) );
+ }
+ }
+
+ current.setChildren( Collections.unmodifiableList( nodes ) );
+
+ return current;
+ }
+
+ private String getVersionSelectedFromRange( VersionConstraint constraint )
+ {
+ if ( ( constraint == null ) || ( constraint.getVersion() != null ) || ( constraint.getRanges().isEmpty() ) )
+ {
+ return null;
+ }
+
+ return constraint.getRanges().iterator().next().toString();
+ }
+
+ private org.apache.maven.artifact.Artifact getDependencyArtifact( Dependency dep )
+ {
+ Artifact artifact = dep.getArtifact();
+
+ try
+ {
+ org.apache.maven.artifact.Artifact mavenArtifact =
+ (org.apache.maven.artifact.Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
+ Artifact.class, artifact );
+
+ mavenArtifact.setScope( dep.getScope() );
+ mavenArtifact.setOptional( dep.isOptional() );
+
+ return mavenArtifact;
+ }
+ catch ( DependencyCollectorException e )
+ {
+ // ReflectionException should not happen
+ throw new RuntimeException( e.getMessage(), e );
+ }
+ }
}
diff --git a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30ConfigUtils.java b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30ConfigUtils.java
new file mode 100644
index 0000000..bc7172e
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30ConfigUtils.java
@@ -0,0 +1,342 @@
+package org.apache.maven.shared.transfer.dependencies.collect.internal;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.sonatype.aether.RepositorySystemSession;
+
+/**
+ * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
+ *
+ * @author Gabriel Belingueres
+ */
+public class Maven30ConfigUtils
+{
+
+ private Maven30ConfigUtils()
+ {
+ // hide constructor
+ }
+
+ /**
+ * Gets the specified configuration property.
+ *
+ * @param properties The configuration properties to read, must not be {@code null}.
+ * @param defaultValue The default value to return in case the property isn't set, may be {@code null}.
+ * @param keys The properties to read, must not be {@code null}. The specified keys are read one after one until a
+ * valid value is found.
+ * @return The property value or {@code null} if none.
+ */
+ public static Object getObject( Map<?, ?> properties, Object defaultValue, String... keys )
+ {
+ for ( String key : keys )
+ {
+ Object value = properties.get( key );
+
+ if ( value != null )
+ {
+ return value;
+ }
+ }
+
+ return defaultValue;
+ }
+
+ /**
+ * Gets the specified configuration property.
+ *
+ * @param session The repository system session from which to read the configuration property, must not be
+ * {@code null}.
+ * @param defaultValue The default value to return in case the property isn't set, may be {@code null}.
+ * @param keys The properties to read, must not be {@code null}. The specified keys are read one after one until a
+ * valid value is found.
+ * @return The property value or {@code null} if none.
+ */
+ public static Object getObject( RepositorySystemSession session, Object defaultValue, String... keys )
+ {
+ return getObject( session.getConfigProperties(), defaultValue, keys );
+ }
+
+ /**
+ * Gets the specified configuration property.
+ *
+ * @param properties The configuration properties to read, must not be {@code null}.
+ * @param defaultValue The default value to return in case the property isn't set, may be {@code null}.
+ * @param keys The properties to read, must not be {@code null}. The specified keys are read one after one until a
+ * valid value is found.
+ * @return The property value or {@code null} if none.
+ */
+ public static String getString( Map<?, ?> properties, String defaultValue, String... keys )
+ {
+ for ( String key : keys )
+ {
+ Object value = properties.get( key );
+
+ if ( value instanceof String )
+ {
+ return (String) value;
+ }
+ }
+
+ return defaultValue;
+ }
+
+ /**
+ * Gets the specified configuration property.
+ *
+ * @param session The repository system session from which to read the configuration property, must not be
+ * {@code null}.
+ * @param defaultValue The default value to return in case the property isn't set, may be {@code null}.
+ * @param keys The properties to read, must not be {@code null}. The specified keys are read one after one until a
+ * valid value is found.
+ * @return The property value or {@code null} if none.
+ */
+ public static String getString( RepositorySystemSession session, String defaultValue, String... keys )
+ {
+ return getString( session.getConfigProperties(), defaultValue, keys );
+ }
+
+ /**
+ * Gets the specified configuration property.
+ *
+ * @param properties The configuration properties to read, must not be {@code null}.
+ * @param defaultValue The default value to return in case the property isn't set.
+ * @param keys The properties to read, must not be {@code null}. The specified keys are read one after one until a
+ * valid value is found.
+ * @return The property value.
+ */
+ public static int getInteger( Map<?, ?> properties, int defaultValue, String... keys )
+ {
+ for ( String key : keys )
+ {
+ Object value = properties.get( key );
+
+ if ( value instanceof Number )
+ {
+ return ( (Number) value ).intValue();
+ }
+
+ try
+ {
+ return Integer.valueOf( (String) value );
+ }
+ catch ( Exception e )
+ {
+ // try next key
+ }
+ }
+
+ return defaultValue;
+ }
+
+ /**
+ * Gets the specified configuration property.
+ *
+ * @param session The repository system session from which to read the configuration property, must not be
+ * {@code null}.
+ * @param defaultValue The default value to return in case the property isn't set.
+ * @param keys The properties to read, must not be {@code null}. The specified keys are read one after one until a
+ * valid value is found.
+ * @return The property value.
+ */
+ public static int getInteger( RepositorySystemSession session, int defaultValue, String... keys )
+ {
+ return getInteger( session.getConfigProperties(), defaultValue, keys );
+ }
+
+ /**
+ * Gets the specified configuration property.
+ *
+ * @param properties The configuration properties to read, must not be {@code null}.
+ * @param defaultValue The default value to return in case the property isn't set.
+ * @param keys The properties to read, must not be {@code null}. The specified keys are read one after one until a
+ * valid value is found.
+ * @return The property value.
+ */
+ public static long getLong( Map<?, ?> properties, long defaultValue, String... keys )
+ {
+ for ( String key : keys )
+ {
+ Object value = properties.get( key );
+
+ if ( value instanceof Number )
+ {
+ return ( (Number) value ).longValue();
+ }
+
+ try
+ {
+ return Long.valueOf( (String) value );
+ }
+ catch ( Exception e )
+ {
+ // try next key
+ }
+ }
+
+ return defaultValue;
+ }
+
+ /**
+ * Gets the specified configuration property.
+ *
+ * @param session The repository system session from which to read the configuration property, must not be
+ * {@code null}.
+ * @param defaultValue The default value to return in case the property isn't set.
+ * @param keys The properties to read, must not be {@code null}. The specified keys are read one after one until a
+ * valid value is found.
+ * @return The property value.
+ */
+ public static long getLong( RepositorySystemSession session, long defaultValue, String... keys )
+ {
+ return getLong( session.getConfigProperties(), defaultValue, keys );
+ }
+
+ /**
+ * Gets the specified configuration property.
+ *
+ * @param properties The configuration properties to read, must not be {@code null}.
+ * @param defaultValue The default value to return in case the property isn't set.
+ * @param keys The properties to read, must not be {@code null}. The specified keys are read one after one until a
+ * valid value is found.
+ * @return The property value.
+ */
+ public static boolean getBoolean( Map<?, ?> properties, boolean defaultValue, String... keys )
+ {
+ for ( String key : keys )
+ {
+ Object value = properties.get( key );
+
+ if ( value instanceof Boolean )
+ {
+ return ( (Boolean) value ).booleanValue();
+ }
+ else if ( value instanceof String )
+ {
+ return Boolean.parseBoolean( (String) value );
+ }
+ }
+
+ return defaultValue;
+ }
+
+ /**
+ * Gets the specified configuration property.
+ *
+ * @param session The repository system session from which to read the configuration property, must not be
+ * {@code null}.
+ * @param defaultValue The default value to return in case the property isn't set.
+ * @param keys The properties to read, must not be {@code null}. The specified keys are read one after one until a
+ * valid value is found.
+ * @return The property value.
+ */
+ public static boolean getBoolean( RepositorySystemSession session, boolean defaultValue, String... keys )
+ {
+ return getBoolean( session.getConfigProperties(), defaultValue, keys );
+ }
+
+ /**
+ * Gets the specified configuration property.
+ *
+ * @param properties The configuration properties to read, must not be {@code null}.
+ * @param defaultValue The default value to return in case the property isn't set, may be {@code null}.
+ * @param keys The properties to read, must not be {@code null}. The specified keys are read one after one until a
+ * valid value is found.
+ * @return The property value or {@code null} if none.
+ */
+ public static List<?> getList( Map<?, ?> properties, List<?> defaultValue, String... keys )
+ {
+ for ( String key : keys )
+ {
+ Object value = properties.get( key );
+
+ if ( value instanceof List )
+ {
+ return (List<?>) value;
+ }
+ else if ( value instanceof Collection )
+ {
+ return Collections.unmodifiableList( new ArrayList<Object>( (Collection<?>) value ) );
+ }
+ }
+
+ return defaultValue;
+ }
+
+ /**
+ * Gets the specified configuration property.
+ *
+ * @param session The repository system session from which to read the configuration property, must not be
+ * {@code null}.
+ * @param defaultValue The default value to return in case the property isn't set, may be {@code null}.
+ * @param keys The properties to read, must not be {@code null}. The specified keys are read one after one until a
+ * valid value is found.
+ * @return The property value or {@code null} if none.
+ */
+ public static List<?> getList( RepositorySystemSession session, List<?> defaultValue, String... keys )
+ {
+ return getList( session.getConfigProperties(), defaultValue, keys );
+ }
+
+ /**
+ * Gets the specified configuration property.
+ *
+ * @param properties The configuration properties to read, must not be {@code null}.
+ * @param defaultValue The default value to return in case the property isn't set, may be {@code null}.
+ * @param keys The properties to read, must not be {@code null}. The specified keys are read one after one until a
+ * valid value is found.
+ * @return The property value or {@code null} if none.
+ */
+ public static Map<?, ?> getMap( Map<?, ?> properties, Map<?, ?> defaultValue, String... keys )
+ {
+ for ( String key : keys )
+ {
+ Object value = properties.get( key );
+
+ if ( value instanceof Map )
+ {
+ return (Map<?, ?>) value;
+ }
+ }
+
+ return defaultValue;
+ }
+
+ /**
+ * Gets the specified configuration property.
+ *
+ * @param session The repository system session from which to read the configuration property, must not be
+ * {@code null}.
+ * @param defaultValue The default value to return in case the property isn't set, may be {@code null}.
+ * @param keys The properties to read, must not be {@code null}. The specified keys are read one after one until a
+ * valid value is found.
+ * @return The property value or {@code null} if none.
+ */
+ public static Map<?, ?> getMap( RepositorySystemSession session, Map<?, ?> defaultValue, String... keys )
+ {
+ return getMap( session.getConfigProperties(), defaultValue, keys );
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30ConflictIdSorter.java b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30ConflictIdSorter.java
new file mode 100644
index 0000000..f8ea6de
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30ConflictIdSorter.java
@@ -0,0 +1,369 @@
+package org.apache.maven.shared.transfer.dependencies.collect.internal;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.sonatype.aether.RepositoryException;
+import org.sonatype.aether.collection.DependencyGraphTransformationContext;
+import org.sonatype.aether.collection.DependencyGraphTransformer;
+import org.sonatype.aether.graph.DependencyNode;
+import org.sonatype.aether.util.graph.transformer.ConflictMarker;
+import org.sonatype.aether.util.graph.transformer.TransformationContextKeys;
+
+/**
+ * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
+ *
+ * @author Gabriel Belingueres
+ */
+public class Maven30ConflictIdSorter
+ implements DependencyGraphTransformer
+{
+
+ public DependencyNode transformGraph( DependencyNode node, DependencyGraphTransformationContext context )
+ throws RepositoryException
+ {
+ Map<?, ?> conflictIds = (Map<?, ?>) context.get( TransformationContextKeys.CONFLICT_IDS );
+ if ( conflictIds == null )
+ {
+ ConflictMarker marker = new ConflictMarker();
+ marker.transformGraph( node, context );
+
+ conflictIds = (Map<?, ?>) context.get( TransformationContextKeys.CONFLICT_IDS );
+ }
+
+// @SuppressWarnings( "unchecked" )
+// Map<String, Object> stats = (Map<String, Object>) context.get( TransformationContextKeys.STATS );
+// long time1 = System.currentTimeMillis();
+
+ Map<Object, ConflictId> ids = new LinkedHashMap<Object, ConflictId>( 256 );
+
+ // CHECKSTYLE_OFF: AvoidNestedBlocks
+ {
+ ConflictId id = null;
+ Object key = conflictIds.get( node );
+ if ( key != null )
+ {
+ id = new ConflictId( key, 0 );
+ ids.put( key, id );
+ }
+
+ Map<DependencyNode, Object> visited = new IdentityHashMap<DependencyNode, Object>( conflictIds.size() );
+
+ buildConflitIdDAG( ids, node, id, 0, visited, conflictIds );
+ }
+ // CHECKSTYLE_NO: AvoidNestedBlocks
+
+// long time2 = System.currentTimeMillis();
+
+ topsortConflictIds( ids.values(), context );
+// int cycles = topsortConflictIds( ids.values(), context );
+
+// if ( stats != null )
+// {
+// long time3 = System.currentTimeMillis();
+// stats.put( "ConflictIdSorter.graphTime", time2 - time1 );
+// stats.put( "ConflictIdSorter.topsortTime", time3 - time2 );
+// stats.put( "ConflictIdSorter.conflictIdCount", ids.size() );
+// stats.put( "ConflictIdSorter.conflictIdCycleCount", cycles );
+// }
+
+ return node;
+ }
+
+ private void buildConflitIdDAG( Map<Object, ConflictId> ids, DependencyNode node, ConflictId id, int depth,
+ Map<DependencyNode, Object> visited, Map<?, ?> conflictIds )
+ {
+ if ( visited.put( node, Boolean.TRUE ) != null )
+ {
+ return;
+ }
+
+ depth++;
+
+ for ( DependencyNode child : node.getChildren() )
+ {
+ Object key = conflictIds.get( child );
+ ConflictId childId = ids.get( key );
+ if ( childId == null )
+ {
+ childId = new ConflictId( key, depth );
+ ids.put( key, childId );
+ }
+ else
+ {
+ childId.pullup( depth );
+ }
+
+ if ( id != null )
+ {
+ id.add( childId );
+ }
+
+ buildConflitIdDAG( ids, child, childId, depth, visited, conflictIds );
+ }
+ }
+
+ private int topsortConflictIds( Collection<ConflictId> conflictIds, DependencyGraphTransformationContext context )
+ {
+ List<Object> sorted = new ArrayList<Object>( conflictIds.size() );
+
+ RootQueue roots = new RootQueue( conflictIds.size() / 2 );
+ for ( ConflictId id : conflictIds )
+ {
+ if ( id.inDegree <= 0 )
+ {
+ roots.add( id );
+ }
+ }
+
+ processRoots( sorted, roots );
+
+ boolean cycle = sorted.size() < conflictIds.size();
+
+ while ( sorted.size() < conflictIds.size() )
+ {
+ // cycle -> deal gracefully with nodes still having positive in-degree
+
+ ConflictId nearest = null;
+ for ( ConflictId id : conflictIds )
+ {
+ if ( id.inDegree <= 0 )
+ {
+ continue;
+ }
+ if ( nearest == null || id.minDepth < nearest.minDepth
+ || ( id.minDepth == nearest.minDepth && id.inDegree < nearest.inDegree ) )
+ {
+ nearest = id;
+ }
+ }
+
+ nearest.inDegree = 0;
+ roots.add( nearest );
+
+ processRoots( sorted, roots );
+ }
+
+ Collection<Collection<Object>> cycles = Collections.emptySet();
+ if ( cycle )
+ {
+ cycles = findCycles( conflictIds );
+ }
+
+ context.put( TransformationContextKeys.SORTED_CONFLICT_IDS, sorted );
+ // context.put( TransformationContextKeys.CYCLIC_CONFLICT_IDS, sorted );
+ context.put( Maven30ConflictResolver.CYCLIC_CONFLICT_IDS, cycles );
+
+ return cycles.size();
+ }
+
+ private void processRoots( List<Object> sorted, RootQueue roots )
+ {
+ while ( !roots.isEmpty() )
+ {
+ ConflictId root = roots.remove();
+
+ sorted.add( root.key );
+
+ for ( ConflictId child : root.children )
+ {
+ child.inDegree--;
+ if ( child.inDegree == 0 )
+ {
+ roots.add( child );
+ }
+ }
+ }
+ }
+
+ private Collection<Collection<Object>> findCycles( Collection<ConflictId> conflictIds )
+ {
+ Collection<Collection<Object>> cycles = new HashSet<Collection<Object>>();
+
+ Map<Object, Integer> stack = new HashMap<Object, Integer>( 128 );
+ Map<ConflictId, Object> visited = new IdentityHashMap<ConflictId, Object>( conflictIds.size() );
+ for ( ConflictId id : conflictIds )
+ {
+ findCycles( id, visited, stack, cycles );
+ }
+
+ return cycles;
+ }
+
+ private void findCycles( ConflictId id, Map<ConflictId, Object> visited, Map<Object, Integer> stack,
+ Collection<Collection<Object>> cycles )
+ {
+ Integer depth = stack.put( id.key, stack.size() );
+ if ( depth != null )
+ {
+ stack.put( id.key, depth );
+ Collection<Object> cycle = new HashSet<Object>();
+ for ( Map.Entry<Object, Integer> entry : stack.entrySet() )
+ {
+ if ( entry.getValue() >= depth )
+ {
+ cycle.add( entry.getKey() );
+ }
+ }
+ cycles.add( cycle );
+ }
+ else
+ {
+ if ( visited.put( id, Boolean.TRUE ) == null )
+ {
+ for ( ConflictId childId : id.children )
+ {
+ findCycles( childId, visited, stack, cycles );
+ }
+ }
+ stack.remove( id.key );
+ }
+ }
+
+ static final class ConflictId
+ {
+
+ final Object key;
+
+ Collection<ConflictId> children = Collections.emptySet();
+
+ int inDegree;
+
+ int minDepth;
+
+ ConflictId( Object key, int depth )
+ {
+ this.key = key;
+ this.minDepth = depth;
+ }
+
+ public void add( ConflictId child )
+ {
+ if ( children.isEmpty() )
+ {
+ children = new HashSet<ConflictId>();
+ }
+ if ( children.add( child ) )
+ {
+ child.inDegree++;
+ }
+ }
+
+ public void pullup( int depth )
+ {
+ if ( depth < minDepth )
+ {
+ minDepth = depth;
+ depth++;
+ for ( ConflictId child : children )
+ {
+ child.pullup( depth );
+ }
+ }
+ }
+
+ @Override
+ public boolean equals( Object obj )
+ {
+ if ( this == obj )
+ {
+ return true;
+ }
+ else if ( !( obj instanceof ConflictId ) )
+ {
+ return false;
+ }
+ ConflictId that = (ConflictId) obj;
+ return this.key.equals( that.key );
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return key.hashCode();
+ }
+
+ @Override
+ public String toString()
+ {
+ return key + " @ " + minDepth + " <" + inDegree;
+ }
+
+ }
+
+ static final class RootQueue
+ {
+
+ private int nextOut;
+
+ private int nextIn;
+
+ private ConflictId[] ids;
+
+ RootQueue( int capacity )
+ {
+ ids = new ConflictId[capacity + 16];
+ }
+
+ boolean isEmpty()
+ {
+ return nextOut >= nextIn;
+ }
+
+ void add( ConflictId id )
+ {
+ if ( nextOut >= nextIn && nextOut > 0 )
+ {
+ nextIn -= nextOut;
+ nextOut = 0;
+ }
+ if ( nextIn >= ids.length )
+ {
+ ConflictId[] tmp = new ConflictId[ids.length + ids.length / 2 + 16];
+ System.arraycopy( ids, nextOut, tmp, 0, nextIn - nextOut );
+ ids = tmp;
+ nextIn -= nextOut;
+ nextOut = 0;
+ }
+ int i;
+ for ( i = nextIn - 1; i >= nextOut && id.minDepth < ids[i].minDepth; i-- )
+ {
+ ids[i + 1] = ids[i];
+ }
+ ids[i + 1] = id;
+ nextIn++;
+ }
+
+ ConflictId remove()
+ {
+ return ids[nextOut++];
+ }
+
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30ConflictResolver.java b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30ConflictResolver.java
new file mode 100644
index 0000000..4ac601a
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30ConflictResolver.java
@@ -0,0 +1,1345 @@
+package org.apache.maven.shared.transfer.dependencies.collect.internal;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+
+import org.eclipse.aether.util.graph.transformer.ConflictResolver;
+import org.sonatype.aether.RepositoryException;
+import org.sonatype.aether.artifact.Artifact;
+import org.sonatype.aether.collection.DependencyGraphTransformationContext;
+import org.sonatype.aether.collection.DependencyGraphTransformer;
+import org.sonatype.aether.graph.Dependency;
+import org.sonatype.aether.graph.DependencyNode;
+import org.sonatype.aether.util.graph.DefaultDependencyNode;
+import org.sonatype.aether.util.graph.transformer.ConflictIdSorter;
+import org.sonatype.aether.util.graph.transformer.TransformationContextKeys;
+
+/**
+ * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
+ *
+ * @author Gabriel Belingueres
+ */
+public class Maven30ConflictResolver
+ implements DependencyGraphTransformer
+{
+
+ /**
+ * NOTE: ADDED THIS CONSTANT THERE BECAUSE IT DOES NOT EXISTS IN SONATYPE AETHER 1.7 The key in the graph
+ * transformation context where a {@code Boolean} is stored that indicates whether the dependencies between conflict
+ * ids form a cycle.
+ *
+ * @see ConflictIdSorter
+ */
+ public static final Object CYCLIC_CONFLICT_IDS = "cyclicConflictIds";
+
+ /**
+ * The key in the repository session's {@link RepositorySystemSession#getConfigProperties() configuration
+ * properties} used to store a {@link Boolean} flag controlling the transformer's verbose mode.
+ */
+ public static final String CONFIG_PROP_VERBOSE = "aether.conflictResolver.verbose";
+
+ /**
+ * The key in the dependency node's {@link DependencyNode#getData() custom data} under which a reference to the
+ * {@link DependencyNode} which has won the conflict is stored.
+ */
+ public static final String NODE_DATA_WINNER = "conflict.winner";
+
+ /**
+ * The key in the dependency node's {@link DependencyNode#getData() custom data} under which the scope of the
+ * dependency before scope derivation and conflict resolution is stored.
+ */
+ public static final String NODE_DATA_ORIGINAL_SCOPE = "conflict.originalScope";
+
+ /**
+ * The key in the dependency node's {@link DependencyNode#getData() custom data} under which the optional flag of
+ * the dependency before derivation and conflict resolution is stored.
+ */
+ public static final String NODE_DATA_ORIGINAL_OPTIONALITY = "conflict.originalOptionality";
+
+ private final VersionSelector versionSelector;
+
+ private final ScopeSelector scopeSelector;
+
+ private final ScopeDeriver scopeDeriver;
+
+ private final OptionalitySelector optionalitySelector;
+
+ private Maven30NodeData nodeData;
+
+ /**
+ * Creates a new conflict resolver instance with the specified hooks.
+ *
+ * @param versionSelector The version selector to use, must not be {@code null}.
+ * @param scopeSelector The scope selector to use, must not be {@code null}.
+ * @param optionalitySelector The optionality selector ot use, must not be {@code null}.
+ * @param scopeDeriver The scope deriver to use, must not be {@code null}.
+ * @param nodeData the object where to save node data since Sonatype Aether 1.7 does not have that info inside the
+ * DependencyNode.
+ */
+ public Maven30ConflictResolver( VersionSelector versionSelector, ScopeSelector scopeSelector,
+ OptionalitySelector optionalitySelector, ScopeDeriver scopeDeriver,
+ Maven30NodeData nodeData )
+ {
+ if ( versionSelector == null )
+ {
+ throw new IllegalArgumentException( "version selector not specified" );
+ }
+ this.versionSelector = versionSelector;
+ if ( scopeSelector == null )
+ {
+ throw new IllegalArgumentException( "scope selector not specified" );
+ }
+ this.scopeSelector = scopeSelector;
+ if ( scopeDeriver == null )
+ {
+ throw new IllegalArgumentException( "scope deriver not specified" );
+ }
+ this.scopeDeriver = scopeDeriver;
+ if ( optionalitySelector == null )
+ {
+ throw new IllegalArgumentException( "optionality selector not specified" );
+ }
+ this.optionalitySelector = optionalitySelector;
+ this.nodeData = nodeData;
+ }
+
+ public DependencyNode transformGraph( DependencyNode node, DependencyGraphTransformationContext context )
+ throws RepositoryException
+ {
+ List<?> sortedConflictIds = (List<?>) context.get( TransformationContextKeys.SORTED_CONFLICT_IDS );
+ if ( sortedConflictIds == null )
+ {
+ Maven30ConflictIdSorter sorter = new Maven30ConflictIdSorter();
+ sorter.transformGraph( node, context );
+
+ sortedConflictIds = (List<?>) context.get( TransformationContextKeys.SORTED_CONFLICT_IDS );
+ }
+
+// @SuppressWarnings( "unchecked" )
+// Map<String, Object> stats = (Map<String, Object>) context.get( TransformationContextKeys.STATS );
+// long time1 = System.currentTimeMillis();
+
+ @SuppressWarnings( "unchecked" )
+ Collection<Collection<?>> conflictIdCycles =
+ (Collection<Collection<?>>) context.get( CYCLIC_CONFLICT_IDS );
+ if ( conflictIdCycles == null )
+ {
+ throw new RepositoryException( "conflict id cycles have not been identified" );
+ }
+
+ Map<?, ?> conflictIds = (Map<?, ?>) context.get( TransformationContextKeys.CONFLICT_IDS );
+ if ( conflictIds == null )
+ {
+ throw new RepositoryException( "conflict groups have not been identified" );
+ }
+
+ Map<Object, Collection<Object>> cyclicPredecessors = new HashMap<Object, Collection<Object>>();
+ for ( Collection<?> cycle : conflictIdCycles )
+ {
+ for ( Object conflictId : cycle )
+ {
+ Collection<Object> predecessors = cyclicPredecessors.get( conflictId );
+ if ( predecessors == null )
+ {
+ predecessors = new HashSet<Object>();
+ cyclicPredecessors.put( conflictId, predecessors );
+ }
+ predecessors.addAll( cycle );
+ }
+ }
+
+ State state = new State( node, conflictIds, sortedConflictIds.size(), context );
+ for ( Iterator<?> it = sortedConflictIds.iterator(); it.hasNext(); )
+ {
+ Object conflictId = it.next();
+
+ // reset data structures for next graph walk
+ state.prepare( conflictId, cyclicPredecessors.get( conflictId ) );
+
+ // find nodes with the current conflict id and while walking the graph (more deeply), nuke leftover losers
+ gatherConflictItems( node, state );
+
+ // now that we know the min depth of the parents, update depth of conflict items
+ state.finish();
+
+ // earlier runs might have nuked all parents of the current conflict id, so it might not exist anymore
+ if ( !state.items.isEmpty() )
+ {
+ ConflictContext ctx = state.conflictCtx;
+ state.versionSelector.selectVersion( ctx );
+ if ( ctx.winner == null )
+ {
+ throw new RepositoryException( "conflict resolver did not select winner among " + state.items );
+ }
+ DependencyNode winner = ctx.winner.node;
+
+ state.scopeSelector.selectScope( ctx );
+ if ( state.verbose )
+ {
+ nodeData.putData( winner, NODE_DATA_ORIGINAL_SCOPE, winner.getDependency().getScope() );
+// winner.setData( NODE_DATA_ORIGINAL_SCOPE, winner.getDependency().getScope() );
+ }
+ winner.setScope( ctx.scope );
+
+ state.optionalitySelector.selectOptionality( ctx );
+ if ( state.verbose )
+ {
+ nodeData.putData( winner, NODE_DATA_ORIGINAL_OPTIONALITY, winner.getDependency().isOptional() );
+// winner.setData( NODE_DATA_ORIGINAL_OPTIONALITY, winner.getDependency().isOptional() );
+ }
+ winner.getDependency().setOptional( ctx.optional );
+// winner.setOptional( ctx.optional );
+
+ removeLosers( state );
+ }
+
+ // record the winner so we can detect leftover losers during future graph walks
+ state.winner();
+
+ // in case of cycles, trigger final graph walk to ensure all leftover losers are gone
+ if ( !it.hasNext() && !conflictIdCycles.isEmpty() && state.conflictCtx.winner != null )
+ {
+ DependencyNode winner = state.conflictCtx.winner.node;
+ state.prepare( state, null );
+ gatherConflictItems( winner, state );
+ }
+ }
+
+// if ( stats != null )
+// {
+// long time2 = System.currentTimeMillis();
+// stats.put( "ConflictResolver.totalTime", time2 - time1 );
+// stats.put( "ConflictResolver.conflictItemCount", state.totalConflictItems );
+// }
+
+ return node;
+ }
+
+ private boolean gatherConflictItems( DependencyNode node, State state )
+ throws RepositoryException
+{
+ Object conflictId = state.conflictIds.get( node );
+ if ( state.currentId.equals( conflictId ) )
+ {
+ // found it, add conflict item (if not already done earlier by another path)
+ state.add( node );
+ // we don't recurse here so we might miss losers beneath us, those will be nuked during future walks below
+ }
+ else if ( state.loser( node, conflictId ) )
+ {
+ // found a leftover loser (likely in a cycle) of an already processed conflict id, tell caller to nuke it
+ return false;
+ }
+ else if ( state.push( node, conflictId ) )
+ {
+ // found potential parent, no cycle and not visisted before with the same derived scope, so recurse
+ for ( Iterator<DependencyNode> it = node.getChildren().iterator(); it.hasNext(); )
+ {
+ DependencyNode child = it.next();
+ if ( !gatherConflictItems( child, state ) )
+ {
+ it.remove();
+ }
+ }
+ state.pop();
+ }
+ return true;
+ }
+
+ private void removeLosers( State state )
+ {
+ ConflictItem winner = state.conflictCtx.winner;
+ List<DependencyNode> previousParent = null;
+ ListIterator<DependencyNode> childIt = null;
+ boolean conflictVisualized = false;
+ for ( ConflictItem item : state.items )
+ {
+ if ( item == winner )
+ {
+ continue;
+ }
+ if ( item.parent != previousParent )
+ {
+ childIt = item.parent.listIterator();
+ previousParent = item.parent;
+ conflictVisualized = false;
+ }
+ while ( childIt.hasNext() )
+ {
+ DependencyNode child = childIt.next();
+ if ( child == item.node )
+ {
+ if ( state.verbose && !conflictVisualized && item.parent != winner.parent )
+ {
+ conflictVisualized = true;
+ DependencyNode loser = new DefaultDependencyNode( child );
+ nodeData.putData( loser, NODE_DATA_WINNER, winner.node );
+ nodeData.putData( loser, NODE_DATA_ORIGINAL_SCOPE, loser.getDependency().getScope() );
+ nodeData.putData( loser, NODE_DATA_ORIGINAL_OPTIONALITY, loser.getDependency().isOptional() );
+// loser.setData( NODE_DATA_WINNER, winner.node );
+// loser.setData( NODE_DATA_ORIGINAL_SCOPE, loser.getDependency().getScope() );
+// loser.setData( NODE_DATA_ORIGINAL_OPTIONALITY, loser.getDependency().isOptional() );
+ loser.setScope( item.getScopes().iterator().next() );
+// loser.setChildren( Collections.<DependencyNode>emptyList() );
+ childIt.set( loser );
+ }
+ else
+ {
+ childIt.remove();
+ }
+ break;
+ }
+ }
+ }
+ // there might still be losers beneath the winner (e.g. in case of cycles)
+ // those will be nuked during future graph walks when we include the winner in the recursion
+ }
+
+ final class NodeInfo
+ {
+
+ /**
+ * The smallest depth at which the node was seen, used for "the" depth of its conflict items.
+ */
+ int minDepth;
+
+ /**
+ * The set of derived scopes the node was visited with, used to check whether an already seen node needs to be
+ * revisited again in context of another scope. To conserve memory, we start with {@code String} and update to
+ * {@code Set<String>} if needed.
+ */
+ Object derivedScopes;
+
+ /**
+ * The set of derived optionalities the node was visited with, used to check whether an already seen node needs
+ * to be revisited again in context of another optionality. To conserve memory, encoded as bit field (bit 0 ->
+ * optional=false, bit 1 -> optional=true).
+ */
+ int derivedOptionalities;
+
+ /**
+ * The conflict items which are immediate children of the node, used to easily update those conflict items after
+ * a new parent scope/optionality was encountered.
+ */
+ List<ConflictItem> children;
+
+ static final int CHANGE_SCOPE = 0x01;
+
+ static final int CHANGE_OPTIONAL = 0x02;
+
+ private static final int OPT_FALSE = 0x01;
+
+ private static final int OPT_TRUE = 0x02;
+
+ NodeInfo( int depth, String derivedScope, boolean optional )
+ {
+ minDepth = depth;
+ derivedScopes = derivedScope;
+ derivedOptionalities = optional ? OPT_TRUE : OPT_FALSE;
+ }
+
+ @SuppressWarnings( "unchecked" )
+ int update( int depth, String derivedScope, boolean optional )
+ {
+ if ( depth < minDepth )
+ {
+ minDepth = depth;
+ }
+ int changes;
+ if ( derivedScopes.equals( derivedScope ) )
+ {
+ changes = 0;
+ }
+ else if ( derivedScopes instanceof Collection )
+ {
+ changes = ( (Collection<String>) derivedScopes ).add( derivedScope ) ? CHANGE_SCOPE : 0;
+ }
+ else
+ {
+ Collection<String> scopes = new HashSet<String>();
+ scopes.add( (String) derivedScopes );
+ scopes.add( derivedScope );
+ derivedScopes = scopes;
+ changes = CHANGE_SCOPE;
+ }
+ int bit = optional ? OPT_TRUE : OPT_FALSE;
+ if ( ( derivedOptionalities & bit ) == 0 )
+ {
+ derivedOptionalities |= bit;
+ changes |= CHANGE_OPTIONAL;
+ }
+ return changes;
+ }
+
+ void add( ConflictItem item )
+ {
+ if ( children == null )
+ {
+ children = new ArrayList<ConflictItem>( 1 );
+ }
+ children.add( item );
+ }
+
+ }
+
+ final class State
+ {
+
+ /**
+ * The conflict id currently processed.
+ */
+ Object currentId;
+
+ /**
+ * Stats counter.
+ */
+ int totalConflictItems;
+
+ /**
+ * Flag whether we should keep losers in the graph to enable visualization/troubleshooting of conflicts.
+ */
+ final boolean verbose;
+
+ /**
+ * A mapping from conflict id to winner node, helps to recognize nodes that have their effective
+ * scope&optionality set or are leftovers from previous removals.
+ */
+ final Map<Object, DependencyNode> resolvedIds;
+
+ /**
+ * The set of conflict ids which could apply to ancestors of nodes with the current conflict id, used to avoid
+ * recursion early on. This is basically a superset of the key set of resolvedIds, the additional ids account
+ * for cyclic dependencies.
+ */
+ final Collection<Object> potentialAncestorIds;
+
+ /**
+ * The output from the conflict marker
+ */
+ final Map<?, ?> conflictIds;
+
+ /**
+ * The conflict items we have gathered so far for the current conflict id.
+ */
+ final List<ConflictItem> items;
+
+ /**
+ * The (conceptual) mapping from nodes to extra infos, technically keyed by the node's child list which better
+ * captures the identity of a node since we're basically concerned with effects towards children.
+ */
+ final Map<List<DependencyNode>, NodeInfo> infos;
+
+ /**
+ * The set of nodes on the DFS stack to detect cycles, technically keyed by the node's child list to match the
+ * dirty graph structure produced by the dependency collector for cycles.
+ */
+ final Map<List<DependencyNode>, Object> stack;
+
+ /**
+ * The stack of parent nodes.
+ */
+ final List<DependencyNode> parentNodes;
+
+ /**
+ * The stack of derived scopes for parent nodes.
+ */
+ final List<String> parentScopes;
+
+ /**
+ * The stack of derived optional flags for parent nodes.
+ */
+ final List<Boolean> parentOptionals;
+
+ /**
+ * The stack of node infos for parent nodes, may contain {@code null} which is used to disable creating new
+ * conflict items when visiting their parent again (conflict items are meant to be unique by parent-node combo).
+ */
+ final List<NodeInfo> parentInfos;
+
+ /**
+ * The conflict context passed to the version/scope/optionality selectors, updated as we move along rather than
+ * recreated to avoid tmp objects.
+ */
+ final ConflictContext conflictCtx;
+
+ /**
+ * The scope context passed to the scope deriver, updated as we move along rather than recreated to avoid tmp
+ * objects.
+ */
+ final ScopeContext scopeCtx;
+
+ /**
+ * The effective version selector, i.e. after initialization.
+ */
+ final VersionSelector versionSelector;
+
+ /**
+ * The effective scope selector, i.e. after initialization.
+ */
+ final ScopeSelector scopeSelector;
+
+ /**
+ * The effective scope deriver, i.e. after initialization.
+ */
+ final ScopeDeriver scopeDeriver;
+
+ /**
+ * The effective optionality selector, i.e. after initialization.
+ */
+ final OptionalitySelector optionalitySelector;
+
+ State( DependencyNode root, Map<?, ?> conflictIds, int conflictIdCount,
+ DependencyGraphTransformationContext context )
+ throws RepositoryException
+ {
+ this.conflictIds = conflictIds;
+ verbose = Maven30ConfigUtils.getBoolean( context.getSession(), false, CONFIG_PROP_VERBOSE );
+ potentialAncestorIds = new HashSet<Object>( conflictIdCount * 2 );
+ resolvedIds = new HashMap<Object, DependencyNode>( conflictIdCount * 2 );
+ items = new ArrayList<ConflictItem>( 256 );
+ infos = new IdentityHashMap<List<DependencyNode>, NodeInfo>( 64 );
+ stack = new IdentityHashMap<List<DependencyNode>, Object>( 64 );
+ parentNodes = new ArrayList<DependencyNode>( 64 );
+ parentScopes = new ArrayList<String>( 64 );
+ parentOptionals = new ArrayList<Boolean>( 64 );
+ parentInfos = new ArrayList<NodeInfo>( 64 );
+ conflictCtx = new ConflictContext( root, conflictIds, items );
+ scopeCtx = new ScopeContext( null, null );
+ versionSelector = Maven30ConflictResolver.this.versionSelector.getInstance( root, context );
+ scopeSelector = Maven30ConflictResolver.this.scopeSelector.getInstance( root, context );
+ scopeDeriver = Maven30ConflictResolver.this.scopeDeriver.getInstance( root, context );
+ optionalitySelector = Maven30ConflictResolver.this.optionalitySelector.getInstance( root, context );
+ }
+
+ void prepare( Object conflictId, Collection<Object> cyclicPredecessors )
+ {
+ currentId = conflictId;
+ conflictCtx.conflictId = conflictId;
+ conflictCtx.winner = null;
+ conflictCtx.scope = null;
+ conflictCtx.optional = null;
+ items.clear();
+ infos.clear();
+ if ( cyclicPredecessors != null )
+ {
+ potentialAncestorIds.addAll( cyclicPredecessors );
+ }
+ }
+
+ void finish()
+ {
+ List<DependencyNode> previousParent = null;
+ int previousDepth = 0;
+ totalConflictItems += items.size();
+ for ( int i = items.size() - 1; i >= 0; i-- )
+ {
+ ConflictItem item = items.get( i );
+ if ( item.parent == previousParent )
+ {
+ item.depth = previousDepth;
+ }
+ else if ( item.parent != null )
+ {
+ previousParent = item.parent;
+ NodeInfo info = infos.get( previousParent );
+ previousDepth = info.minDepth + 1;
+ item.depth = previousDepth;
+ }
+ }
+ potentialAncestorIds.add( currentId );
+ }
+
+ void winner()
+ {
+ resolvedIds.put( currentId, ( conflictCtx.winner != null ) ? conflictCtx.winner.node : null );
+ }
+
+ boolean loser( DependencyNode node, Object conflictId )
+ {
+ DependencyNode winner = resolvedIds.get( conflictId );
+ return winner != null && winner != node;
+ }
+
+ boolean push( DependencyNode node, Object conflictId )
+ throws RepositoryException
+ {
+ if ( conflictId == null )
+ {
+ if ( node.getDependency() != null )
+ {
+ if ( nodeData.getData( node ).get( NODE_DATA_WINNER ) != null )
+// if ( node.getData().get( NODE_DATA_WINNER ) != null )
+ {
+ return false;
+ }
+ throw new RepositoryException( "missing conflict id for node " + node );
+ }
+ }
+ else if ( !potentialAncestorIds.contains( conflictId ) )
+ {
+ return false;
+ }
+
+ List<DependencyNode> graphNode = node.getChildren();
+ if ( stack.put( graphNode, Boolean.TRUE ) != null )
+ {
+ return false;
+ }
+
+ int depth = depth();
+ String scope = deriveScope( node, conflictId );
+ boolean optional = deriveOptional( node, conflictId );
+ NodeInfo info = infos.get( graphNode );
+ if ( info == null )
+ {
+ info = new NodeInfo( depth, scope, optional );
+ infos.put( graphNode, info );
+ parentInfos.add( info );
+ parentNodes.add( node );
+ parentScopes.add( scope );
+ parentOptionals.add( optional );
+ }
+ else
+ {
+ int changes = info.update( depth, scope, optional );
+ if ( changes == 0 )
+ {
+ stack.remove( graphNode );
+ return false;
+ }
+ parentInfos.add( null ); // disable creating new conflict items, we update the existing ones below
+ parentNodes.add( node );
+ parentScopes.add( scope );
+ parentOptionals.add( optional );
+ if ( info.children != null )
+ {
+ if ( ( changes & NodeInfo.CHANGE_SCOPE ) != 0 )
+ {
+ for ( int i = info.children.size() - 1; i >= 0; i-- )
+ {
+ ConflictItem item = info.children.get( i );
+ String childScope = deriveScope( item.node, null );
+ item.addScope( childScope );
+ }
+ }
+ if ( ( changes & NodeInfo.CHANGE_OPTIONAL ) != 0 )
+ {
+ for ( int i = info.children.size() - 1; i >= 0; i-- )
+ {
+ ConflictItem item = info.children.get( i );
+ boolean childOptional = deriveOptional( item.node, null );
+ item.addOptional( childOptional );
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ void pop()
+ {
+ int last = parentInfos.size() - 1;
+ parentInfos.remove( last );
+ parentScopes.remove( last );
+ parentOptionals.remove( last );
+ DependencyNode node = parentNodes.remove( last );
+ stack.remove( node.getChildren() );
+ }
+
+ void add( DependencyNode node )
+ throws RepositoryException
+ {
+ DependencyNode parent = parent();
+ if ( parent == null )
+ {
+ ConflictItem item = newConflictItem( parent, node );
+ items.add( item );
+ }
+ else
+ {
+ NodeInfo info = parentInfos.get( parentInfos.size() - 1 );
+ if ( info != null )
+ {
+ ConflictItem item = newConflictItem( parent, node );
+ info.add( item );
+ items.add( item );
+ }
+ }
+ }
+
+ private ConflictItem newConflictItem( DependencyNode parent, DependencyNode node )
+ throws RepositoryException
+ {
+ return new ConflictItem( parent, node, deriveScope( node, null ), deriveOptional( node, null ) );
+ }
+
+ private int depth()
+ {
+ return parentNodes.size();
+ }
+
+ private DependencyNode parent()
+ {
+ int size = parentNodes.size();
+ return ( size <= 0 ) ? null : parentNodes.get( size - 1 );
+ }
+
+ private String deriveScope( DependencyNode node, Object conflictId )
+ throws RepositoryException
+ {
+ if ( node.getPremanagedScope() != null || ( conflictId != null && resolvedIds.containsKey( conflictId ) ) )
+// if ( ( node.getManagedBits() & DependencyNode.MANAGED_SCOPE ) != 0
+// || ( conflictId != null && resolvedIds.containsKey( conflictId ) ) )
+ {
+ return scope( node.getDependency() );
+ }
+
+ int depth = parentNodes.size();
+ scopes( depth, node.getDependency() );
+ if ( depth > 0 )
+ {
+ scopeDeriver.deriveScope( scopeCtx );
+ }
+ return scopeCtx.derivedScope;
+ }
+
+ private void scopes( int parent, Dependency child )
+ {
+ scopeCtx.parentScope = ( parent > 0 ) ? parentScopes.get( parent - 1 ) : null;
+ scopeCtx.derivedScope = scope( child );
+ scopeCtx.childScope = scopeCtx.derivedScope;
+ }
+
+ private String scope( Dependency dependency )
+ {
+ return ( dependency != null ) ? dependency.getScope() : null;
+ }
+
+ private boolean deriveOptional( DependencyNode node, Object conflictId )
+ {
+ Dependency dep = node.getDependency();
+ boolean optional = ( dep != null ) ? dep.isOptional() : false;
+// sonatype aether 1.7
+ if ( optional
+ || ( nodeData.getData( node ).get( NODE_DATA_ORIGINAL_OPTIONALITY ) != null
+ && ( (Boolean) nodeData.getData( node ).get( NODE_DATA_ORIGINAL_OPTIONALITY ) ).booleanValue() )
+ || ( conflictId != null && resolvedIds.containsKey( conflictId ) ) )
+// sonatype aether 1.13.1
+// if ( optional
+// || ( node.getData().get( NODE_DATA_ORIGINAL_OPTIONALITY ) != null
+// && ( (Boolean) node.getData().get( NODE_DATA_ORIGINAL_OPTIONALITY ) ).booleanValue() )
+// || ( conflictId != null && resolvedIds.containsKey( conflictId ) ) )
+
+// eclipse aether 0.90
+// if ( optional || ( node.getManagedBits() & DependencyNode.MANAGED_OPTIONAL ) != 0
+// || ( conflictId != null && resolvedIds.containsKey( conflictId ) ) )
+ {
+ return optional;
+ }
+ int depth = parentNodes.size();
+ return ( depth > 0 ) ? parentOptionals.get( depth - 1 ) : false;
+ }
+
+ }
+
+ /**
+ * A context used to hold information that is relevant for deriving the scope of a child dependency.
+ *
+ * @see ScopeDeriver
+ * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may
+ * change without notice and only exists to enable unit testing.
+ */
+ public static final class ScopeContext
+ {
+
+ String parentScope;
+
+ String childScope;
+
+ String derivedScope;
+
+ /**
+ * Creates a new scope context with the specified properties.
+ *
+ * @param parentScope The scope of the parent dependency, may be {@code null}.
+ * @param childScope The scope of the child dependency, may be {@code null}.
+ * @noreference This class is not intended to be instantiated by clients in production code, the constructor may
+ * change without notice and only exists to enable unit testing.
+ */
+ public ScopeContext( String parentScope, String childScope )
+ {
+ this.parentScope = ( parentScope != null ) ? parentScope : "";
+ derivedScope = ( childScope != null ) ? childScope : "";
+ this.childScope = derivedScope;
+ }
+
+ /**
+ * Gets the scope of the parent dependency. This is usually the scope that was derived by earlier invocations of
+ * the scope deriver.
+ *
+ * @return The scope of the parent dependency, never {@code null}.
+ */
+ public String getParentScope()
+ {
+ return parentScope;
+ }
+
+ /**
+ * Gets the original scope of the child dependency. This is the scope that was declared in the artifact
+ * descriptor of the parent dependency.
+ *
+ * @return The original scope of the child dependency, never {@code null}.
+ */
+ public String getChildScope()
+ {
+ return childScope;
+ }
+
+ /**
+ * Gets the derived scope of the child dependency. This is initially equal to {@link #getChildScope()} until the
+ * scope deriver makes changes.
+ *
+ * @return The derived scope of the child dependency, never {@code null}.
+ */
+ public String getDerivedScope()
+ {
+ return derivedScope;
+ }
+
+ /**
+ * Sets the derived scope of the child dependency.
+ *
+ * @param derivedScope The derived scope of the dependency, may be {@code null}.
+ */
+ public void setDerivedScope( String derivedScope )
+ {
+ this.derivedScope = ( derivedScope != null ) ? derivedScope : "";
+ }
+
+ }
+
+ /**
+ * A conflicting dependency.
+ *
+ * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may
+ * change without notice and only exists to enable unit testing.
+ */
+ public static final class ConflictItem
+ {
+
+ // nodes can share child lists, we care about the unique owner of a child node which is the child list
+ final List<DependencyNode> parent;
+
+ // only for debugging/toString() to help identify the parent node(s)
+ final Artifact artifact;
+
+ final DependencyNode node;
+
+ int depth;
+
+ // we start with String and update to Set<String> if needed
+ Object scopes;
+
+ // bit field of OPTIONAL_FALSE and OPTIONAL_TRUE
+ int optionalities;
+
+ /**
+ * Bit flag indicating whether one or more paths consider the dependency non-optional.
+ */
+ public static final int OPTIONAL_FALSE = 0x01;
+
+ /**
+ * Bit flag indicating whether one or more paths consider the dependency optional.
+ */
+ public static final int OPTIONAL_TRUE = 0x02;
+
+ ConflictItem( DependencyNode parent, DependencyNode node, String scope, boolean optional )
+ {
+ if ( parent != null )
+ {
+ this.parent = parent.getChildren();
+ this.artifact = parent.getDependency().getArtifact();
+// this.artifact = parent.getArtifact();
+ }
+ else
+ {
+ this.parent = null;
+ this.artifact = null;
+ }
+ this.node = node;
+ this.scopes = scope;
+ this.optionalities = optional ? OPTIONAL_TRUE : OPTIONAL_FALSE;
+ }
+
+ /**
+ * Creates a new conflict item with the specified properties.
+ *
+ * @param parent The parent node of the conflicting dependency, may be {@code null}.
+ * @param node The conflicting dependency, must not be {@code null}.
+ * @param depth The zero-based depth of the conflicting dependency.
+ * @param optionalities The optionalities the dependency was encountered with, encoded as a bit field consisting
+ * of {@link ConflictResolver.ConflictItem#OPTIONAL_TRUE} and
+ * {@link ConflictResolver.ConflictItem#OPTIONAL_FALSE}.
+ * @param scopes The derived scopes of the conflicting dependency, must not be {@code null}.
+ * @noreference This class is not intended to be instantiated by clients in production code, the constructor may
+ * change without notice and only exists to enable unit testing.
+ */
+ public ConflictItem( DependencyNode parent, DependencyNode node, int depth, int optionalities,
+ String... scopes )
+ {
+ this.parent = ( parent != null ) ? parent.getChildren() : null;
+ this.artifact = ( parent != null ) ? parent.getDependency().getArtifact() : null;
+// this.artifact = ( parent != null ) ? parent.getArtifact() : null;
+ this.node = node;
+ this.depth = depth;
+ this.optionalities = optionalities;
+ this.scopes = Arrays.asList( scopes );
+ }
+
+ /**
+ * Determines whether the specified conflict item is a sibling of this item.
+ *
+ * @param item The other conflict item, must not be {@code null}.
+ * @return {@code true} if the given item has the same parent as this item, {@code false} otherwise.
+ */
+ public boolean isSibling( ConflictItem item )
+ {
+ return parent == item.parent;
+ }
+
+ /**
+ * Gets the dependency node involved in the conflict.
+ *
+ * @return The involved dependency node, never {@code null}.
+ */
+ public DependencyNode getNode()
+ {
+ return node;
+ }
+
+ /**
+ * Gets the dependency involved in the conflict, short for {@code getNode.getDependency()}.
+ *
+ * @return The involved dependency, never {@code null}.
+ */
+ public Dependency getDependency()
+ {
+ return node.getDependency();
+ }
+
+ /**
+ * Gets the zero-based depth at which the conflicting node occurs in the graph. As such, the depth denotes the
+ * number of parent nodes. If actually multiple paths lead to the node, the return value denotes the smallest
+ * possible depth.
+ *
+ * @return The zero-based depth of the node in the graph.
+ */
+ public int getDepth()
+ {
+ return depth;
+ }
+
+ /**
+ * Gets the derived scopes of the dependency. In general, the same dependency node could be reached via
+ * different paths and each path might result in a different derived scope.
+ *
+ * @see ScopeDeriver
+ * @return The (read-only) set of derived scopes of the dependency, never {@code null}.
+ */
+ @SuppressWarnings( "unchecked" )
+ public Collection<String> getScopes()
+ {
+ if ( scopes instanceof String )
+ {
+ return Collections.singleton( (String) scopes );
+ }
+ return (Collection<String>) scopes;
+ }
+
+ @SuppressWarnings( "unchecked" )
+ void addScope( String scope )
+ {
+ if ( scopes instanceof Collection )
+ {
+ ( (Collection<String>) scopes ).add( scope );
+ }
+ else if ( !scopes.equals( scope ) )
+ {
+ Collection<Object> set = new HashSet<Object>();
+ set.add( scopes );
+ set.add( scope );
+ scopes = set;
+ }
+ }
+
+ /**
+ * Gets the derived optionalities of the dependency. In general, the same dependency node could be reached via
+ * different paths and each path might result in a different derived optionality.
+ *
+ * @return A bit field consisting of {@link ConflictResolver.ConflictItem#OPTIONAL_FALSE} and/or
+ * {@link ConflictResolver.ConflictItem#OPTIONAL_TRUE} indicating the derived optionalities the
+ * dependency was encountered with.
+ */
+ public int getOptionalities()
+ {
+ return optionalities;
+ }
+
+ void addOptional( boolean optional )
+ {
+ optionalities |= optional ? OPTIONAL_TRUE : OPTIONAL_FALSE;
+ }
+
+ @Override
+ public String toString()
+ {
+ return node + " @ " + depth + " < " + artifact;
+ }
+
+ }
+
+ /**
+ * A context used to hold information that is relevant for resolving version and scope conflicts.
+ *
+ * @see VersionSelector
+ * @see ScopeSelector
+ * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may
+ * change without notice and only exists to enable unit testing.
+ */
+ public static final class ConflictContext
+ {
+
+ final DependencyNode root;
+
+ final Map<?, ?> conflictIds;
+
+ final Collection<ConflictItem> items;
+
+ Object conflictId;
+
+ ConflictItem winner;
+
+ String scope;
+
+ Boolean optional;
+
+ ConflictContext( DependencyNode root, Map<?, ?> conflictIds, Collection<ConflictItem> items )
+ {
+ this.root = root;
+ this.conflictIds = conflictIds;
+ this.items = Collections.unmodifiableCollection( items );
+ }
+
+ /**
+ * Creates a new conflict context.
+ *
+ * @param root The root node of the dependency graph, must not be {@code null}.
+ * @param conflictId The conflict id for the set of conflicting dependencies in this context, must not be
+ * {@code null}.
+ * @param conflictIds The mapping from dependency node to conflict id, must not be {@code null}.
+ * @param items The conflict items in this context, must not be {@code null}.
+ * @noreference This class is not intended to be instantiated by clients in production code, the constructor may
+ * change without notice and only exists to enable unit testing.
+ */
+ public ConflictContext( DependencyNode root, Object conflictId, Map<DependencyNode, Object> conflictIds,
+ Collection<ConflictItem> items )
+ {
+ this( root, conflictIds, items );
+ this.conflictId = conflictId;
+ }
+
+ /**
+ * Gets the root node of the dependency graph being transformed.
+ *
+ * @return The root node of the dependeny graph, never {@code null}.
+ */
+ public DependencyNode getRoot()
+ {
+ return root;
+ }
+
+ /**
+ * Determines whether the specified dependency node belongs to this conflict context.
+ *
+ * @param node The dependency node to check, must not be {@code null}.
+ * @return {@code true} if the given node belongs to this conflict context, {@code false} otherwise.
+ */
+ public boolean isIncluded( DependencyNode node )
+ {
+ return conflictId.equals( conflictIds.get( node ) );
+ }
+
+ /**
+ * Gets the collection of conflict items in this context.
+ *
+ * @return The (read-only) collection of conflict items in this context, never {@code null}.
+ */
+ public Collection<ConflictItem> getItems()
+ {
+ return items;
+ }
+
+ /**
+ * Gets the conflict item which has been selected as the winner among the conflicting dependencies.
+ *
+ * @return The winning conflict item or {@code null} if not set yet.
+ */
+ public ConflictItem getWinner()
+ {
+ return winner;
+ }
+
+ /**
+ * Sets the conflict item which has been selected as the winner among the conflicting dependencies.
+ *
+ * @param winner The winning conflict item, may be {@code null}.
+ */
+ public void setWinner( ConflictItem winner )
+ {
+ this.winner = winner;
+ }
+
+ /**
+ * Gets the effective scope of the winning dependency.
+ *
+ * @return The effective scope of the winning dependency or {@code null} if none.
+ */
+ public String getScope()
+ {
+ return scope;
+ }
+
+ /**
+ * Sets the effective scope of the winning dependency.
+ *
+ * @param scope The effective scope, may be {@code null}.
+ */
+ public void setScope( String scope )
+ {
+ this.scope = scope;
+ }
+
+ /**
+ * Gets the effective optional flag of the winning dependency.
+ *
+ * @return The effective optional flag or {@code null} if none.
+ */
+ public Boolean getOptional()
+ {
+ return optional;
+ }
+
+ /**
+ * Sets the effective optional flag of the winning dependency.
+ *
+ * @param optional The effective optional flag, may be {@code null}.
+ */
+ public void setOptional( Boolean optional )
+ {
+ this.optional = optional;
+ }
+
+ @Override
+ public String toString()
+ {
+ return winner + " @ " + scope + " < " + items;
+ }
+
+ }
+
+ /**
+ * An extension point of {@link ConflictResolver} that determines the winner among conflicting dependencies. The
+ * winning node (and its children) will be retained in the dependency graph, the other nodes will get removed. The
+ * version selector does not need to deal with potential scope conflicts, these will be addressed afterwards by the
+ * {@link ScopeSelector}. Implementations must be stateless.
+ */
+ public abstract static class VersionSelector
+ {
+
+ /**
+ * Retrieves the version selector for use during the specified graph transformation. The conflict resolver calls
+ * this method once per
+ * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to
+ * allow implementations to prepare any auxiliary data that is needed for their operation. Given that
+ * implementations need to be stateless, a new instance needs to be returned to hold such auxiliary data. The
+ * default implementation simply returns the current instance which is appropriate for implementations which do
+ * not require auxiliary data.
+ *
+ * @param root The root node of the (possibly cyclic!) graph to transform, must not be {@code null}.
+ * @param context The graph transformation context, must not be {@code null}.
+ * @return The scope deriver to use for the given graph transformation, never {@code null}.
+ * @throws RepositoryException If the instance could not be retrieved.
+ */
+ public VersionSelector getInstance( DependencyNode root, DependencyGraphTransformationContext context )
+ throws RepositoryException
+ {
+ return this;
+ }
+
+ /**
+ * Determines the winning node among conflicting dependencies. Implementations will usually iterate
+ * {@link ConflictContext#getItems()}, inspect {@link ConflictItem#getNode()} and eventually call
+ * {@link ConflictContext#setWinner(ConflictResolver.ConflictItem)} to deliver the winner. Failure to select a
+ * winner will automatically fail the entire conflict resolution.
+ *
+ * @param context The conflict context, must not be {@code null}.
+ * @throws RepositoryException If the version selection failed.
+ */
+ public abstract void selectVersion( ConflictContext context )
+ throws RepositoryException;
+
+ }
+
+ /**
+ * An extension point of {@link ConflictResolver} that determines the effective scope of a dependency from a
+ * potentially conflicting set of {@link ScopeDeriver derived scopes}. The scope selector gets invoked after the
+ * {@link VersionSelector} has picked the winning node. Implementations must be stateless.
+ */
+ public abstract static class ScopeSelector
+ {
+
+ /**
+ * Retrieves the scope selector for use during the specified graph transformation. The conflict resolver calls
+ * this method once per
+ * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to
+ * allow implementations to prepare any auxiliary data that is needed for their operation. Given that
+ * implementations need to be stateless, a new instance needs to be returned to hold such auxiliary data. The
+ * default implementation simply returns the current instance which is appropriate for implementations which do
+ * not require auxiliary data.
+ *
+ * @param root The root node of the (possibly cyclic!) graph to transform, must not be {@code null}.
+ * @param context The graph transformation context, must not be {@code null}.
+ * @return The scope selector to use for the given graph transformation, never {@code null}.
+ * @throws RepositoryException If the instance could not be retrieved.
+ */
+ public ScopeSelector getInstance( DependencyNode root, DependencyGraphTransformationContext context )
+ throws RepositoryException
+ {
+ return this;
+ }
+
+ /**
+ * Determines the effective scope of the dependency given by {@link ConflictContext#getWinner()}.
+ * Implementations will usually iterate {@link ConflictContext#getItems()}, inspect
+ * {@link ConflictItem#getScopes()} and eventually call {@link ConflictContext#setScope(String)} to deliver the
+ * effective scope.
+ *
+ * @param context The conflict context, must not be {@code null}.
+ * @throws RepositoryException If the scope selection failed.
+ */
+ public abstract void selectScope( ConflictContext context )
+ throws RepositoryException;
+
+ }
+
+ /**
+ * An extension point of {@link ConflictResolver} that determines the scope of a dependency in relation to the scope
+ * of its parent. Implementations must be stateless.
+ */
+ public abstract static class ScopeDeriver
+ {
+
+ /**
+ * Retrieves the scope deriver for use during the specified graph transformation. The conflict resolver calls
+ * this method once per
+ * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to
+ * allow implementations to prepare any auxiliary data that is needed for their operation. Given that
+ * implementations need to be stateless, a new instance needs to be returned to hold such auxiliary data. The
+ * default implementation simply returns the current instance which is appropriate for implementations which do
+ * not require auxiliary data.
+ *
+ * @param root The root node of the (possibly cyclic!) graph to transform, must not be {@code null}.
+ * @param context The graph transformation context, must not be {@code null}.
+ * @return The scope deriver to use for the given graph transformation, never {@code null}.
+ * @throws RepositoryException If the instance could not be retrieved.
+ */
+ public ScopeDeriver getInstance( DependencyNode root, DependencyGraphTransformationContext context )
+ throws RepositoryException
+ {
+ return this;
+ }
+
+ /**
+ * Determines the scope of a dependency in relation to the scope of its parent. Implementors need to call
+ * {@link ScopeContext#setDerivedScope(String)} to deliver the result of their calculation. If said method is
+ * not invoked, the conflict resolver will assume the scope of the child dependency remains unchanged.
+ *
+ * @param context The scope context, must not be {@code null}.
+ * @throws RepositoryException If the scope deriviation failed.
+ */
+ public abstract void deriveScope( ScopeContext context )
+ throws RepositoryException;
+
+ }
+
+ /**
+ * An extension point of {@link ConflictResolver} that determines the effective optional flag of a dependency from a
+ * potentially conflicting set of derived optionalities. The optionality selector gets invoked after the
+ * {@link VersionSelector} has picked the winning node. Implementations must be stateless.
+ */
+ public abstract static class OptionalitySelector
+ {
+
+ /**
+ * Retrieves the optionality selector for use during the specified graph transformation. The conflict resolver
+ * calls this method once per
+ * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to
+ * allow implementations to prepare any auxiliary data that is needed for their operation. Given that
+ * implementations need to be stateless, a new instance needs to be returned to hold such auxiliary data. The
+ * default implementation simply returns the current instance which is appropriate for implementations which do
+ * not require auxiliary data.
+ *
+ * @param root The root node of the (possibly cyclic!) graph to transform, must not be {@code null}.
+ * @param context The graph transformation context, must not be {@code null}.
+ * @return The optionality selector to use for the given graph transformation, never {@code null}.
+ * @throws RepositoryException If the instance could not be retrieved.
+ */
+ public OptionalitySelector getInstance( DependencyNode root, DependencyGraphTransformationContext context )
+ throws RepositoryException
+ {
+ return this;
+ }
+
+ /**
+ * Determines the effective optional flag of the dependency given by {@link ConflictContext#getWinner()}.
+ * Implementations will usually iterate {@link ConflictContext#getItems()}, inspect
+ * {@link ConflictItem#getOptionalities()} and eventually call {@link ConflictContext#setOptional(Boolean)} to
+ * deliver the effective optional flag.
+ *
+ * @param context The conflict context, must not be {@code null}.
+ * @throws RepositoryException If the optionality selection failed.
+ */
+ public abstract void selectOptionality( ConflictContext context )
+ throws RepositoryException;
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30DependencyCollector.java b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30DependencyCollector.java
index 4b4eaf2..a52c065 100644
--- a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30DependencyCollector.java
+++ b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30DependencyCollector.java
@@ -25,8 +25,10 @@
import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.handler.ArtifactHandler;
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
+import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.model.Model;
-
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.shared.transfer.dependencies.DependableCoordinate;
import org.apache.maven.shared.transfer.dependencies.collect.CollectorResult;
import org.apache.maven.shared.transfer.dependencies.collect.DependencyCollector;
@@ -36,10 +38,18 @@
import org.sonatype.aether.artifact.Artifact;
import org.sonatype.aether.artifact.ArtifactTypeRegistry;
import org.sonatype.aether.collection.CollectRequest;
+import org.sonatype.aether.collection.CollectResult;
import org.sonatype.aether.collection.DependencyCollectionException;
+import org.sonatype.aether.collection.DependencyGraphTransformer;
+import org.sonatype.aether.collection.DependencySelector;
import org.sonatype.aether.graph.Dependency;
import org.sonatype.aether.repository.RemoteRepository;
+import org.sonatype.aether.util.DefaultRepositorySystemSession;
import org.sonatype.aether.util.artifact.DefaultArtifact;
+import org.sonatype.aether.util.artifact.JavaScopes;
+import org.sonatype.aether.util.graph.selector.AndDependencySelector;
+import org.sonatype.aether.util.graph.selector.ExclusionDependencySelector;
+import org.sonatype.aether.util.graph.selector.OptionalDependencySelector;
/**
* Maven 3.0 implementation of the {@link DependencyCollector}
@@ -141,6 +151,96 @@
return collectDependencies( request );
}
+ @Override
+ public CollectorResult collectDependenciesGraph( ProjectBuildingRequest buildingRequest )
+ throws DependencyCollectorException
+ {
+ try
+ {
+ MavenProject project = buildingRequest.getProject();
+
+ org.apache.maven.artifact.Artifact projectArtifact = project.getArtifact();
+ List<ArtifactRepository> remoteArtifactRepositories = project.getRemoteArtifactRepositories();
+
+ RepositorySystemSession repositorySystemSession = buildingRequest.getRepositorySession();
+
+ DefaultRepositorySystemSession session = new DefaultRepositorySystemSession( repositorySystemSession );
+
+ DependencyGraphTransformer transformer =
+ new Maven30ConflictResolver( new Maven30NearestVersionSelector(), new Maven30JavaScopeSelector(),
+ new Maven30SimpleOptionalitySelector(), new Maven30JavaScopeDeriver(),
+ new Maven30NodeData() );
+ session.setDependencyGraphTransformer( transformer );
+
+ DependencySelector depFilter =
+ new AndDependencySelector( new Maven30DirectScopeDependencySelector( JavaScopes.TEST ),
+ new OptionalDependencySelector(), new ExclusionDependencySelector() );
+ session.setDependencySelector( depFilter );
+
+ session.setConfigProperty( Maven30ConflictResolver.CONFIG_PROP_VERBOSE, true );
+ session.setConfigProperty( "aether.dependencyManager.verbose", true );
+
+ Artifact aetherArtifact =
+ (Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
+ org.apache.maven.artifact.Artifact.class, projectArtifact );
+
+ @SuppressWarnings( "unchecked" )
+ List<RemoteRepository> aetherRepos =
+ (List<RemoteRepository>) Invoker.invoke( RepositoryUtils.class, "toRepos", List.class,
+ remoteArtifactRepositories );
+
+ CollectRequest collectRequest = new CollectRequest();
+ collectRequest.setRoot( new Dependency( aetherArtifact, "" ) );
+ collectRequest.setRepositories( aetherRepos );
+
+ ArtifactTypeRegistry stereotypes = session.getArtifactTypeRegistry();
+ collectDependencyList( collectRequest, project, stereotypes );
+ collectManagedDependencyList( collectRequest, project, stereotypes );
+
+ CollectResult collectResult = repositorySystem.collectDependencies( session, collectRequest );
+
+ return new Maven30CollectorResult( collectResult );
+
+// DependencyNode rootNode = collectResult.getRoot();
+
+// if ( getLogger().isDebugEnabled() )
+// {
+// logTree( rootNode );
+// }
+
+// return buildDependencyNode( null, rootNode, projectArtifact, filter );
+ }
+ catch ( DependencyCollectionException e )
+ {
+ throw new DependencyCollectorException( "Could not collect dependencies: " + e.getResult(), e );
+ }
+ }
+
+ private void collectManagedDependencyList( CollectRequest collectRequest, MavenProject project,
+ ArtifactTypeRegistry typeRegistry )
+ throws DependencyCollectorException
+ {
+ if ( project.getDependencyManagement() != null )
+ {
+ for ( org.apache.maven.model.Dependency dependency : project.getDependencyManagement().getDependencies() )
+ {
+ Dependency aetherDep = toDependency( dependency, typeRegistry );
+ collectRequest.addManagedDependency( aetherDep );
+ }
+ }
+ }
+
+ private void collectDependencyList( CollectRequest collectRequest, MavenProject project,
+ ArtifactTypeRegistry typeRegistry )
+ throws DependencyCollectorException
+ {
+ for ( org.apache.maven.model.Dependency dependency : project.getDependencies() )
+ {
+ Dependency aetherDep = toDependency( dependency, typeRegistry );
+ collectRequest.addDependency( aetherDep );
+ }
+ }
+
private CollectorResult collectDependencies( CollectRequest request )
throws DependencyCollectorException
{
diff --git a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30DirectScopeDependencySelector.java b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30DirectScopeDependencySelector.java
new file mode 100644
index 0000000..debc778
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30DirectScopeDependencySelector.java
@@ -0,0 +1,130 @@
+package org.apache.maven.shared.transfer.dependencies.collect.internal;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.sonatype.aether.collection.DependencyCollectionContext;
+import org.sonatype.aether.collection.DependencySelector;
+import org.sonatype.aether.graph.Dependency;
+
+/**
+ * A dependency selector that excludes dependencies of an specific Scope which occur beyond level one of the dependency
+ * graph.
+ *
+ * @see {@link Dependency#getScope()}
+ * @author Gabriel Belingueres
+ */
+public class Maven30DirectScopeDependencySelector
+ implements DependencySelector
+{
+
+ private final String scope;
+
+ private final int depth;
+
+ public Maven30DirectScopeDependencySelector( String scope )
+ {
+ this( scope, 0 );
+ }
+
+ private Maven30DirectScopeDependencySelector( String scope, int depth )
+ {
+ if ( scope == null )
+ {
+ throw new IllegalArgumentException( "scope is null!" );
+ }
+ this.scope = scope;
+ this.depth = depth;
+ }
+
+ /**
+ * Decides whether the specified dependency should be included in the dependency graph.
+ *
+ * @param dependency The dependency to check, must not be {@code null}.
+ * @return {@code false} if the dependency should be excluded from the children of the current node, {@code true}
+ * otherwise.
+ */
+ @Override
+ public boolean selectDependency( Dependency dependency )
+ {
+ return depth < 2 || !scope.equals( dependency.getScope() );
+ }
+
+ /**
+ * Derives a dependency selector for the specified collection context. When calculating the child selector,
+ * implementors are strongly advised to simply return the current instance if nothing changed to help save memory.
+ *
+ * @param context The dependency collection context, must not be {@code null}.
+ * @return The dependency selector for the target node, must not be {@code null}.
+ */
+ @Override
+ public DependencySelector deriveChildSelector( DependencyCollectionContext context )
+ {
+ if ( depth >= 2 )
+ {
+ return this;
+ }
+
+ return new Maven30DirectScopeDependencySelector( scope, depth + 1 );
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + depth;
+ result = prime * result + ( ( scope == null ) ? 0 : scope.hashCode() );
+ return result;
+ }
+
+ @Override
+ public boolean equals( Object obj )
+ {
+ if ( this == obj )
+ {
+ return true;
+ }
+ if ( obj == null )
+ {
+ return false;
+ }
+ if ( getClass() != obj.getClass() )
+ {
+ return false;
+ }
+ Maven30DirectScopeDependencySelector other = (Maven30DirectScopeDependencySelector) obj;
+ if ( depth != other.depth )
+ {
+ return false;
+ }
+ if ( scope == null )
+ {
+ if ( other.scope != null )
+ {
+ return false;
+ }
+ }
+ else if ( !scope.equals( other.scope ) )
+ {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30JavaScopeDeriver.java b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30JavaScopeDeriver.java
new file mode 100644
index 0000000..70dc35d
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30JavaScopeDeriver.java
@@ -0,0 +1,71 @@
+package org.apache.maven.shared.transfer.dependencies.collect.internal;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.shared.transfer.dependencies.collect.internal.Maven30ConflictResolver.ScopeContext;
+import org.apache.maven.shared.transfer.dependencies.collect.internal.Maven30ConflictResolver.ScopeDeriver;
+import org.sonatype.aether.RepositoryException;
+import org.sonatype.aether.util.artifact.JavaScopes;
+
+/**
+ * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
+ *
+ * @author Gabriel Belingueres
+ */
+public class Maven30JavaScopeDeriver
+ extends ScopeDeriver
+{
+
+ @Override
+ public void deriveScope( ScopeContext context )
+ throws RepositoryException
+ {
+ context.setDerivedScope( getDerivedScope( context.getParentScope(), context.getChildScope() ) );
+ }
+
+ private String getDerivedScope( String parentScope, String childScope )
+ {
+ String derivedScope;
+
+ if ( JavaScopes.SYSTEM.equals( childScope ) || JavaScopes.TEST.equals( childScope ) )
+ {
+ derivedScope = childScope;
+ }
+ else if ( parentScope == null || parentScope.length() <= 0 || JavaScopes.COMPILE.equals( parentScope ) )
+ {
+ derivedScope = childScope;
+ }
+ else if ( JavaScopes.TEST.equals( parentScope ) || JavaScopes.RUNTIME.equals( parentScope ) )
+ {
+ derivedScope = parentScope;
+ }
+ else if ( JavaScopes.SYSTEM.equals( parentScope ) || JavaScopes.PROVIDED.equals( parentScope ) )
+ {
+ derivedScope = JavaScopes.PROVIDED;
+ }
+ else
+ {
+ derivedScope = JavaScopes.RUNTIME;
+ }
+
+ return derivedScope;
+ }
+
+}
diff --git a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30JavaScopeSelector.java b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30JavaScopeSelector.java
new file mode 100644
index 0000000..f569d40
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30JavaScopeSelector.java
@@ -0,0 +1,100 @@
+package org.apache.maven.shared.transfer.dependencies.collect.internal;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.maven.shared.transfer.dependencies.collect.internal.Maven30ConflictResolver.ConflictContext;
+import org.apache.maven.shared.transfer.dependencies.collect.internal.Maven30ConflictResolver.ConflictItem;
+import org.apache.maven.shared.transfer.dependencies.collect.internal.Maven30ConflictResolver.ScopeSelector;
+import org.sonatype.aether.RepositoryException;
+import org.sonatype.aether.util.artifact.JavaScopes;
+
+/**
+ * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
+ *
+ * @author Gabriel Belingueres
+ */
+public class Maven30JavaScopeSelector
+ extends ScopeSelector
+{
+
+ @Override
+ public void selectScope( ConflictContext context )
+ throws RepositoryException
+ {
+ String scope = context.getWinner().getDependency().getScope();
+ if ( !JavaScopes.SYSTEM.equals( scope ) )
+ {
+ scope = chooseEffectiveScope( context.getItems() );
+ }
+ context.setScope( scope );
+ }
+
+ private String chooseEffectiveScope( Collection<ConflictItem> items )
+ {
+ Set<String> scopes = new HashSet<String>();
+ for ( ConflictItem item : items )
+ {
+ if ( item.getDepth() <= 1 )
+ {
+ return item.getDependency().getScope();
+ }
+ scopes.addAll( item.getScopes() );
+ }
+ return chooseEffectiveScope( scopes );
+ }
+
+ private String chooseEffectiveScope( Set<String> scopes )
+ {
+ if ( scopes.size() > 1 )
+ {
+ scopes.remove( JavaScopes.SYSTEM );
+ }
+
+ String effectiveScope = "";
+
+ if ( scopes.size() == 1 )
+ {
+ effectiveScope = scopes.iterator().next();
+ }
+ else if ( scopes.contains( JavaScopes.COMPILE ) )
+ {
+ effectiveScope = JavaScopes.COMPILE;
+ }
+ else if ( scopes.contains( JavaScopes.RUNTIME ) )
+ {
+ effectiveScope = JavaScopes.RUNTIME;
+ }
+ else if ( scopes.contains( JavaScopes.PROVIDED ) )
+ {
+ effectiveScope = JavaScopes.PROVIDED;
+ }
+ else if ( scopes.contains( JavaScopes.TEST ) )
+ {
+ effectiveScope = JavaScopes.TEST;
+ }
+
+ return effectiveScope;
+ }
+
+}
diff --git a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30NearestVersionSelector.java b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30NearestVersionSelector.java
new file mode 100644
index 0000000..c913c54
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30NearestVersionSelector.java
@@ -0,0 +1,181 @@
+package org.apache.maven.shared.transfer.dependencies.collect.internal;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.maven.shared.transfer.dependencies.collect.internal.Maven30ConflictResolver.ConflictContext;
+import org.apache.maven.shared.transfer.dependencies.collect.internal.Maven30ConflictResolver.ConflictItem;
+import org.apache.maven.shared.transfer.dependencies.collect.internal.Maven30ConflictResolver.VersionSelector;
+import org.sonatype.aether.RepositoryException;
+import org.sonatype.aether.collection.UnsolvableVersionConflictException;
+import org.sonatype.aether.graph.DependencyFilter;
+import org.sonatype.aether.graph.DependencyNode;
+import org.sonatype.aether.version.Version;
+import org.sonatype.aether.version.VersionConstraint;
+
+/**
+ * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
+ *
+ * @author Gabriel Belingueres
+ */
+public class Maven30NearestVersionSelector
+ extends VersionSelector
+{
+
+ @Override
+ public void selectVersion( ConflictContext context )
+ throws RepositoryException
+ {
+ ConflictGroup group = new ConflictGroup();
+ for ( ConflictItem item : context.getItems() )
+ {
+ DependencyNode node = item.getNode();
+ VersionConstraint constraint = node.getVersionConstraint();
+
+ boolean backtrack = false;
+ boolean hardConstraint = !constraint.getRanges().isEmpty();
+// boolean hardConstraint = constraint.getRange() != null;
+
+ if ( hardConstraint )
+ {
+ if ( group.constraints.add( constraint ) )
+ {
+ if ( group.winner != null && !constraint.containsVersion( group.winner.getNode().getVersion() ) )
+ {
+ backtrack = true;
+ }
+ }
+ }
+
+ if ( isAcceptable( group, node.getVersion() ) )
+ {
+ group.candidates.add( item );
+
+ if ( backtrack )
+ {
+ backtrack( group, context );
+ }
+ else if ( group.winner == null || isNearer( item, group.winner ) )
+ {
+ group.winner = item;
+ }
+ }
+ else if ( backtrack )
+ {
+ backtrack( group, context );
+ }
+ }
+ context.setWinner( group.winner );
+ }
+
+ private void backtrack( ConflictGroup group, ConflictContext context )
+ throws UnsolvableVersionConflictException
+ {
+ group.winner = null;
+
+ for ( Iterator<ConflictItem> it = group.candidates.iterator(); it.hasNext(); )
+ {
+ ConflictItem candidate = it.next();
+
+ if ( !isAcceptable( group, candidate.getNode().getVersion() ) )
+ {
+ it.remove();
+ }
+ else if ( group.winner == null || isNearer( candidate, group.winner ) )
+ {
+ group.winner = candidate;
+ }
+ }
+
+ if ( group.winner == null )
+ {
+ throw newFailure( context );
+ }
+ }
+
+ private boolean isAcceptable( ConflictGroup group, Version version )
+ {
+ for ( VersionConstraint constraint : group.constraints )
+ {
+ if ( !constraint.containsVersion( version ) )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean isNearer( ConflictItem item1, ConflictItem item2 )
+ {
+ if ( item1.isSibling( item2 ) )
+ {
+ return item1.getNode().getVersion().compareTo( item2.getNode().getVersion() ) > 0;
+ }
+ else
+ {
+ return item1.getDepth() < item2.getDepth();
+ }
+ }
+
+ private UnsolvableVersionConflictException newFailure( final ConflictContext context )
+ {
+ DependencyFilter filter = new DependencyFilter()
+ {
+ public boolean accept( DependencyNode node, List<DependencyNode> parents )
+ {
+ return context.isIncluded( node );
+ }
+ };
+ Maven30PathRecordingDependencyVisitor visitor = new Maven30PathRecordingDependencyVisitor( filter );
+ context.getRoot().accept( visitor );
+ return new UnsolvableVersionConflictException( visitor.getPaths(), null );
+// return new UnsolvableVersionConflictException( visitor.getPaths(), context.conflictId );
+// return new UnsolvableVersionConflictException( visitor.getPaths() );
+ }
+
+ static final class ConflictGroup
+ {
+
+ final Collection<VersionConstraint> constraints;
+
+ final Collection<ConflictItem> candidates;
+
+ ConflictItem winner;
+
+ ConflictGroup()
+ {
+ constraints = new HashSet<VersionConstraint>();
+ candidates = new ArrayList<ConflictItem>( 64 );
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.valueOf( winner );
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30NodeData.java b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30NodeData.java
new file mode 100644
index 0000000..d22f232
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30NodeData.java
@@ -0,0 +1,91 @@
+package org.apache.maven.shared.transfer.dependencies.collect.internal;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+import org.sonatype.aether.graph.DependencyNode;
+
+/**
+ * This class replace the internal data Map lacking inside DependencyNode on earlier Sonatype Aether versions.
+ *
+ * @author Gabriel Belingueres
+ */
+public class Maven30NodeData
+{
+
+ private IdentityHashMap<DependencyNode, Map<Object, Object>> nodeMap =
+ new IdentityHashMap<DependencyNode, Map<Object, Object>>();
+
+ public void putData( DependencyNode node, Object key, Object value )
+ {
+ Map<Object, Object> dataMap = nodeMap.get( node );
+ if ( dataMap == null )
+ {
+ dataMap = Collections.emptyMap();
+ }
+ dataMap = setData( dataMap, key, value );
+ nodeMap.put( node, dataMap );
+ }
+
+ public Map<Object, Object> getData( DependencyNode node )
+ {
+ Map<Object, Object> dataMap = nodeMap.get( node );
+ if ( dataMap == null )
+ {
+ dataMap = Collections.emptyMap();
+ }
+ return dataMap;
+ }
+
+ private Map<Object, Object> setData( Map<Object, Object> data, Object key, Object value )
+ {
+ if ( key == null )
+ {
+ throw new IllegalArgumentException( "key must not be null" );
+ }
+
+ if ( value == null )
+ {
+ if ( !data.isEmpty() )
+ {
+ data.remove( key );
+
+ if ( data.isEmpty() )
+ {
+ data = Collections.emptyMap();
+ }
+ }
+ }
+ else
+ {
+ if ( data.isEmpty() )
+ {
+ data = new HashMap<Object, Object>();
+ }
+ data.put( key, value );
+ }
+
+ return data;
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30PathRecordingDependencyVisitor.java b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30PathRecordingDependencyVisitor.java
new file mode 100644
index 0000000..59f98ec
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30PathRecordingDependencyVisitor.java
@@ -0,0 +1,125 @@
+package org.apache.maven.shared.transfer.dependencies.collect.internal;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.sonatype.aether.graph.DependencyFilter;
+import org.sonatype.aether.graph.DependencyNode;
+import org.sonatype.aether.graph.DependencyVisitor;
+
+/**
+ * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
+ *
+ * @author Gabriel Belingueres
+ */
+public class Maven30PathRecordingDependencyVisitor
+ implements DependencyVisitor
+{
+
+ private final DependencyFilter filter;
+
+ private final List<List<DependencyNode>> paths;
+
+ private final Maven30Stack<DependencyNode> parents;
+
+ private final boolean excludeChildrenOfMatches;
+
+ /**
+ * Creates a new visitor that uses the specified filter to identify terminal nodes of interesting paths. The visitor
+ * will not search for paths going beyond an already matched node.
+ *
+ * @param filter The filter used to select terminal nodes of paths to record, may be {@code null} to match any node.
+ */
+ public Maven30PathRecordingDependencyVisitor( DependencyFilter filter )
+ {
+ this( filter, true );
+ }
+
+ /**
+ * Creates a new visitor that uses the specified filter to identify terminal nodes of interesting paths.
+ *
+ * @param filter The filter used to select terminal nodes of paths to record, may be {@code null} to match any node.
+ * @param excludeChildrenOfMatches Flag controlling whether children of matched nodes should be excluded from the
+ * traversal, thereby ignoring any potential paths to other matching nodes beneath a matching ancestor
+ * node. If {@code true}, all recorded paths will have only one matching node (namely the terminal node),
+ * if {@code false} a recorded path can consist of multiple matching nodes.
+ */
+ public Maven30PathRecordingDependencyVisitor( DependencyFilter filter, boolean excludeChildrenOfMatches )
+ {
+ this.filter = filter;
+ this.excludeChildrenOfMatches = excludeChildrenOfMatches;
+ this.paths = new ArrayList<List<DependencyNode>>();
+ this.parents = new Maven30Stack<DependencyNode>();
+ }
+
+ /**
+ * Gets the filter being used to select terminal nodes.
+ *
+ * @return The filter being used or {@code null} if none.
+ */
+ public DependencyFilter getFilter()
+ {
+ return filter;
+ }
+
+ /**
+ * Gets the paths leading to nodes matching the filter that have been recorded during the graph visit. A path is
+ * given as a sequence of nodes, starting with the root node of the graph and ending with the node that matched the
+ * filter.
+ *
+ * @return The recorded paths, never {@code null}.
+ */
+ public List<List<DependencyNode>> getPaths()
+ {
+ return paths;
+ }
+
+ public boolean visitEnter( DependencyNode node )
+ {
+ boolean accept = filter == null || filter.accept( node, parents );
+
+ parents.push( node );
+
+ if ( accept )
+ {
+ DependencyNode[] path = new DependencyNode[parents.size()];
+ int i = parents.size() - 1;
+ for ( DependencyNode n : parents )
+ {
+ path[i] = n;
+ i--;
+ }
+ paths.add( Arrays.asList( path ) );
+ }
+
+ return !( excludeChildrenOfMatches && accept );
+ }
+
+ public boolean visitLeave( DependencyNode node )
+ {
+ parents.pop();
+
+ return true;
+ }
+
+}
diff --git a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30SimpleOptionalitySelector.java b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30SimpleOptionalitySelector.java
new file mode 100644
index 0000000..4335628
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30SimpleOptionalitySelector.java
@@ -0,0 +1,63 @@
+package org.apache.maven.shared.transfer.dependencies.collect.internal;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Collection;
+
+import org.apache.maven.shared.transfer.dependencies.collect.internal.Maven30ConflictResolver.ConflictContext;
+import org.apache.maven.shared.transfer.dependencies.collect.internal.Maven30ConflictResolver.ConflictItem;
+import org.apache.maven.shared.transfer.dependencies.collect.internal.Maven30ConflictResolver.OptionalitySelector;
+import org.sonatype.aether.RepositoryException;
+
+/**
+ * This class is a copy of their homonymous in the Eclipse Aether library, adapted to work with Sonatype Aether.
+ *
+ * @author Gabriel Belingueres
+ */
+public class Maven30SimpleOptionalitySelector
+ extends OptionalitySelector
+{
+
+ @Override
+ public void selectOptionality( ConflictContext context )
+ throws RepositoryException
+ {
+ boolean optional = chooseEffectiveOptionality( context.getItems() );
+ context.setOptional( optional );
+ }
+
+ private boolean chooseEffectiveOptionality( Collection<ConflictItem> items )
+ {
+ boolean optional = true;
+ for ( ConflictItem item : items )
+ {
+ if ( item.getDepth() <= 1 )
+ {
+ return item.getDependency().isOptional();
+ }
+ if ( ( item.getOptionalities() & ConflictItem.OPTIONAL_FALSE ) != 0 )
+ {
+ optional = false;
+ }
+ }
+ return optional;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30Stack.java b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30Stack.java
new file mode 100644
index 0000000..e15373c
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven30Stack.java
@@ -0,0 +1,89 @@
+package org.apache.maven.shared.transfer.dependencies.collect.internal;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.AbstractList;
+import java.util.NoSuchElementException;
+import java.util.RandomAccess;
+
+/**
+ * This class is a copy of the Stack class in a more recent version of Sonatype Aether, since 1.7 does not have it.
+ *
+ * @param <E> the type of the elements of the stack.
+ * @author Gabriel Belingueres
+ */
+public class Maven30Stack<E>
+ extends AbstractList<E>
+ implements RandomAccess
+{
+
+ private Object[] elements = new Object[64];
+
+ private int size;
+
+ public void push( E element )
+ {
+ if ( size >= elements.length )
+ {
+ Object[] tmp = new Object[size + 64];
+ System.arraycopy( elements, 0, tmp, 0, elements.length );
+ elements = tmp;
+ }
+ elements[size++] = element;
+ }
+
+ @SuppressWarnings( "unchecked" )
+ public E pop()
+ {
+ if ( size <= 0 )
+ {
+ throw new NoSuchElementException();
+ }
+ return (E) elements[--size];
+ }
+
+ @SuppressWarnings( "unchecked" )
+ public E peek()
+ {
+ if ( size <= 0 )
+ {
+ return null;
+ }
+ return (E) elements[size - 1];
+ }
+
+ @SuppressWarnings( "unchecked" )
+ @Override
+ public E get( int index )
+ {
+ if ( index < 0 || index >= size )
+ {
+ throw new IndexOutOfBoundsException( "Index: " + index + ", Size: " + size );
+ }
+ return (E) elements[size - index - 1];
+ }
+
+ @Override
+ public int size()
+ {
+ return size;
+ }
+
+}
diff --git a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven31CollectorResult.java b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven31CollectorResult.java
index 8d46390..9ff9f06 100644
--- a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven31CollectorResult.java
+++ b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven31CollectorResult.java
@@ -20,16 +20,25 @@
*/
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
+import org.apache.maven.shared.dependency.graph.internal.DefaultDependencyNode;
import org.apache.maven.shared.transfer.dependencies.collect.CollectorResult;
+import org.apache.maven.shared.transfer.dependencies.collect.DependencyCollectorException;
+import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.collection.CollectResult;
+import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.graph.DependencyVisitor;
import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
+import org.eclipse.aether.version.VersionConstraint;
/**
* CollectorResult wrapper around {@link CollectResult}
@@ -82,4 +91,81 @@
return mavenRepositories;
}
+ @Override
+ public org.apache.maven.shared.dependency.graph.DependencyNode getDependencyGraphRoot()
+ {
+ DependencyNode root = collectResult.getRoot();
+ org.apache.maven.artifact.Artifact rootArtifact = getDependencyArtifact( root.getDependency() );
+
+ return buildDependencyNode( null, root, rootArtifact, null );
+ }
+
+ // CHECKSTYLE_OFF: LineLength
+ private org.apache.maven.shared.dependency.graph.DependencyNode buildDependencyNode( org.apache.maven.shared.dependency.graph.DependencyNode parent,
+ DependencyNode node,
+ org.apache.maven.artifact.Artifact artifact,
+ ArtifactFilter filter )
+ // CHECKSTYLE_ON: LineLength
+ {
+ String premanagedVersion = DependencyManagerUtils.getPremanagedVersion( node );
+ String premanagedScope = DependencyManagerUtils.getPremanagedScope( node );
+
+ Boolean optional = null;
+ if ( node.getDependency() != null )
+ {
+ optional = node.getDependency().isOptional();
+ }
+
+ DefaultDependencyNode current =
+ new DefaultDependencyNode( parent, artifact, premanagedVersion, premanagedScope,
+ getVersionSelectedFromRange( node.getVersionConstraint() ), optional );
+
+ List<org.apache.maven.shared.dependency.graph.DependencyNode> nodes =
+ new ArrayList<org.apache.maven.shared.dependency.graph.DependencyNode>( node.getChildren().size() );
+ for ( DependencyNode child : node.getChildren() )
+ {
+ org.apache.maven.artifact.Artifact childArtifact = getDependencyArtifact( child.getDependency() );
+
+ if ( ( filter == null ) || filter.include( childArtifact ) )
+ {
+ nodes.add( buildDependencyNode( current, child, childArtifact, filter ) );
+ }
+ }
+
+ current.setChildren( Collections.unmodifiableList( nodes ) );
+
+ return current;
+ }
+
+ private String getVersionSelectedFromRange( VersionConstraint constraint )
+ {
+ if ( ( constraint == null ) || ( constraint.getVersion() != null ) )
+ {
+ return null;
+ }
+
+ return constraint.getRange().toString();
+ }
+
+ private org.apache.maven.artifact.Artifact getDependencyArtifact( Dependency dep )
+ {
+ Artifact artifact = dep.getArtifact();
+
+ try
+ {
+ org.apache.maven.artifact.Artifact mavenArtifact =
+ (org.apache.maven.artifact.Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
+ org.eclipse.aether.artifact.Artifact.class, artifact );
+
+ mavenArtifact.setScope( dep.getScope() );
+ mavenArtifact.setOptional( dep.isOptional() );
+
+ return mavenArtifact;
+ }
+ catch ( DependencyCollectorException e )
+ {
+ // ReflectionException should not happen
+ throw new RuntimeException( e.getMessage(), e );
+ }
+ }
}
diff --git a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven31DependencyCollector.java b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven31DependencyCollector.java
index 0ee0385..e557a1e 100644
--- a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven31DependencyCollector.java
+++ b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven31DependencyCollector.java
@@ -25,20 +25,37 @@
import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.handler.ArtifactHandler;
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
+import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.model.Model;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.shared.transfer.dependencies.DependableCoordinate;
import org.apache.maven.shared.transfer.dependencies.collect.CollectorResult;
import org.apache.maven.shared.transfer.dependencies.collect.DependencyCollector;
import org.apache.maven.shared.transfer.dependencies.collect.DependencyCollectorException;
+import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.ArtifactTypeRegistry;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.collection.CollectResult;
import org.eclipse.aether.collection.DependencyCollectionException;
+import org.eclipse.aether.collection.DependencyGraphTransformer;
+import org.eclipse.aether.collection.DependencySelector;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.util.artifact.JavaScopes;
+import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
+import org.eclipse.aether.util.graph.selector.AndDependencySelector;
+import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector;
+import org.eclipse.aether.util.graph.selector.OptionalDependencySelector;
+import org.eclipse.aether.util.graph.transformer.ConflictResolver;
+import org.eclipse.aether.util.graph.transformer.JavaScopeDeriver;
+import org.eclipse.aether.util.graph.transformer.JavaScopeSelector;
+import org.eclipse.aether.util.graph.transformer.NearestVersionSelector;
+import org.eclipse.aether.util.graph.transformer.SimpleOptionalitySelector;
/**
* Maven 3.1+ implementation of the {@link DependencyCollector}
@@ -155,6 +172,105 @@
}
}
+ @Override
+ public CollectorResult collectDependenciesGraph( ProjectBuildingRequest buildingRequest )
+ throws DependencyCollectorException
+ {
+ DefaultRepositorySystemSession session = null;
+ try
+ {
+ MavenProject project = buildingRequest.getProject();
+
+ org.apache.maven.artifact.Artifact projectArtifact = project.getArtifact();
+ List<ArtifactRepository> remoteArtifactRepositories = project.getRemoteArtifactRepositories();
+
+ DefaultRepositorySystemSession repositorySession =
+ (DefaultRepositorySystemSession) Invoker.invoke( buildingRequest, "getRepositorySession" );
+
+ session = new DefaultRepositorySystemSession( repositorySession );
+
+ DependencyGraphTransformer transformer =
+ new ConflictResolver( new NearestVersionSelector(), new JavaScopeSelector(),
+ new SimpleOptionalitySelector(), new JavaScopeDeriver() );
+ session.setDependencyGraphTransformer( transformer );
+
+ DependencySelector depFilter =
+ new AndDependencySelector( new Maven31DirectScopeDependencySelector( JavaScopes.TEST ),
+ new OptionalDependencySelector(), new ExclusionDependencySelector() );
+ session.setDependencySelector( depFilter );
+
+ session.setConfigProperty( ConflictResolver.CONFIG_PROP_VERBOSE, true );
+ session.setConfigProperty( DependencyManagerUtils.CONFIG_PROP_VERBOSE, true );
+
+ Artifact aetherArtifact =
+ (Artifact) Invoker.invoke( RepositoryUtils.class, "toArtifact",
+ org.apache.maven.artifact.Artifact.class, projectArtifact );
+
+ @SuppressWarnings( "unchecked" )
+ List<org.eclipse.aether.repository.RemoteRepository> aetherRepos =
+ (List<org.eclipse.aether.repository.RemoteRepository>) Invoker.invoke( RepositoryUtils.class, "toRepos",
+ List.class,
+ remoteArtifactRepositories );
+
+ CollectRequest collectRequest = new CollectRequest();
+ collectRequest.setRoot( new org.eclipse.aether.graph.Dependency( aetherArtifact, "" ) );
+ collectRequest.setRepositories( aetherRepos );
+
+ org.eclipse.aether.artifact.ArtifactTypeRegistry stereotypes = session.getArtifactTypeRegistry();
+ collectDependencyList( collectRequest, project, stereotypes );
+ collectManagedDependencyList( collectRequest, project, stereotypes );
+
+ CollectResult collectResult = repositorySystem.collectDependencies( session, collectRequest );
+
+ return new Maven31CollectorResult( collectResult );
+
+// org.eclipse.aether.graph.DependencyNode rootNode = collectResult.getRoot();
+
+// if ( getLogger().isDebugEnabled() )
+// {
+// logTree( rootNode );
+// }
+
+// return buildDependencyNode( null, rootNode, projectArtifact, filter );
+ }
+ catch ( DependencyCollectionException e )
+ {
+ throw new DependencyCollectorException( "Could not collect dependencies: " + e.getResult(), e );
+ }
+ finally
+ {
+ if ( session != null )
+ {
+ session.setReadOnly();
+ }
+ }
+ }
+
+ private void collectManagedDependencyList( CollectRequest collectRequest, MavenProject project,
+ ArtifactTypeRegistry typeRegistry )
+ throws DependencyCollectorException
+ {
+ if ( project.getDependencyManagement() != null )
+ {
+ for ( org.apache.maven.model.Dependency dependency : project.getDependencyManagement().getDependencies() )
+ {
+ Dependency aetherDep = toDependency( dependency, typeRegistry );
+ collectRequest.addManagedDependency( aetherDep );
+ }
+ }
+ }
+
+ private void collectDependencyList( CollectRequest collectRequest, MavenProject project,
+ org.eclipse.aether.artifact.ArtifactTypeRegistry typeRegistry )
+ throws DependencyCollectorException
+ {
+ for ( org.apache.maven.model.Dependency dependency : project.getDependencies() )
+ {
+ Dependency aetherDep = toDependency( dependency, typeRegistry );
+ collectRequest.addDependency( aetherDep );
+ }
+ }
+
private static Dependency toDependency( org.apache.maven.model.Dependency root, ArtifactTypeRegistry typeRegistry )
throws DependencyCollectorException
{
diff --git a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven31DirectScopeDependencySelector.java b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven31DirectScopeDependencySelector.java
new file mode 100644
index 0000000..e930597
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/Maven31DirectScopeDependencySelector.java
@@ -0,0 +1,131 @@
+package org.apache.maven.shared.transfer.dependencies.collect.internal;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.eclipse.aether.collection.DependencyCollectionContext;
+import org.eclipse.aether.collection.DependencySelector;
+import org.eclipse.aether.graph.Dependency;
+
+/**
+ * A dependency selector that excludes dependencies of an specific Scope which occur beyond level one of the dependency
+ * graph.
+ *
+ * @see {@link Dependency#getScope()}
+ * @author Gabriel Belingueres
+ */
+public class Maven31DirectScopeDependencySelector
+ implements DependencySelector
+{
+
+ private final String scope;
+
+ private final int depth;
+
+ public Maven31DirectScopeDependencySelector( String scope )
+ {
+ this( scope, 0 );
+ }
+
+ private Maven31DirectScopeDependencySelector( String scope, int depth )
+ {
+ if ( scope == null )
+ {
+ throw new IllegalArgumentException( "scope is null!" );
+ }
+ this.scope = scope;
+ this.depth = depth;
+ }
+
+ /**
+ * Decides whether the specified dependency should be included in the dependency graph.
+ *
+ * @param dependency The dependency to check, must not be {@code null}.
+ * @return {@code false} if the dependency should be excluded from the children of the current node, {@code true}
+ * otherwise.
+ */
+ @Override
+ public boolean selectDependency( Dependency dependency )
+ {
+ return depth < 2 || !scope.equals( dependency.getScope() );
+ }
+
+ /**
+ * Derives a dependency selector for the specified collection context. When calculating the child selector,
+ * implementors are strongly advised to simply return the current instance if nothing changed to help save memory.
+ *
+ * @param context The dependency collection context, must not be {@code null}.
+ * @return The dependency selector for the target node, must not be {@code null}.
+ */
+ @Override
+ public DependencySelector deriveChildSelector( DependencyCollectionContext context )
+ {
+ if ( depth >= 2 )
+ {
+ return this;
+ }
+
+ return new Maven31DirectScopeDependencySelector( scope, depth + 1 );
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + depth;
+ result = prime * result + ( ( scope == null ) ? 0 : scope.hashCode() );
+ return result;
+ }
+
+ @Override
+ public boolean equals( Object obj )
+ {
+ if ( this == obj )
+ {
+ return true;
+ }
+ if ( obj == null )
+ {
+ return false;
+ }
+ if ( getClass() != obj.getClass() )
+ {
+ return false;
+ }
+ Maven31DirectScopeDependencySelector other = (Maven31DirectScopeDependencySelector) obj;
+ if ( depth != other.depth )
+ {
+ return false;
+ }
+ if ( scope == null )
+ {
+ if ( other.scope != null )
+ {
+ return false;
+ }
+ }
+ else if ( !scope.equals( other.scope ) )
+ {
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/MavenDependencyCollector.java b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/MavenDependencyCollector.java
index 53823f8..ff8c314 100644
--- a/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/MavenDependencyCollector.java
+++ b/src/main/java/org/apache/maven/shared/transfer/dependencies/collect/internal/MavenDependencyCollector.java
@@ -21,6 +21,7 @@
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
+import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.shared.transfer.dependencies.DependableCoordinate;
import org.apache.maven.shared.transfer.dependencies.collect.CollectorResult;
import org.apache.maven.shared.transfer.dependencies.collect.DependencyCollectorException;
@@ -41,4 +42,7 @@
CollectorResult collectDependencies( Model root )
throws DependencyCollectorException;
+ CollectorResult collectDependenciesGraph( ProjectBuildingRequest buildingRequest )
+ throws DependencyCollectorException;
+
}