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

This closes #76
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.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 @@
                 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.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 @@
             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 @@
 
     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 @@
 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 @@
             {
                 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 @@
     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 @@
 
 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 @@
 
     VersionRangeResult result;
 
-    int count;
-
-    byte[] deleted = new byte[64];
+    private List<Version> versions;
 
     DefaultVersionFilterContext( RepositorySystemSession session )
     {
@@ -60,40 +56,12 @@
     {
         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 @@
 
     public int getCount()
     {
-        return count;
+        return versions.size();
     }
 
     public ArtifactRepository getRepository( Version version )
@@ -128,7 +96,7 @@
 
     public Iterator<Version> iterator()
     {
-        return ( count > 0 ) ? new VersionIterator() : Collections.<Version>emptySet().iterator();
+        return versions.iterator();
     }
 
     @Override
@@ -136,80 +104,4 @@
     {
         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.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 @@
         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.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 @@
         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 @@
 /**
  * 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 @@
 /**
  * A constraint on versions for a dependency.
  */
-final class TestVersionConstraint
+public final class TestVersionConstraint
     implements VersionConstraint
 {
 
@@ -43,7 +43,7 @@
      *
      * @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 @@
      *
      * @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 @@
             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 @@
                 {
                     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