FELIX-1148: take local copy of another shared Maven component (dependency-tree) so we can apply an outstanding patch

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@810655 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DefaultDependencyTreeBuilder.java b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DefaultDependencyTreeBuilder.java
new file mode 100644
index 0000000..bfce140
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DefaultDependencyTreeBuilder.java
@@ -0,0 +1,120 @@
+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.Map;
+import java.util.Set;
+
+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.resolver.ArtifactResolutionResult;
+import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.artifact.InvalidDependencyVersionException;
+import org.apache.maven.shared.dependency.tree.traversal.CollectingDependencyNodeVisitor;
+import org.codehaus.plexus.logging.AbstractLogEnabled;
+
+/**
+ * Default implementation of <code>DependencyTreeBuilder</code>.
+ * 
+ * @author Edwin Punzalan
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id: DefaultDependencyTreeBuilder.java 661727 2008-05-30 14:21:49Z bentmann $
+ * @plexus.component role="org.apache.maven.shared.dependency.tree.DependencyTreeBuilder"
+ * @see DependencyTreeBuilder
+ */
+public class DefaultDependencyTreeBuilder extends AbstractLogEnabled implements DependencyTreeBuilder
+{
+    // fields -----------------------------------------------------------------
+    
+    private ArtifactResolutionResult result;
+    
+    // DependencyTreeBuilder methods ------------------------------------------
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @deprecated
+     */
+    public DependencyTree buildDependencyTree( MavenProject project, ArtifactRepository repository,
+                                               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() );
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public DependencyNode buildDependencyTree( MavenProject project, ArtifactRepository repository,
+                                               ArtifactFactory factory, ArtifactMetadataSource metadataSource,
+                                               ArtifactFilter filter, ArtifactCollector collector )
+        throws DependencyTreeBuilderException
+    {
+        DependencyTreeResolutionListener listener = new DependencyTreeResolutionListener( getLogger() );
+
+        try
+        {
+            Map managedVersions = project.getManagedVersionMap();
+
+            Set dependencyArtifacts = project.getDependencyArtifacts();
+
+            if ( dependencyArtifacts == null )
+            {
+                dependencyArtifacts = project.createArtifacts( factory, null, null );
+            }
+            
+            getLogger().debug( "Dependency tree resolution listener events:" );
+            
+            // TODO: note that filter does not get applied due to MNG-3236
+
+            result = collector.collect( dependencyArtifacts, project.getArtifact(), managedVersions, repository,
+                               project.getRemoteArtifactRepositories(), metadataSource, filter,
+                               Collections.singletonList( listener ) );
+
+            return listener.getRootNode();
+        }
+        catch ( ArtifactResolutionException exception )
+        {
+            throw new DependencyTreeBuilderException( "Cannot build project dependency tree", exception );
+        }
+        catch ( InvalidDependencyVersionException e )
+        {
+            throw new DependencyTreeBuilderException( "Invalid dependency version for artifact "
+                + project.getArtifact() );
+        }
+    }
+    
+    // protected methods ------------------------------------------------------
+    
+    protected ArtifactResolutionResult getArtifactResolutionResult()
+    {
+        return result;
+    }
+}
diff --git a/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyNode.java b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyNode.java
new file mode 100644
index 0000000..e113c1b
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyNode.java
@@ -0,0 +1,904 @@
+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.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.artifact.versioning.VersionRange;
+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.
+ * 
+ * @author Edwin Punzalan
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id: DependencyNode.java 661727 2008-05-30 14:21:49Z bentmann $
+ */
+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 -----------------------------------------------------------------
+
+    /**
+     * The artifact that is attached to this dependency node.
+     */
+    private final Artifact artifact;
+
+    /**
+     * The list of child dependency nodes of this dependency node.
+     */
+    private final List children;
+
+    /**
+     * The parent dependency node of this dependency node.
+     */
+    private DependencyNode parent;
+
+    /**
+     * 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;
+
+    private VersionRange versionSelectedFromRange;
+    
+    private List availableVersions;
+
+    // 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. With the introduction of node
+     *             visitors and filters this method can give misleading results. For example, consider
+     *             serialising a tree with a filter using a visitor: this method would return the
+     *             unfiltered depth of a node, whereas the correct depth would be calculated by the
+     *             visitor.
+     */
+    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 Collections.unmodifiableList( children );
+    }
+
+    public boolean hasChildren()
+    {
+        return children.size() > 0;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * If the version was selected from a range this method will return the range.
+     * 
+     * @return the version range before a version was selected, or <code>null</code> if the artifact had a explicit
+     *         version.
+     * @since 1.2
+     */
+    public VersionRange getVersionSelectedFromRange()
+    {
+        return versionSelectedFromRange;
+    }
+    
+    public void setVersionSelectedFromRange( VersionRange versionSelectedFromRange )
+    {
+        this.versionSelectedFromRange = versionSelectedFromRange;
+    }
+
+    /**
+     * If the version was selected from a range this method will return the available versions when making the decision.
+     * 
+     * @return {@link List} &lt; {@link String} > the versions available when a version was selected from a range, or
+     *         <code>null</code> if the artifact had a explicit version.
+     * @since 1.2
+     */
+    public List getAvailableVersions()
+    {
+        return availableVersions;
+    }
+    
+    public void setAvailableVersions( List availableVersions )
+    {
+        this.availableVersions = availableVersions;
+    }
+
+    /**
+     * 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 = false;
+        if ( getArtifact().getVersion() != null )
+        {
+            duplicate = getArtifact().getVersion().equals( relatedArtifact.getVersion() );
+        }
+        else if ( getArtifact().getVersionRange() != null )
+        {
+            duplicate = getArtifact().getVersionRange().equals( relatedArtifact.getVersionRange() );
+        }
+        else
+        {
+            throw new RuntimeException( "Artifact version and version range is null: " + getArtifact() );
+        }
+
+        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()
+    {
+        return preorderIterator();
+    }
+
+    /**
+     * 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()
+    {
+        return new DependencyTreePreorderIterator( this );
+    }
+
+    /**
+     * 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()
+    {
+        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 );
+        
+        ItemAppender appender = new ItemAppender( buffer, included ? " (" : " - ", "; ", included ? ")" : "" );
+
+        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() );
+        }
+        
+        if ( getVersionSelectedFromRange() != null )
+        {
+            appender.append( "version selected from range ", getVersionSelectedFromRange().toString() );
+            appender.append( "available versions ", getAvailableVersions().toString() );
+        }
+        
+        switch ( state )
+        {
+            case INCLUDED:
+                break;
+                
+            case OMITTED_FOR_DUPLICATE:
+                appender.append( "omitted for duplicate" );
+                break;
+
+            case OMITTED_FOR_CONFLICT:
+                appender.append( "omitted for conflict with ", relatedArtifact.getVersion() );
+                break;
+
+            case OMITTED_FOR_CYCLE:
+                appender.append( "omitted for cycle" );
+                break;
+        }
+        
+        appender.flush();
+        
+        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 ---------------------------------------------------------
+
+    /**
+     * {@inheritDoc}
+     */
+    public int hashCode()
+    {
+        // TODO: probably better using commons-lang HashCodeBuilder
+        
+        int hashCode = 1;
+        
+        hashCode = hashCode * 31 + getArtifact().hashCode();
+        // DefaultArtifact.hashCode does not consider scope
+        hashCode = hashCode * 31 + nullHashCode( getArtifact().getScope() );
+
+        // TODO: use parent's artifact to prevent recursion - how can we improve this?
+        hashCode = hashCode * 31 + nullHashCode( nullGetArtifact( getParent() ) );
+        
+        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() );
+        hashCode = hashCode * 31 + nullHashCode( getVersionSelectedFromRange() );
+        hashCode = hashCode * 31 + nullHashCode( getAvailableVersions() );
+
+        return hashCode;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean equals( Object object )
+    {
+        // TODO: probably better using commons-lang EqualsBuilder
+        
+        boolean equal;
+
+        if ( object instanceof DependencyNode )
+        {
+            DependencyNode node = (DependencyNode) object;
+
+            equal = getArtifact().equals( node.getArtifact() );
+            // DefaultArtifact.hashCode does not consider scope
+            equal &= nullEquals( getArtifact().getScope(), node.getArtifact().getScope() );
+            
+            // TODO: use parent's artifact to prevent recursion - how can we improve this?
+            equal &= nullEquals( nullGetArtifact( getParent() ), nullGetArtifact( node.getParent() ) );
+            
+            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() );
+            equal &= nullEquals( getVersionSelectedFromRange(), node.getVersionSelectedFromRange() );
+            equal &= nullEquals( getAvailableVersions(), node.getAvailableVersions() );
+        }
+        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 ) );
+    }
+    
+    /**
+     * Gets the artifact for the specified node.
+     * 
+     * @param node
+     *            the dependency node, possibly <code>null</code>
+     * @return the node's artifact, or <code>null</code> if the specified node was <code>null</code>
+     */
+    private static Artifact nullGetArtifact( DependencyNode node )
+    {
+        return ( node != null ) ? node.getArtifact() : null;
+    }
+}
diff --git a/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTree.java b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTree.java
new file mode 100644
index 0000000..921c3be
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTree.java
@@ -0,0 +1,110 @@
+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.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Represents a Maven project's dependency tree.
+ * 
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id: DependencyTree.java 661727 2008-05-30 14:21:49Z bentmann $
+ * @deprecated As of 1.1, replaced by the dependency tree root {@link DependencyNode}
+ */
+public class DependencyTree
+{
+    // fields -----------------------------------------------------------------
+
+    private final DependencyNode rootNode;
+
+    private final Collection nodes;
+
+    // constructors -----------------------------------------------------------
+
+    /**
+     * Create a tree initialized to the arguments
+     * 
+     * @param rootNode
+     * @param nodes
+     */
+    public DependencyTree( DependencyNode rootNode, Collection nodes )
+    {
+        this.rootNode = rootNode;
+        this.nodes = nodes;
+    }
+
+    // public methods ---------------------------------------------------------
+
+    public DependencyNode getRootNode()
+    {
+        return rootNode;
+    }
+
+    public Collection getNodes()
+    {
+        return nodes;
+    }
+
+    public List getArtifacts()
+    {
+        List artifacts = new ArrayList();
+
+        Iterator it = getNodes().iterator();
+        while ( it.hasNext() )
+        {
+            DependencyNode node = (DependencyNode) it.next();
+            artifacts.add( node.getArtifact() );
+        }
+
+        return artifacts;
+    }
+
+    public String toString()
+    {
+        return getRootNode().toString();
+    }
+
+    /**
+     * @see DependencyNode#iterator()
+     */
+    public Iterator iterator()
+    {
+        return getRootNode().iterator();
+    }
+
+    /**
+     * @see DependencyNode#preorderIterator()
+     */
+    public Iterator preorderIterator()
+    {
+        return getRootNode().preorderIterator();
+    }
+
+    /**
+     * @see DependencyNode#inverseIterator()
+     */
+    public Iterator inverseIterator()
+    {
+        return getRootNode().inverseIterator();
+    }
+}
diff --git a/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreeBuilder.java b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreeBuilder.java
new file mode 100644
index 0000000..166c2a8
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreeBuilder.java
@@ -0,0 +1,92 @@
+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.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.filter.ArtifactFilter;
+import org.apache.maven.project.MavenProject;
+
+/**
+ * Builds a tree of dependencies for a given Maven project.
+ * 
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id: DependencyTreeBuilder.java 661727 2008-05-30 14:21:49Z bentmann $
+ */
+public interface DependencyTreeBuilder
+{
+    // fields -----------------------------------------------------------------
+
+    /**
+     * The plexus role for this component.
+     */
+    String ROLE = DependencyTreeBuilder.class.getName();
+
+    // public methods ---------------------------------------------------------
+
+    /**
+     * 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 collector
+     *            the artifact collector to use
+     * @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/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreeBuilderException.java b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreeBuilderException.java
new file mode 100644
index 0000000..8f9a0a8
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreeBuilderException.java
@@ -0,0 +1,48 @@
+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.
+ */
+
+/**
+ * Indicates that a Maven project's dependency tree cannot be resolved.
+ * 
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id: DependencyTreeBuilderException.java 661727 2008-05-30 14:21:49Z bentmann $
+ */
+public class DependencyTreeBuilderException extends Exception
+{
+    // constants --------------------------------------------------------------
+
+    /**
+     * The serialisation unique ID.
+     */
+    private static final long serialVersionUID = -3525803081807951764L;
+
+    // constructors -----------------------------------------------------------
+
+    public DependencyTreeBuilderException( String message )
+    {
+        super( message );
+    }
+
+    public DependencyTreeBuilderException( String message, Throwable cause )
+    {
+        super( message, cause );
+    }
+}
diff --git a/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreeInverseIterator.java b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreeInverseIterator.java
new file mode 100644
index 0000000..1c9e764
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreeInverseIterator.java
@@ -0,0 +1,70 @@
+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.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Stack;
+
+/**
+ * {@link Iterator} for {@link DependencyNode} implementing a traversal from leaves to root.
+ * 
+ * TODO {@link #DependencyTreeInverseIterator(DependencyNode)} is costly,
+ * a better implementation would move the cost to {@link #next()}
+ * 
+ * @author <a href="mailto:carlos@apache.org">Carlos Sanchez</a>
+ * @version $Id: DependencyTreeInverseIterator.java 661727 2008-05-30 14:21:49Z bentmann $
+ */
+public class DependencyTreeInverseIterator
+    implements Iterator
+{
+    private Stack nodesToProcess = new Stack();
+
+    public DependencyTreeInverseIterator( DependencyNode rootNode )
+    {
+        DependencyTreePreorderIterator it = new DependencyTreePreorderIterator( rootNode );
+        while ( it.hasNext() )
+        {
+            nodesToProcess.push( it.next() );
+        }
+    }
+
+    public boolean hasNext()
+    {
+        return !nodesToProcess.isEmpty();
+    }
+
+    public Object next()
+    {
+        if ( !hasNext() )
+        {
+            throw new NoSuchElementException();
+        }
+        return nodesToProcess.pop();
+    }
+
+    /**
+     * @throws UnsupportedOperationException
+     */
+    public void remove()
+    {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreePreorderIterator.java b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreePreorderIterator.java
new file mode 100644
index 0000000..df5277f
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreePreorderIterator.java
@@ -0,0 +1,73 @@
+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.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Stack;
+
+/**
+ * {@link Iterator} for {@link DependencyNode} implementing a preoder traversal.
+ * 
+ * @author <a href="mailto:carlos@apache.org">Carlos Sanchez</a>
+ * @version $Id: DependencyTreePreorderIterator.java 661727 2008-05-30 14:21:49Z bentmann $
+ */
+public class DependencyTreePreorderIterator
+    implements Iterator
+{
+    private Stack nodesToProcess = new Stack();
+
+    public DependencyTreePreorderIterator( DependencyNode rootNode )
+    {
+        nodesToProcess.push( rootNode );
+    }
+
+    public boolean hasNext()
+    {
+        return !nodesToProcess.isEmpty();
+    }
+
+    public Object next()
+    {
+        if ( !hasNext() )
+        {
+            throw new NoSuchElementException();
+        }
+        DependencyNode currentNode = (DependencyNode) nodesToProcess.pop();
+        List children = currentNode.getChildren();
+        if ( children != null )
+        {
+            for ( int i = children.size() - 1; i >= 0; i-- )
+            {
+                nodesToProcess.push( children.get( i ) );
+            }
+        }
+        return currentNode;
+    }
+
+    /**
+     * @throws UnsupportedOperationException
+     */
+    public void remove()
+    {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreeResolutionListener.java b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreeResolutionListener.java
new file mode 100644
index 0000000..492f382
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/DependencyTreeResolutionListener.java
@@ -0,0 +1,547 @@
+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.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+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;
+import org.codehaus.plexus.logging.Logger;
+
+/**
+ * An artifact resolution listener that constructs a dependency tree.
+ * 
+ * @author Edwin Punzalan
+ * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
+ * @version $Id: DependencyTreeResolutionListener.java 661727 2008-05-30 14:21:49Z bentmann $
+ */
+public class DependencyTreeResolutionListener implements ResolutionListener, ResolutionListenerForDepMgmt
+{
+    // fields -----------------------------------------------------------------
+    
+    /**
+     * The log to write debug messages to.
+     */
+    private final Logger logger;
+
+    /**
+     * The parent dependency nodes of the current dependency node.
+     */
+    private final Stack parentNodes;
+
+    /**
+     * 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;
+
+    /**
+     * The dependency node currently being processed by this listener.
+     */
+    private DependencyNode currentNode;
+    
+    /**
+     * Map &lt; String replacementId, String premanaged version >
+     */
+    private Map managedVersions = new HashMap();
+
+    /**
+     * Map &lt; String replacementId, String premanaged scope >
+     */
+    private Map managedScopes = new HashMap();
+
+    // constructors -----------------------------------------------------------
+
+    /**
+     * Creates a new dependency tree resolution listener that writes to the specified log.
+     * 
+     * @param logger
+     *            the log to write debug messages to
+     */
+    public DependencyTreeResolutionListener( Logger logger )
+    {
+        this.logger = logger;
+        
+        parentNodes = new Stack();
+        nodesByArtifact = new IdentityHashMap();
+        rootNode = null;
+        currentNode = null;
+    }
+
+    // ResolutionListener methods ---------------------------------------------
+
+    /**
+     * {@inheritDoc}
+     */
+    public void testArtifact( Artifact artifact )
+    {
+        log( "testArtifact: artifact=" + artifact );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void startProcessChildren( Artifact artifact )
+    {
+        log( "startProcessChildren: artifact=" + artifact );
+        
+        if ( !currentNode.getArtifact().equals( artifact ) )
+        {
+            throw new IllegalStateException( "Artifact was expected to be " + currentNode.getArtifact() + " but was "
+                            + artifact );
+        }
+
+        parentNodes.push( currentNode );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void endProcessChildren( Artifact artifact )
+    {
+        DependencyNode node = (DependencyNode) parentNodes.pop();
+
+        log( "endProcessChildren: artifact=" + artifact );
+        
+        if ( node == null )
+        {
+            throw new IllegalStateException( "Parent dependency node was null" );
+        }
+
+        if ( !node.getArtifact().equals( artifact ) )
+        {
+            throw new IllegalStateException( "Parent dependency node artifact was expected to be " + node.getArtifact()
+                            + " but was " + artifact );
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void includeArtifact( Artifact artifact )
+    {
+        log( "includeArtifact: artifact=" + artifact );
+        
+        DependencyNode existingNode = getNode( artifact );
+
+        /*
+         * 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.
+             */
+            flushDependencyManagement( node );
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void omitForNearer( Artifact omitted, Artifact kept )
+    {
+        log( "omitForNearer: omitted=" + omitted + " kept=" + kept );
+        
+        if ( !omitted.getDependencyConflictId().equals( kept.getDependencyConflictId() ) )
+        {
+            throw new IllegalArgumentException( "Omitted artifact dependency conflict id "
+                            + omitted.getDependencyConflictId() + " differs from kept artifact dependency conflict id "
+                            + kept.getDependencyConflictId() );
+        }
+
+        if ( isCurrentNodeIncluded() )
+        {
+            DependencyNode omittedNode = getNode( omitted );
+
+            if ( omittedNode != null )
+            {
+                removeNode( omitted );
+            }
+            else
+            {
+                omittedNode = createNode( omitted );
+
+                currentNode = omittedNode;
+            }
+
+            omittedNode.omitForConflict( kept );
+            
+            /*
+             * Add the dependency management information cached in any prior manageArtifact calls, since omitForNearer
+             * is always called after manageArtifact.
+             */
+            flushDependencyManagement( omittedNode );
+            
+            DependencyNode keptNode = getNode( kept );
+            
+            if ( keptNode == null )
+            {
+                addNode( kept );
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void updateScope( Artifact artifact, String scope )
+    {
+        log( "updateScope: artifact=" + artifact + ", scope=" + scope );
+        
+        DependencyNode node = getNode( artifact );
+
+        if ( node == null )
+        {
+            // updateScope events can be received prior to includeArtifact events
+            node = addNode( artifact );
+        }
+
+        node.setOriginalScope( artifact.getScope() );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void manageArtifact( Artifact artifact, Artifact replacement )
+    {
+        // TODO: remove when ResolutionListenerForDepMgmt merged into ResolutionListener
+        
+        log( "manageArtifact: artifact=" + artifact + ", replacement=" + replacement );
+        
+        if ( replacement.getVersion() != null )
+        {
+            manageArtifactVersion( artifact, replacement );
+        }
+        
+        if ( replacement.getScope() != null )
+        {
+            manageArtifactScope( artifact, replacement );
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void omitForCycle( Artifact artifact )
+    {
+        log( "omitForCycle: artifact=" + artifact );
+        
+        if ( isCurrentNodeIncluded() )
+        {
+            DependencyNode node = createNode( artifact );
+
+            node.omitForCycle();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void updateScopeCurrentPom( Artifact artifact, String scopeIgnored )
+    {
+        log( "updateScopeCurrentPom: artifact=" + artifact + ", scopeIgnored=" + scopeIgnored );
+        
+        DependencyNode node = getNode( artifact );
+
+        if ( node == null )
+        {
+            // updateScopeCurrentPom events can be received prior to includeArtifact events
+            node = addNode( artifact );
+            // TODO remove the node that tried to impose its scope and add some info
+        }
+
+        node.setFailedUpdateScope( scopeIgnored );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void selectVersionFromRange( Artifact artifact )
+    {
+        log( "selectVersionFromRange: artifact=" + artifact );
+
+        DependencyNode node = getNode( artifact );
+
+        /*
+         * selectVersionFromRange is called before includeArtifact
+         */
+        if ( node == null && isCurrentNodeIncluded() )
+        {
+            node = addNode( artifact );
+        }
+
+        node.setVersionSelectedFromRange( artifact.getVersionRange() );
+        node.setAvailableVersions( artifact.getAvailableVersions() );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void restrictRange( Artifact artifact, Artifact replacement, VersionRange versionRange )
+    {
+        log( "restrictRange: artifact=" + artifact + ", replacement=" + replacement + ", versionRange=" + versionRange );
+        
+        // TODO: track range restriction in node (MNG-3093)
+    }
+    
+    // ResolutionListenerForDepMgmt methods -----------------------------------
+    
+    /**
+     * {@inheritDoc}
+     */
+    public void manageArtifactVersion( Artifact artifact, Artifact replacement )
+    {
+        log( "manageArtifactVersion: artifact=" + artifact + ", replacement=" + 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.
+             */
+            managedVersions.put( replacement.getId(), artifact.getVersion() );
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void manageArtifactScope( Artifact artifact, Artifact replacement )
+    {
+        log( "manageArtifactScope: artifact=" + artifact + ", replacement=" + 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.
+             */
+            managedScopes.put( replacement.getId(), 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 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 --------------------------------------------------------
+    
+    /**
+     * Writes the specified message to the log at debug level with indentation for the current node's depth.
+     * 
+     * @param message
+     *            the message to write to the log
+     */
+    private void log( String message )
+    {
+        int depth = parentNodes.size();
+
+        StringBuffer buffer = new StringBuffer();
+
+        for ( int i = 0; i < depth; i++ )
+        {
+            buffer.append( "  " );
+        }
+
+        buffer.append( message );
+
+        logger.debug( buffer.toString() );
+    }
+
+    /**
+     * 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
+     */
+    // package protected for unit test
+    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;
+    }
+
+    /**
+     * Updates the specified node with any dependency management information cached in prior <code>manageArtifact</code>
+     * calls.
+     * 
+     * @param node
+     *            the node to update
+     */
+    private void flushDependencyManagement( DependencyNode node )
+    {
+        Artifact artifact = node.getArtifact();
+        String premanagedVersion = (String) managedVersions.get( artifact.getId() );
+        String premanagedScope = (String) managedScopes.get( artifact.getId() );
+        
+        if ( premanagedVersion != null || premanagedScope != null )
+        {
+            if ( premanagedVersion != null )
+            {
+                node.setPremanagedVersion( premanagedVersion );
+            }
+            
+            if ( premanagedScope != null )
+            {
+                node.setPremanagedScope( premanagedScope );
+            }
+            
+            premanagedVersion = null;
+            premanagedScope = null;
+        }
+    }
+}
diff --git a/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/filter/AncestorOrSelfDependencyNodeFilter.java b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/filter/AncestorOrSelfDependencyNodeFilter.java
new file mode 100644
index 0000000..7f56896
--- /dev/null
+++ b/bundleplugin/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: AncestorOrSelfDependencyNodeFilter.java 661727 2008-05-30 14:21:49Z bentmann $
+ * @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 -------------------------------------------
+
+    /**
+     * {@inheritDoc}
+     */
+    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/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/filter/AndDependencyNodeFilter.java b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/filter/AndDependencyNodeFilter.java
new file mode 100644
index 0000000..b5f801c
--- /dev/null
+++ b/bundleplugin/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: AndDependencyNodeFilter.java 661727 2008-05-30 14:21:49Z bentmann $
+ * @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 -------------------------------------------
+
+    /**
+     * {@inheritDoc}
+     */
+    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/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/filter/ArtifactDependencyNodeFilter.java b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/filter/ArtifactDependencyNodeFilter.java
new file mode 100644
index 0000000..9db6d4e
--- /dev/null
+++ b/bundleplugin/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: ArtifactDependencyNodeFilter.java 661727 2008-05-30 14:21:49Z bentmann $
+ * @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 -------------------------------------------
+
+    /**
+     * {@inheritDoc}
+     */
+    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/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/filter/DependencyNodeFilter.java b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/filter/DependencyNodeFilter.java
new file mode 100644
index 0000000..221769d
--- /dev/null
+++ b/bundleplugin/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: DependencyNodeFilter.java 661727 2008-05-30 14:21:49Z bentmann $
+ * @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/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/filter/StateDependencyNodeFilter.java b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/filter/StateDependencyNodeFilter.java
new file mode 100644
index 0000000..116cd80
--- /dev/null
+++ b/bundleplugin/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: StateDependencyNodeFilter.java 661727 2008-05-30 14:21:49Z bentmann $
+ * @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 -------------------------------------------
+
+    /**
+     * {@inheritDoc}
+     */
+    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/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/traversal/BuildingDependencyNodeVisitor.java b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/traversal/BuildingDependencyNodeVisitor.java
new file mode 100644
index 0000000..2be5777
--- /dev/null
+++ b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/traversal/BuildingDependencyNodeVisitor.java
@@ -0,0 +1,143 @@
+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: BuildingDependencyNodeVisitor.java 661727 2008-05-30 14:21:49Z bentmann $
+ * @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 ------------------------------------------
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean visit( DependencyNode node )
+    {
+        // clone the node
+        DependencyNode newNode = new DependencyNode( node.getArtifact(), node.getState(), node.getRelatedArtifact() );
+        newNode.setOriginalScope( node.getOriginalScope() );
+        newNode.setFailedUpdateScope( node.getFailedUpdateScope() );
+        newNode.setPremanagedVersion( node.getPremanagedVersion() );
+        newNode.setPremanagedScope( node.getPremanagedScope() );
+
+        if ( parentNodes.empty() )
+        {
+            rootNode = newNode;
+        }
+        else
+        {
+            DependencyNode parentNode = (DependencyNode) parentNodes.peek();
+            parentNode.addChild( newNode );
+        }
+
+        parentNodes.push( newNode );
+
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    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/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/traversal/CollectingDependencyNodeVisitor.java b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/traversal/CollectingDependencyNodeVisitor.java
new file mode 100644
index 0000000..b813267
--- /dev/null
+++ b/bundleplugin/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: CollectingDependencyNodeVisitor.java 661727 2008-05-30 14:21:49Z bentmann $
+ * @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 ------------------------------------------
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean visit( DependencyNode node )
+    {
+        // collect node
+        nodes.add( node );
+
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    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/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/traversal/DependencyNodeVisitor.java b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/traversal/DependencyNodeVisitor.java
new file mode 100644
index 0000000..011c9cb
--- /dev/null
+++ b/bundleplugin/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: DependencyNodeVisitor.java 661727 2008-05-30 14:21:49Z bentmann $
+ * @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/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/traversal/FilteringDependencyNodeVisitor.java b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/traversal/FilteringDependencyNodeVisitor.java
new file mode 100644
index 0000000..86cf6e8
--- /dev/null
+++ b/bundleplugin/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: FilteringDependencyNodeVisitor.java 661727 2008-05-30 14:21:49Z bentmann $
+ * @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 ------------------------------------------
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean visit( DependencyNode node )
+    {
+        boolean visit;
+
+        if ( filter.accept( node ) )
+        {
+            visit = visitor.visit( node );
+        }
+        else
+        {
+            visit = true;
+        }
+
+        return visit;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    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/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/traversal/SerializingDependencyNodeVisitor.java b/bundleplugin/src/main/java/org/apache/maven/shared/dependency/tree/traversal/SerializingDependencyNodeVisitor.java
new file mode 100644
index 0000000..14422a4
--- /dev/null
+++ b/bundleplugin/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: SerializingDependencyNodeVisitor.java 661727 2008-05-30 14:21:49Z bentmann $
+ * @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 ------------------------------------------
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean visit( DependencyNode node )
+    {
+        indent( node );
+
+        writer.println( node.toNodeString() );
+
+        depth++;
+
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    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.write( tokens.getFillIndent( isLast( node, i ) ) );
+        }
+
+        if ( depth > 0 )
+        {
+            writer.write( 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 );
+    }
+}