You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by mi...@apache.org on 2020/10/16 23:19:56 UTC

[maven-resolver] branch MRESOLVER-141 created (now a932eac)

This is an automated email from the ASF dual-hosted git repository.

michaelo pushed a change to branch MRESOLVER-141
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git.


      at a932eac  [MRESOLVER-141] Review index-based access to collections

This branch includes the following new commits:

     new a932eac  [MRESOLVER-141] Review index-based access to collections

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[maven-resolver] 01/01: [MRESOLVER-141] Review index-based access to collections

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelo pushed a commit to branch MRESOLVER-141
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git

commit a932eacf6ccad6db1f8b1b39b7f769bb858fa46f
Author: Michael Boyles <mi...@hotmail.co.uk>
AuthorDate: Sun Oct 11 22:34:39 2020 +0100

    [MRESOLVER-141] Review index-based access to collections
    
    This closes #76
---
 .../aether/internal/impl/DefaultDeployer.java      |   8 +-
 .../aether/internal/impl/DefaultInstaller.java     |   7 +-
 .../internal/impl/PrioritizedComponents.java       |   6 +-
 .../aether/internal/impl/collect/DataPool.java     |   8 +-
 .../impl/collect/DefaultDependencyCycle.java       |   7 +-
 .../impl/collect/DefaultVersionFilterContext.java  | 118 +-------------
 .../internal/impl/PrioritizedComponentsTest.java   |  17 ++
 .../aether/internal/impl/collect/DataPoolTest.java |  18 +++
 .../impl/collect/DefaultDependencyCycleTest.java   |  44 ++++++
 .../collect/DefaultVersionFilterContextTest.java   | 170 ++++++++++++++++++++
 .../aether/internal/test/util/TestVersion.java     |   4 +-
 .../internal/test/util/TestVersionConstraint.java  |   6 +-
 .../util/graph/transformer/ConflictResolver.java   |  14 +-
 .../graph/transformer/ConflictResolverTest.java    | 175 +++++++++++++++++++++
 14 files changed, 463 insertions(+), 139 deletions(-)

diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultDeployer.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultDeployer.java
index 78c83ff..4f76175 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultDeployer.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultDeployer.java
@@ -27,6 +27,8 @@ import java.util.Collection;
 import java.util.IdentityHashMap;
 import java.util.List;
 import static java.util.Objects.requireNonNull;
+
+import java.util.ListIterator;
 import java.util.Set;
 
 import javax.inject.Inject;
@@ -257,16 +259,16 @@ public class DefaultDeployer
                 processedMetadata.put( metadata, null );
             }
 
-            for ( int i = 0; i < artifacts.size(); i++ )
+            for ( ListIterator<Artifact> iterator = artifacts.listIterator(); iterator.hasNext(); )
             {
-                Artifact artifact = artifacts.get( i );
+                Artifact artifact = iterator.next();
 
                 for ( MetadataGenerator generator : generators )
                 {
                     artifact = generator.transformArtifact( artifact );
                 }
 
-                artifacts.set( i, artifact );
+                iterator.set( artifact );
 
                 Collection<FileTransformer> fileTransformers =
                         fileTransformerManager.getTransformersForArtifact( artifact );
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultInstaller.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultInstaller.java
index 38cb2a0..0310524 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultInstaller.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultInstaller.java
@@ -27,6 +27,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.IdentityHashMap;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Set;
 
 import javax.inject.Inject;
@@ -170,16 +171,16 @@ public class DefaultInstaller
             result.addMetadata( metadata );
         }
 
