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>