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.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import junit.framework.AssertionFailedError;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.handler.DefaultArtifactHandler;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.DefaultArtifactRepository;
import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
import org.apache.maven.artifact.resolver.ArtifactCollector;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
import org.apache.maven.artifact.resolver.ResolutionNode;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.PlexusTestCase;

/**
 * Tests <code>DefaultDependencyTreeBuilder</code>.
 * 
 * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
 * @version $Id: DefaultDependencyTreeBuilderTest.java 1100703 2011-05-08 08:27:33Z hboutemy $
 * @see DefaultDependencyTreeBuilder
 */
public class DefaultDependencyTreeBuilderTest
    extends PlexusTestCase
{
    // fields -----------------------------------------------------------------

    private DefaultDependencyTreeBuilder builder;

    private ArtifactRepository artifactRepository;

    private ArtifactFactory artifactFactory;

    private ArtifactMetadataSourceStub artifactMetadataSource;

    private ArtifactCollector artifactCollector;

    // TestCase methods -------------------------------------------------------

    /**
     * {@inheritDoc}
     */
    protected void setUp()
        throws Exception
    {
        super.setUp();

        builder = (DefaultDependencyTreeBuilder) lookup( DependencyTreeBuilder.ROLE );

        String repositoryURL = getTestFile( "target/local-repo" ).toURI().toString();
        artifactRepository = new DefaultArtifactRepository( "local", repositoryURL, new DefaultRepositoryLayout() );

        artifactFactory = (ArtifactFactory) lookup( ArtifactFactory.ROLE );
        artifactMetadataSource = new ArtifactMetadataSourceStub();
        artifactCollector = (ArtifactCollector) lookup( ArtifactCollector.class.getName() );
    }

    /**
     * {@inheritDoc}
     */
    protected void tearDown()
        throws Exception
    {
        super.tearDown();

        builder = null;
    }

    // tests ------------------------------------------------------------------

    /**
     * Tests building a tree for a project with one dependency:
     * 
     * <pre>
     * g:p:t:1
     * \- g:a:t:1
     * </pre>
     * 
     * @throws DependencyTreeBuilderException 
     */
    public void testProjectWithDependency()
        throws DependencyTreeBuilderException
    {
        Artifact projectArtifact = createArtifact( "g:p:t:1" );
        Artifact childArtifact = createArtifact( "g:a:t:1" );

        MavenProject project = createProject( projectArtifact, new Artifact[] { childArtifact } );

        DependencyNode expectedRootNode = createNode( "g:p:t:1" );
        expectedRootNode.addChild( createNode( "g:a:t:1" ) );

        assertDependencyTree( expectedRootNode, project );
    }

    /**
     * Tests building a tree for a project with one transitive dependency:
     * 
     * <pre>
     * g:p:t:1
     * \- g:a:t:1
     *    \- g:b:t:1
     * </pre>
     *
     * @throws DependencyTreeBuilderException
     */
    public void testProjectWithTransitiveDependency()
        throws DependencyTreeBuilderException
    {
        Artifact projectArtifact = createArtifact( "g:p:t:1" );
        Artifact childArtifact = createArtifact( "g:a:t:1" );
        Artifact transitiveArtifact = createArtifact( "g:b:t:1" );
        addArtifactMetadata( childArtifact, transitiveArtifact );

        MavenProject project = createProject( projectArtifact, new Artifact[] { childArtifact } );

        DependencyNode expectedRootNode = createNode( "g:p:t:1" );
        DependencyNode childArtifactNode = createNode( "g:a:t:1" );
        expectedRootNode.addChild( childArtifactNode );
        childArtifactNode.addChild( createNode( "g:b:t:1" ) );

        assertDependencyTree( expectedRootNode, project );
    }

    /**
     * Tests building a tree for a project with a duplicate transitive dependency:
     * 
     * <pre>
     * g:p:t:1
     * +- g:a:t:1
     * |  \- g:c:t:1
     * \- g:b:t:1
     *    \- (g:c:t:1 - omitted for duplicate)
     * </pre>
     *
     * @throws DependencyTreeBuilderException
     */
    public void testProjectWithDuplicateDependency()
        throws DependencyTreeBuilderException
    {
        Artifact projectArtifact = createArtifact( "g:p:t:1" );
        Artifact child1Artifact = createArtifact( "g:a:t:1" );
        Artifact transitiveArtifact = createArtifact( "g:c:t:1" );
        Artifact child2Artifact = createArtifact( "g:b:t:1" );
        Artifact duplicateTransitiveArtifact = createArtifact( "g:c:t:1" );
        addArtifactMetadata( child1Artifact, transitiveArtifact );
        addArtifactMetadata( child2Artifact, duplicateTransitiveArtifact );

        MavenProject project = createProject( projectArtifact, new Artifact[] { child1Artifact, child2Artifact } );

        DependencyNode expectedRootNode = createNode( "g:p:t:1" );
        DependencyNode child1ArtifactNode = createNode( "g:a:t:1" );
        expectedRootNode.addChild( child1ArtifactNode );
        DependencyNode transitiveArtifactNode = createNode( "g:c:t:1" );
        child1ArtifactNode.addChild( transitiveArtifactNode );
        DependencyNode child2ArtifactNode = createNode( "g:b:t:1" );
        expectedRootNode.addChild( child2ArtifactNode );
        child2ArtifactNode.addChild( createNode( "g:c:t:1", DependencyNode.OMITTED_FOR_DUPLICATE, transitiveArtifactNode.getArtifact() ) );

        assertDependencyTree( expectedRootNode, project );
    }

    /**
     * Tests building a tree for a project with a dependency that has conflicting versions, where the nearest is
     * encountered first:
     * 
     * <pre>
     * g:p:t:1
     * +- g:a:t:1
     * \- g:b:t:1
     *    \- (g:a:t:2 - omitted for conflict with 1)
     * </pre>
     *
     * @throws DependencyTreeBuilderException
     */
    public void testProjectWithConflictDependencyVersionFirstWins()
        throws DependencyTreeBuilderException
    {
        Artifact projectArtifact = createArtifact( "g:p:t:1" );
        Artifact nearestArtifact = createArtifact( "g:a:t:1" );
        Artifact childArtifact = createArtifact( "g:b:t:1" );
        Artifact farthestArtifact = createArtifact( "g:a:t:2" );
        addArtifactMetadata( childArtifact, farthestArtifact );

        MavenProject project = createProject( projectArtifact, new Artifact[] { nearestArtifact, childArtifact } );

        DependencyNode expectedRootNode = createNode( "g:p:t:1" );
        DependencyNode nearestArtifactNode = createNode( "g:a:t:1" );
        expectedRootNode.addChild( nearestArtifactNode );
        DependencyNode childArtifactNode = createNode( "g:b:t:1" );
        expectedRootNode.addChild( childArtifactNode );
        childArtifactNode.addChild( createNode( "g:a:t:2", DependencyNode.OMITTED_FOR_CONFLICT, nearestArtifactNode.getArtifact() ) );

        assertDependencyTree( expectedRootNode, project );
    }

    /**
     * Tests building a tree for a project with a dependency that has conflicting versions, where the nearest is
     * encountered last:
     * 
     * <pre>
     * g:p:t:1
     * +- g:a:t:1
     * |  \- (g:b:t:2 - omitted for conflict with 1)
     * \- g:b:t:1
     * </pre>
     *
     * @throws DependencyTreeBuilderException
     */
    public void testProjectWithConflictDependencyVersionLastWins()
        throws DependencyTreeBuilderException
    {
        Artifact projectArtifact = createArtifact( "g:p:t:1" );
        Artifact childArtifact = createArtifact( "g:a:t:1" );
        Artifact farthestArtifact = createArtifact( "g:b:t:2" );
        Artifact nearestArtifact = createArtifact( "g:b:t:1" );
        addArtifactMetadata( childArtifact, farthestArtifact );

        MavenProject project = createProject( projectArtifact, new Artifact[] { childArtifact, nearestArtifact } );

        DependencyNode expectedRootNode = createNode( "g:p:t:1" );
        DependencyNode childArtifactNode = createNode( "g:a:t:1" );
        expectedRootNode.addChild( childArtifactNode );
        DependencyNode farthestArtifactNode = createNode( "g:b:t:1" );
        expectedRootNode.addChild( farthestArtifactNode );
        childArtifactNode.addChild( createNode( "g:b:t:2", DependencyNode.OMITTED_FOR_CONFLICT, farthestArtifactNode.getArtifact() ) );

        assertDependencyTree( expectedRootNode, project );
    }

    /**
     * Tests building a tree for a project with a dependency that has conflicting scopes, where the nearest is not
     * broadened since it is defined in the top-level POM:
     * 
     * <pre>
     * g:p:t:1
     * +- g:b:t:1:test (scope not updated to compile)
     * \- g:a:t:1
     *    \- (g:b:t:1:compile - omitted for duplicate)
     * </pre>
     *
     * @throws DependencyTreeBuilderException
     */
    public void testProjectWithConflictDependencyScopeCurrentPom()
        throws DependencyTreeBuilderException
    {
        Artifact projectArtifact = createArtifact( "g:p:t:1" );
        Artifact nearestArtifact = createArtifact( "g:b:t:1:test" );
        Artifact childArtifact = createArtifact( "g:a:t:1" );
        Artifact farthestArtifact = createArtifact( "g:b:t:1:compile" );
        addArtifactMetadata( childArtifact, farthestArtifact );

        MavenProject project = createProject( projectArtifact, new Artifact[] { nearestArtifact, childArtifact } );

        DependencyNode expectedRootNode = createNode( "g:p:t:1" );
        DependencyNode nearestArtifactNode = createNode( "g:b:t:1:test" );
        nearestArtifactNode.setFailedUpdateScope( "compile" );
        expectedRootNode.addChild( nearestArtifactNode );
        DependencyNode childArtifactNode = createNode( "g:a:t:1" );
        expectedRootNode.addChild( childArtifactNode );
        childArtifactNode.addChild( createNode( "g:b:t:1:compile", DependencyNode.OMITTED_FOR_DUPLICATE, nearestArtifactNode.getArtifact() ) );
        
        assertDependencyTree( expectedRootNode, project );
    }

    /**
     * Tests building a tree for a project with a dependency that has conflicting scopes, where the winner is
     * encountered first:
     * 
     * <pre>
     * g:p:t:1
     * \- g:a:t:1
     *    +- g:b:t:1
     *    |  \- g:c:t:1:compile
     *    \- (g:c:t:1:compile - scope updated from test; omitted for duplicate)
     * </pre>
     *
     * @throws DependencyTreeBuilderException
     */
    public void testProjectWithConflictDependencyScopeFirstWins()
        throws DependencyTreeBuilderException
    {
        Artifact projectArtifact = createArtifact( "g:p:t:1" );
        Artifact childArtifact = createArtifact( "g:a:t:1" );
        Artifact grandchildArtifact = createArtifact( "g:b:t:1" );
        Artifact farthestArtifact = createArtifact( "g:c:t:1:compile" );
        Artifact nearestArtifact = createArtifact( "g:c:t:1:test" );
        addArtifactMetadata( childArtifact, new Artifact[] { grandchildArtifact, nearestArtifact } );
        addArtifactMetadata( grandchildArtifact, farthestArtifact );

        MavenProject project = createProject( projectArtifact, new Artifact[] { childArtifact } );

        /*
         * TODO: Not entirely convinced that the expected tree is correct - I would have expected:
         * 
         * <pre>
         * g:p:t:1
         * \- g:a:t:1
         *    +- g:b:t:1
         *    |  \- (g:c:t:1:compile - omitted for duplicate)
         *    \- g:c:t:1:compile (scope updated from test)
         * </pre>
         * 
         * @see http://www.mail-archive.com/dev@maven.apache.org/msg68011.html
         */
        /*
        DependencyNode expectedRootNode = createNode( "g:p:t:1" );
        DependencyNode childArtifactNode = createNode( "g:a:t:1" );
        expectedRootNode.addChild( childArtifactNode );
        DependencyNode grandchildArtifactNode = createNode( "g:b:t:1" );
        childArtifactNode.addChild( grandchildArtifactNode );
        DependencyNode nearestArtifactNode = createNode( "g:c:t:1:compile" );
        DependencyNode farthestArtifactNode = createNode( "g:c:t:1:compile", DependencyNode.OMITTED_FOR_DUPLICATE, nearestArtifactNode.getArtifact() );
        grandchildArtifactNode.addChild( farthestArtifactNode );
        nearestArtifactNode.setOriginalScope( "test" );
        childArtifactNode.addChild( nearestArtifactNode );
        */

        DependencyNode expectedRootNode = createNode( "g:p:t:1" );
        DependencyNode childArtifactNode = createNode( "g:a:t:1" );
        expectedRootNode.addChild( childArtifactNode );
        DependencyNode grandchildArtifactNode = createNode( "g:b:t:1" );
        childArtifactNode.addChild( grandchildArtifactNode );
        DependencyNode farthestArtifactNode = createNode( "g:c:t:1:compile" );
        grandchildArtifactNode.addChild( farthestArtifactNode );
        DependencyNode nearestArtifactNode = createNode( "g:c:t:1:compile", DependencyNode.OMITTED_FOR_DUPLICATE, farthestArtifactNode.getArtifact() );
        nearestArtifactNode.setOriginalScope( "test" );
        childArtifactNode.addChild( nearestArtifactNode );
        
        assertDependencyTree( expectedRootNode, project );
    }

    /**
     * Tests building a tree for a project with a dependency that has conflicting scopes, where the winner is
     * encountered last:
     * 
     * <pre>
     * g:p:t:1
     * \- g:a:t:1
     *    +- (g:c:t:1:compile - scope updated from test; omitted for duplicate)
     *    \- g:b:t:1
     *       \- g:c:t:1:compile
     * </pre>
     *
     * @throws DependencyTreeBuilderException
     */
    public void testProjectWithConflictDependencyScopeLastWins()
        throws DependencyTreeBuilderException
    {
        Artifact projectArtifact = createArtifact( "g:p:t:1" );
        Artifact childArtifact = createArtifact( "g:a:t:1" );
        Artifact nearestArtifact = createArtifact( "g:c:t:1:test" );
        Artifact grandchildArtifact = createArtifact( "g:b:t:1" );
        Artifact farthestArtifact = createArtifact( "g:c:t:1:compile" );
        addArtifactMetadata( childArtifact, new Artifact[] { nearestArtifact, grandchildArtifact } );
        addArtifactMetadata( grandchildArtifact, farthestArtifact );

        MavenProject project = createProject( projectArtifact, new Artifact[] { childArtifact } );

        /*
         * TODO: Not entirely convinced that the expected tree is correct - I would have expected:
         * 
         * <pre>
         * g:p:t:1
         * \- g:a:t:1
         *    +- g:c:t:1:compile (scope updated from test)
         *    \- g:b:t:1
         *       \- (g:c:t:1:compile - omitted for duplicate)
         * </pre>
         * 
         * @see http://www.mail-archive.com/dev@maven.apache.org/msg68011.html
         */
        /*
        DependencyNode expectedRootNode = createNode( "g:p:t:1" );
        DependencyNode childArtifactNode = createNode( "g:a:t:1" );
        expectedRootNode.addChild( childArtifactNode );
        DependencyNode nearestArtifactNode = createNode( "g:c:t:1:compile" );
        nearestArtifactNode.setOriginalScope( "test" );
        childArtifactNode.addChild( nearestArtifactNode );
        DependencyNode grandchildArtifactNode = createNode( "g:b:t:1" );
        childArtifactNode.addChild( grandchildArtifactNode );
        grandchildArtifactNode.addChild( createNode( "g:c:t:1:compile", DependencyNode.OMITTED_FOR_DUPLICATE, nearestArtifactNode.getArtifact() ) );
        */
        
        DependencyNode expectedRootNode = createNode( "g:p:t:1" );
        DependencyNode childArtifactNode = createNode( "g:a:t:1" );
        expectedRootNode.addChild( childArtifactNode );
        DependencyNode farthestArtifactNode = createNode( "g:c:t:1:compile" );
        DependencyNode nearestArtifactNode = createNode( "g:c:t:1:compile", DependencyNode.OMITTED_FOR_DUPLICATE, farthestArtifactNode.getArtifact() );
        nearestArtifactNode.setOriginalScope( "test" );
        childArtifactNode.addChild( nearestArtifactNode );
        DependencyNode grandchildArtifactNode = createNode( "g:b:t:1" );
        childArtifactNode.addChild( grandchildArtifactNode );
        grandchildArtifactNode.addChild( farthestArtifactNode );
        
        assertDependencyTree( expectedRootNode, project );
    }

    /**
     * Tests building a tree for a project with one transitive dependency whose version is fixed in dependency
     * management:
     * 
     * <pre>
     * g:p:t:1
     * \- g:a:t:1
     *    \- g:b:t:2 (version managed from 1)
     * </pre>
     * 
     * @throws DependencyTreeBuilderException
     */
    public void testProjectWithManagedTransitiveDependencyVersion()
        throws DependencyTreeBuilderException
    {
        Artifact projectArtifact = createArtifact( "g:p:t:1" );
        Artifact childArtifact = createArtifact( "g:a:t:1" );
        Artifact transitiveArtifact = createArtifact( "g:b:t:1" );
        Artifact managedTransitiveArtifact = createArtifact( "g:b:t:2" );
        addArtifactMetadata( childArtifact, transitiveArtifact );

        MavenProject project = createProject( projectArtifact, new Artifact[] { childArtifact } );
        setManagedVersionMap( project, Collections.singleton( managedTransitiveArtifact ) );

        DependencyNode expectedRootNode = createNode( "g:p:t:1" );
        DependencyNode childArtifactNode = createNode( "g:a:t:1" );
        expectedRootNode.addChild( childArtifactNode );
        DependencyNode managedTransitiveArtifactNode = createNode( "g:b:t:2" );
        managedTransitiveArtifactNode.setPremanagedVersion( "1" );
        childArtifactNode.addChild( managedTransitiveArtifactNode );

        assertDependencyTree( expectedRootNode, project );
    }

    // TODO: see MNG-3548
    /**
     * Tests building a tree for a project with a dependency that is duplicated and the version is also fixed in 
     * dependency management:
     * 
     * <pre>
     * g:p:t:1
     * \- g:a:t:1
     *    +- g:b:t:1
     *    |  \- (g:c:t:2 - version managed from 1; omitted for duplicate)
     *    \- g:c:t:2 (version managed from 1)
     * </pre>
     * 
     * @throws DependencyTreeBuilderException
     */
    /*
    public void testProjectWithManagedTransitiveDependencyVersionAndDuplicate() throws DependencyTreeBuilderException
    {
        Artifact projectArtifact = createArtifact( "g:p:t:1" );
        Artifact childArtifact = createArtifact( "g:a:t:1" );
        Artifact grandchildArtifact = createArtifact( "g:b:t:1" );
        Artifact farthestTransitiveArtifact = createArtifact( "g:c:t:1" );
        Artifact nearestTransitiveArtifact = createArtifact( "g:c:t:1" );
        Artifact managedTransitiveArtifact = createArtifact( "g:c:t:2" );
        addArtifactMetadata( childArtifact, new Artifact[] { grandchildArtifact, nearestTransitiveArtifact } );
        addArtifactMetadata( grandchildArtifact, farthestTransitiveArtifact );

        MavenProject project = createProject( projectArtifact, new Artifact[] { childArtifact } );
        setManagedVersionMap( project, Collections.singleton( managedTransitiveArtifact ) );

        DependencyNode expectedRootNode = createNode( "g:p:t:1" );
        DependencyNode childArtifactNode = createNode( "g:a:t:1" );
        expectedRootNode.addChild( childArtifactNode );
        DependencyNode grandchildArtifactNode = createNode( "g:b:t:2" );
        childArtifactNode.addChild( grandchildArtifactNode );
        DependencyNode managedTransitiveArtifactNode = createNode( "g:c:t:2" );
        managedTransitiveArtifactNode.setPremanagedVersion( "1" );
        childArtifactNode.addChild( managedTransitiveArtifactNode );
        DependencyNode omittedManagedTransitiveArtifactNode = createNode( "g:c:t:2" );
        omittedManagedTransitiveArtifactNode.setPremanagedVersion( "1" );
        omittedManagedTransitiveArtifactNode.omitForConflict( managedTransitiveArtifact );
        grandchildArtifactNode.addChild( omittedManagedTransitiveArtifactNode );

        assertDependencyTree( expectedRootNode, project );
    }
    */

    /**
     * Tests building a tree for a project with one transitive dependency whose scope is fixed in dependency management:
     * 
     * <pre>
     * g:p:t:1
     * \- g:a:t:1
     *    \- g:b:t:1:test (scope managed from compile)
     * </pre>
     *
     * @throws DependencyTreeBuilderException
     */
    public void testProjectWithManagedTransitiveDependencyScope()
        throws DependencyTreeBuilderException
    {
        Artifact projectArtifact = createArtifact( "g:p:t:1" );
        Artifact childArtifact = createArtifact( "g:a:t:1" );
        Artifact transitiveArtifact = createArtifact( "g:b:t:1:compile" );
        Artifact managedTransitiveArtifact = createArtifact( "g:b:t:1:test" );
        addArtifactMetadata( childArtifact, transitiveArtifact );

        MavenProject project = createProject( projectArtifact, new Artifact[] { childArtifact } );
        setManagedVersionMap( project, Collections.singleton( managedTransitiveArtifact ) );

        DependencyNode expectedRootNode = createNode( "g:p:t:1" );
        DependencyNode childArtifactNode = createNode( "g:a:t:1" );
        expectedRootNode.addChild( childArtifactNode );
        DependencyNode managedTransitiveArtifactNode = createNode( "g:b:t:1:test" );
        managedTransitiveArtifactNode.setPremanagedScope( "compile" );
        childArtifactNode.addChild( managedTransitiveArtifactNode );

        assertDependencyTree( expectedRootNode, project );
    }

    /**
     * Tests building a tree for a project with one transitive dependency whose version and scope are fixed in
     * dependency management:
     * 
     * <pre>
     * g:p:t:1
     * \- g:a:t:1
     *    \- g:b:t:2:test (version managed from 1; scope managed from compile)
     * </pre>
     * 
     * @throws DependencyTreeBuilderException
     */
    public void testProjectWithManagedTransitiveDependencyVersionAndScope()
        throws DependencyTreeBuilderException
    {
        Artifact projectArtifact = createArtifact( "g:p:t:1" );
        Artifact childArtifact = createArtifact( "g:a:t:1" );
        Artifact transitiveArtifact = createArtifact( "g:b:t:1:compile" );
        Artifact managedTransitiveArtifact = createArtifact( "g:b:t:2:test" );
        addArtifactMetadata( childArtifact, transitiveArtifact );

        MavenProject project = createProject( projectArtifact, new Artifact[] { childArtifact } );
        setManagedVersionMap( project, Collections.singleton( managedTransitiveArtifact ) );

        DependencyNode expectedRootNode = createNode( "g:p:t:1" );
        DependencyNode childArtifactNode = createNode( "g:a:t:1" );
        expectedRootNode.addChild( childArtifactNode );
        DependencyNode managedTransitiveArtifactNode = createNode( "g:b:t:2:test" );
        managedTransitiveArtifactNode.setPremanagedVersion( "1" );
        managedTransitiveArtifactNode.setPremanagedScope( "compile" );
        childArtifactNode.addChild( managedTransitiveArtifactNode );

        assertDependencyTree( expectedRootNode, project );
    }
    
    /**
     * Tests building a tree for a project with a dependency that has conflicting versions and the version is also fixed
     * in dependency management:
     * 
     * <pre>
     * g:p:t:1
     * +- g:a:t:1
     * \- g:b:t:1
     *    \- (g:a:t:3 - version managed from 2; omitted for conflict with 1)
     * </pre>
     * 
     * @throws DependencyTreeBuilderException
     */
    public void testProjectWithManagedTransitiveDependencyVersionAndConflictDependencyVersion()
        throws DependencyTreeBuilderException
    {
        Artifact projectArtifact = createArtifact( "g:p:t:1" );
        Artifact nearestArtifact = createArtifact( "g:a:t:1" );
        Artifact childArtifact = createArtifact( "g:b:t:1" );
        Artifact farthestArtifact = createArtifact( "g:a:t:2" );
        Artifact managedTransitiveArtifact = createArtifact( "g:a:t:3" );
        addArtifactMetadata( childArtifact, farthestArtifact );

        MavenProject project = createProject( projectArtifact, new Artifact[] { nearestArtifact, childArtifact } );
        setManagedVersionMap( project, Collections.singleton( managedTransitiveArtifact ) );

        DependencyNode expectedRootNode = createNode( "g:p:t:1" );
        DependencyNode nearestArtifactNode = createNode( "g:a:t:1" );
        expectedRootNode.addChild( nearestArtifactNode );
        DependencyNode childArtifactNode = createNode( "g:b:t:1" );
        expectedRootNode.addChild( childArtifactNode );
        DependencyNode managedTransitiveArtifactNode = createNode( "g:a:t:3", DependencyNode.OMITTED_FOR_CONFLICT, nearestArtifactNode.getArtifact() );
        managedTransitiveArtifactNode.setPremanagedVersion( "2" );
        childArtifactNode.addChild( managedTransitiveArtifactNode );

        assertDependencyTree( expectedRootNode, project );
    }

    
    /**
     * Tests building a tree for a project with a dependency with version range
     * 
     * <pre>
     * g:p:t:1
     * \- g:a:t:1
     * </pre>
     * 
     * @throws InvalidVersionSpecificationException 
     * @throws DependencyTreeBuilderException 
     */
    public void testProjectWithVersionRange()
        throws InvalidVersionSpecificationException, DependencyTreeBuilderException
    {
        String range = "[1,2)";
        Artifact projectArtifact = createArtifact( "g:p:t:1" );
        Artifact childArtifact = createArtifact( "g:a:t:" + range);

        MavenProject project = createProject( projectArtifact, new Artifact[] { childArtifact } );

        ArtifactVersion version = new DefaultArtifactVersion( "1.0" );
        List<ArtifactVersion> availableVersions = new ArrayList<ArtifactVersion>();
        availableVersions.add( version );

        DependencyNode expectedRootNode = createNode( "g:p:t:1" );
        DependencyNode childNode = createNode( "g:a:t:1.0" );
        childNode.setAvailableVersions( availableVersions );
        childNode.setVersionSelectedFromRange( VersionRange.createFromVersionSpec( range ) );
        expectedRootNode.addChild( childNode );

        artifactMetadataSource.addAvailableVersions( childArtifact, availableVersions );
        assertDependencyTree( expectedRootNode, project );
    }

    // TODO: reinstate when MNG-3236 fixed
    /*
    public void testProjectWithFilter() throws DependencyTreeBuilderException, ArtifactResolutionException
    {
        Artifact projectArtifact = createArtifact( "g:p:t:1" );
        Artifact child1Artifact = createArtifact( "g:a:t:1" );
        Artifact child2Artifact = createArtifact( "g:b:t:1:test" );

        MavenProject project = createProject( projectArtifact, new Artifact[] { child1Artifact, child2Artifact } );

        DependencyNode expectedRootNode = createNode( "g:p:t:1" );
        expectedRootNode.addChild( createNode( "g:a:t:1" ) );

        ArtifactFilter artifactFilter = new ScopeArtifactFilter( Artifact.SCOPE_COMPILE );
        
        assertDependencyTree( expectedRootNode, project, artifactFilter );
    }
    */

    // private methods --------------------------------------------------------
    
    private DependencyNode createNode( String id )
    {
        return createNode( id, DependencyNode.INCLUDED, null );
    }
    
    private DependencyNode createNode( String id, int state, Artifact relatedArtifact )
    {
        return new DependencyNode( createArtifact( id ), state, relatedArtifact );
    }
    
    private Artifact createArtifact( String id )
    {
        String[] tokens = id.split( ":" );

        String groupId = get( tokens, 0 );
        String artifactId = get( tokens, 1 );
        String type = get( tokens, 2, "jar" );
        String version = get( tokens, 3 );
        String scope = get( tokens, 4 );
        
        VersionRange versionRange;
        try
        {
            versionRange = VersionRange.createFromVersionSpec( version );
        }
        catch ( InvalidVersionSpecificationException e )
        {
            throw new RuntimeException(e);
        }

        return new DefaultArtifact( groupId, artifactId, versionRange, scope, type, null, new DefaultArtifactHandler() );
    }
    
    private MavenProject createProject( Artifact projectArtifact, Artifact[] dependencyArtifacts )
    {
        MavenProject project = new MavenProject();
        project.setArtifact( projectArtifact );
        // LinkedHashSet since order is significant when omitting conflicts
        project.setDependencyArtifacts( new LinkedHashSet<Artifact>( Arrays.asList( dependencyArtifacts ) ) );
        project.setManagedVersionMap( new HashMap<String, Artifact>() );
        project.setRemoteArtifactRepositories( Collections.EMPTY_LIST );
        return project;
    }

    private void addArtifactMetadata( Artifact artifact, Artifact dependencyArtifact )
    {
        addArtifactMetadata( artifact, new Artifact[] { dependencyArtifact } );
    }
    
    private void addArtifactMetadata( Artifact artifact, Artifact[] dependencyArtifacts )
    {
        addArtifactMetadata( artifact, new LinkedHashSet<Artifact>( Arrays.asList( dependencyArtifacts ) ) );
    }
    
    private void addArtifactMetadata( Artifact artifact, Set<Artifact> dependencyArtifacts )
    {
        artifactMetadataSource.addArtifactMetadata( artifact, dependencyArtifacts );
    }
    
    private void setManagedVersionMap( MavenProject project, Set<Artifact> managedArtifacts )
    {
        Map<String, Artifact> managedVersionMap = new HashMap<String, Artifact>();
        
        for ( Artifact artifact : managedArtifacts )
        {
            String managementKey = getManagementKey( artifact );
            
            managedVersionMap.put( managementKey, artifact );
        }

        project.setManagedVersionMap( managedVersionMap );
    }
    
    private String getManagementKey( Artifact artifact )
    {
        return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getType()
            + ( artifact.getClassifier() != null ? ":" + artifact.getClassifier() : "" );
    }
    
    private void assertDependencyTree( DependencyNode expectedRootNode, MavenProject project )
        throws DependencyTreeBuilderException
    {
        assertDependencyTree( expectedRootNode, project, null );
    }
    
    private void assertDependencyTree( DependencyNode expectedRootNode, MavenProject project,
                                       ArtifactFilter artifactFilter )
        throws DependencyTreeBuilderException
    {
        // assert built dependency tree is as expected

        DependencyNode actualRootNode =
            builder.buildDependencyTree( project, artifactRepository, artifactFactory, artifactMetadataSource,
                                         artifactFilter, artifactCollector );

        assertEquals( "Dependency tree", expectedRootNode, actualRootNode );

        // assert resolution tree is as expected

        ArtifactResolutionResult result = builder.getArtifactResolutionResult();

        assertTreeEquals( expectedRootNode, project, result );
    }
    
    private void assertTreeEquals( DependencyNode dependencyNode, MavenProject project,
                                   ArtifactResolutionResult resolutionResult )
    {
        List<ResolutionNode> rootChildrenResolutionNodes =
            ResolutionNodeUtils.getRootChildrenResolutionNodes( project, resolutionResult );

        try
        {
            assertEquals( "Root node artifact", dependencyNode.getArtifact(), project.getArtifact() );

            assertNodesEquals( dependencyNode.getChildren(), rootChildrenResolutionNodes );
        }
        catch ( AssertionFailedError error )
        {
            StringBuffer buffer = new StringBuffer();

            buffer.append( error.getMessage() ).append( "; " );
            buffer.append( "expected dependency tree <" ).append( dependencyNode ).append( "> " );
            buffer.append( "actual resolution tree <" );
            ResolutionNodeUtils.append( buffer, project, resolutionResult );
            buffer.append( ">" );

            throw new AssertionFailedError( buffer.toString() );
        }
    }
    
    private void assertNodesEquals( List<DependencyNode> dependencyNodes, List<ResolutionNode> resolutionNodes )
    {
        assertNodesEquals( dependencyNodes.iterator(), resolutionNodes.iterator() );
    }

    private void assertNodesEquals( Iterator<DependencyNode> dependencyNodesIterator,
                                    Iterator<ResolutionNode> resolutionNodesIterator )
    {
        while ( dependencyNodesIterator.hasNext() && resolutionNodesIterator.hasNext() )
        {
            DependencyNode dependencyNode = dependencyNodesIterator.next();
            ResolutionNode resolutionNode = resolutionNodesIterator.next();
            
            assertNodeEquals( dependencyNode, resolutionNode );
        }
        
        if ( dependencyNodesIterator.hasNext() || resolutionNodesIterator.hasNext() )
        {
            fail( "Node list size differs" );
        }
    }

    private void assertNodeEquals( DependencyNode dependencyNode, ResolutionNode resolutionNode )
    {
        assertEquals( "Node state", dependencyNode.getState() == DependencyNode.INCLUDED, resolutionNode.isActive() );
        
        assertEquals( "Node artifact", dependencyNode.getArtifact(), resolutionNode.getArtifact() );
        
        assertNodesEquals( dependencyNode.getChildren().iterator(), resolutionNode.getChildrenIterator() );
    }
    
    private String get( String[] array, int index )
    {
        return get( array, index, null );
    }
    
    private String get( String[] array, int index, String defaultValue )
    {
        return ( index < array.length ) ? array[index] : defaultValue;
    }
}
