Major changes to maven-dependency-tree:
o support for node states: included, omitted for conflict, etc.
o node filter API and various implementations for pruning the tree
o node visitor API and various implementations for processing the tree
o lots of tests and Javadoc
git-svn-id: https://svn.apache.org/repos/asf/maven/shared/trunk@549521 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index 16c967c..d82ed14 100644
--- a/pom.xml
+++ b/pom.xml
@@ -66,12 +66,18 @@
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-project</artifactId>
- <version>2.0.6</version>
+ <version>2.0.8-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>jmock</groupId>
+ <artifactId>jmock</artifactId>
+ <version>1.2.0</version>
+ <scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-plugin-testing-harness</artifactId>
- <version>1.0</version>
+ <version>1.1-SNAPSHOT</version>
<scope>test</scope>
</dependency>
</dependencies>
diff --git a/src/main/java/org/apache/maven/shared/dependency/tree/DefaultDependencyTreeBuilder.java b/src/main/java/org/apache/maven/shared/dependency/tree/DefaultDependencyTreeBuilder.java
index afff68c..d35f12e 100644
--- a/src/main/java/org/apache/maven/shared/dependency/tree/DefaultDependencyTreeBuilder.java
+++ b/src/main/java/org/apache/maven/shared/dependency/tree/DefaultDependencyTreeBuilder.java
@@ -20,23 +20,16 @@
*/
import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
import java.util.Map;
-import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactCollector;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
-import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
-import org.apache.maven.artifact.versioning.VersionRange;
-import org.apache.maven.model.Dependency;
-import org.apache.maven.model.DependencyManagement;
+import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.project.MavenProject;
-import org.apache.maven.project.ProjectBuildingException;
-import org.apache.maven.project.artifact.InvalidDependencyVersionException;
+import org.apache.maven.shared.dependency.tree.traversal.CollectingDependencyNodeVisitor;
/**
* Default implementation of <code>DependencyTreeBuilder</code>.
@@ -49,7 +42,7 @@
*/
public class DefaultDependencyTreeBuilder implements DependencyTreeBuilder
{
- // DependencyTreeAssembler methods ----------------------------------------
+ // DependencyTreeBuilder methods ------------------------------------------
/*
* @see org.apache.maven.shared.dependency.tree.DependencyTreeBuilder#buildDependencyTree(org.apache.maven.project.MavenProject,
@@ -61,93 +54,41 @@
ArtifactFactory factory, ArtifactMetadataSource metadataSource,
ArtifactCollector collector ) throws DependencyTreeBuilderException
{
+ DependencyNode rootNode = buildDependencyTree( project, repository, factory, metadataSource, null, collector );
+
+ CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
+ rootNode.accept( collectingVisitor );
+
+ return new DependencyTree( rootNode, collectingVisitor.getNodes() );
+ }
+
+ /*
+ * @see org.apache.maven.shared.dependency.tree.DependencyTreeBuilder#buildDependencyTree(org.apache.maven.project.MavenProject,
+ * org.apache.maven.artifact.repository.ArtifactRepository, org.apache.maven.artifact.factory.ArtifactFactory,
+ * org.apache.maven.artifact.metadata.ArtifactMetadataSource,
+ * org.apache.maven.artifact.resolver.filter.ArtifactFilter,
+ * org.apache.maven.artifact.resolver.ArtifactCollector)
+ */
+ public DependencyNode buildDependencyTree( MavenProject project, ArtifactRepository repository,
+ ArtifactFactory factory, ArtifactMetadataSource metadataSource,
+ ArtifactFilter filter, ArtifactCollector collector )
+ throws DependencyTreeBuilderException
+ {
DependencyTreeResolutionListener listener = new DependencyTreeResolutionListener();
try
{
- Map managedVersions = getManagedVersionMap( project, factory );
-
- // TODO site:run Why do we need to resolve this...
- if ( project.getDependencyArtifacts() == null )
- {
- project.setDependencyArtifacts( project.createArtifacts( factory, null, null ) );
- }
+ Map managedVersions = project.getManagedVersionMap();
collector.collect( project.getDependencyArtifacts(), project.getArtifact(), managedVersions, repository,
- project.getRemoteArtifactRepositories(), metadataSource, null,
+ project.getRemoteArtifactRepositories(), metadataSource, filter,
Collections.singletonList( listener ) );
- return new DependencyTree( listener.getRootNode(), listener.getNodes() );
- }
- catch ( ProjectBuildingException exception )
- {
- throw new DependencyTreeBuilderException( "Cannot build project dependency tree", exception );
- }
- catch ( InvalidDependencyVersionException exception )
- {
- throw new DependencyTreeBuilderException( "Cannot build project dependency tree", exception );
+ return listener.getRootNode();
}
catch ( ArtifactResolutionException exception )
{
throw new DependencyTreeBuilderException( "Cannot build project dependency tree", exception );
}
}
-
- // private methods --------------------------------------------------------
-
- private Map getManagedVersionMap( MavenProject project, ArtifactFactory factory ) throws ProjectBuildingException
- {
- DependencyManagement dependencyManagement = project.getDependencyManagement();
- Map managedVersionMap;
-
- if ( dependencyManagement != null && dependencyManagement.getDependencies() != null )
- {
- managedVersionMap = new HashMap();
-
- for ( Iterator iterator = dependencyManagement.getDependencies().iterator(); iterator.hasNext(); )
- {
- Dependency dependency = (Dependency) iterator.next();
-
- try
- {
- VersionRange versionRange = VersionRange.createFromVersionSpec( dependency.getVersion() );
-
- Artifact artifact =
- factory.createDependencyArtifact( dependency.getGroupId(), dependency.getArtifactId(),
- versionRange, dependency.getType(),
- dependency.getClassifier(), dependency.getScope() );
-
- managedVersionMap.put( dependency.getManagementKey(), artifact );
- }
- catch ( InvalidVersionSpecificationException exception )
- {
- throw new ProjectBuildingException( project.getId(), "Unable to parse version '"
- + dependency.getVersion() + "' for dependency '" + dependency.getManagementKey()
- + "': " + exception.getMessage(), exception );
- }
- }
-
- /*
- * TODO work around bug MNG-2931, remove after upgrading to fixed version
- * remove the originating artifact if it is also in managed versions to avoid being modified during resolution
- */
- Artifact managedOriginatingArtifact = (Artifact) managedVersionMap.get( project.getArtifact()
- .getDependencyConflictId() );
- if ( managedOriginatingArtifact != null )
- {
- String managedVersion = managedOriginatingArtifact.getVersion();
- String version = project.getArtifact().getVersion();
- if ( !managedVersion.equals( version ) )
- {
- managedVersionMap.remove( project.getArtifact().getDependencyConflictId() );
- }
- }
- }
- else
- {
- managedVersionMap = Collections.EMPTY_MAP;
- }
-
- return managedVersionMap;
- }
}
diff --git a/src/main/java/org/apache/maven/shared/dependency/tree/DependencyNode.java b/src/main/java/org/apache/maven/shared/dependency/tree/DependencyNode.java
index 6899bed..7ae8c63 100644
--- a/src/main/java/org/apache/maven/shared/dependency/tree/DependencyNode.java
+++ b/src/main/java/org/apache/maven/shared/dependency/tree/DependencyNode.java
@@ -19,11 +19,15 @@
* under the License.
*/
+import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.apache.maven.artifact.Artifact;
+import org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor;
+import org.apache.maven.shared.dependency.tree.traversal.SerializingDependencyNodeVisitor;
/**
* Represents an artifact node within a Maven project's dependency tree.
@@ -31,79 +35,552 @@
* @author Edwin Punzalan
* @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
* @version $Id$
- * @see DependencyTree
*/
public class DependencyNode
{
+ // constants --------------------------------------------------------------
+
+ /**
+ * State that represents an included dependency node.
+ *
+ * @since 1.1
+ */
+ public static final int INCLUDED = 0;
+
+ /**
+ * State that represents a dependency node that has been omitted for duplicating another dependency node.
+ *
+ * @since 1.1
+ */
+ public static final int OMITTED_FOR_DUPLICATE = 1;
+
+ /**
+ * State that represents a dependency node that has been omitted for conflicting with another dependency node.
+ *
+ * @since 1.1
+ */
+ public static final int OMITTED_FOR_CONFLICT = 2;
+
+ /**
+ * State that represents a dependency node that has been omitted for introducing a cycle into the dependency tree.
+ *
+ * @since 1.1
+ */
+ public static final int OMITTED_FOR_CYCLE = 3;
+
+ // classes ----------------------------------------------------------------
+
+ /**
+ * Utility class to concatenate a number of parameters with separator tokens.
+ */
+ private static class ItemAppender
+ {
+ private StringBuffer buffer;
+
+ private String startToken;
+
+ private String separatorToken;
+
+ private String endToken;
+
+ private boolean appended;
+
+ public ItemAppender( StringBuffer buffer, String startToken, String separatorToken, String endToken )
+ {
+ this.buffer = buffer;
+ this.startToken = startToken;
+ this.separatorToken = separatorToken;
+ this.endToken = endToken;
+
+ appended = false;
+ }
+
+ public ItemAppender append( String item )
+ {
+ appendToken();
+
+ buffer.append( item );
+
+ return this;
+ }
+
+ public ItemAppender append( String item1, String item2 )
+ {
+ appendToken();
+
+ buffer.append( item1 ).append( item2 );
+
+ return this;
+ }
+
+ public void flush()
+ {
+ if ( appended )
+ {
+ buffer.append( endToken );
+
+ appended = false;
+ }
+ }
+
+ private void appendToken()
+ {
+ buffer.append( appended ? separatorToken : startToken );
+
+ appended = true;
+ }
+ }
+
// fields -----------------------------------------------------------------
- DependencyNode parent;
+ /**
+ * The artifact that is attached to this dependency node.
+ */
+ private final Artifact artifact;
- Artifact artifact;
+ /**
+ * The list of child dependency nodes of this dependency node.
+ */
+ private final List children;
- int depth;
+ /**
+ * The parent dependency node of this dependency node.
+ */
+ private DependencyNode parent;
- List children;
+ /**
+ * The state of this dependency node. This can be either <code>INCLUDED</code>,
+ * <code>OMITTED_FOR_DUPLICATE</code>, <code>OMITTED_FOR_CONFLICT</code> or <code>OMITTED_FOR_CYCLE</code>.
+ *
+ * @see #INCLUDED
+ * @see #OMITTED_FOR_DUPLICATE
+ * @see #OMITTED_FOR_CONFLICT
+ * @see #OMITTED_FOR_CYCLE
+ */
+ private int state;
+ /**
+ * The artifact related to the state of this dependency node. For dependency nodes with a state of
+ * <code>OMITTED_FOR_DUPLICATE</code> or <code>OMITTED_FOR_CONFLICT</code>, this represents the artifact that
+ * was conflicted with. For dependency nodes of other states, this is always <code>null</code>.
+ */
+ private Artifact relatedArtifact;
+
+ /**
+ * The scope of this node's artifact before it was updated due to conflicts, or <code>null</code> if the artifact
+ * scope has not been updated.
+ */
+ private String originalScope;
+
+ /**
+ * The scope that this node's artifact was attempted to be updated to due to conflicts, or <code>null</code> if
+ * the artifact scope has not failed being updated.
+ */
+ private String failedUpdateScope;
+
+ /**
+ * The version of this node's artifact before it was updated by dependency management, or <code>null</code> if the
+ * artifact version has not been managed.
+ */
+ private String premanagedVersion;
+
+ /**
+ * The scope of this node's artifact before it was updated by dependency management, or <code>null</code> if the
+ * artifact scope has not been managed.
+ */
+ private String premanagedScope;
+
// constructors -----------------------------------------------------------
+ /**
+ * Creates a new dependency node for the specified artifact with an included state.
+ *
+ * @param artifact
+ * the artifact attached to the new dependency node
+ * @throws IllegalArgumentException
+ * if the parameter constraints were violated
+ * @since 1.1
+ */
+ public DependencyNode( Artifact artifact )
+ {
+ this( artifact, INCLUDED );
+ }
+
+ /**
+ * Creates a new dependency node for the specified artifact with the specified state.
+ *
+ * @param artifact
+ * the artifact attached to the new dependency node
+ * @param state
+ * the state of the new dependency node. This can be either <code>INCLUDED</code> or
+ * <code>OMITTED_FOR_CYCLE</code>.
+ * @throws IllegalArgumentException
+ * if the parameter constraints were violated
+ * @since 1.1
+ */
+ public DependencyNode( Artifact artifact, int state )
+ {
+ this( artifact, state, null );
+ }
+
+ /**
+ * Creates a new dependency node for the specified artifact with the specified state and related artifact.
+ *
+ * @param artifact
+ * the artifact attached to the new dependency node
+ * @param state
+ * the state of the new dependency node. This can be either <code>INCLUDED</code>,
+ * <code>OMITTED_FOR_DUPLICATE</code>, <code>OMITTED_FOR_CONFLICT</code> or
+ * <code>OMITTED_FOR_CYCLE</code>.
+ * @param relatedArtifact
+ * the artifact related to the state of this dependency node. For dependency nodes with a state of
+ * <code>OMITTED_FOR_DUPLICATE</code> or <code>OMITTED_FOR_CONFLICT</code>, this represents the
+ * artifact that was conflicted with. For dependency nodes of other states, this should always be
+ * <code>null</code>.
+ * @throws IllegalArgumentException
+ * if the parameter constraints were violated
+ * @since 1.1
+ */
+ public DependencyNode( Artifact artifact, int state, Artifact relatedArtifact )
+ {
+ if ( artifact == null )
+ {
+ throw new IllegalArgumentException( "artifact cannot be null" );
+ }
+
+ if ( state < INCLUDED || state > OMITTED_FOR_CYCLE )
+ {
+ throw new IllegalArgumentException( "Unknown state: " + state );
+ }
+
+ boolean requiresRelatedArtifact = ( state == OMITTED_FOR_DUPLICATE || state == OMITTED_FOR_CONFLICT );
+
+ if ( requiresRelatedArtifact && relatedArtifact == null )
+ {
+ throw new IllegalArgumentException( "Related artifact is required for states "
+ + "OMITTED_FOR_DUPLICATE and OMITTED_FOR_CONFLICT" );
+ }
+
+ if ( !requiresRelatedArtifact && relatedArtifact != null )
+ {
+ throw new IllegalArgumentException( "Related artifact is only required for states "
+ + "OMITTED_FOR_DUPLICATE and OMITTED_FOR_CONFLICT" );
+ }
+
+ this.artifact = artifact;
+ this.state = state;
+ this.relatedArtifact = relatedArtifact;
+
+ children = new ArrayList();
+ }
+
+ /**
+ * Creates a new dependency node.
+ *
+ * @deprecated As of 1.1, replaced by {@link #DependencyNode(Artifact, int, Artifact)}
+ */
DependencyNode()
{
+ artifact = null;
children = new ArrayList();
}
// public methods ---------------------------------------------------------
+ /**
+ * Applies the specified dependency node visitor to this dependency node and its children.
+ *
+ * @param visitor
+ * the dependency node visitor to use
+ * @return the visitor result of ending the visit to this node
+ * @since 1.1
+ */
+ public boolean accept( DependencyNodeVisitor visitor )
+ {
+ if ( visitor.visit( this ) )
+ {
+ boolean visiting = true;
+
+ for ( Iterator iterator = getChildren().iterator(); visiting && iterator.hasNext(); )
+ {
+ DependencyNode child = (DependencyNode) iterator.next();
+
+ visiting = child.accept( visitor );
+ }
+ }
+
+ return visitor.endVisit( this );
+ }
+
+ /**
+ * Adds the specified dependency node to this dependency node's children.
+ *
+ * @param child
+ * the child dependency node to add
+ * @since 1.1
+ */
+ public void addChild( DependencyNode child )
+ {
+ children.add( child );
+ child.parent = this;
+ }
+
+ /**
+ * Removes the specified dependency node from this dependency node's children.
+ *
+ * @param child
+ * the child dependency node to remove
+ * @since 1.1
+ */
+ public void removeChild( DependencyNode child )
+ {
+ children.remove( child );
+ child.parent = null;
+ }
+
+ /**
+ * Gets the parent dependency node of this dependency node.
+ *
+ * @return the parent dependency node
+ */
public DependencyNode getParent()
{
return parent;
}
+ /**
+ * Gets the artifact attached to this dependency node.
+ *
+ * @return the artifact
+ */
public Artifact getArtifact()
{
return artifact;
}
-
+
+ /**
+ * Gets the depth of this dependency node within its hierarchy.
+ *
+ * @return the depth
+ * @deprecated As of 1.1, depth is computed by node hierarchy
+ */
public int getDepth()
{
+ int depth = 0;
+
+ DependencyNode node = getParent();
+
+ while ( node != null )
+ {
+ depth++;
+
+ node = node.getParent();
+ }
+
return depth;
}
+ /**
+ * Gets the list of child dependency nodes of this dependency node.
+ *
+ * @return the list of child dependency nodes
+ */
public List getChildren()
{
- return children;
- }
-
- public String toString()
- {
- return toString( 0 );
- }
-
- public String toString( int indentDepth )
- {
- StringBuffer sb = new StringBuffer();
-
- for ( int i = 0; i < indentDepth; i++ )
- {
- sb.append( " " );
- }
-
- sb.append( artifact == null ? null : artifact.toString() );
- sb.append( "\n" );
-
- if ( getChildren() != null )
- {
- for ( Iterator it = getChildren().iterator(); it.hasNext(); )
- {
- DependencyNode dependencyNode = (DependencyNode) it.next();
- sb.append( dependencyNode.toString( indentDepth + 2 ) );
- }
- }
-
- return sb.toString();
+ return Collections.unmodifiableList( children );
}
/**
+ * Gets the state of this dependency node.
+ *
+ * @return the state: either <code>INCLUDED</code>, <code>OMITTED_FOR_DUPLICATE</code>,
+ * <code>OMITTED_FOR_CONFLICT</code> or <code>OMITTED_FOR_CYCLE</code>.
+ * @since 1.1
+ */
+ public int getState()
+ {
+ return state;
+ }
+
+ /**
+ * Gets the artifact related to the state of this dependency node. For dependency nodes with a state of
+ * <code>OMITTED_FOR_CONFLICT</code>, this represents the artifact that was conflicted with. For dependency nodes
+ * of other states, this is always <code>null</code>.
+ *
+ * @return the related artifact
+ * @since 1.1
+ */
+ public Artifact getRelatedArtifact()
+ {
+ return relatedArtifact;
+ }
+
+ /**
+ * Gets the scope of this node's artifact before it was updated due to conflicts.
+ *
+ * @return the original scope, or <code>null</code> if the artifact scope has not been updated
+ * @since 1.1
+ */
+ public String getOriginalScope()
+ {
+ return originalScope;
+ }
+
+ /**
+ * Sets the scope of this node's artifact before it was updated due to conflicts.
+ *
+ * @param originalScope
+ * the original scope, or <code>null</code> if the artifact scope has not been updated
+ * @since 1.1
+ */
+ public void setOriginalScope( String originalScope )
+ {
+ this.originalScope = originalScope;
+ }
+
+ /**
+ * Gets the scope that this node's artifact was attempted to be updated to due to conflicts.
+ *
+ * @return the failed update scope, or <code>null</code> if the artifact scope has not failed being updated
+ * @since 1.1
+ */
+ public String getFailedUpdateScope()
+ {
+ return failedUpdateScope;
+ }
+
+ /**
+ * Sets the scope that this node's artifact was attempted to be updated to due to conflicts.
+ *
+ * @param failedUpdateScope
+ * the failed update scope, or <code>null</code> if the artifact scope has not failed being updated
+ * @since 1.1
+ */
+ public void setFailedUpdateScope( String failedUpdateScope )
+ {
+ this.failedUpdateScope = failedUpdateScope;
+ }
+
+ /**
+ * Gets the version of this node's artifact before it was updated by dependency management.
+ *
+ * @return the premanaged version, or <code>null</code> if the artifact version has not been managed
+ * @since 1.1
+ */
+ public String getPremanagedVersion()
+ {
+ return premanagedVersion;
+ }
+
+ /**
+ * Sets the version of this node's artifact before it was updated by dependency management.
+ *
+ * @param premanagedVersion
+ * the premanaged version, or <code>null</code> if the artifact version has not been managed
+ * @since 1.1
+ */
+ public void setPremanagedVersion( String premanagedVersion )
+ {
+ this.premanagedVersion = premanagedVersion;
+ }
+
+ /**
+ * Gets the scope of this node's artifact before it was updated by dependency management.
+ *
+ * @return the premanaged scope, or <code>null</code> if the artifact scope has not been managed
+ * @since 1.1
+ */
+ public String getPremanagedScope()
+ {
+ return premanagedScope;
+ }
+
+ /**
+ * Sets the scope of this node's artifact before it was updated by dependency management.
+ *
+ * @param premanagedScope
+ * the premanaged scope, or <code>null</code> if the artifact scope has not been managed
+ * @since 1.1
+ */
+ public void setPremanagedScope( String premanagedScope )
+ {
+ this.premanagedScope = premanagedScope;
+ }
+
+ /**
+ * Changes the state of this dependency node to be omitted for conflict or duplication, depending on the specified
+ * related artifact.
+ *
+ * <p>
+ * If the related artifact has a version equal to this dependency node's artifact, then this dependency node's state
+ * is changed to <code>OMITTED_FOR_DUPLICATE</code>, otherwise it is changed to <code>OMITTED_FOR_CONFLICT</code>.
+ * Omitting this dependency node also removes all of its children.
+ * </p>
+ *
+ * @param relatedArtifact
+ * the artifact that this dependency node conflicted with
+ * @throws IllegalStateException
+ * if this dependency node's state is not <code>INCLUDED</code>
+ * @throws IllegalArgumentException
+ * if the related artifact was <code>null</code> or had a different dependency conflict id to this
+ * dependency node's artifact
+ * @see #OMITTED_FOR_DUPLICATE
+ * @see #OMITTED_FOR_CONFLICT
+ * @since 1.1
+ */
+ public void omitForConflict( Artifact relatedArtifact )
+ {
+ if ( getState() != INCLUDED )
+ {
+ throw new IllegalStateException( "Only INCLUDED dependency nodes can be omitted for conflict" );
+ }
+
+ if ( relatedArtifact == null )
+ {
+ throw new IllegalArgumentException( "Related artifact cannot be null" );
+ }
+
+ if ( !relatedArtifact.getDependencyConflictId().equals( getArtifact().getDependencyConflictId() ) )
+ {
+ throw new IllegalArgumentException( "Related artifact has a different dependency conflict id" );
+ }
+
+ this.relatedArtifact = relatedArtifact;
+
+ boolean duplicate = getArtifact().getVersion().equals( relatedArtifact.getVersion() );
+ state = duplicate ? OMITTED_FOR_DUPLICATE : OMITTED_FOR_CONFLICT;
+
+ removeAllChildren();
+ }
+
+ /**
+ * Changes the state of this dependency node to be omitted for a cycle in the dependency tree.
+ *
+ * <p>
+ * Omitting this node sets its state to <code>OMITTED_FOR_CYCLE</code> and removes all of its children.
+ * </p>
+ *
+ * @throws IllegalStateException
+ * if this dependency node's state is not <code>INCLUDED</code>
+ * @see #OMITTED_FOR_CYCLE
+ * @since 1.1
+ */
+ public void omitForCycle()
+ {
+ if ( getState() != INCLUDED )
+ {
+ throw new IllegalStateException( "Only INCLUDED dependency nodes can be omitted for cycle" );
+ }
+
+ state = OMITTED_FOR_CYCLE;
+
+ removeAllChildren();
+ }
+
+ /**
+ * Gets an iterator that returns this dependency node and it's children in preorder traversal.
+ *
+ * @return the preorder traversal iterator
* @see #preorderIterator()
*/
public Iterator iterator()
@@ -112,6 +589,9 @@
}
/**
+ * Gets an iterator that returns this dependency node and it's children in preorder traversal.
+ *
+ * @return the preorder traversal iterator
* @see DependencyTreePreorderIterator
*/
public Iterator preorderIterator()
@@ -120,6 +600,9 @@
}
/**
+ * Gets an iterator that returns this dependency node and it's children in postorder traversal.
+ *
+ * @return the postorder traversal iterator
* @see DependencyTreeInverseIterator
*/
public Iterator inverseIterator()
@@ -127,4 +610,205 @@
return new DependencyTreeInverseIterator( this );
}
+ /**
+ * Returns a string representation of this dependency node.
+ *
+ * @return the string representation
+ * @see #toString()
+ * @since 1.1
+ */
+ public String toNodeString()
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ boolean included = ( getState() == INCLUDED );
+
+ if ( !included )
+ {
+ buffer.append( '(' );
+ }
+
+ buffer.append( artifact );
+
+ switch ( state )
+ {
+ case INCLUDED:
+ ItemAppender appender = new ItemAppender( buffer, " (", "; ", ")" );
+
+ if ( getPremanagedVersion() != null )
+ {
+ appender.append( "version managed from ", getPremanagedVersion() );
+ }
+
+ if ( getPremanagedScope() != null )
+ {
+ appender.append( "scope managed from ", getPremanagedScope() );
+ }
+
+ if ( getOriginalScope() != null )
+ {
+ appender.append( "scope updated from ", getOriginalScope() );
+ }
+
+ if ( getFailedUpdateScope() != null )
+ {
+ appender.append( "scope not updated to ", getFailedUpdateScope() );
+ }
+
+ appender.flush();
+ break;
+
+ case OMITTED_FOR_DUPLICATE:
+ buffer.append( " - omitted for duplicate" );
+ break;
+
+ case OMITTED_FOR_CONFLICT:
+ buffer.append( " - omitted for conflict with " ).append( relatedArtifact.getVersion() );
+ break;
+
+ case OMITTED_FOR_CYCLE:
+ buffer.append( " - omitted for cycle" );
+ break;
+ }
+
+ if ( !included )
+ {
+ buffer.append( ')' );
+ }
+
+ return buffer.toString();
+ }
+
+ /**
+ * Returns a string representation of this dependency node and its children, indented to the specified depth.
+ *
+ * <p>
+ * As of 1.1, this method ignores the indentation depth and simply delegates to <code>toString()</code>.
+ * </p>
+ *
+ * @param indentDepth
+ * the indentation depth
+ * @return the string representation
+ * @deprecated As of 1.1, replaced by {@link #toString()}
+ */
+ public String toString( int indentDepth )
+ {
+ return toString();
+ }
+
+ // Object methods ---------------------------------------------------------
+
+ /*
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode()
+ {
+ int hashCode = 1;
+
+ hashCode = hashCode * 31 + nullHashCode( getParent() );
+ hashCode = hashCode * 31 + getArtifact().hashCode();
+
+ // DefaultArtifact.hashCode does not consider scope
+ hashCode = hashCode * 31 + nullHashCode( getArtifact().getScope() );
+
+ hashCode = hashCode * 31 + getChildren().hashCode();
+ hashCode = hashCode * 31 + getState();
+ hashCode = hashCode * 31 + nullHashCode( getRelatedArtifact() );
+ hashCode = hashCode * 31 + nullHashCode( getPremanagedVersion() );
+ hashCode = hashCode * 31 + nullHashCode( getPremanagedScope() );
+ hashCode = hashCode * 31 + nullHashCode( getOriginalScope() );
+ hashCode = hashCode * 31 + nullHashCode( getFailedUpdateScope() );
+
+ return hashCode;
+ }
+
+ /*
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals( Object object )
+ {
+ boolean equal;
+
+ if ( object instanceof DependencyNode )
+ {
+ DependencyNode node = (DependencyNode) object;
+
+ // TODO: no parent.equals() to prevent recursion
+ equal = getArtifact().equals( node.getArtifact() );
+
+ // DefaultArtifact.hashCode does not consider scope
+ equal &= nullEquals( getArtifact().getScope(), node.getArtifact().getScope() );
+
+ equal &= getChildren().equals( node.getChildren() );
+ equal &= getState() == node.getState();
+ equal &= nullEquals( getRelatedArtifact(), node.getRelatedArtifact() );
+ equal &= nullEquals( getPremanagedVersion(), node.getPremanagedVersion() );
+ equal &= nullEquals( getPremanagedScope(), node.getPremanagedScope() );
+ equal &= nullEquals( getOriginalScope(), node.getOriginalScope() );
+ equal &= nullEquals( getFailedUpdateScope(), node.getFailedUpdateScope() );
+ }
+ else
+ {
+ equal = false;
+ }
+
+ return equal;
+ }
+
+ /**
+ * Returns a string representation of this dependency node and its children.
+ *
+ * @return the string representation
+ * @see #toNodeString()
+ * @see java.lang.Object#toString()
+ */
+ public String toString()
+ {
+ StringWriter writer = new StringWriter();
+ accept( new SerializingDependencyNodeVisitor( writer ) );
+ return writer.toString();
+ }
+
+ // private methods --------------------------------------------------------
+
+ /**
+ * Removes all of this dependency node's children.
+ */
+ private void removeAllChildren()
+ {
+ for ( Iterator iterator = children.iterator(); iterator.hasNext(); )
+ {
+ DependencyNode child = (DependencyNode) iterator.next();
+
+ child.parent = null;
+ }
+
+ children.clear();
+ }
+
+ /**
+ * Computes a hash-code for the specified object.
+ *
+ * @param a
+ * the object to compute a hash-code for, possibly <code>null</code>
+ * @return the computed hash-code
+ */
+ private int nullHashCode( Object a )
+ {
+ return ( a == null ) ? 0 : a.hashCode();
+ }
+
+ /**
+ * Gets whether the specified objects are equal.
+ *
+ * @param a
+ * the first object to compare, possibly <code>null</code>
+ * @param b
+ * the second object to compare, possibly <code>null</code>
+ * @return <code>true</code> if the specified objects are equal
+ */
+ private boolean nullEquals( Object a, Object b )
+ {
+ return ( a == null ? b == null : a.equals( b ) );
+ }
}
diff --git a/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTree.java b/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTree.java
index 6c41774..f61ab75 100644
--- a/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTree.java
+++ b/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTree.java
@@ -29,6 +29,7 @@
*
* @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
* @version $Id$
+ * @deprecated As of 1.1, replaced by the dependency tree root {@link DependencyNode}
*/
public class DependencyTree
{
diff --git a/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreeBuilder.java b/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreeBuilder.java
index cd51f5c..16bdefb 100644
--- a/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreeBuilder.java
+++ b/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreeBuilder.java
@@ -23,6 +23,7 @@
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactCollector;
+import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.project.MavenProject;
/**
@@ -58,8 +59,34 @@
* @return the dependency tree of the specified Maven project
* @throws DependencyTreeBuilderException
* if the dependency tree cannot be resolved
+ * @deprecated As of 1.1, replaced by
+ * {@link #buildDependencyTree(MavenProject, ArtifactRepository, ArtifactFactory, ArtifactMetadataSource, ArtifactFilter, ArtifactCollector)}
*/
DependencyTree buildDependencyTree( MavenProject project, ArtifactRepository repository, ArtifactFactory factory,
ArtifactMetadataSource metadataSource, ArtifactCollector collector )
throws DependencyTreeBuilderException;
+
+ /**
+ * Builds a tree of dependencies for the specified Maven project.
+ *
+ * @param project
+ * the Maven project
+ * @param repository
+ * the artifact repository to resolve against
+ * @param factory
+ * the artifact factory to use
+ * @param metadataSource
+ * the artifact metadata source to use
+ * @param filter
+ * the artifact filter to use
+ * @param collector
+ * the artifact collector to use
+ * @return the dependency tree root node of the specified Maven project
+ * @throws DependencyTreeBuilderException
+ * if the dependency tree cannot be resolved
+ * @since 1.1
+ */
+ DependencyNode buildDependencyTree( MavenProject project, ArtifactRepository repository, ArtifactFactory factory,
+ ArtifactMetadataSource metadataSource, ArtifactFilter filter, ArtifactCollector collector )
+ throws DependencyTreeBuilderException;
}
diff --git a/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreeResolutionListener.java b/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreeResolutionListener.java
index c1bf049..6bc1679 100644
--- a/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreeResolutionListener.java
+++ b/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreeResolutionListener.java
@@ -20,13 +20,17 @@
*/
import java.util.Collection;
-import java.util.HashMap;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
import java.util.Map;
import java.util.Stack;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.ResolutionListener;
+import org.apache.maven.artifact.resolver.ResolutionListenerForDepMgmt;
import org.apache.maven.artifact.versioning.VersionRange;
+import org.apache.maven.shared.dependency.tree.traversal.CollectingDependencyNodeVisitor;
/**
* An artifact resolution listener that constructs a dependency tree.
@@ -35,27 +39,61 @@
* @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
* @version $Id$
*/
-public class DependencyTreeResolutionListener implements ResolutionListener
+public class DependencyTreeResolutionListener implements ResolutionListener, ResolutionListenerForDepMgmt
{
// fields -----------------------------------------------------------------
- private final Stack parents;
+ /**
+ * The parent dependency nodes of the current dependency node.
+ */
+ private final Stack parentNodes;
- private final Map artifacts;
+ /**
+ * A map of dependency nodes by their attached artifact.
+ */
+ private final Map nodesByArtifact;
+ /**
+ * The root dependency node of the computed dependency tree.
+ */
private DependencyNode rootNode;
- private int currentDepth;
+ /**
+ * The dependency node currently being processed by this listener.
+ */
+ private DependencyNode currentNode;
+
+ /**
+ * The id of the currently managed artifact, or <code>null</code> if the current artifact is not managed.
+ */
+ private String managedId;
+
+ /**
+ * The original version of the currently managed artifact, or <code>null</code> if the current artifact is not
+ * managed.
+ */
+ private String premanagedVersion;
+
+ /**
+ * The original scope of the currently managed artifact, or <code>null</code> if the current artifact is not
+ * managed.
+ */
+ private String premanagedScope;
// constructors -----------------------------------------------------------
+ /**
+ * Creates a new dependency tree resolution listener.
+ */
public DependencyTreeResolutionListener()
{
- parents = new Stack();
- artifacts = new HashMap();
-
+ parentNodes = new Stack();
+ nodesByArtifact = new IdentityHashMap();
rootNode = null;
- currentDepth = 0;
+ currentNode = null;
+ managedId = null;
+ premanagedScope = null;
+ premanagedScope = null;
}
// ResolutionListener methods ---------------------------------------------
@@ -65,7 +103,7 @@
*/
public void testArtifact( Artifact artifact )
{
- // intentionally blank
+ // no-op
}
/*
@@ -73,16 +111,13 @@
*/
public void startProcessChildren( Artifact artifact )
{
- DependencyNode node = (DependencyNode) artifacts.get( artifact.getDependencyConflictId() );
-
- node.depth = currentDepth++;
-
- if ( parents.isEmpty() )
+ if ( !currentNode.getArtifact().equals( artifact ) )
{
- rootNode = node;
+ throw new IllegalStateException( "Artifact was expected to be " + currentNode.getArtifact() + " but was "
+ + artifact );
}
- parents.push( node );
+ parentNodes.push( currentNode );
}
/*
@@ -90,11 +125,18 @@
*/
public void endProcessChildren( Artifact artifact )
{
- DependencyNode check = (DependencyNode) parents.pop();
+ DependencyNode node = (DependencyNode) parentNodes.pop();
- assert artifact.equals( check.artifact );
+ if ( node == null )
+ {
+ throw new IllegalStateException( "Parent dependency node was null" );
+ }
- currentDepth--;
+ if ( !node.getArtifact().equals( artifact ) )
+ {
+ throw new IllegalStateException( "Parent dependency node artifact was expected to be " + node.getArtifact()
+ + " but was " + artifact );
+ }
}
/*
@@ -102,29 +144,42 @@
*/
public void includeArtifact( Artifact artifact )
{
- if ( artifacts.containsKey( artifact.getDependencyConflictId() ) )
- {
- DependencyNode prev = (DependencyNode) artifacts.get( artifact.getDependencyConflictId() );
+ DependencyNode existingNode = getNode( artifact );
- if ( prev.parent != null )
+ /*
+ * Ignore duplicate includeArtifact calls since omitForNearer can be called prior to includeArtifact on the same
+ * artifact, and we don't wish to include it twice.
+ */
+ if ( existingNode == null && isCurrentNodeIncluded() )
+ {
+ DependencyNode node = addNode( artifact );
+
+ /*
+ * Add the dependency management information cached in any prior manageArtifact calls, since includeArtifact
+ * is always called after manageArtifact.
+ */
+ if ( premanagedVersion != null || premanagedScope != null )
{
- prev.parent.children.remove( prev );
+ if ( !managedId.equals( artifact.getId() ) )
+ {
+ throw new IllegalStateException( "Managed artifact id was expected to be " + managedId + " but was " + artifact.getId() );
+ }
+
+ if ( premanagedVersion != null )
+ {
+ node.setPremanagedVersion( premanagedVersion );
+ }
+
+ if ( premanagedScope != null )
+ {
+ node.setPremanagedScope( premanagedScope );
+ }
+
+ managedId = null;
+ premanagedVersion = null;
+ premanagedScope = null;
}
-
- artifacts.remove( artifact.getDependencyConflictId() );
}
-
- DependencyNode node = new DependencyNode();
- node.artifact = artifact;
-
- if ( !parents.isEmpty() )
- {
- node.parent = (DependencyNode) parents.peek();
- node.parent.children.add( node );
- node.depth = currentDepth;
- }
-
- artifacts.put( artifact.getDependencyConflictId(), node );
}
/*
@@ -133,21 +188,39 @@
*/
public void omitForNearer( Artifact omitted, Artifact kept )
{
- assert omitted.getDependencyConflictId().equals( kept.getDependencyConflictId() );
-
- DependencyNode prev = (DependencyNode) artifacts.get( omitted.getDependencyConflictId() );
-
- if ( prev != null )
+ if ( !omitted.getDependencyConflictId().equals( kept.getDependencyConflictId() ) )
{
- if ( prev.parent != null )
- {
- prev.parent.children.remove( prev );
- }
-
- artifacts.remove( omitted.getDependencyConflictId() );
+ throw new IllegalArgumentException( "Omitted artifact dependency conflict id "
+ + omitted.getDependencyConflictId() + " differs from kept artifact dependency conflict id "
+ + kept.getDependencyConflictId() );
}
- includeArtifact( kept );
+ if ( isCurrentNodeIncluded() )
+ {
+ DependencyNode omittedNode = getNode( omitted );
+
+ if ( omittedNode != null )
+ {
+ removeNode( omitted );
+ }
+ else
+ {
+ omittedNode = createNode( omitted );
+
+ currentNode = omittedNode;
+ }
+
+ omittedNode.omitForConflict( kept );
+
+
+
+ DependencyNode keptNode = getNode( kept );
+
+ if ( keptNode == null )
+ {
+ addNode( kept );
+ }
+ }
}
/*
@@ -156,9 +229,14 @@
*/
public void updateScope( Artifact artifact, String scope )
{
- DependencyNode node = (DependencyNode) artifacts.get( artifact.getDependencyConflictId() );
+ DependencyNode node = getNode( artifact );
- node.artifact.setScope( scope );
+ if ( node == null )
+ {
+ throw new IllegalStateException( "Cannot find dependency node for artifact " + artifact );
+ }
+
+ node.setOriginalScope( artifact.getScope() );
}
/*
@@ -167,18 +245,16 @@
*/
public void manageArtifact( Artifact artifact, Artifact replacement )
{
- DependencyNode node = (DependencyNode) artifacts.get( artifact.getDependencyConflictId() );
-
- if ( node != null )
+ // TODO: remove when ResolutionListenerForDepMgmt merged into ResolutionListener
+
+ if ( replacement.getVersion() != null )
{
- if ( replacement.getVersion() != null )
- {
- node.artifact.setVersion( replacement.getVersion() );
- }
- if ( replacement.getScope() != null )
- {
- node.artifact.setScope( replacement.getScope() );
- }
+ manageArtifactVersion( artifact, replacement );
+ }
+
+ if ( replacement.getScope() != null )
+ {
+ manageArtifactScope( artifact, replacement );
}
}
@@ -187,16 +263,28 @@
*/
public void omitForCycle( Artifact artifact )
{
- // TODO: Track omit for cycle
+ if ( isCurrentNodeIncluded() )
+ {
+ DependencyNode node = createNode( artifact );
+
+ node.omitForCycle();
+ }
}
/*
* @see org.apache.maven.artifact.resolver.ResolutionListener#updateScopeCurrentPom(org.apache.maven.artifact.Artifact,
* java.lang.String)
*/
- public void updateScopeCurrentPom( Artifact artifact, String key )
+ public void updateScopeCurrentPom( Artifact artifact, String scope )
{
- // TODO: Track scope update
+ DependencyNode node = getNode( artifact );
+
+ if ( node == null )
+ {
+ throw new IllegalStateException( "Cannot find dependency node for artifact " + artifact );
+ }
+
+ node.setFailedUpdateScope( scope );
}
/*
@@ -204,7 +292,7 @@
*/
public void selectVersionFromRange( Artifact artifact )
{
- // TODO: Track version selection from range
+ // TODO: track version selection from range in node
}
/*
@@ -213,18 +301,179 @@
*/
public void restrictRange( Artifact artifact, Artifact artifact1, VersionRange versionRange )
{
- // TODO: Track range restriction.
+ // TODO: track range restriction in node
+ }
+
+ // ResolutionListenerForDepMgmt methods -----------------------------------
+
+ /*
+ * @see org.apache.maven.artifact.resolver.ResolutionListenerForDepMgmt#manageArtifactVersion(org.apache.maven.artifact.Artifact,
+ * org.apache.maven.artifact.Artifact)
+ */
+ public void manageArtifactVersion( Artifact artifact, Artifact replacement )
+ {
+ /*
+ * DefaultArtifactCollector calls manageArtifact twice: first with the change; then subsequently with no change.
+ * We ignore the second call when the versions are equal.
+ */
+ if ( isCurrentNodeIncluded() && !replacement.getVersion().equals( artifact.getVersion() ) )
+ {
+ /*
+ * Cache management information and apply in includeArtifact, since DefaultArtifactCollector mutates the
+ * artifact and then calls includeArtifact after manageArtifact.
+ */
+ managedId = replacement.getId();
+ premanagedVersion = artifact.getVersion();
+ }
+ }
+
+ /*
+ * @see org.apache.maven.artifact.resolver.ResolutionListenerForDepMgmt#manageArtifactScope(org.apache.maven.artifact.Artifact,
+ * org.apache.maven.artifact.Artifact)
+ */
+ public void manageArtifactScope( Artifact artifact, Artifact replacement )
+ {
+ /*
+ * DefaultArtifactCollector calls manageArtifact twice: first with the change; then subsequently with no change.
+ * We ignore the second call when the scopes are equal.
+ */
+ if ( isCurrentNodeIncluded() && !replacement.getScope().equals( artifact.getScope() ) )
+ {
+ /*
+ * Cache management information and apply in includeArtifact, since DefaultArtifactCollector mutates the
+ * artifact and then calls includeArtifact after manageArtifact.
+ */
+ managedId = replacement.getId();
+ premanagedScope = artifact.getScope();
+ }
}
// public methods ---------------------------------------------------------
-
+
+ /**
+ * Gets a list of all dependency nodes in the computed dependency tree.
+ *
+ * @return a list of dependency nodes
+ * @deprecated As of 1.1, use a {@link CollectingDependencyNodeVisitor} on the root dependency node
+ */
public Collection getNodes()
{
- return artifacts.values();
+ return Collections.unmodifiableCollection( nodesByArtifact.values() );
}
+ /**
+ * Gets the root dependency node of the computed dependency tree.
+ *
+ * @return the root node
+ */
public DependencyNode getRootNode()
{
return rootNode;
}
+
+ // private methods --------------------------------------------------------
+
+ /**
+ * Creates a new dependency node for the specified artifact and appends it to the current parent dependency node.
+ *
+ * @param artifact
+ * the attached artifact for the new dependency node
+ * @return the new dependency node
+ */
+ private DependencyNode createNode( Artifact artifact )
+ {
+ DependencyNode node = new DependencyNode( artifact );
+
+ if ( !parentNodes.isEmpty() )
+ {
+ DependencyNode parent = (DependencyNode) parentNodes.peek();
+
+ parent.addChild( node );
+ }
+
+ return node;
+ }
+
+ /**
+ * Creates a new dependency node for the specified artifact, appends it to the current parent dependency node and
+ * puts it into the dependency node cache.
+ *
+ * @param artifact
+ * the attached artifact for the new dependency node
+ * @return the new dependency node
+ */
+ private DependencyNode addNode( Artifact artifact )
+ {
+ DependencyNode node = createNode( artifact );
+
+ DependencyNode previousNode = (DependencyNode) nodesByArtifact.put( node.getArtifact(), node );
+
+ if ( previousNode != null )
+ {
+ throw new IllegalStateException( "Duplicate node registered for artifact: " + node.getArtifact() );
+ }
+
+ if ( rootNode == null )
+ {
+ rootNode = node;
+ }
+
+ currentNode = node;
+
+ return node;
+ }
+
+ /**
+ * Gets the dependency node for the specified artifact from the dependency node cache.
+ *
+ * @param artifact
+ * the artifact to find the dependency node for
+ * @return the dependency node, or <code>null</code> if the specified artifact has no corresponding dependency
+ * node
+ */
+ private DependencyNode getNode( Artifact artifact )
+ {
+ return (DependencyNode) nodesByArtifact.get( artifact );
+ }
+
+ /**
+ * Removes the dependency node for the specified artifact from the dependency node cache.
+ *
+ * @param artifact
+ * the artifact to remove the dependency node for
+ */
+ private void removeNode( Artifact artifact )
+ {
+ DependencyNode node = (DependencyNode) nodesByArtifact.remove( artifact );
+
+ if ( !artifact.equals( node.getArtifact() ) )
+ {
+ throw new IllegalStateException( "Removed dependency node artifact was expected to be " + artifact
+ + " but was " + node.getArtifact() );
+ }
+ }
+
+ /**
+ * Gets whether the all the ancestors of the dependency node currently being processed by this listener have an
+ * included state.
+ *
+ * @return <code>true</code> if all the ancestors of the current dependency node have a state of
+ * <code>INCLUDED</code>
+ */
+ private boolean isCurrentNodeIncluded()
+ {
+ boolean included = true;
+
+ for ( Iterator iterator = parentNodes.iterator(); included && iterator.hasNext(); )
+ {
+ DependencyNode node = (DependencyNode) iterator.next();
+
+ if ( node.getState() != DependencyNode.INCLUDED )
+ {
+ included = false;
+ }
+ }
+
+ return included;
+ }
}
diff --git a/src/main/java/org/apache/maven/shared/dependency/tree/filter/AncestorOrSelfDependencyNodeFilter.java b/src/main/java/org/apache/maven/shared/dependency/tree/filter/AncestorOrSelfDependencyNodeFilter.java
new file mode 100644
index 0000000..cbb3677
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/tree/filter/AncestorOrSelfDependencyNodeFilter.java
@@ -0,0 +1,110 @@
+package org.apache.maven.shared.dependency.tree.filter;
+
+/*
+ * 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.Iterator;
+import java.util.List;
+
+import org.apache.maven.shared.dependency.tree.DependencyNode;
+
+/**
+ * A dependency node filter than only accepts nodes that are ancestors of, or equal to, a given list of nodes.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @since 1.1
+ */
+public class AncestorOrSelfDependencyNodeFilter implements DependencyNodeFilter
+{
+ // fields -----------------------------------------------------------------
+
+ /**
+ * The list of nodes that this filter accepts ancestors-or-self of.
+ */
+ private final List descendantNodes;
+
+ // constructors -----------------------------------------------------------
+
+ public AncestorOrSelfDependencyNodeFilter( DependencyNode descendantNode )
+ {
+ this( Collections.singletonList( descendantNode ) );
+ }
+
+ /**
+ * Creates a dependency node filter that only accepts nodes that are ancestors of, or equal to, the specified list
+ * of nodes.
+ *
+ * @param descendantNodes
+ * the list of nodes to accept ancestors-or-self of
+ */
+ public AncestorOrSelfDependencyNodeFilter( List descendantNodes )
+ {
+ this.descendantNodes = descendantNodes;
+ }
+
+ // DependencyNodeFilter methods -------------------------------------------
+
+ /*
+ * @see org.apache.maven.shared.dependency.tree.filter.DependencyNodeFilter#accept(org.apache.maven.shared.dependency.tree.DependencyNode)
+ */
+ public boolean accept( DependencyNode node )
+ {
+ boolean accept = false;
+
+ for ( Iterator iterator = descendantNodes.iterator(); !accept && iterator.hasNext(); )
+ {
+ DependencyNode descendantNode = (DependencyNode) iterator.next();
+
+ if ( isAncestorOrSelf( node, descendantNode ) )
+ {
+ accept = true;
+ }
+ }
+
+ return accept;
+ }
+
+ // private methods --------------------------------------------------------
+
+ /**
+ * Gets whether the first dependency node is an ancestor-or-self of the second.
+ *
+ * @param ancestorNode
+ * the ancestor-or-self dependency node
+ * @param descendantNode
+ * the dependency node to test
+ * @return <code>true</code> if <code>ancestorNode</code> is an ancestor, or equal to,
+ * <code>descendantNode</code>
+ */
+ private boolean isAncestorOrSelf( DependencyNode ancestorNode, DependencyNode descendantNode )
+ {
+ boolean ancestor = false;
+
+ while ( !ancestor && descendantNode != null )
+ {
+ ancestor = ancestorNode.equals( descendantNode );
+
+ descendantNode = descendantNode.getParent();
+ }
+
+ return ancestor;
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/tree/filter/AndDependencyNodeFilter.java b/src/main/java/org/apache/maven/shared/dependency/tree/filter/AndDependencyNodeFilter.java
new file mode 100644
index 0000000..0c7bba0
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/tree/filter/AndDependencyNodeFilter.java
@@ -0,0 +1,101 @@
+package org.apache.maven.shared.dependency.tree.filter;
+
+/*
+ * 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.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.maven.shared.dependency.tree.DependencyNode;
+
+/**
+ * A dependency node filter that logically ANDs together a number of other dependency node filters.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @since 1.1
+ */
+public class AndDependencyNodeFilter implements DependencyNodeFilter
+{
+ // fields -----------------------------------------------------------------
+
+ /**
+ * The dependency node filters that this filter ANDs together.
+ */
+ private final List filters;
+
+ // constructors -----------------------------------------------------------
+
+ /**
+ * Creates a dependency node filter that logically ANDs together the two specified dependency node filters.
+ *
+ * @param filter1
+ * the first dependency node filter to logically AND together
+ * @param filter2
+ * the second dependency node filter to logically AND together
+ */
+ public AndDependencyNodeFilter( DependencyNodeFilter filter1, DependencyNodeFilter filter2 )
+ {
+ this( Arrays.asList( new DependencyNodeFilter[] { filter1, filter2 } ) );
+ }
+
+ /**
+ * Creates a dependency node filter that logically ANDs together the specified dependency node filters.
+ *
+ * @param filters
+ * the list of dependency node filters to logically AND together
+ */
+ public AndDependencyNodeFilter( List filters )
+ {
+ this.filters = Collections.unmodifiableList( filters );
+ }
+
+ // DependencyNodeFilter methods -------------------------------------------
+
+ /*
+ * @see org.apache.maven.shared.dependency.tree.filter.DependencyNodeFilter#accept(org.apache.maven.shared.dependency.tree.DependencyNode)
+ */
+ public boolean accept( DependencyNode node )
+ {
+ boolean accept = true;
+
+ for ( Iterator iterator = filters.iterator(); accept && iterator.hasNext(); )
+ {
+ DependencyNodeFilter filter = (DependencyNodeFilter) iterator.next();
+
+ accept = filter.accept( node );
+ }
+
+ return accept;
+ }
+
+ // public methods ---------------------------------------------------------
+
+ /**
+ * Gets the list of dependency node filters that this filter ANDs together.
+ *
+ * @return the dependency node filters that this filter ANDs together
+ */
+ public List getDependencyNodeFilters()
+ {
+ return filters;
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/tree/filter/ArtifactDependencyNodeFilter.java b/src/main/java/org/apache/maven/shared/dependency/tree/filter/ArtifactDependencyNodeFilter.java
new file mode 100644
index 0000000..28d1161
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/tree/filter/ArtifactDependencyNodeFilter.java
@@ -0,0 +1,78 @@
+package org.apache.maven.shared.dependency.tree.filter;
+
+/*
+ * 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.artifact.Artifact;
+import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
+import org.apache.maven.shared.dependency.tree.DependencyNode;
+
+/**
+ * A dependency node filter that delegates to an artifact filter.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @since 1.1
+ */
+public class ArtifactDependencyNodeFilter implements DependencyNodeFilter
+{
+ // fields -----------------------------------------------------------------
+
+ /**
+ * The artifact filter this dependency node filter delegates to.
+ */
+ private final ArtifactFilter filter;
+
+ // constructors -----------------------------------------------------------
+
+ /**
+ * Creates a dependency node filter that delegates to the specified artifact filter.
+ *
+ * @param filter
+ * the artifact filter to delegate to
+ */
+ public ArtifactDependencyNodeFilter( ArtifactFilter filter )
+ {
+ this.filter = filter;
+ }
+
+ // DependencyNodeFilter methods -------------------------------------------
+
+ /*
+ * @see org.apache.maven.shared.dependency.tree.filter.DependencyNodeFilter#accept(org.apache.maven.shared.dependency.tree.DependencyNode)
+ */
+ public boolean accept( DependencyNode node )
+ {
+ Artifact artifact = node.getArtifact();
+
+ return filter.include( artifact );
+ }
+
+ // public methods ---------------------------------------------------------
+
+ /**
+ * Gets the artifact filter this dependency node filter delegates to.
+ *
+ * @return the artifact filter this dependency node filter delegates to
+ */
+ public ArtifactFilter getArtifactFilter()
+ {
+ return filter;
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/tree/filter/DependencyNodeFilter.java b/src/main/java/org/apache/maven/shared/dependency/tree/filter/DependencyNodeFilter.java
new file mode 100644
index 0000000..25b02fa
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/tree/filter/DependencyNodeFilter.java
@@ -0,0 +1,41 @@
+package org.apache.maven.shared.dependency.tree.filter;
+
+/*
+ * 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.dependency.tree.DependencyNode;
+
+/**
+ * Defines a filter for dependency nodes.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @since 1.1
+ */
+public interface DependencyNodeFilter
+{
+ /**
+ * Gets whether this filter accepts the specified dependency node.
+ *
+ * @param node
+ * the dependency node to check
+ * @return <code>true</code> if this filter accepts the specified dependency node
+ */
+ boolean accept( DependencyNode node );
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/tree/filter/StateDependencyNodeFilter.java b/src/main/java/org/apache/maven/shared/dependency/tree/filter/StateDependencyNodeFilter.java
new file mode 100644
index 0000000..50bf263
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/tree/filter/StateDependencyNodeFilter.java
@@ -0,0 +1,90 @@
+package org.apache.maven.shared.dependency.tree.filter;
+
+/*
+ * 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.dependency.tree.DependencyNode;
+
+/**
+ * A dependency node filter that accepts nodes depending on their state.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @since 1.1
+ */
+public class StateDependencyNodeFilter implements DependencyNodeFilter
+{
+ // constants --------------------------------------------------------------
+
+ /**
+ * A dependency node filter that only accepts included nodes.
+ */
+ public static final StateDependencyNodeFilter INCLUDED = new StateDependencyNodeFilter( DependencyNode.INCLUDED );
+
+ // fields -----------------------------------------------------------------
+
+ /**
+ * The state of dependency nodes to accept.
+ *
+ * @see DependencyNode
+ */
+ private final int state;
+
+ // constructors -----------------------------------------------------------
+
+ /**
+ * Creates a dependency node filter that only accepts dependency nodes of the specified state.
+ *
+ * @param state
+ * the state of dependency nodes to accept
+ * @throws IllegalArgumentException
+ * if the specified state is invalid
+ */
+ public StateDependencyNodeFilter( int state )
+ {
+ if ( state < DependencyNode.INCLUDED || state > DependencyNode.OMITTED_FOR_CYCLE )
+ {
+ throw new IllegalArgumentException( "Unknown state: " + state );
+ }
+
+ this.state = state;
+ }
+
+ // DependencyNodeFilter methods -------------------------------------------
+
+ /*
+ * @see org.apache.maven.shared.dependency.tree.filter.DependencyNodeFilter#accept(org.apache.maven.shared.dependency.tree.DependencyNode)
+ */
+ public boolean accept( DependencyNode node )
+ {
+ return node.getState() == state;
+ }
+
+ // public methods ---------------------------------------------------------
+
+ /**
+ * Gets the dependency node state that this filter accepts.
+ *
+ * @return the dependency node state that this filter accepts
+ */
+ public int getState()
+ {
+ return state;
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/tree/traversal/BuildingDependencyNodeVisitor.java b/src/main/java/org/apache/maven/shared/dependency/tree/traversal/BuildingDependencyNodeVisitor.java
new file mode 100644
index 0000000..d451da4
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/tree/traversal/BuildingDependencyNodeVisitor.java
@@ -0,0 +1,139 @@
+package org.apache.maven.shared.dependency.tree.traversal;
+
+/*
+ * 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.Stack;
+
+import org.apache.maven.shared.dependency.tree.DependencyNode;
+
+/**
+ * A dependency node visitor that clones visited nodes into a new dependency tree. This can be used in conjunction with
+ * a dependency node filter to construct subtrees.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @since 1.1
+ */
+public class BuildingDependencyNodeVisitor implements DependencyNodeVisitor
+{
+ // fields -----------------------------------------------------------------
+
+ /**
+ * The dependency node visitor to apply on the resultant dependency tree, or <code>null</code> for none.
+ */
+ private final DependencyNodeVisitor visitor;
+
+ /**
+ * The resultant tree parent nodes for the currently visited node.
+ */
+ private final Stack parentNodes;
+
+ /**
+ * The root node of the resultant tree.
+ */
+ private DependencyNode rootNode;
+
+ // constructors -----------------------------------------------------------
+
+ /**
+ * Creates a dependency node visitor that clones visited nodes into a new dependency tree.
+ */
+ public BuildingDependencyNodeVisitor()
+ {
+ this( null );
+ }
+
+ /**
+ * Creates a dependency node visitor that clones visited nodes into a new dependency tree, and then applies the
+ * specified dependency node visitor on the resultant dependency tree.
+ *
+ * @param visitor
+ * the dependency node visitor to apply on the resultant dependency tree, or <code>null</code> for none
+ */
+ public BuildingDependencyNodeVisitor( DependencyNodeVisitor visitor )
+ {
+ this.visitor = visitor;
+
+ parentNodes = new Stack();
+ }
+
+ // DependencyNodeVisitor methods ------------------------------------------
+
+ /*
+ * @see org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor#visit(org.apache.maven.shared.dependency.tree.DependencyNode)
+ */
+ public boolean visit( DependencyNode node )
+ {
+ // clone the node
+ DependencyNode newNode = new DependencyNode( node.getArtifact(), node.getState(), node.getRelatedArtifact() );
+
+ if ( parentNodes.empty() )
+ {
+ rootNode = newNode;
+ }
+ else
+ {
+ DependencyNode parentNode = (DependencyNode) parentNodes.peek();
+ parentNode.addChild( newNode );
+ }
+
+ parentNodes.push( newNode );
+
+ return true;
+ }
+
+ /*
+ * @see org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor#endVisit(org.apache.maven.shared.dependency.tree.DependencyNode)
+ */
+ public boolean endVisit( DependencyNode node )
+ {
+ parentNodes.pop();
+
+ // apply the visitor to the resultant tree on the last visit
+ if ( parentNodes.empty() && visitor != null )
+ {
+ rootNode.accept( visitor );
+ }
+
+ return true;
+ }
+
+ // public methods ---------------------------------------------------------
+
+ /**
+ * Gets the dependency node visitor that this visitor applies on the resultant dependency tree.
+ *
+ * @return the dependency node visitor, or <code>null</code> for none
+ */
+ public DependencyNodeVisitor getDependencyNodeVisitor()
+ {
+ return visitor;
+ }
+
+ /**
+ * Gets the root node of the resultant dependency tree constructed by this visitor.
+ *
+ * @return the root node, or <code>null</code> if the source tree has not yet been visited
+ */
+ public DependencyNode getDependencyTree()
+ {
+ return rootNode;
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/tree/traversal/CollectingDependencyNodeVisitor.java b/src/main/java/org/apache/maven/shared/dependency/tree/traversal/CollectingDependencyNodeVisitor.java
new file mode 100644
index 0000000..b43f988
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/tree/traversal/CollectingDependencyNodeVisitor.java
@@ -0,0 +1,86 @@
+package org.apache.maven.shared.dependency.tree.traversal;
+
+/*
+ * 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.Collections;
+import java.util.List;
+
+import org.apache.maven.shared.dependency.tree.DependencyNode;
+
+/**
+ * A dependency node visitor that collects visited nodes for further processing.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @since 1.1
+ */
+public class CollectingDependencyNodeVisitor implements DependencyNodeVisitor
+{
+ // fields -----------------------------------------------------------------
+
+ /**
+ * The collected list of nodes.
+ */
+ private final List nodes;
+
+ // constructors -----------------------------------------------------------
+
+ /**
+ * Creates a dependency node visitor that collects visited nodes for further processing.
+ */
+ public CollectingDependencyNodeVisitor()
+ {
+ nodes = new ArrayList();
+ }
+
+ // DependencyNodeVisitor methods ------------------------------------------
+
+ /*
+ * @see org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor#visit(org.apache.maven.shared.dependency.tree.DependencyNode)
+ */
+ public boolean visit( DependencyNode node )
+ {
+ // collect node
+ nodes.add( node );
+
+ return true;
+ }
+
+ /*
+ * @see org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor#endVisit(org.apache.maven.shared.dependency.tree.DependencyNode)
+ */
+ public boolean endVisit( DependencyNode node )
+ {
+ return true;
+ }
+
+ // public methods ---------------------------------------------------------
+
+ /**
+ * Gets the list of collected dependency nodes.
+ *
+ * @return the list of collected dependency nodes
+ */
+ public List getNodes()
+ {
+ return Collections.unmodifiableList( nodes );
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/tree/traversal/DependencyNodeVisitor.java b/src/main/java/org/apache/maven/shared/dependency/tree/traversal/DependencyNodeVisitor.java
new file mode 100644
index 0000000..2299126
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/tree/traversal/DependencyNodeVisitor.java
@@ -0,0 +1,52 @@
+package org.apache.maven.shared.dependency.tree.traversal;
+
+/*
+ * 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.dependency.tree.DependencyNode;
+
+/**
+ * Defines a hierarchical visitor for processing dependency node trees.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @since 1.1
+ */
+public interface DependencyNodeVisitor
+{
+ /**
+ * Starts the visit to the specified dependency node.
+ *
+ * @param node
+ * the dependency node to visit
+ * @return <code>true</code> to visit the specified dependency node's children, <code>false</code> to skip the
+ * specified dependency node's children and proceed to its next sibling
+ */
+ boolean visit( DependencyNode node );
+
+ /**
+ * Ends the visit to to the specified dependency node.
+ *
+ * @param node
+ * the dependency node to visit
+ * @return <code>true</code> to visit the specified dependency node's next sibling, <code>false</code> to skip
+ * the specified dependency node's next siblings and proceed to its parent
+ */
+ boolean endVisit( DependencyNode node );
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/tree/traversal/FilteringDependencyNodeVisitor.java b/src/main/java/org/apache/maven/shared/dependency/tree/traversal/FilteringDependencyNodeVisitor.java
new file mode 100644
index 0000000..0c41fa1
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/tree/traversal/FilteringDependencyNodeVisitor.java
@@ -0,0 +1,124 @@
+package org.apache.maven.shared.dependency.tree.traversal;
+
+/*
+ * 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.dependency.tree.DependencyNode;
+import org.apache.maven.shared.dependency.tree.filter.DependencyNodeFilter;
+
+/**
+ * A dependency node visitor that filters nodes and delegates to another visitor.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @since 1.1
+ */
+public class FilteringDependencyNodeVisitor implements DependencyNodeVisitor
+{
+ // fields -----------------------------------------------------------------
+
+ /**
+ * The dependency node visitor to delegate to.
+ */
+ private final DependencyNodeVisitor visitor;
+
+ /**
+ * The dependency node filter to apply before delegation.
+ */
+ private final DependencyNodeFilter filter;
+
+ // constructors -----------------------------------------------------------
+
+ /**
+ * Creates a dependency node visitor that delegates nodes that are accepted by the specified filter to the specified
+ * visitor.
+ *
+ * @param visitor
+ * the dependency node visitor to delegate to
+ * @param filter
+ * the dependency node filter to apply before delegation
+ */
+ public FilteringDependencyNodeVisitor( DependencyNodeVisitor visitor, DependencyNodeFilter filter )
+ {
+ this.visitor = visitor;
+ this.filter = filter;
+ }
+
+ // DependencyNodeVisitor methods ------------------------------------------
+
+ /*
+ * @see org.apache.maven.shared.dependency.tree.DependencyNodeVisitor#visit(org.apache.maven.shared.dependency.tree.DependencyNode)
+ */
+ public boolean visit( DependencyNode node )
+ {
+ boolean visit;
+
+ if ( filter.accept( node ) )
+ {
+ visit = visitor.visit( node );
+ }
+ else
+ {
+ visit = true;
+ }
+
+ return visit;
+ }
+
+ /*
+ * @see org.apache.maven.shared.dependency.tree.DependencyNodeVisitor#endVisit(org.apache.maven.shared.dependency.tree.DependencyNode)
+ */
+ public boolean endVisit( DependencyNode node )
+ {
+ boolean visit;
+
+ if ( filter.accept( node ) )
+ {
+ visit = visitor.endVisit( node );
+ }
+ else
+ {
+ visit = true;
+ }
+
+ return visit;
+ }
+
+ // public methods ---------------------------------------------------------
+
+ /**
+ * Gets the dependency node visitor that this visitor delegates to.
+ *
+ * @return the dependency node visitor
+ */
+ public DependencyNodeVisitor getDependencyNodeVisitor()
+ {
+ return visitor;
+ }
+
+ /**
+ * Gets the dependency node filter that this visitor applies before delegation.
+ *
+ * @return the dependency node filter
+ */
+ public DependencyNodeFilter getDependencyNodeFilter()
+ {
+ return filter;
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/dependency/tree/traversal/SerializingDependencyNodeVisitor.java b/src/main/java/org/apache/maven/shared/dependency/tree/traversal/SerializingDependencyNodeVisitor.java
new file mode 100644
index 0000000..759566a
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/dependency/tree/traversal/SerializingDependencyNodeVisitor.java
@@ -0,0 +1,242 @@
+package org.apache.maven.shared.dependency.tree.traversal;
+
+/*
+ * 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.PrintWriter;
+import java.io.Writer;
+import java.util.List;
+
+import org.apache.maven.shared.dependency.tree.DependencyNode;
+
+/**
+ * A dependency node visitor that serializes visited nodes to a writer.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @since 1.1
+ */
+public class SerializingDependencyNodeVisitor implements DependencyNodeVisitor
+{
+ // classes ----------------------------------------------------------------
+
+ /**
+ * Provides tokens to use when serializing the dependency tree.
+ */
+ public static class TreeTokens
+ {
+ private final String nodeIndent;
+
+ private final String lastNodeIndent;
+
+ private final String fillIndent;
+
+ private final String lastFillIndent;
+
+ public TreeTokens( String nodeIndent, String lastNodeIndent, String fillIndent, String lastFillIndent )
+ {
+ this.nodeIndent = nodeIndent;
+ this.lastNodeIndent = lastNodeIndent;
+ this.fillIndent = fillIndent;
+ this.lastFillIndent = lastFillIndent;
+ }
+
+ public String getNodeIndent( boolean last )
+ {
+ return last ? lastNodeIndent : nodeIndent;
+ }
+
+ public String getFillIndent( boolean last )
+ {
+ return last ? lastFillIndent : fillIndent;
+ }
+ }
+
+ // constants --------------------------------------------------------------
+
+ /**
+ * Whitespace tokens to use when outputing the dependency tree.
+ */
+ public static final TreeTokens WHITESPACE_TOKENS = new TreeTokens( " ", " ", " ", " " );
+
+ /**
+ * The standard ASCII tokens to use when outputing the dependency tree.
+ */
+ public static final TreeTokens STANDARD_TOKENS = new TreeTokens( "+- ", "\\- ", "| ", " " );
+
+ /**
+ * The extended ASCII tokens to use when outputing the dependency tree.
+ */
+ public static final TreeTokens EXTENDED_TOKENS =
+ new TreeTokens( "\u00c3\u00c4 ", "\u00c0\u00c4 ", "\u00b3 ", " " );
+
+ // fields -----------------------------------------------------------------
+
+ /**
+ * The writer to serialize to.
+ */
+ private final PrintWriter writer;
+
+ /**
+ * The tokens to use when serializing the dependency tree.
+ */
+ private final TreeTokens tokens;
+
+ /**
+ * The depth of the currently visited dependency node.
+ */
+ private int depth;
+
+ // constructors -----------------------------------------------------------
+
+ /**
+ * Creates a dependency node visitor that serializes visited nodes to the specified writer using whitespace tokens.
+ *
+ * @param writer
+ * the writer to serialize to
+ */
+ public SerializingDependencyNodeVisitor( Writer writer )
+ {
+ this( writer, WHITESPACE_TOKENS );
+ }
+
+ /**
+ * Creates a dependency node visitor that serializes visited nodes to the specified writer using the specified
+ * tokens.
+ *
+ * @param writer
+ * the writer to serialize to
+ * @param tokens
+ * the tokens to use when serializing the dependency tree
+ */
+ public SerializingDependencyNodeVisitor( Writer writer, TreeTokens tokens )
+ {
+ if ( writer instanceof PrintWriter )
+ {
+ this.writer = (PrintWriter) writer;
+ }
+ else
+ {
+ this.writer = new PrintWriter( writer, true );
+ }
+
+ this.tokens = tokens;
+
+ depth = 0;
+ }
+
+ // DependencyNodeVisitor methods ------------------------------------------
+
+ /*
+ * @see org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor#visit(org.apache.maven.shared.dependency.tree.DependencyNode)
+ */
+ public boolean visit( DependencyNode node )
+ {
+ indent( node );
+
+ writer.println( node.toNodeString() );
+
+ depth++;
+
+ return true;
+ }
+
+ /*
+ * @see org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor#endVisit(org.apache.maven.shared.dependency.tree.DependencyNode)
+ */
+ public boolean endVisit( DependencyNode node )
+ {
+ depth--;
+
+ return true;
+ }
+
+ // private methods --------------------------------------------------------
+
+ /**
+ * Writes the necessary tokens to indent the specified dependency node to this visitor's writer.
+ *
+ * @param node
+ * the dependency node to indent
+ */
+ private void indent( DependencyNode node )
+ {
+ for ( int i = 1; i < depth; i++ )
+ {
+ writer.append( tokens.getFillIndent( isLast( node, i ) ) );
+ }
+
+ if ( depth > 0 )
+ {
+ writer.append( tokens.getNodeIndent( isLast( node ) ) );
+ }
+ }
+
+ /**
+ * Gets whether the specified dependency node is the last of its siblings.
+ *
+ * @param node
+ * the dependency node to check
+ * @return <code>true</code> if the specified dependency node is the last of its last siblings
+ */
+ private boolean isLast( DependencyNode node )
+ {
+ // TODO: remove node argument and calculate from visitor calls only
+
+ DependencyNode parent = node.getParent();
+
+ boolean last;
+
+ if ( parent == null )
+ {
+ last = true;
+ }
+ else
+ {
+ List siblings = parent.getChildren();
+
+ last = ( siblings.indexOf( node ) == siblings.size() - 1 );
+ }
+
+ return last;
+ }
+
+ /**
+ * Gets whether the specified dependency node ancestor is the last of its siblings.
+ *
+ * @param node
+ * the dependency node whose ancestor to check
+ * @param ancestorDepth
+ * the depth of the ancestor of the specified dependency node to check
+ * @return <code>true</code> if the specified dependency node ancestor is the last of its siblings
+ */
+ private boolean isLast( DependencyNode node, int ancestorDepth )
+ {
+ // TODO: remove node argument and calculate from visitor calls only
+
+ int distance = depth - ancestorDepth;
+
+ while ( distance-- > 0 )
+ {
+ node = node.getParent();
+ }
+
+ return isLast( node );
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/dependency/tree/AbstractDependencyNodeTest.java b/src/test/java/org/apache/maven/shared/dependency/tree/AbstractDependencyNodeTest.java
new file mode 100644
index 0000000..079a629
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/dependency/tree/AbstractDependencyNodeTest.java
@@ -0,0 +1,77 @@
+package org.apache.maven.shared.dependency.tree;
+
+/*
+ * 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.artifact.Artifact;
+import org.apache.maven.plugin.testing.stubs.ArtifactStub;
+import org.jmock.MockObjectTestCase;
+
+/**
+ * Provides utility methods for testing dependency nodes.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ */
+public abstract class AbstractDependencyNodeTest extends MockObjectTestCase
+{
+ // protected methods ------------------------------------------------------
+
+ protected DependencyNode createNode( String id )
+ {
+ return new DependencyNode( createArtifact( id ) );
+ }
+
+ protected Artifact createArtifact( String id )
+ {
+ String[] tokens = id.split( ":" );
+
+ return createArtifact( get( tokens, 0 ), get( tokens, 1 ), get( tokens, 2 ), get( tokens, 3 ), get( tokens, 4 ) );
+ }
+
+ protected Artifact createArtifact( String groupId, String artifactId, String version )
+ {
+ return createArtifact( groupId, artifactId, "jar", version );
+ }
+
+ protected Artifact createArtifact( String groupId, String artifactId, String type, String version )
+ {
+ return createArtifact( groupId, artifactId, type, version, null );
+ }
+
+ protected Artifact createArtifact( String groupId, String artifactId, String type, String version, String scope )
+ {
+ ArtifactStub artifact = new ArtifactStub();
+
+ artifact.setGroupId( groupId );
+ artifact.setArtifactId( artifactId );
+ artifact.setType( type );
+ artifact.setVersion( version );
+ artifact.setScope( scope );
+
+ return artifact;
+ }
+
+ // private methods --------------------------------------------------------
+
+ private String get( String[] array, int index )
+ {
+ return ( index < array.length ) ? array[index] : null;
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/dependency/tree/ArtifactMetadataSourceStub.java b/src/test/java/org/apache/maven/shared/dependency/tree/ArtifactMetadataSourceStub.java
new file mode 100644
index 0000000..94e5ff5
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/dependency/tree/ArtifactMetadataSourceStub.java
@@ -0,0 +1,107 @@
+package org.apache.maven.shared.dependency.tree;
+
+/*
+ * 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.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException;
+import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
+import org.apache.maven.artifact.metadata.ResolutionGroup;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+
+/**
+ * Provides a stub to simulate an artifact metadata source.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ */
+public class ArtifactMetadataSourceStub implements ArtifactMetadataSource
+{
+ // TODO: move to maven-plugin-testing-harness?
+
+ // fields -----------------------------------------------------------------
+
+ /**
+ * Map of resolution groups by artifact.
+ */
+ private final Map resolutionGroupsByArtifact;
+
+ // constructors -----------------------------------------------------------
+
+ /**
+ * Creates a new artifact metadata source stub.
+ */
+ public ArtifactMetadataSourceStub()
+ {
+ resolutionGroupsByArtifact = new HashMap();
+ }
+
+ // ArtifactMetadataSource methods -----------------------------------------
+
+ /*
+ * @see org.apache.maven.artifact.metadata.ArtifactMetadataSource#retrieve(org.apache.maven.artifact.Artifact,
+ * org.apache.maven.artifact.repository.ArtifactRepository, java.util.List)
+ */
+ public ResolutionGroup retrieve( Artifact artifact, ArtifactRepository localRepository, List remoteRepositories )
+ throws ArtifactMetadataRetrievalException
+ {
+ ResolutionGroup resolution = (ResolutionGroup) resolutionGroupsByArtifact.get( artifact );
+
+ // if we return null then the artifact gets excluded in DefaultArtifactCollector
+ if ( resolution == null )
+ {
+ resolution = new ResolutionGroup( artifact, Collections.EMPTY_SET, Collections.EMPTY_LIST );
+ }
+
+ return resolution;
+ }
+
+ /*
+ * @see org.apache.maven.artifact.metadata.ArtifactMetadataSource#retrieveAvailableVersions(org.apache.maven.artifact.Artifact,
+ * org.apache.maven.artifact.repository.ArtifactRepository, java.util.List)
+ */
+ public List retrieveAvailableVersions( Artifact artifact, ArtifactRepository localRepository,
+ List remoteRepositories ) throws ArtifactMetadataRetrievalException
+ {
+ return Collections.EMPTY_LIST;
+ }
+
+ // public methods ---------------------------------------------------------
+
+ /**
+ * Adds the specified dependency artifacts for the specified artifact to this artifact metadata source stub.
+ *
+ * @param artifact
+ * the artifact to add metadata to
+ * @param dependencyArtifacts
+ * the set of artifacts to register as dependencies of the specified artifact
+ */
+ public void addArtifactMetadata( Artifact artifact, Set dependencyArtifacts )
+ {
+ ResolutionGroup resolution = new ResolutionGroup( artifact, dependencyArtifacts, Collections.EMPTY_LIST );
+
+ resolutionGroupsByArtifact.put( artifact, resolution );
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/dependency/tree/DependencyNodeTest.java b/src/test/java/org/apache/maven/shared/dependency/tree/DependencyNodeTest.java
new file mode 100644
index 0000000..0bae008
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/dependency/tree/DependencyNodeTest.java
@@ -0,0 +1,197 @@
+package org.apache.maven.shared.dependency.tree;
+
+/*
+ * 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.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Iterator;
+
+import org.apache.maven.artifact.Artifact;
+
+/**
+ * Tests <code>DependencyNode</code>.
+ *
+ * @author <a href="mailto:carlos@apache.org">Carlos Sanchez</a>
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @see DependencyNode
+ */
+public class DependencyNodeTest
+ extends AbstractDependencyNodeTest
+{
+ private DependencyNode rootNode, node1, node2, node3, node4, node5, node6, node7;
+
+ protected void setUp()
+ throws Exception
+ {
+ super.setUp();
+
+ /*
+ * ------1------
+ * ----2---- 3
+ * 4 5 7
+ * 6
+ */
+
+ node1 = createNode( 1 );
+ node2 = createNode( node1, 2 );
+ node3 = createNode( node1, 3 );
+ node4 = createNode( node2, 4 );
+ node5 = createNode( node2, 5 );
+ node6 = createNode( node5, 6 );
+ node7 = createNode( node3, 7 );
+
+ rootNode = node1;
+ }
+
+ private void assertNode( Iterator it, DependencyNode node )
+ {
+ assertTrue( it.hasNext() );
+ assertSame( node, it.next() );
+ }
+
+ public void testPreorderIterator()
+ {
+ Iterator it = rootNode.iterator();
+
+ assertNode( it, node1 );
+ assertNode( it, node2 );
+ assertNode( it, node4 );
+ assertNode( it, node5 );
+ assertNode( it, node6 );
+ assertNode( it, node3 );
+ assertNode( it, node7 );
+ assertFalse( it.hasNext() );
+ }
+
+ public void testInverseIterator()
+ {
+ Iterator it = rootNode.inverseIterator();
+
+ assertNode( it, node7 );
+ assertNode( it, node3 );
+ assertNode( it, node6 );
+ assertNode( it, node5 );
+ assertNode( it, node4 );
+ assertNode( it, node2 );
+ assertNode( it, node1 );
+ assertFalse( it.hasNext() );
+ }
+
+ public void testToNodeString()
+ {
+ assertEquals( "groupId1:artifactId1:jar:1:compile", node1.toNodeString() );
+ }
+
+ public void testToString()
+ throws Exception
+ {
+ BufferedReader reader = new BufferedReader( new StringReader( node1.toString() ) );
+
+ assertLine( reader, 1, 0 );
+ assertLine( reader, 2, 1 );
+ assertLine( reader, 4, 2 );
+ assertLine( reader, 5, 2 );
+ assertLine( reader, 6, 3 );
+ assertLine( reader, 3, 1 );
+ assertLine( reader, 7, 2 );
+ }
+
+ public void testOmitForConflict()
+ {
+ Artifact relatedArtifact = createArtifact( createArtifactId( 2, "3" ) );
+ node2.omitForConflict( relatedArtifact );
+
+ assertEquals( DependencyNode.OMITTED_FOR_CONFLICT, node2.getState() );
+ assertEquals( relatedArtifact, node2.getRelatedArtifact() );
+
+ assertTrue( node2.getChildren().isEmpty() );
+ assertNull( node4.getParent() );
+ assertNull( node5.getParent() );
+ }
+
+ public void testOmitForConflictWithDuplicate()
+ {
+ Artifact relatedArtifact = createArtifact( createArtifactId( 2 ) );
+ node2.omitForConflict( relatedArtifact );
+
+ assertEquals( DependencyNode.OMITTED_FOR_DUPLICATE, node2.getState() );
+ assertEquals( relatedArtifact, node2.getRelatedArtifact() );
+
+ assertTrue( node2.getChildren().isEmpty() );
+ assertNull( node4.getParent() );
+ assertNull( node5.getParent() );
+ }
+
+ public void testOmitForCycle()
+ {
+ node2.omitForCycle();
+
+ assertEquals( DependencyNode.OMITTED_FOR_CYCLE, node2.getState() );
+
+ assertTrue( node2.getChildren().isEmpty() );
+ assertNull( node4.getParent() );
+ assertNull( node5.getParent() );
+ }
+
+ private void assertLine( BufferedReader reader, int i, int depth )
+ throws IOException
+ {
+ String line = reader.readLine();
+ StringBuffer sb = new StringBuffer();
+ for ( int j = 0; j < depth; j++ )
+ {
+ sb.append( " " );
+ }
+ sb.append( "groupId" );
+ sb.append( i );
+ sb.append( ":artifactId" );
+ sb.append( i );
+ sb.append( ":jar:" );
+ sb.append( i );
+ sb.append( ":compile" );
+ assertEquals( sb.toString(), line );
+ }
+
+ private DependencyNode createNode( DependencyNode parent, int i )
+ {
+ DependencyNode node = createNode( i );
+
+ parent.addChild( node );
+
+ return node;
+ }
+
+ private DependencyNode createNode( int i )
+ {
+ return createNode( createArtifactId( i ) );
+ }
+
+ private String createArtifactId( int i )
+ {
+ return createArtifactId( i, Integer.toString( i ) );
+ }
+
+ private String createArtifactId( int i, String version )
+ {
+ return "groupId" + i + ":artifactId" + i + ":jar:" + version + ":compile";
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/dependency/tree/DependencyTreeBuilderTest.java b/src/test/java/org/apache/maven/shared/dependency/tree/DependencyTreeBuilderTest.java
new file mode 100644
index 0000000..5802c7c
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/dependency/tree/DependencyTreeBuilderTest.java
@@ -0,0 +1,469 @@
+package org.apache.maven.shared.dependency.tree;
+
+/*
+ * 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.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import junit.framework.AssertionFailedError;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.DefaultArtifact;
+import org.apache.maven.artifact.factory.ArtifactFactory;
+import org.apache.maven.artifact.handler.DefaultArtifactHandler;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.artifact.repository.DefaultArtifactRepository;
+import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
+import org.apache.maven.artifact.resolver.ArtifactCollector;
+import org.apache.maven.artifact.resolver.ArtifactResolutionException;
+import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
+import org.apache.maven.artifact.resolver.ResolutionNode;
+import org.apache.maven.artifact.versioning.VersionRange;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.PlexusTestCase;
+
+/**
+ * Tests <code>DependencyTreeBuilder</code>.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @see DependencyTreeBuilder
+ */
+public class DependencyTreeBuilderTest extends PlexusTestCase
+{
+ // fields -----------------------------------------------------------------
+
+ private DependencyTreeBuilder builder;
+
+ private ArtifactRepository artifactRepository;
+
+ private ArtifactFactory artifactFactory;
+
+ private ArtifactMetadataSourceStub artifactMetadataSource;
+
+ private ArtifactCollector artifactCollector;
+
+ // TestCase methods -------------------------------------------------------
+
+ /*
+ * @see org.codehaus.plexus.PlexusTestCase#setUp()
+ */
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ builder = (DependencyTreeBuilder) lookup( DependencyTreeBuilder.ROLE );
+
+ String repositoryURL = getTestFile( "target/local-repo" ).toURI().toString();
+ artifactRepository = new DefaultArtifactRepository( "local", repositoryURL, new DefaultRepositoryLayout() );
+
+ artifactFactory = (ArtifactFactory) lookup( ArtifactFactory.ROLE );
+ artifactMetadataSource = new ArtifactMetadataSourceStub();
+ artifactCollector = (ArtifactCollector) lookup( ArtifactCollector.class.getName() );
+ }
+
+ /*
+ * @see org.codehaus.plexus.PlexusTestCase#tearDown()
+ */
+ protected void tearDown() throws Exception
+ {
+ super.tearDown();
+
+ builder = null;
+ }
+
+ // tests ------------------------------------------------------------------
+
+ public void testProjectWithDependency() throws DependencyTreeBuilderException, ArtifactResolutionException
+ {
+ Artifact projectArtifact = createArtifact( "g:p:t:1" );
+ Artifact childArtifact = createArtifact( "g:a:t:1" );
+
+ MavenProject project = createProject( projectArtifact, new Artifact[] { childArtifact } );
+
+ DependencyNode expectedRootNode = createNode( "g:p:t:1" );
+ expectedRootNode.addChild( createNode( "g:a:t:1" ) );
+
+ assertDependencyTree( expectedRootNode, project );
+ }
+
+ public void testProjectWithTransitiveDependency() throws DependencyTreeBuilderException, ArtifactResolutionException
+ {
+ Artifact projectArtifact = createArtifact( "g:p:t:1" );
+ Artifact childArtifact = createArtifact( "g:a:t:1" );
+ Artifact transitiveArtifact = createArtifact( "g:b:t:1" );
+ addArtifactMetadata( childArtifact, transitiveArtifact );
+
+ MavenProject project = createProject( projectArtifact, new Artifact[] { childArtifact } );
+
+ DependencyNode expectedRootNode = createNode( "g:p:t:1" );
+ DependencyNode childArtifactNode = createNode( "g:a:t:1" );
+ expectedRootNode.addChild( childArtifactNode );
+ childArtifactNode.addChild( createNode( "g:b:t:1" ) );
+
+ assertDependencyTree( expectedRootNode, project );
+ }
+
+ public void testProjectWithDuplicateDependency() throws DependencyTreeBuilderException, ArtifactResolutionException
+ {
+ Artifact projectArtifact = createArtifact( "g:p:t:1" );
+ Artifact child1Artifact = createArtifact( "g:a:t:1" );
+ Artifact transitiveArtifact = createArtifact( "g:c:t:1" );
+ Artifact child2Artifact = createArtifact( "g:b:t:1" );
+ Artifact duplicateTransitiveArtifact = createArtifact( "g:c:t:1" );
+ addArtifactMetadata( child1Artifact, transitiveArtifact );
+ addArtifactMetadata( child2Artifact, duplicateTransitiveArtifact );
+
+ MavenProject project = createProject( projectArtifact, new Artifact[] { child1Artifact, child2Artifact } );
+
+ DependencyNode expectedRootNode = createNode( "g:p:t:1" );
+ DependencyNode child1ArtifactNode = createNode( "g:a:t:1" );
+ expectedRootNode.addChild( child1ArtifactNode );
+ DependencyNode transitiveArtifactNode = createNode( "g:c:t:1" );
+ child1ArtifactNode.addChild( transitiveArtifactNode );
+ DependencyNode child2ArtifactNode = createNode( "g:b:t:1" );
+ expectedRootNode.addChild( child2ArtifactNode );
+ child2ArtifactNode.addChild( createNode( "g:c:t:1", DependencyNode.OMITTED_FOR_DUPLICATE, transitiveArtifactNode.getArtifact() ) );
+
+ assertDependencyTree( expectedRootNode, project );
+ }
+
+ public void testProjectWithConflictDependencyVersionFirstWins() throws DependencyTreeBuilderException, ArtifactResolutionException
+ {
+ Artifact projectArtifact = createArtifact( "g:p:t:1" );
+ Artifact nearestArtifact = createArtifact( "g:a:t:1" );
+ Artifact childArtifact = createArtifact( "g:b:t:1" );
+ Artifact farthestArtifact = createArtifact( "g:a:t:2" );
+ addArtifactMetadata( childArtifact, farthestArtifact );
+
+ MavenProject project = createProject( projectArtifact, new Artifact[] { nearestArtifact, childArtifact } );
+
+ DependencyNode expectedRootNode = createNode( "g:p:t:1" );
+ DependencyNode nearestArtifactNode = createNode( "g:a:t:1" );
+ expectedRootNode.addChild( nearestArtifactNode );
+ DependencyNode childArtifactNode = createNode( "g:b:t:1" );
+ expectedRootNode.addChild( childArtifactNode );
+ childArtifactNode.addChild( createNode( "g:a:t:2", DependencyNode.OMITTED_FOR_CONFLICT, nearestArtifactNode.getArtifact() ) );
+
+ assertDependencyTree( expectedRootNode, project );
+ }
+
+ public void testProjectWithConflictDependencyVersionLastWins() throws DependencyTreeBuilderException, ArtifactResolutionException
+ {
+ Artifact projectArtifact = createArtifact( "g:p:t:1" );
+ Artifact childArtifact = createArtifact( "g:a:t:1" );
+ Artifact farthestArtifact = createArtifact( "g:b:t:2" );
+ Artifact nearestArtifact = createArtifact( "g:b:t:1" );
+ addArtifactMetadata( childArtifact, farthestArtifact );
+
+ MavenProject project = createProject( projectArtifact, new Artifact[] { childArtifact, nearestArtifact } );
+
+ DependencyNode expectedRootNode = createNode( "g:p:t:1" );
+ DependencyNode childArtifactNode = createNode( "g:a:t:1" );
+ expectedRootNode.addChild( childArtifactNode );
+ DependencyNode farthestArtifactNode = createNode( "g:b:t:1" );
+ expectedRootNode.addChild( farthestArtifactNode );
+ childArtifactNode.addChild( createNode( "g:b:t:2", DependencyNode.OMITTED_FOR_CONFLICT, farthestArtifactNode.getArtifact() ) );
+
+ assertDependencyTree( expectedRootNode, project );
+ }
+
+ public void testProjectWithConflictDependencyScopeCurrentPom() throws DependencyTreeBuilderException, ArtifactResolutionException
+ {
+ Artifact projectArtifact = createArtifact( "g:p:t:1" );
+ Artifact nearestArtifact = createArtifact( "g:b:t:1:test" );
+ Artifact childArtifact = createArtifact( "g:a:t:1" );
+ Artifact farthestArtifact = createArtifact( "g:b:t:1:compile" );
+ addArtifactMetadata( childArtifact, farthestArtifact );
+
+ MavenProject project = createProject( projectArtifact, new Artifact[] { nearestArtifact, childArtifact } );
+
+ DependencyNode expectedRootNode = createNode( "g:p:t:1" );
+ DependencyNode nearestArtifactNode = createNode( "g:b:t:1:test" );
+ nearestArtifactNode.setFailedUpdateScope( "compile" );
+ expectedRootNode.addChild( nearestArtifactNode );
+ DependencyNode childArtifactNode = createNode( "g:a:t:1" );
+ expectedRootNode.addChild( childArtifactNode );
+ childArtifactNode.addChild( createNode( "g:b:t:1:compile", DependencyNode.OMITTED_FOR_DUPLICATE, nearestArtifactNode.getArtifact() ) );
+
+ assertDependencyTree( expectedRootNode, project );
+ }
+
+ // TODO: fix when discussion resolved
+ /*
+ public void testProjectWithConflictDependencyScope() throws DependencyTreeBuilderException, ArtifactResolutionException
+ {
+ Artifact projectArtifact = createArtifact( "g:p:t:1" );
+ Artifact childArtifact = createArtifact( "g:a:t:1" );
+ Artifact nearestArtifact = createArtifact( "g:c:t:1:test" );
+ Artifact grandchildArtifact = createArtifact( "g:b:t:1" );
+ Artifact farthestArtifact = createArtifact( "g:c:t:1:compile" );
+ addArtifactMetadata( childArtifact, new Artifact[] { nearestArtifact, grandchildArtifact } );
+ addArtifactMetadata( grandchildArtifact, farthestArtifact );
+
+ MavenProject project = createProject( projectArtifact, new Artifact[] { childArtifact } );
+
+ DependencyNode expectedRootNode = createNode( "g:p:t:1" );
+ DependencyNode childArtifactNode = createNode( "g:a:t:1" );
+ expectedRootNode.addChild( childArtifactNode );
+ DependencyNode nearestArtifactNode = createNode( "g:c:t:1:compile" );
+ nearestArtifactNode.setOriginalScope( "test" );
+ childArtifactNode.addChild( nearestArtifactNode );
+ DependencyNode grandchildArtifactNode = createNode( "g:b:t:1" );
+ childArtifactNode.addChild( grandchildArtifactNode );
+ grandchildArtifactNode.addChild( createNode( "g:c:t:1:compile", DependencyNode.OMITTED_FOR_DUPLICATE, nearestArtifactNode.getArtifact() ) );
+
+ assertDependencyTree( expectedRootNode, project );
+ }
+ */
+
+ public void testProjectWithManagedTransitiveDependencyVersion() throws DependencyTreeBuilderException, ArtifactResolutionException
+ {
+ Artifact projectArtifact = createArtifact( "g:p:t:1" );
+ Artifact childArtifact = createArtifact( "g:a:t:1" );
+ Artifact transitiveArtifact = createArtifact( "g:b:t:1" );
+ Artifact managedTransitiveArtifact = createArtifact( "g:b:t:2" );
+ addArtifactMetadata( childArtifact, transitiveArtifact );
+
+ MavenProject project = createProject( projectArtifact, new Artifact[] { childArtifact } );
+ setManagedVersionMap( project, Collections.singleton( managedTransitiveArtifact ) );
+
+ DependencyNode expectedRootNode = createNode( "g:p:t:1" );
+ DependencyNode childArtifactNode = createNode( "g:a:t:1" );
+ expectedRootNode.addChild( childArtifactNode );
+ DependencyNode managedTransitiveArtifactNode = createNode( "g:b:t:2" );
+ managedTransitiveArtifactNode.setPremanagedVersion( "1" );
+ childArtifactNode.addChild( managedTransitiveArtifactNode );
+
+ assertDependencyTree( expectedRootNode, project );
+ }
+
+ public void testProjectWithManagedTransitiveDependencyScope() throws DependencyTreeBuilderException, ArtifactResolutionException
+ {
+ Artifact projectArtifact = createArtifact( "g:p:t:1" );
+ Artifact childArtifact = createArtifact( "g:a:t:1" );
+ Artifact transitiveArtifact = createArtifact( "g:b:t:1:compile" );
+ Artifact managedTransitiveArtifact = createArtifact( "g:b:t:1:test" );
+ addArtifactMetadata( childArtifact, transitiveArtifact );
+
+ MavenProject project = createProject( projectArtifact, new Artifact[] { childArtifact } );
+ setManagedVersionMap( project, Collections.singleton( managedTransitiveArtifact ) );
+
+ DependencyNode expectedRootNode = createNode( "g:p:t:1" );
+ DependencyNode childArtifactNode = createNode( "g:a:t:1" );
+ expectedRootNode.addChild( childArtifactNode );
+ DependencyNode managedTransitiveArtifactNode = createNode( "g:b:t:1:test" );
+ managedTransitiveArtifactNode.setPremanagedScope( "compile" );
+ childArtifactNode.addChild( managedTransitiveArtifactNode );
+
+ assertDependencyTree( expectedRootNode, project );
+ }
+
+ public void testProjectWithManagedTransitiveDependencyVersionAndScope() throws DependencyTreeBuilderException, ArtifactResolutionException
+ {
+ Artifact projectArtifact = createArtifact( "g:p:t:1" );
+ Artifact childArtifact = createArtifact( "g:a:t:1" );
+ Artifact transitiveArtifact = createArtifact( "g:b:t:1:compile" );
+ Artifact managedTransitiveArtifact = createArtifact( "g:b:t:2:test" );
+ addArtifactMetadata( childArtifact, transitiveArtifact );
+
+ MavenProject project = createProject( projectArtifact, new Artifact[] { childArtifact } );
+ setManagedVersionMap( project, Collections.singleton( managedTransitiveArtifact ) );
+
+ DependencyNode expectedRootNode = createNode( "g:p:t:1" );
+ DependencyNode childArtifactNode = createNode( "g:a:t:1" );
+ expectedRootNode.addChild( childArtifactNode );
+ DependencyNode managedTransitiveArtifactNode = createNode( "g:b:t:2:test" );
+ managedTransitiveArtifactNode.setPremanagedVersion( "1" );
+ managedTransitiveArtifactNode.setPremanagedScope( "compile" );
+ childArtifactNode.addChild( managedTransitiveArtifactNode );
+
+ assertDependencyTree( expectedRootNode, project );
+ }
+
+ // private methods --------------------------------------------------------
+
+ private DependencyNode createNode( String id )
+ {
+ return createNode( id, DependencyNode.INCLUDED, null );
+ }
+
+ private DependencyNode createNode( String id, int state, Artifact relatedArtifact )
+ {
+ return new DependencyNode( createArtifact( id ), state, relatedArtifact );
+ }
+
+ private Artifact createArtifact( String id )
+ {
+ String[] tokens = id.split( ":" );
+
+ String groupId = get( tokens, 0 );
+ String artifactId = get( tokens, 1 );
+ String type = get( tokens, 2, "jar" );
+ String version = get( tokens, 3 );
+ String scope = get( tokens, 4 );
+
+ VersionRange versionRange = VersionRange.createFromVersion( version );
+
+ return new DefaultArtifact( groupId, artifactId, versionRange, scope, type, null, new DefaultArtifactHandler() );
+ }
+
+ private MavenProject createProject( Artifact projectArtifact, Artifact[] dependencyArtifacts )
+ {
+ MavenProject project = new MavenProject();
+ project.setArtifact( projectArtifact );
+ // LinkedHashSet since order is significant when omitting conflicts
+ project.setDependencyArtifacts( new LinkedHashSet( Arrays.asList( dependencyArtifacts ) ) );
+ project.setManagedVersionMap( new HashMap() );
+ project.setRemoteArtifactRepositories( Collections.EMPTY_LIST );
+ return project;
+ }
+
+ private void addArtifactMetadata( Artifact artifact, Artifact dependencyArtifact )
+ {
+ addArtifactMetadata( artifact, new Artifact[] { dependencyArtifact } );
+ }
+
+ private void addArtifactMetadata( Artifact artifact, Artifact[] dependencyArtifacts )
+ {
+ addArtifactMetadata( artifact, new HashSet( Arrays.asList( dependencyArtifacts ) ) );
+ }
+
+ private void addArtifactMetadata( Artifact artifact, Set dependencyArtifacts )
+ {
+ artifactMetadataSource.addArtifactMetadata( artifact, dependencyArtifacts );
+ }
+
+ private void setManagedVersionMap( MavenProject project, Set managedArtifacts )
+ {
+ Map managedVersionMap = new HashMap();
+
+ for ( Iterator iterator = managedArtifacts.iterator(); iterator.hasNext(); )
+ {
+ Artifact artifact = (Artifact) iterator.next();
+ String managementKey = getManagementKey( artifact );
+
+ managedVersionMap.put( managementKey, artifact );
+ }
+
+ project.setManagedVersionMap( managedVersionMap );
+ }
+
+ private String getManagementKey( Artifact artifact )
+ {
+ return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getType() + (artifact.getClassifier() != null ? ":" + artifact.getClassifier() : "");
+ }
+
+ private void assertDependencyTree( DependencyNode expectedRootNode, MavenProject project ) throws DependencyTreeBuilderException, ArtifactResolutionException
+ {
+ // assert built dependency tree is as expected
+
+ DependencyNode actualRootNode =
+ builder.buildDependencyTree( project, artifactRepository, artifactFactory, artifactMetadataSource, null,
+ artifactCollector );
+
+ assertEquals( "Dependency tree", expectedRootNode, actualRootNode );
+
+ // assert resolution tree is as expected
+
+ ArtifactResolutionResult result = collect( project );
+
+ assertTreeEquals( expectedRootNode, project, result );
+ }
+
+ private ArtifactResolutionResult collect( MavenProject project ) throws ArtifactResolutionException
+ {
+ return artifactCollector.collect( project.getDependencyArtifacts(), project.getArtifact(),
+ project.getManagedVersionMap(), artifactRepository,
+ project.getRemoteArtifactRepositories(), artifactMetadataSource, null,
+ Collections.EMPTY_LIST );
+ }
+
+ private void assertTreeEquals( DependencyNode dependencyNode, MavenProject project, ArtifactResolutionResult resolutionResult )
+ {
+ List rootChildrenResolutionNodes = ResolutionNodeUtils.getRootChildrenResolutionNodes( project, resolutionResult );
+
+ try
+ {
+ assertEquals( "Root node artifact", dependencyNode.getArtifact(), project.getArtifact() );
+
+ assertNodesEquals( dependencyNode.getChildren(), rootChildrenResolutionNodes );
+ }
+ catch ( AssertionFailedError error )
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append( error.getMessage() ).append( "; " );
+ buffer.append( "expected dependency tree <" ).append( dependencyNode ).append( "> " );
+ buffer.append( "actual resolution tree <" );
+ ResolutionNodeUtils.append( buffer, project, resolutionResult );
+ buffer.append( ">" );
+
+ throw new AssertionFailedError( buffer.toString() );
+ }
+ }
+
+ private void assertNodesEquals( List dependencyNodes, List resolutionNodes )
+ {
+ assertNodesEquals( dependencyNodes.iterator(), resolutionNodes.iterator() );
+ }
+
+ private void assertNodesEquals( Iterator dependencyNodesIterator, Iterator resolutionNodesIterator )
+ {
+ while ( dependencyNodesIterator.hasNext() && resolutionNodesIterator.hasNext() )
+ {
+ DependencyNode dependencyNode = (DependencyNode) dependencyNodesIterator.next();
+ ResolutionNode resolutionNode = (ResolutionNode) resolutionNodesIterator.next();
+
+ assertNodeEquals( dependencyNode, resolutionNode );
+ }
+
+ if ( dependencyNodesIterator.hasNext() || resolutionNodesIterator.hasNext() )
+ {
+ fail( "Node list size differs" );
+ }
+ }
+
+ private void assertNodeEquals( DependencyNode dependencyNode, ResolutionNode resolutionNode )
+ {
+ assertEquals( "Node state", dependencyNode.getState() == DependencyNode.INCLUDED, resolutionNode.isActive() );
+
+ assertEquals( "Node artifact", dependencyNode.getArtifact(), resolutionNode.getArtifact() );
+
+ assertNodesEquals( dependencyNode.getChildren().iterator(), resolutionNode.getChildrenIterator() );
+ }
+
+ private String get( String[] array, int index )
+ {
+ return get( array, index, null );
+ }
+
+ private String get( String[] array, int index, String defaultValue )
+ {
+ return ( index < array.length ) ? array[index] : defaultValue;
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/dependency/tree/DependencyTreeResolutionListenerTest.java b/src/test/java/org/apache/maven/shared/dependency/tree/DependencyTreeResolutionListenerTest.java
index 636ea64..e1a7194 100644
--- a/src/test/java/org/apache/maven/shared/dependency/tree/DependencyTreeResolutionListenerTest.java
+++ b/src/test/java/org/apache/maven/shared/dependency/tree/DependencyTreeResolutionListenerTest.java
@@ -19,18 +19,11 @@
* under the License.
*/
-import junit.framework.TestCase;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.handler.DefaultArtifactHandler;
import org.apache.maven.artifact.versioning.VersionRange;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-
/**
* Tests <code>DependencyTreeResolutionListener</code>.
*
@@ -39,12 +32,8 @@
* @version $Id$
* @see DependencyTreeResolutionListener
*/
-public class DependencyTreeResolutionListenerTest extends TestCase
+public class DependencyTreeResolutionListenerTest extends AbstractDependencyNodeTest
{
- // constants --------------------------------------------------------------
-
- private static final Artifact[] EMPTY_ARTIFACTS = new Artifact[0];
-
// fields -----------------------------------------------------------------
private DependencyTreeResolutionListener listener;
@@ -81,15 +70,12 @@
listener.endProcessChildren( projectArtifact );
- Collection artifacts = listener.getNodes();
- assertTrue( "Check artifact lists match", compareNodeListToArtifacts( artifacts, new Artifact[] {
- depArtifact01, depArtifact02, depArtifact03, projectArtifact } ) );
+ DependencyNode projectArtifactNode = new DependencyNode( projectArtifact );
+ projectArtifactNode.addChild( new DependencyNode( depArtifact01 ) );
+ projectArtifactNode.addChild( new DependencyNode( depArtifact02 ) );
+ projectArtifactNode.addChild( new DependencyNode( depArtifact03 ) );
- assertEquals( "Test dependency map key", projectArtifact, listener.getRootNode().getArtifact() );
-
- assertTrue( "Check artifact lists match", compareNodeListToArtifacts( listener.getRootNode().getChildren(),
- new Artifact[] { depArtifact01,
- depArtifact02, depArtifact03 } ) );
+ assertEquals( projectArtifactNode, listener.getRootNode() );
}
public void testSimpleDepTreeWithTransitiveDeps()
@@ -120,16 +106,15 @@
listener.endProcessChildren( projectArtifact );
- Collection artifacts = listener.getNodes();
- assertTrue( compareNodeListToArtifacts( artifacts, new Artifact[] { depArtifact1, depArtifact2, depArtifact3,
- depArtifact01, depArtifact02, projectArtifact } ) );
+ DependencyNode projectArtifactNode = new DependencyNode( projectArtifact );
+ DependencyNode depArtifact1Node = new DependencyNode( depArtifact1 );
+ projectArtifactNode.addChild( depArtifact1Node );
+ depArtifact1Node.addChild( new DependencyNode( depArtifact01 ) );
+ depArtifact1Node.addChild( new DependencyNode( depArtifact02 ) );
+ projectArtifactNode.addChild( new DependencyNode( depArtifact2 ) );
+ projectArtifactNode.addChild( new DependencyNode( depArtifact3 ) );
- assertEquals( "Check root", projectArtifact, listener.getRootNode().getArtifact() );
- assertTrue( compareNodeListToArtifacts( listener.getRootNode().getChildren(), new Artifact[] { depArtifact1,
- depArtifact2, depArtifact3 } ) );
-
- DependencyNode depNode1 = getChild( listener.getRootNode(), depArtifact1 );
- assertTrue( compareNodeListToArtifacts( depNode1.getChildren(), new Artifact[] { depArtifact01, depArtifact02 } ) );
+ assertEquals( projectArtifactNode, listener.getRootNode() );
}
public void testComplexDependencyTree()
@@ -139,7 +124,7 @@
listener.startProcessChildren( projectArtifact );
- Artifact depArtifact1 = createArtifact( "test-dep", "dependency-one", "1.0", Artifact.SCOPE_COMPILE );
+ Artifact depArtifact1 = createArtifact( "test-dep", "dependency-one", "jar", "1.0", Artifact.SCOPE_COMPILE );
listener.includeArtifact( depArtifact1 );
listener.startProcessChildren( depArtifact1 );
@@ -159,80 +144,254 @@
listener.endProcessChildren( depArtifact1 );
- Artifact depArtifact2 = createArtifact( "test-dep", "dependency-two", "1.0", Artifact.SCOPE_TEST );
+ Artifact depArtifact2 = createArtifact( "test-dep", "dependency-two", "jar", "1.0", Artifact.SCOPE_TEST );
listener.includeArtifact( depArtifact2 );
listener.startProcessChildren( depArtifact2 );
Artifact depArtifact21 = createArtifact( "test-dep", "dep-zero-two-1", "1.0" );
- listener.includeArtifact( depArtifact21 );
listener.omitForNearer( depArtifact121, depArtifact21 );
+ listener.includeArtifact( depArtifact21 );
listener.endProcessChildren( depArtifact2 );
- Artifact depArtifact3 = createArtifact( "test-dep", "dependency-three", "1.0", Artifact.SCOPE_COMPILE );
+ Artifact depArtifact3 = createArtifact( "test-dep", "dependency-three", "jar", "1.0", Artifact.SCOPE_COMPILE );
listener.includeArtifact( depArtifact3 );
listener.endProcessChildren( projectArtifact );
- Collection artifacts = listener.getNodes();
- assertTrue( compareNodeListToArtifacts( artifacts, new Artifact[] { depArtifact1, depArtifact2, depArtifact3,
- depArtifact11, depArtifact12, depArtifact21, projectArtifact } ) );
+ DependencyNode projectArtifactNode = new DependencyNode( projectArtifact );
+ DependencyNode depArtifact1Node = new DependencyNode( depArtifact1 );
+ projectArtifactNode.addChild( depArtifact1Node );
+ depArtifact1Node.addChild( new DependencyNode( depArtifact11 ) );
+ DependencyNode depArtifact12Node = new DependencyNode( depArtifact12 );
+ depArtifact1Node.addChild( depArtifact12Node );
+ depArtifact12Node.addChild( new DependencyNode( depArtifact121, DependencyNode.OMITTED_FOR_DUPLICATE,
+ depArtifact21 ) );
+ DependencyNode depArtifact2Node = new DependencyNode( depArtifact2 );
+ projectArtifactNode.addChild( depArtifact2Node );
+ depArtifact2Node.addChild( new DependencyNode( depArtifact21 ) );
+ projectArtifactNode.addChild( new DependencyNode( depArtifact3 ) );
- assertEquals( projectArtifact, listener.getRootNode().getArtifact() );
-
- assertTrue( compareNodeListToArtifacts( listener.getRootNode().getChildren(), new Artifact[] { depArtifact1,
- depArtifact2, depArtifact3 } ) );
-
- DependencyNode node = getChild( listener.getRootNode(), depArtifact1 );
- assertTrue( compareNodeListToArtifacts( node.getChildren(), new Artifact[] { depArtifact11, depArtifact12 } ) );
-
- node = getChild( node, depArtifact12 );
- assertTrue( compareNodeListToArtifacts( node.getChildren(), EMPTY_ARTIFACTS ) );
-
- node = getChild( listener.getRootNode(), depArtifact2 );
- assertTrue( compareNodeListToArtifacts( node.getChildren(), new Artifact[] { depArtifact21 } ) );
+ assertEquals( projectArtifactNode, listener.getRootNode() );
}
- // private methods --------------------------------------------------------
-
- private boolean compareNodeListToArtifacts( Collection nodes, Artifact[] artifacts )
+ public void testIncludeArtifactDuplicate()
{
- List artifactsRemaining = new ArrayList( Arrays.asList( artifacts ) );
+ Artifact projectArtifact = createArtifact( "test-project", "project-artifact", "1.0" );
+ listener.includeArtifact( projectArtifact );
- for ( Iterator i = nodes.iterator(); i.hasNext(); )
- {
- DependencyNode node = (DependencyNode) i.next();
+ listener.startProcessChildren( projectArtifact );
- if ( !artifactsRemaining.remove( node.getArtifact() ) )
- {
- return false;
- }
- }
- return artifactsRemaining.isEmpty();
+ Artifact depArtifact1 = createArtifact( "test-dep", "dependency", "1.0" );
+ listener.includeArtifact( depArtifact1 );
+
+ Artifact depArtifact2 = createArtifact( "test-dep", "dependency", "1.0" );
+ listener.omitForNearer( depArtifact1, depArtifact2 );
+ listener.includeArtifact( depArtifact2 );
+
+ listener.endProcessChildren( projectArtifact );
+
+ DependencyNode projectArtifactNode = new DependencyNode( projectArtifact );
+ projectArtifactNode.addChild( new DependencyNode( depArtifact1, DependencyNode.OMITTED_FOR_DUPLICATE, depArtifact2 ) );
+ projectArtifactNode.addChild( new DependencyNode( depArtifact2 ) );
+
+ assertEquals( projectArtifactNode, listener.getRootNode() );
}
- private DependencyNode getChild( DependencyNode node, Artifact artifact )
+ public void testIncludeArtifactDuplicateWithChildren()
{
- DependencyNode result = null;
- for ( Iterator i = node.getChildren().iterator(); i.hasNext() && result == null; )
- {
- DependencyNode child = (DependencyNode) i.next();
- if ( child.getArtifact().equals( artifact ) )
- {
- result = child;
- }
- }
- return result;
+ Artifact projectArtifact = createArtifact( "test-project", "project-artifact", "1.0" );
+ listener.includeArtifact( projectArtifact );
+
+ listener.startProcessChildren( projectArtifact );
+
+ Artifact depArtifact1 = createArtifact( "test-dep", "dependency", "1.0" );
+ listener.includeArtifact( depArtifact1 );
+
+ listener.startProcessChildren( depArtifact1 );
+
+ Artifact depArtifact11 = createArtifact( "test-dep", "child", "1.0" );
+ listener.includeArtifact( depArtifact11 );
+
+ listener.endProcessChildren( depArtifact1 );
+
+ Artifact depArtifact2 = createArtifact( "test-dep", "dependency", "1.0" );
+ listener.omitForNearer( depArtifact1, depArtifact2 );
+ listener.includeArtifact( depArtifact2 );
+
+ listener.startProcessChildren( depArtifact2 );
+
+ Artifact depArtifact21 = createArtifact( "test-dep", "child", "1.0" );
+ listener.includeArtifact( depArtifact21 );
+
+ listener.endProcessChildren( depArtifact2 );
+
+ listener.endProcessChildren( projectArtifact );
+
+ DependencyNode projectArtifactNode = new DependencyNode( projectArtifact );
+ DependencyNode depArtifact1Node = new DependencyNode( depArtifact1, DependencyNode.OMITTED_FOR_DUPLICATE, depArtifact2 );
+ projectArtifactNode.addChild( depArtifact1Node );
+ DependencyNode depArtifact2Node = new DependencyNode( depArtifact2 );
+ projectArtifactNode.addChild( depArtifact2Node );
+ depArtifact2Node.addChild( new DependencyNode( depArtifact21 ) );
+
+ assertEquals( projectArtifactNode, listener.getRootNode() );
}
- private Artifact createArtifact( String groupId, String artifactId, String version )
+ public void testOmitForConflictKept()
{
- return createArtifact( groupId, artifactId, version, null );
+ Artifact projectArtifact = createArtifact( "test-project", "project-artifact", "1.0" );
+ listener.includeArtifact( projectArtifact );
+
+ listener.startProcessChildren( projectArtifact );
+
+ Artifact depArtifact1 = createArtifact( "test-dep", "dependency", "1.0" );
+ listener.includeArtifact( depArtifact1 );
+
+ Artifact depArtifact2 = createArtifact( "test-dep", "dependency", "2.0" );
+ listener.omitForNearer( depArtifact1, depArtifact2 );
+ listener.includeArtifact( depArtifact2 );
+
+ listener.endProcessChildren( projectArtifact );
+
+ DependencyNode projectArtifactNode = new DependencyNode( projectArtifact );
+ projectArtifactNode.addChild( new DependencyNode( depArtifact1, DependencyNode.OMITTED_FOR_CONFLICT,
+ depArtifact2 ) );
+ projectArtifactNode.addChild( new DependencyNode( depArtifact2 ) );
+
+ assertEquals( projectArtifactNode, listener.getRootNode() );
}
- private Artifact createArtifact( String groupId, String artifactId, String version, String scope )
+ public void testOmitForConflictKeptWithChildren()
{
+ Artifact projectArtifact = createArtifact( "test-project", "project-artifact", "1.0" );
+ listener.includeArtifact( projectArtifact );
+
+ listener.startProcessChildren( projectArtifact );
+
+ Artifact depArtifact1 = createArtifact( "test-dep", "dependency", "1.0" );
+ listener.includeArtifact( depArtifact1 );
+
+ listener.startProcessChildren( depArtifact1 );
+
+ Artifact depArtifact11 = createArtifact( "test-dep", "child", "1.0" );
+ listener.includeArtifact( depArtifact11 );
+
+ listener.endProcessChildren( depArtifact1 );
+
+ Artifact depArtifact2 = createArtifact( "test-dep", "dependency", "2.0" );
+ listener.omitForNearer( depArtifact1, depArtifact2 );
+ listener.includeArtifact( depArtifact2 );
+
+ listener.startProcessChildren( depArtifact2 );
+
+ Artifact depArtifact21 = createArtifact( "test-dep", "child", "2.0" );
+ listener.includeArtifact( depArtifact21 );
+
+ listener.endProcessChildren( depArtifact2 );
+
+ listener.endProcessChildren( projectArtifact );
+
+ DependencyNode projectArtifactNode = new DependencyNode( projectArtifact );
+ projectArtifactNode.addChild( new DependencyNode( depArtifact1, DependencyNode.OMITTED_FOR_CONFLICT,
+ depArtifact2 ) );
+ DependencyNode depArtifact2Node = new DependencyNode( depArtifact2 );
+ projectArtifactNode.addChild( depArtifact2Node );
+ depArtifact2Node.addChild( new DependencyNode( depArtifact21 ) );
+
+ assertEquals( projectArtifactNode, listener.getRootNode() );
+ }
+
+ public void testOmitForConflictOmitted()
+ {
+ Artifact projectArtifact = createArtifact( "test-project", "project-artifact", "1.0" );
+ listener.includeArtifact( projectArtifact );
+
+ listener.startProcessChildren( projectArtifact );
+
+ Artifact depArtifact2 = createArtifact( "test-dep", "dependency", "2.0" );
+ listener.includeArtifact( depArtifact2 );
+
+ Artifact depArtifact1 = createArtifact( "test-dep", "dependency", "1.0" );
+ listener.omitForNearer( depArtifact1, depArtifact2 );
+
+ listener.endProcessChildren( projectArtifact );
+
+ DependencyNode projectArtifactNode = new DependencyNode( projectArtifact );
+ projectArtifactNode.addChild( new DependencyNode( depArtifact2 ) );
+ projectArtifactNode.addChild( new DependencyNode( depArtifact1, DependencyNode.OMITTED_FOR_CONFLICT,
+ depArtifact2 ) );
+
+ assertEquals( projectArtifactNode, listener.getRootNode() );
+ }
+
+ public void testOmitForConflictOmittedWithChildren()
+ {
+ Artifact projectArtifact = createArtifact( "test-project", "project-artifact", "1.0" );
+ listener.includeArtifact( projectArtifact );
+
+ listener.startProcessChildren( projectArtifact );
+
+ Artifact depArtifact2 = createArtifact( "test-dep", "dependency", "2.0" );
+ listener.includeArtifact( depArtifact2 );
+
+ listener.startProcessChildren( depArtifact2 );
+
+ Artifact depArtifact21 = createArtifact( "test-dep", "child", "2.0" );
+ listener.includeArtifact( depArtifact21 );
+
+ listener.endProcessChildren( depArtifact2 );
+
+ Artifact depArtifact1 = createArtifact( "test-dep", "dependency", "1.0" );
+ listener.omitForNearer( depArtifact1, depArtifact2 );
+
+ listener.startProcessChildren( depArtifact1 );
+
+ Artifact depArtifact11 = createArtifact( "test-dep", "child", "1.0" );
+ listener.includeArtifact( depArtifact11 );
+
+ listener.endProcessChildren( depArtifact1 );
+
+ listener.endProcessChildren( projectArtifact );
+
+ DependencyNode projectArtifactNode = new DependencyNode( projectArtifact );
+ DependencyNode depArtifact2Node = new DependencyNode( depArtifact2 );
+ projectArtifactNode.addChild( depArtifact2Node );
+ depArtifact2Node.addChild( new DependencyNode( depArtifact21 ) );
+ projectArtifactNode.addChild( new DependencyNode( depArtifact1, DependencyNode.OMITTED_FOR_CONFLICT,
+ depArtifact2 ) );
+
+ assertEquals( projectArtifactNode, listener.getRootNode() );
+ }
+
+ public void testOmitForCycle()
+ {
+ Artifact projectArtifact = createArtifact( "test-project", "project-artifact", "1.0" );
+ listener.includeArtifact( projectArtifact );
+
+ listener.startProcessChildren( projectArtifact );
+
+ listener.omitForCycle( projectArtifact );
+
+ listener.endProcessChildren( projectArtifact );
+
+ DependencyNode projectArtifactNode = new DependencyNode( projectArtifact );
+ projectArtifactNode.addChild( new DependencyNode( projectArtifact, DependencyNode.OMITTED_FOR_CYCLE ) );
+
+ assertEquals( projectArtifactNode, listener.getRootNode() );
+ }
+
+ // protected methods ------------------------------------------------------
+
+ /*
+ * @see org.apache.maven.shared.dependency.tree.AbstractDependencyNodeTest#createArtifact(java.lang.String,
+ * java.lang.String, java.lang.String, java.lang.String, java.lang.String)
+ */
+ protected Artifact createArtifact( String groupId, String artifactId, String type, String version, String scope )
+ {
+ // TODO: use super.createArtifact when possible
+
VersionRange versionRange = VersionRange.createFromVersion( version );
return new DefaultArtifact( groupId, artifactId, versionRange, scope, "jar", null,
diff --git a/src/test/java/org/apache/maven/shared/dependency/tree/DependencyTreeTest.java b/src/test/java/org/apache/maven/shared/dependency/tree/DependencyTreeTest.java
deleted file mode 100644
index 98fe969..0000000
--- a/src/test/java/org/apache/maven/shared/dependency/tree/DependencyTreeTest.java
+++ /dev/null
@@ -1,169 +0,0 @@
-package org.apache.maven.shared.dependency.tree;
-
-/*
- * 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.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.Arrays;
-import java.util.Iterator;
-
-import junit.framework.TestCase;
-
-import org.apache.maven.artifact.Artifact;
-import org.apache.maven.plugin.testing.stubs.ArtifactStub;
-
-/**
- * Tests for {@link DependencyTree} and {@link DependencyNode}
- *
- * @author <a href="mailto:carlos@apache.org">Carlos Sanchez</a>
- * @version $Id$
- */
-public class DependencyTreeTest
- extends TestCase
-{
- private DependencyTree tree;
- private DependencyNode node1, node2, node3, node4, node5, node6, node7;
-
- protected void setUp()
- throws Exception
- {
- super.setUp();
-
- /*
- * ------1------
- * ----2---- 3
- * 4 5 7
- * 6
- */
-
- node1 = getDependencyNode( 1 );
- node2 = getDependencyNode( 2 );
- node3 = getDependencyNode( 3 );
- node4 = getDependencyNode( 4 );
- node5 = getDependencyNode( 5 );
- node6 = getDependencyNode( 6 );
- node7 = getDependencyNode( 7 );
-
- node3.children.add( node7 );
-
- node5.children.add( node6 );
-
- node1.children.add( node2 );
- node1.children.add( node3 );
-
- node2.children.add( node4 );
- node2.children.add( node5 );
-
- tree = new DependencyTree( node1, Arrays.asList( new DependencyNode[] {
- node1,
- node2,
- node3,
- node4,
- node5,
- node6,
- node7 } ) );
- }
-
- private void assertNode( Iterator it, DependencyNode node )
- {
- assertTrue( it.hasNext() );
- assertSame( node, it.next() );
- }
-
- public void testPreorderIterator()
- {
- Iterator it = tree.iterator();
-
- assertNode( it, node1 );
- assertNode( it, node2 );
- assertNode( it, node4 );
- assertNode( it, node5 );
- assertNode( it, node6 );
- assertNode( it, node3 );
- assertNode( it, node7 );
- assertFalse( it.hasNext() );
- }
-
- public void testInverseIterator()
- {
- Iterator it = tree.inverseIterator();
-
- assertNode( it, node7 );
- assertNode( it, node3 );
- assertNode( it, node6 );
- assertNode( it, node5 );
- assertNode( it, node4 );
- assertNode( it, node2 );
- assertNode( it, node1 );
- assertFalse( it.hasNext() );
- }
-
- public void testToString()
- throws Exception
- {
- System.out.println( node1 );
-
- BufferedReader reader = new BufferedReader( new StringReader( node1.toString() ) );
-
- assertLine( reader, 1, 0 );
- assertLine( reader, 2, 1 );
- assertLine( reader, 4, 2 );
- assertLine( reader, 5, 2 );
- assertLine( reader, 6, 3 );
- assertLine( reader, 3, 1 );
- assertLine( reader, 7, 2 );
- }
-
- private void assertLine( BufferedReader reader, int i, int depth )
- throws IOException
- {
- String line = reader.readLine();
- StringBuffer sb = new StringBuffer();
- for ( int j = 0; j < depth; j++ )
- {
- sb.append( " " );
- }
- sb.append( "groupId" );
- sb.append( i );
- sb.append( ":artifactId" );
- sb.append( i );
- sb.append( ":jar:" );
- sb.append( i );
- assertEquals( sb.toString(), line );
- }
-
- private DependencyNode getDependencyNode( int i )
- {
- DependencyNode node = new DependencyNode();
- node.artifact = getArtifact( i );
- return node;
- }
-
- private Artifact getArtifact( int i )
- {
- ArtifactStub artifact = new ArtifactStub();
- artifact.setGroupId( "groupId" + i );
- artifact.setArtifactId( "artifactId" + i );
- artifact.setVersion( new Integer( i ).toString() );
- artifact.setType( "jar" );
- return artifact;
- }
-}
diff --git a/src/test/java/org/apache/maven/shared/dependency/tree/ResolutionNodeUtils.java b/src/test/java/org/apache/maven/shared/dependency/tree/ResolutionNodeUtils.java
new file mode 100644
index 0000000..321333c
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/dependency/tree/ResolutionNodeUtils.java
@@ -0,0 +1,137 @@
+package org.apache.maven.shared.dependency.tree;
+
+/*
+ * 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.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
+import org.apache.maven.artifact.resolver.ResolutionNode;
+import org.apache.maven.project.MavenProject;
+
+/**
+ * Utilities for working with resolution nodes.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @see ResolutionNode
+ */
+public final class ResolutionNodeUtils
+{
+ // constructors -----------------------------------------------------------
+
+ private ResolutionNodeUtils()
+ {
+ // private constructor for utility class
+ }
+
+ // public methods ---------------------------------------------------------
+
+ public static List getRootChildrenResolutionNodes( MavenProject project, ArtifactResolutionResult resolutionResult )
+ {
+ Set resolutionNodes = resolutionResult.getArtifactResolutionNodes();
+
+ // obtain root children nodes
+
+ Map rootChildrenResolutionNodesByArtifact = new HashMap();
+
+ for ( Iterator iterator = resolutionNodes.iterator(); iterator.hasNext(); )
+ {
+ ResolutionNode resolutionNode = (ResolutionNode) iterator.next();
+
+ if ( resolutionNode.isChildOfRootNode() )
+ {
+ rootChildrenResolutionNodesByArtifact.put( resolutionNode.getArtifact(), resolutionNode );
+ }
+ }
+
+ // order root children by project dependencies
+
+ List rootChildrenResolutionNodes = new ArrayList();
+
+ for ( Iterator iterator = project.getDependencyArtifacts().iterator(); iterator.hasNext(); )
+ {
+ Artifact artifact = (Artifact) iterator.next();
+ ResolutionNode resolutionNode = (ResolutionNode) rootChildrenResolutionNodesByArtifact.get( artifact );
+
+ rootChildrenResolutionNodes.add( resolutionNode );
+ }
+
+ return rootChildrenResolutionNodes;
+ }
+
+ public static String toString( MavenProject project, ArtifactResolutionResult result )
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ append( buffer, project, result );
+
+ return buffer.toString();
+ }
+
+ public static StringBuffer append( StringBuffer buffer, MavenProject project, ArtifactResolutionResult result )
+ {
+ ResolutionNode rootNode = new ResolutionNode( project.getArtifact(), Collections.EMPTY_LIST );
+ append( buffer, rootNode, 0 );
+
+ List rootChildrenNodes = getRootChildrenResolutionNodes( project, result );
+ append( buffer, rootChildrenNodes.iterator(), 1 );
+
+ return buffer;
+ }
+
+ // private methods --------------------------------------------------------
+
+ private static StringBuffer append( StringBuffer buffer, Iterator nodesIterator, int depth )
+ {
+ while ( nodesIterator.hasNext() )
+ {
+ ResolutionNode node = (ResolutionNode) nodesIterator.next();
+
+ append( buffer, node, depth );
+ }
+
+ return buffer;
+ }
+
+ private static StringBuffer append( StringBuffer buffer, ResolutionNode node, int depth )
+ {
+ for ( int i = 0; i < depth; i++ )
+ {
+ buffer.append( " " );
+ }
+
+ buffer.append( node );
+ buffer.append( System.getProperty( "line.separator" ) );
+
+ if ( node.isResolved() )
+ {
+ append( buffer, node.getChildrenIterator(), depth + 1 );
+ }
+
+ return buffer;
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/dependency/tree/filter/AbstractDependencyNodeFilterTest.java b/src/test/java/org/apache/maven/shared/dependency/tree/filter/AbstractDependencyNodeFilterTest.java
new file mode 100644
index 0000000..f24a705
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/dependency/tree/filter/AbstractDependencyNodeFilterTest.java
@@ -0,0 +1,44 @@
+package org.apache.maven.shared.dependency.tree.filter;
+
+/*
+ * 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.dependency.tree.DependencyNode;
+import org.jmock.Mock;
+import org.jmock.MockObjectTestCase;
+
+/**
+ * Provides utility methods for testing dependency node filters.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ */
+public abstract class AbstractDependencyNodeFilterTest extends MockObjectTestCase
+{
+ // protected methods ---------------------------------------------------------
+
+ protected DependencyNodeFilter createDependencyNodeFilter( DependencyNode node, boolean accept )
+ {
+ Mock mock = mock( DependencyNodeFilter.class );
+
+ mock.stubs().method( "accept" ).with( same( node ) ).will( returnValue( accept ) );
+
+ return (DependencyNodeFilter) mock.proxy();
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/dependency/tree/filter/AncestorOrSelfDependencyNodeFilterTest.java b/src/test/java/org/apache/maven/shared/dependency/tree/filter/AncestorOrSelfDependencyNodeFilterTest.java
new file mode 100644
index 0000000..e87575f
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/dependency/tree/filter/AncestorOrSelfDependencyNodeFilterTest.java
@@ -0,0 +1,113 @@
+package org.apache.maven.shared.dependency.tree.filter;
+
+/*
+ * 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.dependency.tree.AbstractDependencyNodeTest;
+import org.apache.maven.shared.dependency.tree.DependencyNode;
+
+/**
+ * Tests <code>AncestorOrSelfDependencyNodeFilter</code>.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @see AncestorOrSelfDependencyNodeFilter
+ */
+public class AncestorOrSelfDependencyNodeFilterTest extends AbstractDependencyNodeTest
+{
+ // constants --------------------------------------------------------------
+
+ private DependencyNode rootNode;
+
+ private DependencyNode childNode1;
+
+ private DependencyNode childNode2;
+
+ private DependencyNode grandChildNode;
+
+ private AncestorOrSelfDependencyNodeFilter filter;
+
+ // TestCase methods -------------------------------------------------------
+
+ /*
+ * @see junit.framework.TestCase#setUp()
+ */
+ protected void setUp() throws Exception
+ {
+ /*
+ * p -> a -> c
+ * -> b
+ */
+
+ rootNode = createNode( "g:p:t:1" );
+
+ childNode1 = createNode( "g:a:t:1" );
+ rootNode.addChild( childNode1 );
+
+ childNode2 = createNode( "g:b:t:1" );
+ rootNode.addChild( childNode2 );
+
+ grandChildNode = createNode( "g:c:t:1" );
+ childNode1.addChild( grandChildNode );
+ }
+
+ // tests ------------------------------------------------------------------
+
+ public void testSelf()
+ {
+ filter = new AncestorOrSelfDependencyNodeFilter( rootNode );
+
+ assertTrue( filter.accept( rootNode ) );
+ }
+
+ public void testParent()
+ {
+ filter = new AncestorOrSelfDependencyNodeFilter( childNode1 );
+
+ assertTrue( filter.accept( rootNode ) );
+ }
+
+ public void testGrandParent()
+ {
+ filter = new AncestorOrSelfDependencyNodeFilter( grandChildNode );
+
+ assertTrue( filter.accept( rootNode ) );
+ }
+
+ public void testCousin()
+ {
+ filter = new AncestorOrSelfDependencyNodeFilter( childNode2 );
+
+ assertFalse( filter.accept( childNode1 ) );
+ }
+
+ public void testChild()
+ {
+ filter = new AncestorOrSelfDependencyNodeFilter( rootNode );
+
+ assertFalse( filter.accept( childNode1 ) );
+ }
+
+ public void testGrandChild()
+ {
+ filter = new AncestorOrSelfDependencyNodeFilter( rootNode );
+
+ assertFalse( filter.accept( grandChildNode ) );
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/dependency/tree/filter/AndDependencyNodeFilterTest.java b/src/test/java/org/apache/maven/shared/dependency/tree/filter/AndDependencyNodeFilterTest.java
new file mode 100644
index 0000000..b5044f2
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/dependency/tree/filter/AndDependencyNodeFilterTest.java
@@ -0,0 +1,90 @@
+package org.apache.maven.shared.dependency.tree.filter;
+
+/*
+ * 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.artifact.Artifact;
+import org.apache.maven.plugin.testing.stubs.ArtifactStub;
+import org.apache.maven.shared.dependency.tree.DependencyNode;
+
+/**
+ * Tests <code>AndDependencyNodeFilter</code>.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @see AndDependencyNodeFilter
+ */
+public class AndDependencyNodeFilterTest extends AbstractDependencyNodeFilterTest
+{
+ // fields -----------------------------------------------------------------
+
+ private Artifact artifact;
+
+ private DependencyNode node;
+
+ private DependencyNodeFilter includeFilter;
+
+ private DependencyNodeFilter excludeFilter;
+
+ private AndDependencyNodeFilter filter;
+
+ // TestCase methods -------------------------------------------------------
+
+ /*
+ * @see junit.framework.TestCase#setUp()
+ */
+ protected void setUp() throws Exception
+ {
+ artifact = new ArtifactStub();
+ node = new DependencyNode( artifact );
+
+ includeFilter = createDependencyNodeFilter( node, true );
+ excludeFilter = createDependencyNodeFilter( node, false );
+ }
+
+ // tests ------------------------------------------------------------------
+
+ public void testIncludeInclude()
+ {
+ filter = new AndDependencyNodeFilter( includeFilter, includeFilter );
+
+ assertTrue( filter.accept( node ) );
+ }
+
+ public void testIncludeExclude()
+ {
+ filter = new AndDependencyNodeFilter( includeFilter, excludeFilter );
+
+ assertFalse( filter.accept( node ) );
+ }
+
+ public void testExcludeInclude()
+ {
+ filter = new AndDependencyNodeFilter( excludeFilter, includeFilter );
+
+ assertFalse( filter.accept( node ) );
+ }
+
+ public void testExcludeExclude()
+ {
+ filter = new AndDependencyNodeFilter( excludeFilter, excludeFilter );
+
+ assertFalse( filter.accept( node ) );
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/dependency/tree/filter/ArtifactDependencyNodeFilterTest.java b/src/test/java/org/apache/maven/shared/dependency/tree/filter/ArtifactDependencyNodeFilterTest.java
new file mode 100644
index 0000000..b506268
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/dependency/tree/filter/ArtifactDependencyNodeFilterTest.java
@@ -0,0 +1,87 @@
+package org.apache.maven.shared.dependency.tree.filter;
+
+/*
+ * 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.artifact.Artifact;
+import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
+import org.apache.maven.plugin.testing.stubs.ArtifactStub;
+import org.apache.maven.shared.dependency.tree.DependencyNode;
+import org.jmock.Mock;
+import org.jmock.MockObjectTestCase;
+
+/**
+ * Tests <code>ArtifactDependencyNodeFilter</code>.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @see ArtifactDependencyNodeFilter
+ */
+public class ArtifactDependencyNodeFilterTest extends MockObjectTestCase
+{
+ // fields -----------------------------------------------------------------
+
+ private Artifact artifact;
+
+ private DependencyNode node;
+
+ private ArtifactDependencyNodeFilter nodeFilter;
+
+ private ArtifactFilter artifactFilter;
+
+ // TestCase methods -------------------------------------------------------
+
+ /*
+ * @see junit.framework.TestCase#setUp()
+ */
+ protected void setUp() throws Exception
+ {
+ artifact = new ArtifactStub();
+ node = new DependencyNode( artifact );
+ }
+
+ // tests ------------------------------------------------------------------
+
+ public void testArtifactFilterInclude()
+ {
+ artifactFilter = createArtifactFilter( artifact, true );
+ nodeFilter = new ArtifactDependencyNodeFilter( artifactFilter );
+
+ assertTrue( nodeFilter.accept( node ) );
+ }
+
+ public void testArtifactFilterExclude()
+ {
+ artifactFilter = createArtifactFilter( artifact, false );
+ nodeFilter = new ArtifactDependencyNodeFilter( artifactFilter );
+
+ assertFalse( nodeFilter.accept( node ) );
+ }
+
+ // private methods --------------------------------------------------------
+
+ private ArtifactFilter createArtifactFilter( Artifact artifact, boolean include )
+ {
+ Mock mock = mock( ArtifactFilter.class );
+
+ mock.stubs().method( "include" ).with( same( artifact ) ).will( returnValue( include ) );
+
+ return (ArtifactFilter) mock.proxy();
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/dependency/tree/filter/StateDependencyNodeFilterTest.java b/src/test/java/org/apache/maven/shared/dependency/tree/filter/StateDependencyNodeFilterTest.java
new file mode 100644
index 0000000..14b6c35
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/dependency/tree/filter/StateDependencyNodeFilterTest.java
@@ -0,0 +1,106 @@
+package org.apache.maven.shared.dependency.tree.filter;
+
+/*
+ * 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 junit.framework.TestCase;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugin.testing.stubs.ArtifactStub;
+import org.apache.maven.shared.dependency.tree.DependencyNode;
+
+/**
+ * Tests <code>StateDependencyNodeFilter</code>.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @see StateDependencyNodeFilter
+ */
+public class StateDependencyNodeFilterTest extends TestCase
+{
+ // fields -----------------------------------------------------------------
+
+ private StateDependencyNodeFilter filter;
+
+ private DependencyNode includedNode;
+
+ private DependencyNode omittedForDuplicateNode;
+
+ private DependencyNode omittedForConflictNode;
+
+ private DependencyNode omittedForCycleNode;
+
+ // TestCase methods -------------------------------------------------------
+
+ /*
+ * @see junit.framework.TestCase#setUp()
+ */
+ protected void setUp() throws Exception
+ {
+ Artifact artifact = new ArtifactStub();
+ Artifact relatedArtifact = new ArtifactStub();
+
+ includedNode = new DependencyNode( artifact, DependencyNode.INCLUDED );
+ omittedForDuplicateNode = new DependencyNode( artifact, DependencyNode.OMITTED_FOR_DUPLICATE, relatedArtifact );
+ omittedForConflictNode = new DependencyNode( artifact, DependencyNode.OMITTED_FOR_CONFLICT, relatedArtifact );
+ omittedForCycleNode = new DependencyNode( artifact, DependencyNode.OMITTED_FOR_CYCLE );
+ }
+
+ // tests ------------------------------------------------------------------
+
+ public void testIncluded()
+ {
+ filter = new StateDependencyNodeFilter( DependencyNode.INCLUDED );
+
+ assertTrue( filter.accept( includedNode ) );
+ assertFalse( filter.accept( omittedForDuplicateNode ) );
+ assertFalse( filter.accept( omittedForConflictNode ) );
+ assertFalse( filter.accept( omittedForCycleNode ) );
+ }
+
+ public void testOmittedForDuplicate()
+ {
+ filter = new StateDependencyNodeFilter( DependencyNode.OMITTED_FOR_DUPLICATE );
+
+ assertFalse( filter.accept( includedNode ) );
+ assertTrue( filter.accept( omittedForDuplicateNode ) );
+ assertFalse( filter.accept( omittedForConflictNode ) );
+ assertFalse( filter.accept( omittedForCycleNode ) );
+ }
+
+ public void testOmittedForConflict()
+ {
+ filter = new StateDependencyNodeFilter( DependencyNode.OMITTED_FOR_CONFLICT );
+
+ assertFalse( filter.accept( includedNode ) );
+ assertFalse( filter.accept( omittedForDuplicateNode ) );
+ assertTrue( filter.accept( omittedForConflictNode ) );
+ assertFalse( filter.accept( omittedForCycleNode ) );
+ }
+
+ public void testOmittedForCycle()
+ {
+ filter = new StateDependencyNodeFilter( DependencyNode.OMITTED_FOR_CYCLE );
+
+ assertFalse( filter.accept( includedNode ) );
+ assertFalse( filter.accept( omittedForDuplicateNode ) );
+ assertFalse( filter.accept( omittedForConflictNode ) );
+ assertTrue( filter.accept( omittedForCycleNode ) );
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/dependency/tree/traversal/BuildingDependencyNodeVisitorTest.java b/src/test/java/org/apache/maven/shared/dependency/tree/traversal/BuildingDependencyNodeVisitorTest.java
new file mode 100644
index 0000000..19190b2
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/dependency/tree/traversal/BuildingDependencyNodeVisitorTest.java
@@ -0,0 +1,112 @@
+package org.apache.maven.shared.dependency.tree.traversal;
+
+/*
+ * 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.dependency.tree.AbstractDependencyNodeTest;
+import org.apache.maven.shared.dependency.tree.DependencyNode;
+import org.jmock.Mock;
+
+/**
+ * Tests <code>BuildingDependencyNodeVisitor</code>.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @see BuildingDependencyNodeVisitor
+ */
+public class BuildingDependencyNodeVisitorTest extends AbstractDependencyNodeTest
+{
+ // fields -----------------------------------------------------------------
+
+ private BuildingDependencyNodeVisitor visitor;
+
+ // tests ------------------------------------------------------------------
+
+ public void testVisitNode()
+ {
+ DependencyNode sourceNode = createNode( "g:a:t:1" );
+
+ visitor = new BuildingDependencyNodeVisitor();
+ visitor.visit( sourceNode );
+ visitor.endVisit( sourceNode );
+
+ DependencyNode resultNode = visitor.getDependencyTree();
+ assertNotSame( sourceNode, resultNode );
+ assertEquals( sourceNode, resultNode );
+ }
+
+ public void testVisitNodeWithChild()
+ {
+ DependencyNode sourceNode = createNode( "g:a:t:1" );
+ DependencyNode sourceChildNode = createNode( "g:b:t:1" );
+ sourceNode.addChild( sourceChildNode );
+
+ visitor = new BuildingDependencyNodeVisitor();
+ visitor.visit( sourceNode );
+ visitor.visit( sourceChildNode );
+ visitor.endVisit( sourceChildNode );
+ visitor.endVisit( sourceNode );
+
+ DependencyNode resultNode = visitor.getDependencyTree();
+ assertNotSame( sourceNode, resultNode );
+ assertEquals( sourceNode, resultNode );
+ }
+
+ public void testVisitNodeWithVisitor()
+ {
+ DependencyNode sourceNode = createNode( "g:a:t:1" );
+
+ Mock nextVisitorMock = mock( DependencyNodeVisitor.class );
+ nextVisitorMock.expects( once() ).method( "visit" ).with( eq( sourceNode ) ).will( returnValue( true ) ).id( "1" );
+ nextVisitorMock.expects( once() ).method( "endVisit" ).with( eq( sourceNode ) ).after( "1" ).will( returnValue( true ) );
+ DependencyNodeVisitor nextVisitor = (DependencyNodeVisitor) nextVisitorMock.proxy();
+
+ visitor = new BuildingDependencyNodeVisitor( nextVisitor );
+ visitor.visit( sourceNode );
+ visitor.endVisit( sourceNode );
+
+ DependencyNode resultNode = visitor.getDependencyTree();
+ assertNotSame( sourceNode, resultNode );
+ assertEquals( sourceNode, resultNode );
+ }
+
+ public void testVisitNodeWithChildAndVisitor()
+ {
+ DependencyNode sourceNode = createNode( "g:a:t:1" );
+ DependencyNode sourceChildNode = createNode( "g:b:t:1" );
+ sourceNode.addChild( sourceChildNode );
+
+ Mock nextVisitorMock = mock( DependencyNodeVisitor.class );
+ nextVisitorMock.expects( once() ).method( "visit" ).with( eq( sourceNode ) ).will( returnValue( true ) ).id( "1" );
+ nextVisitorMock.expects( once() ).method( "visit" ).with( eq( sourceChildNode ) ).after( "1" ).will( returnValue( true ) ).id( "2" );
+ nextVisitorMock.expects( once() ).method( "endVisit" ).with( eq( sourceChildNode ) ).after( "2" ).will( returnValue( true ) ).id( "3" );
+ nextVisitorMock.expects( once() ).method( "endVisit" ).with( eq( sourceNode ) ).after( "3" ).will( returnValue( true ) );
+ DependencyNodeVisitor nextVisitor = (DependencyNodeVisitor) nextVisitorMock.proxy();
+
+ visitor = new BuildingDependencyNodeVisitor( nextVisitor );
+ visitor.visit( sourceNode );
+ visitor.visit( sourceChildNode );
+ visitor.endVisit( sourceChildNode );
+ visitor.endVisit( sourceNode );
+
+ DependencyNode resultNode = visitor.getDependencyTree();
+ assertNotSame( sourceNode, resultNode );
+ assertEquals( sourceNode, resultNode );
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/dependency/tree/traversal/CollectingDependencyNodeVisitorTest.java b/src/test/java/org/apache/maven/shared/dependency/tree/traversal/CollectingDependencyNodeVisitorTest.java
new file mode 100644
index 0000000..f6dc5b1
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/dependency/tree/traversal/CollectingDependencyNodeVisitorTest.java
@@ -0,0 +1,102 @@
+package org.apache.maven.shared.dependency.tree.traversal;
+
+/*
+ * 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.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.maven.shared.dependency.tree.AbstractDependencyNodeTest;
+import org.apache.maven.shared.dependency.tree.DependencyNode;
+
+/**
+ * Tests <code>CollectingDependencyNodeVisitor</code>.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @see CollectingDependencyNodeVisitor
+ */
+public class CollectingDependencyNodeVisitorTest extends AbstractDependencyNodeTest
+{
+ // fields -----------------------------------------------------------------
+
+ private CollectingDependencyNodeVisitor visitor;
+
+ private DependencyNode node1;
+
+ private DependencyNode node2;
+
+ private DependencyNode node3;
+
+ // TestCase methods -------------------------------------------------------
+
+ /*
+ * @see junit.framework.TestCase#setUp()
+ */
+ protected void setUp() throws Exception
+ {
+ visitor = new CollectingDependencyNodeVisitor();
+ node1 = createNode( "g:a:t:1" );
+ node2 = createNode( "g:b:t:1" );
+ node3 = createNode( "g:c:t:1" );
+ }
+
+ // tests ------------------------------------------------------------------
+
+ public void testVisitSingleNode()
+ {
+ assertEmptyNodes();
+ assertTrue( visitor.visit( node1 ) );
+ assertNodes( node1 );
+ }
+
+ public void testVisitMultipleNodes()
+ {
+ assertEmptyNodes();
+ assertTrue( visitor.visit( node1 ) );
+ assertTrue( visitor.visit( node2 ) );
+ assertTrue( visitor.visit( node3 ) );
+ assertNodes( Arrays.asList( new Object[] { node1, node2, node3 } ) );
+ }
+
+ public void testEndVisit()
+ {
+ assertEmptyNodes();
+ assertTrue( visitor.endVisit( node1 ) );
+ assertEmptyNodes();
+ }
+
+ // private methods --------------------------------------------------------
+
+ private void assertEmptyNodes()
+ {
+ assertNodes( Collections.EMPTY_LIST );
+ }
+
+ private void assertNodes( DependencyNode node )
+ {
+ assertNodes( Collections.singletonList( node ) );
+ }
+
+ private void assertNodes( List expectedNodes )
+ {
+ assertEquals( "Collected nodes", expectedNodes, visitor.getNodes() );
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/dependency/tree/traversal/FilteringDependencyNodeVisitorTest.java b/src/test/java/org/apache/maven/shared/dependency/tree/traversal/FilteringDependencyNodeVisitorTest.java
new file mode 100644
index 0000000..4593023
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/dependency/tree/traversal/FilteringDependencyNodeVisitorTest.java
@@ -0,0 +1,117 @@
+package org.apache.maven.shared.dependency.tree.traversal;
+
+/*
+ * 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.plugin.testing.stubs.ArtifactStub;
+import org.apache.maven.shared.dependency.tree.DependencyNode;
+import org.apache.maven.shared.dependency.tree.filter.AbstractDependencyNodeFilterTest;
+import org.apache.maven.shared.dependency.tree.filter.DependencyNodeFilter;
+import org.jmock.Mock;
+
+/**
+ * Tests <code>FilteringDependencyNodeVisitor</code>.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @see FilteringDependencyNodeVisitor
+ */
+public class FilteringDependencyNodeVisitorTest extends AbstractDependencyNodeFilterTest
+{
+ // fields -----------------------------------------------------------------
+
+ private FilteringDependencyNodeVisitor visitor;
+
+ private DependencyNode node;
+
+ private DependencyNodeFilter acceptingFilter;
+
+ private DependencyNodeFilter rejectingFilter;
+
+ // TestCase methods -------------------------------------------------------
+
+ /*
+ * @see junit.framework.TestCase#setUp()
+ */
+ protected void setUp() throws Exception
+ {
+ node = new DependencyNode( new ArtifactStub() );
+
+ acceptingFilter = createDependencyNodeFilter( node, true );
+ rejectingFilter = createDependencyNodeFilter( node, false );
+ }
+
+ // tests ------------------------------------------------------------------
+
+ public void testVisitAcceptTrue()
+ {
+ Mock filteredVisitorMock = mock( DependencyNodeVisitor.class );
+ filteredVisitorMock.expects( once() ).method( "visit" ).with( eq( node ) ).will( returnValue( true ) );
+ DependencyNodeVisitor filteredVisitor = (DependencyNodeVisitor) filteredVisitorMock.proxy();
+
+ visitor = new FilteringDependencyNodeVisitor( filteredVisitor, acceptingFilter );
+ assertTrue( visitor.visit( node ) );
+ }
+
+ public void testVisitAcceptFalse()
+ {
+ Mock filteredVisitorMock = mock( DependencyNodeVisitor.class );
+ filteredVisitorMock.expects( once() ).method( "visit" ).with( eq( node ) ).will( returnValue( false ) );
+ DependencyNodeVisitor filteredVisitor = (DependencyNodeVisitor) filteredVisitorMock.proxy();
+
+ visitor = new FilteringDependencyNodeVisitor( filteredVisitor, acceptingFilter );
+ assertFalse( visitor.visit( node ) );
+ }
+
+ public void testVisitReject()
+ {
+ DependencyNodeVisitor filteredVisitor = (DependencyNodeVisitor) mock( DependencyNodeVisitor.class ).proxy();
+
+ visitor = new FilteringDependencyNodeVisitor( filteredVisitor, rejectingFilter );
+ assertTrue( visitor.visit( node ) );
+ }
+
+ public void testEndVisitAcceptTrue()
+ {
+ Mock filteredVisitorMock = mock( DependencyNodeVisitor.class );
+ filteredVisitorMock.expects( once() ).method( "endVisit" ).with( eq( node ) ).will( returnValue( true ) );
+ DependencyNodeVisitor filteredVisitor = (DependencyNodeVisitor) filteredVisitorMock.proxy();
+
+ visitor = new FilteringDependencyNodeVisitor( filteredVisitor, acceptingFilter );
+ assertTrue( visitor.endVisit( node ) );
+ }
+
+ public void testEndVisitAcceptFalse()
+ {
+ Mock filteredVisitorMock = mock( DependencyNodeVisitor.class );
+ filteredVisitorMock.expects( once() ).method( "endVisit" ).with( eq( node ) ).will( returnValue( false ) );
+ DependencyNodeVisitor filteredVisitor = (DependencyNodeVisitor) filteredVisitorMock.proxy();
+
+ visitor = new FilteringDependencyNodeVisitor( filteredVisitor, acceptingFilter );
+ assertFalse( visitor.endVisit( node ) );
+ }
+
+ public void testEndVisitReject()
+ {
+ DependencyNodeVisitor filteredVisitor = (DependencyNodeVisitor) mock( DependencyNodeVisitor.class ).proxy();
+
+ visitor = new FilteringDependencyNodeVisitor( filteredVisitor, rejectingFilter );
+ assertTrue( visitor.endVisit( node ) );
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/dependency/tree/traversal/SerializingDependencyNodeVisitorTest.java b/src/test/java/org/apache/maven/shared/dependency/tree/traversal/SerializingDependencyNodeVisitorTest.java
new file mode 100644
index 0000000..8d1ba9e
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/dependency/tree/traversal/SerializingDependencyNodeVisitorTest.java
@@ -0,0 +1,134 @@
+package org.apache.maven.shared.dependency.tree.traversal;
+
+/*
+ * 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.StringWriter;
+
+import org.apache.maven.shared.dependency.tree.AbstractDependencyNodeTest;
+import org.apache.maven.shared.dependency.tree.DependencyNode;
+
+/**
+ * Tests <code>SerializingDependencyNodeVisitor</code>.
+ *
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id$
+ * @see SerializingDependencyNodeVisitor
+ */
+public class SerializingDependencyNodeVisitorTest extends AbstractDependencyNodeTest
+{
+ // constants --------------------------------------------------------------
+
+ private static final String NEWLINE = System.getProperty( "line.separator" );
+
+ // fields -----------------------------------------------------------------
+
+ private StringWriter writer;
+
+ private SerializingDependencyNodeVisitor serializer;
+
+ // TestCase methods -------------------------------------------------------
+
+ /*
+ * @see junit.framework.TestCase#setUp()
+ */
+ protected void setUp() throws Exception
+ {
+ writer = new StringWriter();
+
+ serializer = new SerializingDependencyNodeVisitor( writer, SerializingDependencyNodeVisitor.STANDARD_TOKENS );
+ }
+
+ // tests ------------------------------------------------------------------
+
+ public void testSingleNode()
+ {
+ DependencyNode rootNode = createNode( "g:p:t:1" );
+
+ assertTree( "g:p:t:1" + NEWLINE, rootNode );
+ }
+
+ public void testNodeWithChild()
+ {
+ DependencyNode rootNode = createNode( "g:p:t:1" );
+ rootNode.addChild( createNode( "g:a:t:1" ) );
+
+ assertTree(
+ "g:p:t:1" + NEWLINE +
+ "\\- g:a:t:1" + NEWLINE,
+ rootNode );
+ }
+
+ public void testNodeWithMultipleChildren()
+ {
+ DependencyNode rootNode = createNode( "g:p:t:1" );
+ rootNode.addChild( createNode( "g:a:t:1" ) );
+ rootNode.addChild( createNode( "g:b:t:1" ) );
+ rootNode.addChild( createNode( "g:c:t:1" ) );
+
+ assertTree(
+ "g:p:t:1" + NEWLINE +
+ "+- g:a:t:1" + NEWLINE +
+ "+- g:b:t:1" + NEWLINE +
+ "\\- g:c:t:1" + NEWLINE,
+ rootNode );
+ }
+
+ public void testNodeWithGrandchild()
+ {
+ DependencyNode rootNode = createNode( "g:p:t:1" );
+ DependencyNode childNode = createNode( "g:a:t:1" );
+ rootNode.addChild( childNode );
+ childNode.addChild( createNode( "g:b:t:1" ) );
+
+ assertTree(
+ "g:p:t:1" + NEWLINE +
+ "\\- g:a:t:1" + NEWLINE +
+ " \\- g:b:t:1" + NEWLINE,
+ rootNode );
+ }
+
+ public void testNodeWithMultipleGrandchildren()
+ {
+ DependencyNode rootNode = createNode( "g:p:t:1" );
+ DependencyNode child1Node = createNode( "g:a:t:1" );
+ rootNode.addChild( child1Node );
+ child1Node.addChild( createNode( "g:b:t:1" ) );
+ DependencyNode child2Node = createNode( "g:c:t:1" );
+ rootNode.addChild( child2Node );
+ child2Node.addChild( createNode( "g:d:t:1" ) );
+
+ assertTree(
+ "g:p:t:1" + NEWLINE +
+ "+- g:a:t:1" + NEWLINE +
+ "| \\- g:b:t:1" + NEWLINE +
+ "\\- g:c:t:1" + NEWLINE +
+ " \\- g:d:t:1" + NEWLINE,
+ rootNode );
+ }
+
+ // private methods --------------------------------------------------------
+
+ private void assertTree( String expectedTree, DependencyNode actualNode )
+ {
+ actualNode.accept( serializer );
+
+ assertEquals( expectedTree, writer.toString() );
+ }
+}
diff --git a/src/test/resources/org/apache/maven/shared/dependency/tree/DependencyTreeBuilderTest.xml b/src/test/resources/org/apache/maven/shared/dependency/tree/DependencyTreeBuilderTest.xml
new file mode 100644
index 0000000..cf6ed39
--- /dev/null
+++ b/src/test/resources/org/apache/maven/shared/dependency/tree/DependencyTreeBuilderTest.xml
@@ -0,0 +1,36 @@
+<!--
+ ~ 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.
+ -->
+
+<component-set>
+ <components>
+ <component>
+ <role>org.apache.maven.artifact.factory.ArtifactFactory</role>
+ <implementation>org.apache.maven.artifact.factory.DefaultArtifactFactory</implementation>
+ <requirements>
+ <requirement>
+ <role>org.apache.maven.artifact.handler.manager.ArtifactHandlerManager</role>
+ </requirement>
+ </requirements>
+ </component>
+ <component>
+ <role>org.apache.maven.artifact.resolver.ArtifactCollector</role>
+ <implementation>org.apache.maven.artifact.resolver.DefaultArtifactCollector</implementation>
+ </component>
+ </components>
+</component-set>