-        for ( int i = 0; i < artifacts.size(); i++ )
+        for ( ListIterator<Artifact> iterator = artifacts.listIterator(); iterator.hasNext(); )
         {
-            Artifact artifact = artifacts.get( i );
+            Artifact artifact = iterator.next();
 
             for ( MetadataGenerator generator : generators )
             {
                 artifact = generator.transformArtifact( artifact );
             }
 
-            artifacts.set( i, artifact );
+            iterator.set( artifact );
 
             install( session, trace, artifact );
             result.addArtifact( artifact );
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/PrioritizedComponents.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/PrioritizedComponents.java
index fb17b8d..8877bdc 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/PrioritizedComponents.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/PrioritizedComponents.java
@@ -133,13 +133,13 @@ final class PrioritizedComponents<T>
 
     public void list( StringBuilder buffer )
     {
-        for ( int i = 0; i < components.size(); i++ )
+        int i = 0;
+        for ( PrioritizedComponent<?> component : components )
         {
-            if ( i > 0 )
+            if ( i++ > 0 )
             {
                 buffer.append( ", " );
             }
-            PrioritizedComponent<?> component = components.get( i );
             buffer.append( component.getType().getSimpleName() );
             if ( component.isDisabled() )
             {
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DataPool.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DataPool.java
index 41b5703..0d3187c 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DataPool.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DataPool.java
@@ -22,6 +22,7 @@ package org.eclipse.aether.internal.impl.collect;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -320,10 +321,11 @@ final class DataPool
             {
                 return false;
             }
-            for ( int i = 0, n = repos1.size(); i < n; i++ )
+            for ( Iterator<RemoteRepository> it1 = repos1.iterator(), it2 = repos2.iterator();
+                  it1.hasNext() && it2.hasNext(); )
             {
-                RemoteRepository repo1 = repos1.get( i );
-                RemoteRepository repo2 = repos2.get( i );
+                RemoteRepository repo1 = it1.next();
+                RemoteRepository repo2 = it2.next();
                 if ( repo1.isRepositoryManager() != repo2.isRepositoryManager() )
                 {
                     return false;
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCycle.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCycle.java
index 15e1835..42a56c2 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCycle.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCycle.java
@@ -73,13 +73,14 @@ final class DefaultDependencyCycle
     public String toString()
     {
         StringBuilder buffer = new StringBuilder( 256 );
-        for ( int i = 0, n = dependencies.size(); i < n; i++ )
+        int i = 0;
+        for ( Dependency dependency : dependencies )
         {
-            if ( i > 0 )
+            if ( i++ > 0 )
             {
                 buffer.append( " -> " );
             }
-            buffer.append( ArtifactIdUtils.toVersionlessId( dependencies.get( i ).getArtifact() ) );
+            buffer.append( ArtifactIdUtils.toVersionlessId( dependency.getArtifact() ) );
         }
         return buffer.toString();
     }
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultVersionFilterContext.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultVersionFilterContext.java
index 4488492..bfea062 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultVersionFilterContext.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultVersionFilterContext.java
@@ -21,10 +21,8 @@ package org.eclipse.aether.internal.impl.collect;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.ConcurrentModificationException;
 import java.util.Iterator;
 import java.util.List;
-import java.util.NoSuchElementException;
 
 import org.eclipse.aether.RepositorySystemSession;
 import org.eclipse.aether.collection.VersionFilter;
@@ -47,9 +45,7 @@ final class DefaultVersionFilterContext
 
     VersionRangeResult result;
 
-    int count;
-
-    byte[] deleted = new byte[64];
+    private List<Version> versions;
 
     DefaultVersionFilterContext( RepositorySystemSession session )
     {
@@ -60,40 +56,12 @@ final class DefaultVersionFilterContext
     {
         this.dependency = dependency;
         this.result = result;
-        count = result.getVersions().size();
-        if ( deleted.length < count )
-        {
-            deleted = new byte[count];
-        }
-        else
-        {
-            for ( int i = count - 1; i >= 0; i-- )
-            {
-                deleted[i] = (byte) 0;
-            }
-        }
+        this.versions = new ArrayList<>( result.getVersions() );
     }
 
     public List<Version> get()
     {
-        if ( count == result.getVersions().size() )
-        {
-            return result.getVersions();
-        }
-        if ( count <= 1 )
-        {
-            if ( count <= 0 )
-            {
-                return Collections.emptyList();
-            }
-            return Collections.singletonList( iterator().next() );
-        }
-        List<Version> versions = new ArrayList<>( count );
-        for ( Version version : this )
-        {
-            versions.add( version );
-        }
-        return versions;
+        return new ArrayList<>( versions );
     }
 
     public RepositorySystemSession getSession()
@@ -113,7 +81,7 @@ final class DefaultVersionFilterContext
 
     public int getCount()
     {
-        return count;
+        return versions.size();
     }
 
     public ArtifactRepository getRepository( Version version )
@@ -128,7 +96,7 @@ final class DefaultVersionFilterContext
 
     public Iterator<Version> iterator()
     {
-        return ( count > 0 ) ? new VersionIterator() : Collections.<Version>emptySet().iterator();
+        return versions.iterator();
     }
 
     @Override
@@ -136,80 +104,4 @@ final class DefaultVersionFilterContext
     {
         return dependency + " " + result.getVersions();
     }
-
-    private class VersionIterator
-        implements Iterator<Version>
-    {
-
-        private final List<Version> versions;
-
-        private final int size;
-
-        private int count;
-
-        private int index;
-
-        private int next;
-
-        VersionIterator()
-        {
-            count = DefaultVersionFilterContext.this.count;
-            index = -1;
-            next = 0;
-            versions = result.getVersions();
-            size = versions.size();
-            advance();
-        }
-
-        @SuppressWarnings( "StatementWithEmptyBody" )
-        private void advance()
-        {
-            for ( next = index + 1; next < size && deleted[next] != (byte) 0; next++ )
-            {
-                // just advancing index
-            }
-        }
-
-        public boolean hasNext()
-        {
-            return next < size;
-        }
-
-        public Version next()
-        {
-            if ( count != DefaultVersionFilterContext.this.count )
-            {
-                throw new ConcurrentModificationException();
-            }
-            if ( next >= size )
-            {
-                throw new NoSuchElementException();
-            }
-            index = next;
-            advance();
-            return versions.get( index );
-        }
-
-        public void remove()
-        {
-            if ( count != DefaultVersionFilterContext.this.count )
-            {
-                throw new ConcurrentModificationException();
-            }
-            if ( index < 0 || deleted[index] == (byte) 1 )
-            {
-                throw new IllegalStateException();
-            }
-            deleted[index] = (byte) 1;
-            count = --DefaultVersionFilterContext.this.count;
-        }
-
-        @Override
-        public String toString()
-        {
-            return ( index < 0 ) ? "null" : String.valueOf( versions.get( index ) );
-        }
-
-    }
-
 }
diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/PrioritizedComponentsTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/PrioritizedComponentsTest.java
index 764a130..379fe53 100644
--- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/PrioritizedComponentsTest.java
+++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/PrioritizedComponentsTest.java
@@ -23,6 +23,7 @@ import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertSame;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -112,4 +113,20 @@ public class PrioritizedComponentsTest
         assertSame( comp1, sorted.get( 0 ).getComponent() );
         assertSame( comp2, sorted.get( 1 ).getComponent() );
     }
+
+    @Test
+    public void testList()
+    {
+        Exception comp1 = new IllegalArgumentException();
+        Exception comp2 = new NullPointerException();
+
+        PrioritizedComponents<Exception> components = new PrioritizedComponents<>( Collections.emptyMap() );
+        components.add( comp1, 1 );
+        components.add( comp2, 0 );
+
+        StringBuilder stringBuilder = new StringBuilder();
+        components.list( stringBuilder );
+
+        assertEquals( "IllegalArgumentException, NullPointerException", stringBuilder.toString() );
+    }
 }
diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DataPoolTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DataPoolTest.java
index 6baffd2..d8f5524 100644
--- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DataPoolTest.java
+++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DataPoolTest.java
@@ -25,8 +25,11 @@ import org.eclipse.aether.graph.Dependency;
 import org.eclipse.aether.repository.RemoteRepository;
 import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
 import org.eclipse.aether.resolution.ArtifactDescriptorResult;
+import org.eclipse.aether.resolution.VersionRangeRequest;
 import org.junit.Test;
 
+import java.util.Collections;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
@@ -64,4 +67,19 @@ public class DataPoolTest
         assertEquals( result.getAliases(), cached.getAliases() );
     }
 
+    @Test
+    public void testConstraintKey()
+    {
+        VersionRangeRequest request = new VersionRangeRequest();
+        request.setRepositories(
+            Collections.singletonList( new RemoteRepository.Builder( "some-id", "some-type", "http://www.example.com" ).build() )
+        );
+        request.setArtifact( new DefaultArtifact("group:artifact:1.0") );
+
+        DataPool pool = newDataPool();
+
+        Object key1 = pool.toKey( request );
+        Object key2 = pool.toKey( request );
+        assertEquals(key1, key2);
+    }
 }
diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCycleTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCycleTest.java
new file mode 100644
index 0000000..53cddc2
--- /dev/null
+++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCycleTest.java
@@ -0,0 +1,44 @@
+package org.eclipse.aether.internal.impl.collect;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.graph.DefaultDependencyNode;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.graph.DependencyCycle;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class DefaultDependencyCycleTest
+{
+    private static final Dependency FOO_DEPENDENCY = new Dependency( new DefaultArtifact( "group-id:foo:1.0" ), "test" );
+    private static final Dependency BAR_DEPENDENCY = new Dependency( new DefaultArtifact( "group-id:bar:1.0" ), "test" );
+
+    @Test
+    public void testToString()
+    {
+        NodeStack nodeStack = new NodeStack();
+        nodeStack.push( new DefaultDependencyNode( FOO_DEPENDENCY ) );
+        DependencyCycle cycle = new DefaultDependencyCycle( nodeStack, 1, BAR_DEPENDENCY );
+
+        assertEquals( "group-id:foo:jar -> group-id:bar:jar", cycle.toString() );
+    }
+}
\ No newline at end of file
diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DefaultVersionFilterContextTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DefaultVersionFilterContextTest.java
new file mode 100644
index 0000000..b6fbf5f
--- /dev/null
+++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DefaultVersionFilterContextTest.java
@@ -0,0 +1,170 @@
+package org.eclipse.aether.internal.impl.collect;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.internal.test.util.TestVersion;
+import org.eclipse.aether.resolution.VersionRangeRequest;
+import org.eclipse.aether.resolution.VersionRangeResult;
+import org.eclipse.aether.version.Version;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class DefaultVersionFilterContextTest
+{
+    private static final Dependency FOO_DEPENDENCY = new Dependency( new DefaultArtifact( "group-id:foo:1.0" ), "test" );
+    private static final Dependency BAR_DEPENDENCY = new Dependency( new DefaultArtifact( "group-id:bar:1.0" ), "test" );
+
+    @Test
+    public void iteratorOneItem()
+    {
+        DefaultVersionFilterContext context = new DefaultVersionFilterContext( new DefaultRepositorySystemSession() );
+        VersionRangeResult result = new VersionRangeResult( new VersionRangeRequest() );
+        result.addVersion( new TestVersion( "1.0" ) );
+        context.set( FOO_DEPENDENCY, result );
+
+        Iterator<Version> iterator = context.iterator();
+        assertTrue( iterator.hasNext() );
+        assertEquals( new TestVersion( "1.0" ), iterator.next() );
+    }
+
+    @Test
+    public void getCountOneItem()
+    {
+        DefaultVersionFilterContext context = new DefaultVersionFilterContext( new DefaultRepositorySystemSession() );
+        VersionRangeResult result = new VersionRangeResult( new VersionRangeRequest() );
+        result.addVersion( new TestVersion( "1.0" ) );
+        context.set( FOO_DEPENDENCY, result );
+
+        assertEquals(1, context.getCount());
+    }
+
+    @Test
+    public void getOneItem()
+    {
+        DefaultVersionFilterContext context = new DefaultVersionFilterContext( new DefaultRepositorySystemSession() );
+        VersionRangeResult result = new VersionRangeResult( new VersionRangeRequest() );
+        result.addVersion( new TestVersion( "1.0" ) );
+        context.set( FOO_DEPENDENCY, result );
+
+        assertEquals( Collections.singletonList( new TestVersion( "1.0") ), context.get() );
+    }
+
+    @Test
+    public void iteratorDelete()
+    {
+        DefaultVersionFilterContext context = new DefaultVersionFilterContext( new DefaultRepositorySystemSession() );
+        VersionRangeResult result = new VersionRangeResult( new VersionRangeRequest() );
+        result.addVersion( new TestVersion( "1.0" ) );
+        context.set( FOO_DEPENDENCY, result );
+
+        Iterator<Version> iterator = context.iterator();
+        iterator.next();
+        iterator.remove();
+
+        assertEquals( 0, context.getCount() );
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void nextBeyondEnd()
+    {
+        DefaultVersionFilterContext context = new DefaultVersionFilterContext( new DefaultRepositorySystemSession() );
+        VersionRangeResult result = new VersionRangeResult( new VersionRangeRequest() );
+        result.addVersion( new TestVersion( "1.0" ) );
+        context.set( FOO_DEPENDENCY, result );
+
+        Iterator<Version> iterator = context.iterator();
+        iterator.next();
+        iterator.next();
+    }
+
+    @Test
+    public void removeOneOfOne()
+    {
+        DefaultVersionFilterContext context = new DefaultVersionFilterContext( new DefaultRepositorySystemSession() );
+        VersionRangeResult result = new VersionRangeResult( new VersionRangeRequest() );
+        result.addVersion( new TestVersion( "1.0" ) );
+        context.set( FOO_DEPENDENCY, result );
+
+        Iterator<Version> iterator = context.iterator();
+        iterator.next();
+        iterator.remove();
+
+        assertEquals( Collections.emptyList(), context.get() );
+    }
+
+    @Test
+    public void removeOneOfTwo()
+    {
+        DefaultVersionFilterContext context = new DefaultVersionFilterContext( new DefaultRepositorySystemSession() );
+        VersionRangeResult result = new VersionRangeResult( new VersionRangeRequest() );
+        result.addVersion( new TestVersion( "1.0" ) );
+        result.addVersion( new TestVersion( "2.0" ) );
+        context.set( FOO_DEPENDENCY, result );
+
+        Iterator<Version> iterator = context.iterator();
+        iterator.next();
+        iterator.remove();
+
+        assertEquals( Collections.singletonList( new TestVersion( "2.0") ), context.get() );
+    }
+
+    @Test
+    public void removeOneOfThree()
+    {
+        DefaultVersionFilterContext context = new DefaultVersionFilterContext( new DefaultRepositorySystemSession() );
+        VersionRangeResult result = new VersionRangeResult( new VersionRangeRequest() );
+        result.addVersion( new TestVersion( "1.0" ) );
+        result.addVersion( new TestVersion( "2.0" ) );
+        result.addVersion( new TestVersion( "3.0" ) );
+        context.set( FOO_DEPENDENCY, result );
+
+        Iterator<Version> iterator = context.iterator();
+        iterator.next();
+        iterator.remove();
+
+        assertEquals( Arrays.asList( new TestVersion( "2.0" ), new TestVersion( "3.0" ) ), context.get() );
+    }
+
+    @Test
+    public void setTwice()
+    {
+        DefaultVersionFilterContext context = new DefaultVersionFilterContext( new DefaultRepositorySystemSession() );
+        VersionRangeResult fooResult = new VersionRangeResult( new VersionRangeRequest() );
+        fooResult.addVersion( new TestVersion( "1.0" ) );
+        context.set( FOO_DEPENDENCY, fooResult );
+
+        VersionRangeResult barResult = new VersionRangeResult( new VersionRangeRequest() );
+        barResult.addVersion( new TestVersion( "1.0" ) );
+        barResult.addVersion( new TestVersion( "2.0" ) );
+        context.set( BAR_DEPENDENCY, barResult );
+
+        assertEquals( Arrays.asList( new TestVersion( "1.0" ), new TestVersion( "2.0" ) ), context.get() );
+    }
+}
\ No newline at end of file
diff --git a/maven-resolver-test-util/src/main/java/org/eclipse/aether/internal/test/util/TestVersion.java b/maven-resolver-test-util/src/main/java/org/eclipse/aether/internal/test/util/TestVersion.java
index 7d77a16..8109beb 100644
--- a/maven-resolver-test-util/src/main/java/org/eclipse/aether/internal/test/util/TestVersion.java
+++ b/maven-resolver-test-util/src/main/java/org/eclipse/aether/internal/test/util/TestVersion.java
@@ -24,13 +24,13 @@ import org.eclipse.aether.version.Version;
 /**
  * Version ordering by {@link String#compareToIgnoreCase(String)}.
  */
-final class TestVersion
+public final class TestVersion
     implements Version
 {
 
     private String version;
 
-    TestVersion( String version )
+    public TestVersion( String version )
     {
         this.version = version == null ? "" : version;
     }
diff --git a/maven-resolver-test-util/src/main/java/org/eclipse/aether/internal/test/util/TestVersionConstraint.java b/maven-resolver-test-util/src/main/java/org/eclipse/aether/internal/test/util/TestVersionConstraint.java
index 0f93a59..9edd1c9 100644
--- a/maven-resolver-test-util/src/main/java/org/eclipse/aether/internal/test/util/TestVersionConstraint.java
+++ b/maven-resolver-test-util/src/main/java/org/eclipse/aether/internal/test/util/TestVersionConstraint.java
@@ -30,7 +30,7 @@ import java.util.Objects;
 /**
  * A constraint on versions for a dependency.
  */
-final class TestVersionConstraint
+public final class TestVersionConstraint
     implements VersionConstraint
 {
 
@@ -43,7 +43,7 @@ final class TestVersionConstraint
      *
      * @param range The version range, must not be {@code null}.
      */
-    TestVersionConstraint( VersionRange range )
+    public TestVersionConstraint( VersionRange range )
     {
         this.range = requireNonNull( range, "version range cannot be null" );
         this.version = null;
@@ -54,7 +54,7 @@ final class TestVersionConstraint
      *
      * @param version The version, must not be {@code null}.
      */
-    TestVersionConstraint( Version version )
+    public TestVersionConstraint( Version version )
     {
         this.version = requireNonNull( version, "version cannot be null" );
         this.range = null;
diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/transformer/ConflictResolver.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/transformer/ConflictResolver.java
index 149dc2e..e4471a0 100644
--- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/transformer/ConflictResolver.java
+++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/transformer/ConflictResolver.java
@@ -536,9 +536,9 @@ public final class ConflictResolver
             List<DependencyNode> previousParent = null;
             int previousDepth = 0;
             totalConflictItems += items.size();
-            for ( int i = items.size() - 1; i >= 0; i-- )
+            for ( ListIterator<ConflictItem> iterator = items.listIterator( items.size() ); iterator.hasPrevious(); )
             {
-                ConflictItem item = items.get( i );
+                ConflictItem item = iterator.previous();
                 if ( item.parent == previousParent )
                 {
                     item.depth = previousDepth;
@@ -619,18 +619,20 @@ public final class ConflictResolver
                 {
                     if ( ( changes & NodeInfo.CHANGE_SCOPE ) != 0 )
                     {
-                        for ( int i = info.children.size() - 1; i >= 0; i-- )
+                        ListIterator<ConflictItem> itemIterator = info.children.listIterator( info.children.size() );
+                        while ( itemIterator.hasPrevious() )
                         {
-                            ConflictItem item = info.children.get( i );
+                            ConflictItem item = itemIterator.previous();
                             String childScope = deriveScope( item.node, null );
                             item.addScope( childScope );
                         }
                     }
                     if ( ( changes & NodeInfo.CHANGE_OPTIONAL ) != 0 )
                     {
-                        for ( int i = info.children.size() - 1; i >= 0; i-- )
+                        ListIterator<ConflictItem> itemIterator = info.children.listIterator( info.children.size() );
+                        while ( itemIterator.hasPrevious() )
                         {
-                            ConflictItem item = info.children.get( i );
+                            ConflictItem item = itemIterator.previous();
                             boolean childOptional = deriveOptional( item.node, null );
                             item.addOptional( childOptional );
                         }
diff --git a/maven-resolver-util/src/test/java/org/eclipse/aether/util/graph/transformer/ConflictResolverTest.java b/maven-resolver-util/src/test/java/org/eclipse/aether/util/graph/transformer/ConflictResolverTest.java
new file mode 100644
index 0000000..40410a3
--- /dev/null
+++ b/maven-resolver-util/src/test/java/org/eclipse/aether/util/graph/transformer/ConflictResolverTest.java
@@ -0,0 +1,175 @@
+package org.eclipse.aether.util.graph.transformer;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.eclipse.aether.RepositoryException;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.graph.DefaultDependencyNode;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.graph.DependencyNode;
+import org.eclipse.aether.internal.test.util.TestUtils;
+import org.eclipse.aether.internal.test.util.TestVersion;
+import org.eclipse.aether.internal.test.util.TestVersionConstraint;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+public class ConflictResolverTest
+{
+    @Test
+    public void noTransformationRequired() throws RepositoryException
+    {
+        ConflictResolver resolver = makeDefaultResolver();
+
+        // Foo -> Bar
+        DependencyNode fooNode = makeDependencyNode( "group-id", "foo", "1.0" );
+        DependencyNode barNode = makeDependencyNode( "group-id", "bar", "1.0" );
+        fooNode.setChildren( mutableList( barNode ) );
+
+        DependencyNode transformedNode = resolver.transformGraph(
+            fooNode, TestUtils.newTransformationContext( TestUtils.newSession() )
+        );
+
+        assertSame( fooNode, transformedNode );
+        assertEquals( 1, transformedNode.getChildren().size() );
+        assertSame( barNode, transformedNode.getChildren().get( 0 ) );
+    }
+
+    @Test
+    public void versionClash() throws RepositoryException
+    {
+        ConflictResolver resolver = makeDefaultResolver();
+
+        // Foo -> Bar -> Baz 2.0
+        //  |---> Baz 1.0
+        DependencyNode fooNode = makeDependencyNode( "some-group", "foo", "1.0" );
+        DependencyNode barNode = makeDependencyNode( "some-group", "bar", "1.0" );
+        DependencyNode baz1Node = makeDependencyNode( "some-group", "baz", "1.0" );
+        DependencyNode baz2Node = makeDependencyNode( "some-group", "baz", "2.0" );
+        fooNode.setChildren( mutableList( barNode, baz1Node ) );
+        barNode.setChildren( mutableList( baz2Node ) );
+
+        DependencyNode transformedNode = resolver.transformGraph(
+            fooNode, TestUtils.newTransformationContext( TestUtils.newSession() )
+        );
+
+        assertSame( fooNode, transformedNode );
+        assertEquals( 2, fooNode.getChildren().size() );
+        assertSame( barNode, fooNode.getChildren().get( 0 ) );
+        assertTrue( barNode.getChildren().isEmpty() );
+        assertSame( baz1Node, fooNode.getChildren().get( 1 ) );
+    }
+
+    @Test
+    public void derivedScopeChange() throws RepositoryException
+    {
+        ConflictResolver resolver = makeDefaultResolver();
+
+        // Foo -> Bar (test) -> Jaz
+        //  |---> Baz -> Jaz
+        DependencyNode fooNode = makeDependencyNode( "some-group", "foo", "1.0" );
+        DependencyNode barNode = makeDependencyNode( "some-group", "bar", "1.0", "test" );
+        DependencyNode bazNode = makeDependencyNode( "some-group", "baz", "1.0" );
+        DependencyNode jazNode = makeDependencyNode( "some-group", "jaz", "1.0" );
+        fooNode.setChildren( mutableList( barNode, bazNode ) );
+
+        List<DependencyNode> jazList = mutableList( jazNode );
+        barNode.setChildren( jazList );
+        bazNode.setChildren( jazList );
+
+        DependencyNode transformedNode = resolver.transformGraph(
+            fooNode, TestUtils.newTransformationContext( TestUtils.newSession() )
+        );
+
+        assertSame( fooNode, transformedNode );
+        assertEquals( 2, fooNode.getChildren().size() );
+        assertSame( barNode, fooNode.getChildren().get( 0 ) );
+        assertEquals( 1, barNode.getChildren().size() );
+        assertSame( jazNode, barNode.getChildren().get( 0 ) );
+        assertSame( bazNode, fooNode.getChildren().get( 1 ) );
+        assertEquals( 1, barNode.getChildren().size() );
+        assertSame( jazNode, barNode.getChildren().get( 0 ) );
+    }
+
+    @Test
+    public void derivedOptionalStatusChange() throws RepositoryException
+    {
+        ConflictResolver resolver = makeDefaultResolver();
+
+        // Foo -> Bar (optional) -> Jaz
+        //  |---> Baz -> Jaz
+        DependencyNode fooNode = makeDependencyNode( "some-group", "foo", "1.0" );
+        DependencyNode barNode = makeDependencyNode( "some-group", "bar", "1.0" );
+        barNode.setOptional(true);
+        DependencyNode bazNode = makeDependencyNode( "some-group", "baz", "1.0" );
+        DependencyNode jazNode = makeDependencyNode( "some-group", "jaz", "1.0" );
+        fooNode.setChildren( mutableList( barNode, bazNode ) );
+
+        List<DependencyNode> jazList = mutableList( jazNode );
+        barNode.setChildren( jazList );
+        bazNode.setChildren( jazList );
+
+        DependencyNode transformedNode = resolver.transformGraph(
+            fooNode, TestUtils.newTransformationContext( TestUtils.newSession() )
+        );
+
+        assertSame( fooNode, transformedNode );
+        assertEquals( 2, fooNode.getChildren().size() );
+        assertSame( barNode, fooNode.getChildren().get( 0 ) );
+        assertEquals( 1, barNode.getChildren().size() );
+        assertSame( jazNode, barNode.getChildren().get( 0 ) );
+        assertSame( bazNode, fooNode.getChildren().get( 1 ) );
+        assertEquals( 1, barNode.getChildren().size() );
+        assertSame( jazNode, barNode.getChildren().get( 0 ) );
+    }
+
+    private static ConflictResolver makeDefaultResolver()
+    {
+        return new ConflictResolver(
+            new NearestVersionSelector(), new JavaScopeSelector(), new SimpleOptionalitySelector(), new JavaScopeDeriver()
+        );
+    }
+
+    private static DependencyNode makeDependencyNode( String groupId, String artifactId, String version )
+    {
+        return makeDependencyNode( groupId, artifactId, version, "compile" );
+    }
+
+    private static DependencyNode makeDependencyNode( String groupId, String artifactId, String version, String scope )
+    {
+        DefaultDependencyNode node = new DefaultDependencyNode(
+            new Dependency( new DefaultArtifact( groupId + ':' + artifactId + ':' + version ), scope )
+        );
+        node.setVersion( new TestVersion( version ) );
+        node.setVersionConstraint( new TestVersionConstraint( node.getVersion() ) );
+        return node;
+    }
+
+    private static List<DependencyNode> mutableList(DependencyNode... nodes)
+    {
+        return new ArrayList<>( Arrays.asList( nodes ) );
+    }
+}
\ No newline at end of file