You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by hb...@apache.org on 2017/01/15 19:23:39 UTC

[05/53] [abbrv] [partial] maven-resolver git commit: [MNG-6007] renamed Aether to Maven Artifact Resolver

http://git-wip-us.apache.org/repos/asf/maven-resolver/blob/3a1b8ae0/maven-resolver-api/src/main/java/org/eclipse/aether/collection/package-info.java
----------------------------------------------------------------------
diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/collection/package-info.java b/maven-resolver-api/src/main/java/org/eclipse/aether/collection/package-info.java
new file mode 100644
index 0000000..414629f
--- /dev/null
+++ b/maven-resolver-api/src/main/java/org/eclipse/aether/collection/package-info.java
@@ -0,0 +1,25 @@
+// CHECKSTYLE_OFF: RegexpHeader
+/*
+ * 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.
+ */
+/**
+ * The types and extension points for collecting the transitive dependencies of an artifact and building a dependency
+ * graph.
+ */
+package org.eclipse.aether.collection;
+

http://git-wip-us.apache.org/repos/asf/maven-resolver/blob/3a1b8ae0/maven-resolver-api/src/main/java/org/eclipse/aether/deployment/DeployRequest.java
----------------------------------------------------------------------
diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/deployment/DeployRequest.java b/maven-resolver-api/src/main/java/org/eclipse/aether/deployment/DeployRequest.java
new file mode 100644
index 0000000..637f47d
--- /dev/null
+++ b/maven-resolver-api/src/main/java/org/eclipse/aether/deployment/DeployRequest.java
@@ -0,0 +1,202 @@
+package org.eclipse.aether.deployment;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.RequestTrace;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.metadata.Metadata;
+import org.eclipse.aether.repository.RemoteRepository;
+
+/**
+ * A request to deploy artifacts and their accompanying metadata into the a remote repository.
+ * 
+ * @see RepositorySystem#deploy(RepositorySystemSession, DeployRequest)
+ */
+public final class DeployRequest
+{
+
+    private Collection<Artifact> artifacts = Collections.emptyList();
+
+    private Collection<Metadata> metadata = Collections.emptyList();
+
+    private RemoteRepository repository;
+
+    private RequestTrace trace;
+
+    /**
+     * Creates an uninitialized request.
+     */
+    public DeployRequest()
+    {
+    }
+
+    /**
+     * Gets the artifact to deploy.
+     * 
+     * @return The artifacts to deploy, never {@code null}.
+     */
+    public Collection<Artifact> getArtifacts()
+    {
+        return artifacts;
+    }
+
+    /**
+     * Sets the artifacts to deploy.
+     * 
+     * @param artifacts The artifacts to deploy, may be {@code null}.
+     * @return This request for chaining, never {@code null}.
+     */
+    public DeployRequest setArtifacts( Collection<Artifact> artifacts )
+    {
+        if ( artifacts == null )
+        {
+            this.artifacts = Collections.emptyList();
+        }
+        else
+        {
+            this.artifacts = artifacts;
+        }
+        return this;
+    }
+
+    /**
+     * Adds the specified artifacts for deployment.
+     * 
+     * @param artifact The artifact to add, may be {@code null}.
+     * @return This request for chaining, never {@code null}.
+     */
+    public DeployRequest addArtifact( Artifact artifact )
+    {
+        if ( artifact != null )
+        {
+            if ( artifacts.isEmpty() )
+            {
+                artifacts = new ArrayList<Artifact>();
+            }
+            artifacts.add( artifact );
+        }
+        return this;
+    }
+
+    /**
+     * Gets the metadata to deploy.
+     * 
+     * @return The metadata to deploy, never {@code null}.
+     */
+    public Collection<Metadata> getMetadata()
+    {
+        return metadata;
+    }
+
+    /**
+     * Sets the metadata to deploy.
+     * 
+     * @param metadata The metadata to deploy, may be {@code null}.
+     * @return This request for chaining, never {@code null}.
+     */
+    public DeployRequest setMetadata( Collection<Metadata> metadata )
+    {
+        if ( metadata == null )
+        {
+            this.metadata = Collections.emptyList();
+        }
+        else
+        {
+            this.metadata = metadata;
+        }
+        return this;
+    }
+
+    /**
+     * Adds the specified metadata for deployment.
+     * 
+     * @param metadata The metadata to add, may be {@code null}.
+     * @return This request for chaining, never {@code null}.
+     */
+    public DeployRequest addMetadata( Metadata metadata )
+    {
+        if ( metadata != null )
+        {
+            if ( this.metadata.isEmpty() )
+            {
+                this.metadata = new ArrayList<Metadata>();
+            }
+            this.metadata.add( metadata );
+        }
+        return this;
+    }
+
+    /**
+     * Gets the repository to deploy to.
+     * 
+     * @return The repository to deploy to or {@code null} if not set.
+     */
+    public RemoteRepository getRepository()
+    {
+        return repository;
+    }
+
+    /**
+     * Sets the repository to deploy to.
+     * 
+     * @param repository The repository to deploy to, may be {@code null}.
+     * @return This request for chaining, never {@code null}.
+     */
+    public DeployRequest setRepository( RemoteRepository repository )
+    {
+        this.repository = repository;
+        return this;
+    }
+
+    /**
+     * Gets the trace information that describes the higher level request/operation in which this request is issued.
+     * 
+     * @return The trace information about the higher level operation or {@code null} if none.
+     */
+    public RequestTrace getTrace()
+    {
+        return trace;
+    }
+
+    /**
+     * Sets the trace information that describes the higher level request/operation in which this request is issued.
+     * 
+     * @param trace The trace information about the higher level operation, may be {@code null}.
+     * @return This request for chaining, never {@code null}.
+     */
+    public DeployRequest setTrace( RequestTrace trace )
+    {
+        this.trace = trace;
+        return this;
+    }
+
+    @Override
+    public String toString()
+    {
+        return getArtifacts() + ", " + getMetadata() + " > " + getRepository();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/maven-resolver/blob/3a1b8ae0/maven-resolver-api/src/main/java/org/eclipse/aether/deployment/DeployResult.java
----------------------------------------------------------------------
diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/deployment/DeployResult.java b/maven-resolver-api/src/main/java/org/eclipse/aether/deployment/DeployResult.java
new file mode 100644
index 0000000..f75705e
--- /dev/null
+++ b/maven-resolver-api/src/main/java/org/eclipse/aether/deployment/DeployResult.java
@@ -0,0 +1,174 @@
+package org.eclipse.aether.deployment;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.metadata.Metadata;
+
+/**
+ * The result of deploying artifacts and their accompanying metadata into the a remote repository.
+ * 
+ * @see RepositorySystem#deploy(RepositorySystemSession, DeployRequest)
+ */
+public final class DeployResult
+{
+
+    private final DeployRequest request;
+
+    private Collection<Artifact> artifacts;
+
+    private Collection<Metadata> metadata;
+
+    /**
+     * Creates a new result for the specified request.
+     * 
+     * @param request The deployment request, must not be {@code null}.
+     */
+    public DeployResult( DeployRequest request )
+    {
+        if ( request == null )
+        {
+            throw new IllegalArgumentException( "deploy request has not been specified" );
+        }
+        this.request = request;
+        artifacts = Collections.emptyList();
+        metadata = Collections.emptyList();
+    }
+
+    /**
+     * Gets the deploy request that was made.
+     * 
+     * @return The deploy request, never {@code null}.
+     */
+    public DeployRequest getRequest()
+    {
+        return request;
+    }
+
+    /**
+     * Gets the artifacts that got deployed.
+     * 
+     * @return The deployed artifacts, never {@code null}.
+     */
+    public Collection<Artifact> getArtifacts()
+    {
+        return artifacts;
+    }
+
+    /**
+     * Sets the artifacts that got deployed.
+     * 
+     * @param artifacts The deployed artifacts, may be {@code null}.
+     * @return This result for chaining, never {@code null}.
+     */
+    public DeployResult setArtifacts( Collection<Artifact> artifacts )
+    {
+        if ( artifacts == null )
+        {
+            this.artifacts = Collections.emptyList();
+        }
+        else
+        {
+            this.artifacts = artifacts;
+        }
+        return this;
+    }
+
+    /**
+     * Adds the specified artifacts to the result.
+     * 
+     * @param artifact The deployed artifact to add, may be {@code null}.
+     * @return This result for chaining, never {@code null}.
+     */
+    public DeployResult addArtifact( Artifact artifact )
+    {
+        if ( artifact != null )
+        {
+            if ( artifacts.isEmpty() )
+            {
+                artifacts = new ArrayList<Artifact>();
+            }
+            artifacts.add( artifact );
+        }
+        return this;
+    }
+
+    /**
+     * Gets the metadata that got deployed. Note that due to automatically generated metadata, there might have been
+     * more metadata deployed than originally specified in the deploy request.
+     * 
+     * @return The deployed metadata, never {@code null}.
+     */
+    public Collection<Metadata> getMetadata()
+    {
+        return metadata;
+    }
+
+    /**
+     * Sets the metadata that got deployed.
+     * 
+     * @param metadata The deployed metadata, may be {@code null}.
+     * @return This result for chaining, never {@code null}.
+     */
+    public DeployResult setMetadata( Collection<Metadata> metadata )
+    {
+        if ( metadata == null )
+        {
+            this.metadata = Collections.emptyList();
+        }
+        else
+        {
+            this.metadata = metadata;
+        }
+        return this;
+    }
+
+    /**
+     * Adds the specified metadata to this result.
+     * 
+     * @param metadata The deployed metadata to add, may be {@code null}.
+     * @return This result for chaining, never {@code null}.
+     */
+    public DeployResult addMetadata( Metadata metadata )
+    {
+        if ( metadata != null )
+        {
+            if ( this.metadata.isEmpty() )
+            {
+                this.metadata = new ArrayList<Metadata>();
+            }
+            this.metadata.add( metadata );
+        }
+        return this;
+    }
+
+    @Override
+    public String toString()
+    {
+        return getArtifacts() + ", " + getMetadata();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/maven-resolver/blob/3a1b8ae0/maven-resolver-api/src/main/java/org/eclipse/aether/deployment/DeploymentException.java
----------------------------------------------------------------------
diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/deployment/DeploymentException.java b/maven-resolver-api/src/main/java/org/eclipse/aether/deployment/DeploymentException.java
new file mode 100644
index 0000000..53252ba
--- /dev/null
+++ b/maven-resolver-api/src/main/java/org/eclipse/aether/deployment/DeploymentException.java
@@ -0,0 +1,52 @@
+package org.eclipse.aether.deployment;
+
+/*
+ * 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;
+
+/**
+ * Thrown in case of a deployment error like authentication failure.
+ */
+public class DeploymentException
+    extends RepositoryException
+{
+
+    /**
+     * Creates a new exception with the specified detail message.
+     * 
+     * @param message The detail message, may be {@code null}.
+     */
+    public DeploymentException( String message )
+    {
+        super( message );
+    }
+
+    /**
+     * Creates a new exception with the specified detail message and cause.
+     * 
+     * @param message The detail message, may be {@code null}.
+     * @param cause The exception that caused this one, may be {@code null}.
+     */
+    public DeploymentException( String message, Throwable cause )
+    {
+        super( message, cause );
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/maven-resolver/blob/3a1b8ae0/maven-resolver-api/src/main/java/org/eclipse/aether/deployment/package-info.java
----------------------------------------------------------------------
diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/deployment/package-info.java b/maven-resolver-api/src/main/java/org/eclipse/aether/deployment/package-info.java
new file mode 100644
index 0000000..dc50c21
--- /dev/null
+++ b/maven-resolver-api/src/main/java/org/eclipse/aether/deployment/package-info.java
@@ -0,0 +1,24 @@
+// CHECKSTYLE_OFF: RegexpHeader
+/*
+ * 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.
+ */
+/**
+ * The types supporting the publishing of artifacts to a remote repository.
+ */
+package org.eclipse.aether.deployment;
+

http://git-wip-us.apache.org/repos/asf/maven-resolver/blob/3a1b8ae0/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DefaultDependencyNode.java
----------------------------------------------------------------------
diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DefaultDependencyNode.java b/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DefaultDependencyNode.java
new file mode 100644
index 0000000..66a60c6
--- /dev/null
+++ b/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DefaultDependencyNode.java
@@ -0,0 +1,368 @@
+package org.eclipse.aether.graph;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.version.Version;
+import org.eclipse.aether.version.VersionConstraint;
+
+/**
+ * A node within a dependency graph.
+ */
+public final class DefaultDependencyNode
+    implements DependencyNode
+{
+
+    private List<DependencyNode> children;
+
+    private Dependency dependency;
+
+    private Artifact artifact;
+
+    private List<? extends Artifact> relocations;
+
+    private Collection<? extends Artifact> aliases;
+
+    private VersionConstraint versionConstraint;
+
+    private Version version;
+
+    private byte managedBits;
+
+    private List<RemoteRepository> repositories;
+
+    private String context;
+
+    private Map<Object, Object> data;
+
+    /**
+     * Creates a new node with the specified dependency.
+     * 
+     * @param dependency The dependency associated with this node, may be {@code null} for a root node.
+     */
+    public DefaultDependencyNode( Dependency dependency )
+    {
+        this.dependency = dependency;
+        artifact = ( dependency != null ) ? dependency.getArtifact() : null;
+        children = new ArrayList<DependencyNode>( 0 );
+        aliases = relocations = Collections.emptyList();
+        repositories = Collections.emptyList();
+        context = "";
+        data = Collections.emptyMap();
+    }
+
+    /**
+     * Creates a new root node with the specified artifact as its label. Note that the new node has no dependency, i.e.
+     * {@link #getDependency()} will return {@code null}. Put differently, the specified artifact will not be subject to
+     * dependency collection/resolution.
+     * 
+     * @param artifact The artifact to use as label for this node, may be {@code null}.
+     */
+    public DefaultDependencyNode( Artifact artifact )
+    {
+        this.artifact = artifact;
+        children = new ArrayList<DependencyNode>( 0 );
+        aliases = relocations = Collections.emptyList();
+        repositories = Collections.emptyList();
+        context = "";
+        data = Collections.emptyMap();
+    }
+
+    /**
+     * Creates a mostly shallow clone of the specified node. The new node has its own copy of any custom data and
+     * initially no children.
+     * 
+     * @param node The node to copy, must not be {@code null}.
+     */
+    public DefaultDependencyNode( DependencyNode node )
+    {
+        dependency = node.getDependency();
+        artifact = node.getArtifact();
+        children = new ArrayList<DependencyNode>( 0 );
+        setAliases( node.getAliases() );
+        setRequestContext( node.getRequestContext() );
+        setManagedBits( node.getManagedBits() );
+        setRelocations( node.getRelocations() );
+        setRepositories( node.getRepositories() );
+        setVersion( node.getVersion() );
+        setVersionConstraint( node.getVersionConstraint() );
+        Map<?, ?> data = node.getData();
+        setData( data.isEmpty() ? null : new HashMap<Object, Object>( data ) );
+    }
+
+    public List<DependencyNode> getChildren()
+    {
+        return children;
+    }
+
+    public void setChildren( List<DependencyNode> children )
+    {
+        if ( children == null )
+        {
+            this.children = new ArrayList<DependencyNode>( 0 );
+        }
+        else
+        {
+            this.children = children;
+        }
+    }
+
+    public Dependency getDependency()
+    {
+        return dependency;
+    }
+
+    public Artifact getArtifact()
+    {
+        return artifact;
+    }
+
+    public void setArtifact( Artifact artifact )
+    {
+        if ( dependency == null )
+        {
+            throw new UnsupportedOperationException( "node does not have a dependency" );
+        }
+        dependency = dependency.setArtifact( artifact );
+        this.artifact = dependency.getArtifact();
+    }
+
+    public List<? extends Artifact> getRelocations()
+    {
+        return relocations;
+    }
+
+    /**
+     * Sets the sequence of relocations that was followed to resolve this dependency's artifact.
+     * 
+     * @param relocations The sequence of relocations, may be {@code null}.
+     */
+    public void setRelocations( List<? extends Artifact> relocations )
+    {
+        if ( relocations == null || relocations.isEmpty() )
+        {
+            this.relocations = Collections.emptyList();
+        }
+        else
+        {
+            this.relocations = relocations;
+        }
+    }
+
+    public Collection<? extends Artifact> getAliases()
+    {
+        return aliases;
+    }
+
+    /**
+     * Sets the known aliases for this dependency's artifact.
+     * 
+     * @param aliases The known aliases, may be {@code null}.
+     */
+    public void setAliases( Collection<? extends Artifact> aliases )
+    {
+        if ( aliases == null || aliases.isEmpty() )
+        {
+            this.aliases = Collections.emptyList();
+        }
+        else
+        {
+            this.aliases = aliases;
+        }
+    }
+
+    public VersionConstraint getVersionConstraint()
+    {
+        return versionConstraint;
+    }
+
+    /**
+     * Sets the version constraint that was parsed from the dependency's version declaration.
+     * 
+     * @param versionConstraint The version constraint for this node, may be {@code null}.
+     */
+    public void setVersionConstraint( VersionConstraint versionConstraint )
+    {
+        this.versionConstraint = versionConstraint;
+    }
+
+    public Version getVersion()
+    {
+        return version;
+    }
+
+    /**
+     * Sets the version that was selected for the dependency's target artifact.
+     * 
+     * @param version The parsed version, may be {@code null}.
+     */
+    public void setVersion( Version version )
+    {
+        this.version = version;
+    }
+
+    public void setScope( String scope )
+    {
+        if ( dependency == null )
+        {
+            throw new UnsupportedOperationException( "node does not have a dependency" );
+        }
+        dependency = dependency.setScope( scope );
+    }
+
+    public void setOptional( Boolean optional )
+    {
+        if ( dependency == null )
+        {
+            throw new UnsupportedOperationException( "node does not have a dependency" );
+        }
+        dependency = dependency.setOptional( optional );
+    }
+
+    public int getManagedBits()
+    {
+        return managedBits;
+    }
+
+    /**
+     * Sets a bit field indicating which attributes of this node were subject to dependency management.
+     * 
+     * @param managedBits The bit field indicating the managed attributes or {@code 0} if dependency management wasn't
+     *            applied.
+     */
+    public void setManagedBits( int managedBits )
+    {
+        this.managedBits = (byte) ( managedBits & 0x1F );
+    }
+
+    public List<RemoteRepository> getRepositories()
+    {
+        return repositories;
+    }
+
+    /**
+     * Sets the remote repositories from which this node's artifact shall be resolved.
+     * 
+     * @param repositories The remote repositories to use for artifact resolution, may be {@code null}.
+     */
+    public void setRepositories( List<RemoteRepository> repositories )
+    {
+        if ( repositories == null || repositories.isEmpty() )
+        {
+            this.repositories = Collections.emptyList();
+        }
+        else
+        {
+            this.repositories = repositories;
+        }
+    }
+
+    public String getRequestContext()
+    {
+        return context;
+    }
+
+    public void setRequestContext( String context )
+    {
+        this.context = ( context != null ) ? context : "";
+    }
+
+    public Map<Object, Object> getData()
+    {
+        return data;
+    }
+
+    public void setData( Map<Object, Object> data )
+    {
+        if ( data == null )
+        {
+            this.data = Collections.emptyMap();
+        }
+        else
+        {
+            this.data = data;
+        }
+    }
+
+    public void setData( Object key, Object value )
+    {
+        if ( key == null )
+        {
+            throw new IllegalArgumentException( "key must not be null" );
+        }
+
+        if ( value == null )
+        {
+            if ( !data.isEmpty() )
+            {
+                data.remove( key );
+
+                if ( data.isEmpty() )
+                {
+                    data = Collections.emptyMap();
+                }
+            }
+        }
+        else
+        {
+            if ( data.isEmpty() )
+            {
+                data = new HashMap<Object, Object>( 1, 2 ); // nodes can be numerous so let's be space conservative
+            }
+            data.put( key, value );
+        }
+    }
+
+    public boolean accept( DependencyVisitor visitor )
+    {
+        if ( visitor.visitEnter( this ) )
+        {
+            for ( DependencyNode child : children )
+            {
+                if ( !child.accept( visitor ) )
+                {
+                    break;
+                }
+            }
+        }
+
+        return visitor.visitLeave( this );
+    }
+
+    @Override
+    public String toString()
+    {
+        Dependency dep = getDependency();
+        if ( dep == null )
+        {
+            return String.valueOf( getArtifact() );
+        }
+        return dep.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/maven-resolver/blob/3a1b8ae0/maven-resolver-api/src/main/java/org/eclipse/aether/graph/Dependency.java
----------------------------------------------------------------------
diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/graph/Dependency.java b/maven-resolver-api/src/main/java/org/eclipse/aether/graph/Dependency.java
new file mode 100644
index 0000000..b9e7cb4
--- /dev/null
+++ b/maven-resolver-api/src/main/java/org/eclipse/aether/graph/Dependency.java
@@ -0,0 +1,330 @@
+package org.eclipse.aether.graph;
+
+/*
+ * 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.AbstractSet;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import org.eclipse.aether.artifact.Artifact;
+
+/**
+ * A dependency to some artifact. <em>Note:</em> Instances of this class are immutable and the exposed mutators return
+ * new objects rather than changing the current instance.
+ */
+public final class Dependency
+{
+
+    private final Artifact artifact;
+
+    private final String scope;
+
+    private final Boolean optional;
+
+    private final Set<Exclusion> exclusions;
+
+    /**
+     * Creates a mandatory dependency on the specified artifact with the given scope.
+     * 
+     * @param artifact The artifact being depended on, must not be {@code null}.
+     * @param scope The scope of the dependency, may be {@code null}.
+     */
+    public Dependency( Artifact artifact, String scope )
+    {
+        this( artifact, scope, false );
+    }
+
+    /**
+     * Creates a dependency on the specified artifact with the given scope.
+     * 
+     * @param artifact The artifact being depended on, must not be {@code null}.
+     * @param scope The scope of the dependency, may be {@code null}.
+     * @param optional A flag whether the dependency is optional or mandatory, may be {@code null}.
+     */
+    public Dependency( Artifact artifact, String scope, Boolean optional )
+    {
+        this( artifact, scope, optional, null );
+    }
+
+    /**
+     * Creates a dependency on the specified artifact with the given scope and exclusions.
+     * 
+     * @param artifact The artifact being depended on, must not be {@code null}.
+     * @param scope The scope of the dependency, may be {@code null}.
+     * @param optional A flag whether the dependency is optional or mandatory, may be {@code null}.
+     * @param exclusions The exclusions that apply to transitive dependencies, may be {@code null} if none.
+     */
+    public Dependency( Artifact artifact, String scope, Boolean optional, Collection<Exclusion> exclusions )
+    {
+        this( artifact, scope, Exclusions.copy( exclusions ), optional );
+    }
+
+    private Dependency( Artifact artifact, String scope, Set<Exclusion> exclusions, Boolean optional )
+    {
+        // NOTE: This constructor assumes immutability of the provided exclusion collection, for internal use only
+        if ( artifact == null )
+        {
+            throw new IllegalArgumentException( "no artifact specified for dependency" );
+        }
+        this.artifact = artifact;
+        this.scope = ( scope != null ) ? scope : "";
+        this.optional = optional;
+        this.exclusions = exclusions;
+    }
+
+    /**
+     * Gets the artifact being depended on.
+     * 
+     * @return The artifact, never {@code null}.
+     */
+    public Artifact getArtifact()
+    {
+        return artifact;
+    }
+
+    /**
+     * Sets the artifact being depended on.
+     * 
+     * @param artifact The artifact, must not be {@code null}.
+     * @return The new dependency, never {@code null}.
+     */
+    public Dependency setArtifact( Artifact artifact )
+    {
+        if ( this.artifact.equals( artifact ) )
+        {
+            return this;
+        }
+        return new Dependency( artifact, scope, exclusions, optional );
+    }
+
+    /**
+     * Gets the scope of the dependency. The scope defines in which context this dependency is relevant.
+     * 
+     * @return The scope or an empty string if not set, never {@code null}.
+     */
+    public String getScope()
+    {
+        return scope;
+    }
+
+    /**
+     * Sets the scope of the dependency, e.g. "compile".
+     * 
+     * @param scope The scope of the dependency, may be {@code null}.
+     * @return The new dependency, never {@code null}.
+     */
+    public Dependency setScope( String scope )
+    {
+        if ( this.scope.equals( scope ) || ( scope == null && this.scope.length() <= 0 ) )
+        {
+            return this;
+        }
+        return new Dependency( artifact, scope, exclusions, optional );
+    }
+
+    /**
+     * Indicates whether this dependency is optional or not. Optional dependencies can be ignored in some contexts.
+     * 
+     * @return {@code true} if the dependency is (definitively) optional, {@code false} otherwise.
+     */
+    public boolean isOptional()
+    {
+        return Boolean.TRUE.equals( optional );
+    }
+
+    /**
+     * Gets the optional flag for the dependency. Note: Most clients will usually call {@link #isOptional()} to
+     * determine the optional flag, this method is for advanced use cases where three-valued logic is required.
+     * 
+     * @return The optional flag or {@code null} if unspecified.
+     */
+    public Boolean getOptional()
+    {
+        return optional;
+    }
+
+    /**
+     * Sets the optional flag for the dependency.
+     * 
+     * @param optional {@code true} if the dependency is optional, {@code false} if the dependency is mandatory, may be
+     *            {@code null} if unspecified.
+     * @return The new dependency, never {@code null}.
+     */
+    public Dependency setOptional( Boolean optional )
+    {
+        if ( eq( this.optional, optional ) )
+        {
+            return this;
+        }
+        return new Dependency( artifact, scope, exclusions, optional );
+    }
+
+    /**
+     * Gets the exclusions for this dependency. Exclusions can be used to remove transitive dependencies during
+     * resolution.
+     * 
+     * @return The (read-only) exclusions, never {@code null}.
+     */
+    public Collection<Exclusion> getExclusions()
+    {
+        return exclusions;
+    }
+
+    /**
+     * Sets the exclusions for the dependency.
+     * 
+     * @param exclusions The exclusions, may be {@code null}.
+     * @return The new dependency, never {@code null}.
+     */
+    public Dependency setExclusions( Collection<Exclusion> exclusions )
+    {
+        if ( hasEquivalentExclusions( exclusions ) )
+        {
+            return this;
+        }
+        return new Dependency( artifact, scope, optional, exclusions );
+    }
+
+    private boolean hasEquivalentExclusions( Collection<Exclusion> exclusions )
+    {
+        if ( exclusions == null || exclusions.isEmpty() )
+        {
+            return this.exclusions.isEmpty();
+        }
+        if ( exclusions instanceof Set )
+        {
+            return this.exclusions.equals( exclusions );
+        }
+        return exclusions.size() >= this.exclusions.size() && this.exclusions.containsAll( exclusions )
+            && exclusions.containsAll( this.exclusions );
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.valueOf( getArtifact() ) + " (" + getScope() + ( isOptional() ? "?" : "" ) + ")";
+    }
+
+    @Override
+    public boolean equals( Object obj )
+    {
+        if ( obj == this )
+        {
+            return true;
+        }
+        else if ( obj == null || !getClass().equals( obj.getClass() ) )
+        {
+            return false;
+        }
+
+        Dependency that = (Dependency) obj;
+
+        return artifact.equals( that.artifact ) && scope.equals( that.scope ) && eq( optional, that.optional )
+            && exclusions.equals( that.exclusions );
+    }
+
+    private static <T> boolean eq( T o1, T o2 )
+    {
+        return ( o1 != null ) ? o1.equals( o2 ) : o2 == null;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int hash = 17;
+        hash = hash * 31 + artifact.hashCode();
+        hash = hash * 31 + scope.hashCode();
+        hash = hash * 31 + ( optional != null ? optional.hashCode() : 0 );
+        hash = hash * 31 + exclusions.size();
+        return hash;
+    }
+
+    private static class Exclusions
+        extends AbstractSet<Exclusion>
+    {
+
+        private final Exclusion[] exclusions;
+
+        public static Set<Exclusion> copy( Collection<Exclusion> exclusions )
+        {
+            if ( exclusions == null || exclusions.isEmpty() )
+            {
+                return Collections.emptySet();
+            }
+            return new Exclusions( exclusions );
+        }
+
+        private Exclusions( Collection<Exclusion> exclusions )
+        {
+            if ( exclusions.size() > 1 && !( exclusions instanceof Set ) )
+            {
+                exclusions = new LinkedHashSet<Exclusion>( exclusions );
+            }
+            this.exclusions = exclusions.toArray( new Exclusion[exclusions.size()] );
+        }
+
+        @Override
+        public Iterator<Exclusion> iterator()
+        {
+            return new Iterator<Exclusion>()
+            {
+
+                private int cursor = 0;
+
+                public boolean hasNext()
+                {
+                    return cursor < exclusions.length;
+                }
+
+                public Exclusion next()
+                {
+                    try
+                    {
+                        Exclusion exclusion = exclusions[cursor];
+                        cursor++;
+                        return exclusion;
+                    }
+                    catch ( IndexOutOfBoundsException e )
+                    {
+                        throw new NoSuchElementException();
+                    }
+                }
+
+                public void remove()
+                {
+                    throw new UnsupportedOperationException();
+                }
+
+            };
+        }
+
+        @Override
+        public int size()
+        {
+            return exclusions.length;
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/maven-resolver/blob/3a1b8ae0/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DependencyCycle.java
----------------------------------------------------------------------
diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DependencyCycle.java b/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DependencyCycle.java
new file mode 100644
index 0000000..1076ab8
--- /dev/null
+++ b/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DependencyCycle.java
@@ -0,0 +1,53 @@
+package org.eclipse.aether.graph;
+
+/*
+ * 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.List;
+
+/**
+ * A cycle within a dependency graph, that is a sequence of dependencies d_1, d_2, ..., d_n where d_1 and d_n have the
+ * same versionless coordinates. In more practical terms, a cycle occurs when a project directly or indirectly depends
+ * on its own output artifact.
+ * 
+ * @noimplement This interface is not intended to be implemented by clients.
+ * @noextend This interface is not intended to be extended by clients.
+ */
+public interface DependencyCycle
+{
+
+    /**
+     * Gets the dependencies that lead to the first dependency on the cycle, starting from the root of the dependency
+     * graph.
+     * 
+     * @return The (read-only) sequence of dependencies that precedes the cycle in the graph, potentially empty but
+     *         never {@code null}.
+     */
+    List<Dependency> getPrecedingDependencies();
+
+    /**
+     * Gets the dependencies that actually form the cycle. For example, a -&gt; b -&gt; c -&gt; a, i.e. the last
+     * dependency in this sequence duplicates the first element and closes the cycle. Hence the length of the cycle is
+     * the size of the returned sequence minus 1.
+     * 
+     * @return The (read-only) sequence of dependencies that forms the cycle, never {@code null}.
+     */
+    List<Dependency> getCyclicDependencies();
+
+}

http://git-wip-us.apache.org/repos/asf/maven-resolver/blob/3a1b8ae0/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DependencyFilter.java
----------------------------------------------------------------------
diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DependencyFilter.java b/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DependencyFilter.java
new file mode 100644
index 0000000..41776ff
--- /dev/null
+++ b/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DependencyFilter.java
@@ -0,0 +1,42 @@
+package org.eclipse.aether.graph;
+
+/*
+ * 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.List;
+
+/**
+ * A filter to include/exclude dependency nodes during other operations.
+ */
+public interface DependencyFilter
+{
+
+    /**
+     * Indicates whether the specified dependency node shall be included or excluded.
+     * 
+     * @param node The dependency node to filter, must not be {@code null}.
+     * @param parents The (read-only) chain of parent nodes that leads to the node to be filtered, must not be
+     *            {@code null}. Iterating this (possibly empty) list walks up the dependency graph towards the root
+     *            node, i.e. the immediate parent node (if any) is the first node in the list. The size of the list also
+     *            denotes the zero-based depth of the filtered node.
+     * @return {@code true} to include the dependency node, {@code false} to exclude it.
+     */
+    boolean accept( DependencyNode node, List<DependencyNode> parents );
+
+}

http://git-wip-us.apache.org/repos/asf/maven-resolver/blob/3a1b8ae0/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DependencyNode.java
----------------------------------------------------------------------
diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DependencyNode.java b/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DependencyNode.java
new file mode 100644
index 0000000..2551043
--- /dev/null
+++ b/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DependencyNode.java
@@ -0,0 +1,232 @@
+package org.eclipse.aether.graph;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.version.Version;
+import org.eclipse.aether.version.VersionConstraint;
+
+/**
+ * A node within a dependency graph. To conserve memory, dependency graphs may reuse a given node instance multiple
+ * times to represent reoccurring dependencies. As such clients traversing a dependency graph should be prepared to
+ * discover multiple paths leading to the same node instance unless the input graph is known to be a duplicate-free
+ * tree. <em>Note:</em> Unless otherwise noted, implementation classes are not thread-safe and dependency nodes should
+ * not be mutated by concurrent threads.
+ * 
+ * @noimplement This interface is not intended to be implemented by clients.
+ * @noextend This interface is not intended to be extended by clients.
+ */
+public interface DependencyNode
+{
+
+    /**
+     * A bit flag indicating the dependency version was subject to dependency management
+     * 
+     * @see #getManagedBits()
+     */
+    int MANAGED_VERSION = 0x01;
+
+    /**
+     * A bit flag indicating the dependency scope was subject to dependency management
+     * 
+     * @see #getManagedBits()
+     */
+    int MANAGED_SCOPE = 0x02;
+
+    /**
+     * A bit flag indicating the optional flag was subject to dependency management
+     * 
+     * @see #getManagedBits()
+     */
+    int MANAGED_OPTIONAL = 0x04;
+
+    /**
+     * A bit flag indicating the artifact properties were subject to dependency management
+     * 
+     * @see #getManagedBits()
+     */
+    int MANAGED_PROPERTIES = 0x08;
+
+    /**
+     * A bit flag indicating the exclusions were subject to dependency management
+     * 
+     * @see #getManagedBits()
+     */
+    int MANAGED_EXCLUSIONS = 0x10;
+
+    /**
+     * Gets the child nodes of this node. To conserve memory, dependency nodes with equal dependencies may share the
+     * same child list instance. Hence clients mutating the child list need to be aware that these changes might affect
+     * more than this node. Where this is not desired, the child list should be copied before mutation if the client
+     * cannot be sure whether it might be shared with other nodes in the graph.
+     * 
+     * @return The child nodes of this node, never {@code null}.
+     */
+    List<DependencyNode> getChildren();
+
+    /**
+     * Sets the child nodes of this node.
+     * 
+     * @param children The child nodes, may be {@code null}
+     */
+    void setChildren( List<DependencyNode> children );
+
+    /**
+     * Gets the dependency associated with this node. <em>Note:</em> For dependency graphs that have been constructed
+     * without a root dependency, this method will yield {@code null} when invoked on the graph's root node. The root
+     * node of such graphs may however still have a label as returned by {@link #getArtifact()}.
+     * 
+     * @return The dependency or {@code null} if none.
+     */
+    Dependency getDependency();
+
+    /**
+     * Gets the artifact associated with this node. If this node is associated with a dependency, this is equivalent to
+     * {@code getDependency().getArtifact()}. Otherwise the artifact merely provides a label for this node in which case
+     * the artifact must not be subjected to dependency collection/resolution.
+     * 
+     * @return The associated artifact or {@code null} if none.
+     */
+    Artifact getArtifact();
+
+    /**
+     * Updates the artifact of the dependency after resolution. The new artifact must have the same coordinates as the
+     * original artifact. This method may only be invoked if this node actually has a dependency, i.e. if
+     * {@link #getDependency()} is not null.
+     * 
+     * @param artifact The artifact satisfying the dependency, must not be {@code null}.
+     */
+    void setArtifact( Artifact artifact );
+
+    /**
+     * Gets the sequence of relocations that was followed to resolve the artifact referenced by the dependency.
+     * 
+     * @return The (read-only) sequence of relocations, never {@code null}.
+     */
+    List<? extends Artifact> getRelocations();
+
+    /**
+     * Gets the known aliases for this dependency's artifact. An alias can be used to mark a patched rebuild of some
+     * other artifact as such, thereby allowing conflict resolution to consider the patched and the original artifact as
+     * a conflict.
+     * 
+     * @return The (read-only) set of known aliases, never {@code null}.
+     */
+    Collection<? extends Artifact> getAliases();
+
+    /**
+     * Gets the version constraint that was parsed from the dependency's version declaration.
+     * 
+     * @return The version constraint for this node or {@code null}.
+     */
+    VersionConstraint getVersionConstraint();
+
+    /**
+     * Gets the version that was selected for the dependency's target artifact.
+     * 
+     * @return The parsed version or {@code null}.
+     */
+    Version getVersion();
+
+    /**
+     * Sets the scope of the dependency. This method may only be invoked if this node actually has a dependency, i.e. if
+     * {@link #getDependency()} is not null.
+     * 
+     * @param scope The scope, may be {@code null}.
+     */
+    void setScope( String scope );
+
+    /**
+     * Sets the optional flag of the dependency. This method may only be invoked if this node actually has a dependency,
+     * i.e. if {@link #getDependency()} is not null.
+     * 
+     * @param optional The optional flag, may be {@code null}.
+     */
+    void setOptional( Boolean optional );
+
+    /**
+     * Gets a bit field indicating which attributes of this node were subject to dependency management.
+     * 
+     * @return A bit field containing any of the bits {@link #MANAGED_VERSION}, {@link #MANAGED_SCOPE},
+     *         {@link #MANAGED_OPTIONAL}, {@link #MANAGED_PROPERTIES} and {@link #MANAGED_EXCLUSIONS} if the
+     *         corresponding attribute was set via dependency management.
+     */
+    int getManagedBits();
+
+    /**
+     * Gets the remote repositories from which this node's artifact shall be resolved.
+     * 
+     * @return The (read-only) list of remote repositories to use for artifact resolution, never {@code null}.
+     */
+    List<RemoteRepository> getRepositories();
+
+    /**
+     * Gets the request context in which this dependency node was created.
+     * 
+     * @return The request context, never {@code null}.
+     */
+    String getRequestContext();
+
+    /**
+     * Sets the request context in which this dependency node was created.
+     * 
+     * @param context The context, may be {@code null}.
+     */
+    void setRequestContext( String context );
+
+    /**
+     * Gets the custom data associated with this dependency node. Clients of the repository system can use this data to
+     * annotate dependency nodes with domain-specific information. Note that the returned map is read-only and
+     * {@link #setData(Object, Object)} needs to be used to update the custom data.
+     * 
+     * @return The (read-only) key-value mappings, never {@code null}.
+     */
+    Map<?, ?> getData();
+
+    /**
+     * Sets the custom data associated with this dependency node.
+     * 
+     * @param data The new custom data, may be {@code null}.
+     */
+    void setData( Map<Object, Object> data );
+
+    /**
+     * Associates the specified dependency node data with the given key. <em>Note:</em> This method must not be called
+     * while {@link #getData()} is being iterated.
+     * 
+     * @param key The key under which to store the data, must not be {@code null}.
+     * @param value The data to associate with the key, may be {@code null} to remove the mapping.
+     */
+    void setData( Object key, Object value );
+
+    /**
+     * Traverses this node and potentially its children using the specified visitor.
+     * 
+     * @param visitor The visitor to call back, must not be {@code null}.
+     * @return {@code true} to visit siblings nodes of this node as well, {@code false} to skip siblings.
+     */
+    boolean accept( DependencyVisitor visitor );
+
+}

http://git-wip-us.apache.org/repos/asf/maven-resolver/blob/3a1b8ae0/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DependencyVisitor.java
----------------------------------------------------------------------
diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DependencyVisitor.java b/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DependencyVisitor.java
new file mode 100644
index 0000000..2a85f2d
--- /dev/null
+++ b/maven-resolver-api/src/main/java/org/eclipse/aether/graph/DependencyVisitor.java
@@ -0,0 +1,47 @@
+package org.eclipse.aether.graph;
+
+/*
+ * 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.
+ */
+
+/**
+ * A visitor for nodes of the dependency graph.
+ * 
+ * @see DependencyNode#accept(DependencyVisitor)
+ */
+public interface DependencyVisitor
+{
+
+    /**
+     * Notifies the visitor of a node visit before its children have been processed.
+     * 
+     * @param node The dependency node being visited, must not be {@code null}.
+     * @return {@code true} to visit child nodes of the specified node as well, {@code false} to skip children.
+     */
+    boolean visitEnter( DependencyNode node );
+
+    /**
+     * Notifies the visitor of a node visit after its children have been processed. Note that this method is always
+     * invoked regardless whether any children have actually been visited.
+     * 
+     * @param node The dependency node being visited, must not be {@code null}.
+     * @return {@code true} to visit siblings nodes of the specified node as well, {@code false} to skip siblings.
+     */
+    boolean visitLeave( DependencyNode node );
+
+}

http://git-wip-us.apache.org/repos/asf/maven-resolver/blob/3a1b8ae0/maven-resolver-api/src/main/java/org/eclipse/aether/graph/Exclusion.java
----------------------------------------------------------------------
diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/graph/Exclusion.java b/maven-resolver-api/src/main/java/org/eclipse/aether/graph/Exclusion.java
new file mode 100644
index 0000000..497cf43
--- /dev/null
+++ b/maven-resolver-api/src/main/java/org/eclipse/aether/graph/Exclusion.java
@@ -0,0 +1,131 @@
+package org.eclipse.aether.graph;
+
+/*
+ * 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.
+ */
+
+/**
+ * An exclusion of one or more transitive dependencies. <em>Note:</em> Instances of this class are immutable and the
+ * exposed mutators return new objects rather than changing the current instance.
+ * 
+ * @see Dependency#getExclusions()
+ */
+public final class Exclusion
+{
+
+    private final String groupId;
+
+    private final String artifactId;
+
+    private final String classifier;
+
+    private final String extension;
+
+    /**
+     * Creates an exclusion for artifacts with the specified coordinates.
+     * 
+     * @param groupId The group identifier, may be {@code null}.
+     * @param artifactId The artifact identifier, may be {@code null}.
+     * @param classifier The classifier, may be {@code null}.
+     * @param extension The file extension, may be {@code null}.
+     */
+    public Exclusion( String groupId, String artifactId, String classifier, String extension )
+    {
+        this.groupId = ( groupId != null ) ? groupId : "";
+        this.artifactId = ( artifactId != null ) ? artifactId : "";
+        this.classifier = ( classifier != null ) ? classifier : "";
+        this.extension = ( extension != null ) ? extension : "";
+    }
+
+    /**
+     * Gets the group identifier for artifacts to exclude.
+     * 
+     * @return The group identifier, never {@code null}.
+     */
+    public String getGroupId()
+    {
+        return groupId;
+    }
+
+    /**
+     * Gets the artifact identifier for artifacts to exclude.
+     * 
+     * @return The artifact identifier, never {@code null}.
+     */
+    public String getArtifactId()
+    {
+        return artifactId;
+    }
+
+    /**
+     * Gets the classifier for artifacts to exclude.
+     * 
+     * @return The classifier, never {@code null}.
+     */
+    public String getClassifier()
+    {
+        return classifier;
+    }
+
+    /**
+     * Gets the file extension for artifacts to exclude.
+     * 
+     * @return The file extension of artifacts to exclude, never {@code null}.
+     */
+    public String getExtension()
+    {
+        return extension;
+    }
+
+    @Override
+    public String toString()
+    {
+        return getGroupId() + ':' + getArtifactId() + ':' + getExtension()
+            + ( getClassifier().length() > 0 ? ':' + getClassifier() : "" );
+    }
+
+    @Override
+    public boolean equals( Object obj )
+    {
+        if ( obj == this )
+        {
+            return true;
+        }
+        else if ( obj == null || !getClass().equals( obj.getClass() ) )
+        {
+            return false;
+        }
+
+        Exclusion that = (Exclusion) obj;
+
+        return artifactId.equals( that.artifactId ) && groupId.equals( that.groupId )
+            && extension.equals( that.extension ) && classifier.equals( that.classifier );
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int hash = 17;
+        hash = hash * 31 + artifactId.hashCode();
+        hash = hash * 31 + groupId.hashCode();
+        hash = hash * 31 + classifier.hashCode();
+        hash = hash * 31 + extension.hashCode();
+        return hash;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/maven-resolver/blob/3a1b8ae0/maven-resolver-api/src/main/java/org/eclipse/aether/graph/package-info.java
----------------------------------------------------------------------
diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/graph/package-info.java b/maven-resolver-api/src/main/java/org/eclipse/aether/graph/package-info.java
new file mode 100644
index 0000000..c3ba9db
--- /dev/null
+++ b/maven-resolver-api/src/main/java/org/eclipse/aether/graph/package-info.java
@@ -0,0 +1,24 @@
+// CHECKSTYLE_OFF: RegexpHeader
+/*
+ * 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.
+ */
+/**
+ * The representation of a dependency graph by means of connected dependency nodes.
+ */
+package org.eclipse.aether.graph;
+

http://git-wip-us.apache.org/repos/asf/maven-resolver/blob/3a1b8ae0/maven-resolver-api/src/main/java/org/eclipse/aether/installation/InstallRequest.java
----------------------------------------------------------------------
diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/installation/InstallRequest.java b/maven-resolver-api/src/main/java/org/eclipse/aether/installation/InstallRequest.java
new file mode 100644
index 0000000..f9b3163
--- /dev/null
+++ b/maven-resolver-api/src/main/java/org/eclipse/aether/installation/InstallRequest.java
@@ -0,0 +1,177 @@
+package org.eclipse.aether.installation;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.RequestTrace;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.metadata.Metadata;
+
+/**
+ * A request to install artifacts and their accompanying metadata into the local repository.
+ * 
+ * @see RepositorySystem#install(RepositorySystemSession, InstallRequest)
+ */
+public final class InstallRequest
+{
+
+    private Collection<Artifact> artifacts = Collections.emptyList();
+
+    private Collection<Metadata> metadata = Collections.emptyList();
+
+    private RequestTrace trace;
+
+    /**
+     * Creates an uninitialized request.
+     */
+    public InstallRequest()
+    {
+    }
+
+    /**
+     * Gets the artifact to install.
+     * 
+     * @return The artifacts to install, never {@code null}.
+     */
+    public Collection<Artifact> getArtifacts()
+    {
+        return artifacts;
+    }
+
+    /**
+     * Sets the artifacts to install.
+     * 
+     * @param artifacts The artifacts to install, may be {@code null}.
+     * @return This request for chaining, never {@code null}.
+     */
+    public InstallRequest setArtifacts( Collection<Artifact> artifacts )
+    {
+        if ( artifacts == null )
+        {
+            this.artifacts = Collections.emptyList();
+        }
+        else
+        {
+            this.artifacts = artifacts;
+        }
+        return this;
+    }
+
+    /**
+     * Adds the specified artifacts for installation.
+     * 
+     * @param artifact The artifact to add, may be {@code null}.
+     * @return This request for chaining, never {@code null}.
+     */
+    public InstallRequest addArtifact( Artifact artifact )
+    {
+        if ( artifact != null )
+        {
+            if ( artifacts.isEmpty() )
+            {
+                artifacts = new ArrayList<Artifact>();
+            }
+            artifacts.add( artifact );
+        }
+        return this;
+    }
+
+    /**
+     * Gets the metadata to install.
+     * 
+     * @return The metadata to install, never {@code null}.
+     */
+    public Collection<Metadata> getMetadata()
+    {
+        return metadata;
+    }
+
+    /**
+     * Sets the metadata to install.
+     * 
+     * @param metadata The metadata to install.
+     * @return This request for chaining, never {@code null}.
+     */
+    public InstallRequest setMetadata( Collection<Metadata> metadata )
+    {
+        if ( metadata == null )
+        {
+            this.metadata = Collections.emptyList();
+        }
+        else
+        {
+            this.metadata = metadata;
+        }
+        return this;
+    }
+
+    /**
+     * Adds the specified metadata for installation.
+     * 
+     * @param metadata The metadata to add, may be {@code null}.
+     * @return This request for chaining, never {@code null}.
+     */
+    public InstallRequest addMetadata( Metadata metadata )
+    {
+        if ( metadata != null )
+        {
+            if ( this.metadata.isEmpty() )
+            {
+                this.metadata = new ArrayList<Metadata>();
+            }
+            this.metadata.add( metadata );
+        }
+        return this;
+    }
+
+    /**
+     * Gets the trace information that describes the higher level request/operation in which this request is issued.
+     * 
+     * @return The trace information about the higher level operation or {@code null} if none.
+     */
+    public RequestTrace getTrace()
+    {
+        return trace;
+    }
+
+    /**
+     * Sets the trace information that describes the higher level request/operation in which this request is issued.
+     * 
+     * @param trace The trace information about the higher level operation, may be {@code null}.
+     * @return This request for chaining, never {@code null}.
+     */
+    public InstallRequest setTrace( RequestTrace trace )
+    {
+        this.trace = trace;
+        return this;
+    }
+
+    @Override
+    public String toString()
+    {
+        return getArtifacts() + ", " + getMetadata();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/maven-resolver/blob/3a1b8ae0/maven-resolver-api/src/main/java/org/eclipse/aether/installation/InstallResult.java
----------------------------------------------------------------------
diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/installation/InstallResult.java b/maven-resolver-api/src/main/java/org/eclipse/aether/installation/InstallResult.java
new file mode 100644
index 0000000..6624585
--- /dev/null
+++ b/maven-resolver-api/src/main/java/org/eclipse/aether/installation/InstallResult.java
@@ -0,0 +1,174 @@
+package org.eclipse.aether.installation;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.metadata.Metadata;
+
+/**
+ * The result of installing artifacts and their accompanying metadata into the a remote repository.
+ * 
+ * @see RepositorySystem#install(RepositorySystemSession, InstallRequest)
+ */
+public final class InstallResult
+{
+
+    private final InstallRequest request;
+
+    private Collection<Artifact> artifacts;
+
+    private Collection<Metadata> metadata;
+
+    /**
+     * Creates a new result for the specified request.
+     * 
+     * @param request The installation request, must not be {@code null}.
+     */
+    public InstallResult( InstallRequest request )
+    {
+        if ( request == null )
+        {
+            throw new IllegalArgumentException( "install request has not been specified" );
+        }
+        this.request = request;
+        artifacts = Collections.emptyList();
+        metadata = Collections.emptyList();
+    }
+
+    /**
+     * Gets the install request that was made.
+     * 
+     * @return The install request, never {@code null}.
+     */
+    public InstallRequest getRequest()
+    {
+        return request;
+    }
+
+    /**
+     * Gets the artifacts that got installed.
+     * 
+     * @return The installed artifacts, never {@code null}.
+     */
+    public Collection<Artifact> getArtifacts()
+    {
+        return artifacts;
+    }
+
+    /**
+     * Sets the artifacts that got installed.
+     * 
+     * @param artifacts The installed artifacts, may be {@code null}.
+     * @return This result for chaining, never {@code null}.
+     */
+    public InstallResult setArtifacts( Collection<Artifact> artifacts )
+    {
+        if ( artifacts == null )
+        {
+            this.artifacts = Collections.emptyList();
+        }
+        else
+        {
+            this.artifacts = artifacts;
+        }
+        return this;
+    }
+
+    /**
+     * Adds the specified artifacts to the result.
+     * 
+     * @param artifact The installed artifact to add, may be {@code null}.
+     * @return This result for chaining, never {@code null}.
+     */
+    public InstallResult addArtifact( Artifact artifact )
+    {
+        if ( artifact != null )
+        {
+            if ( artifacts.isEmpty() )
+            {
+                artifacts = new ArrayList<Artifact>();
+            }
+            artifacts.add( artifact );
+        }
+        return this;
+    }
+
+    /**
+     * Gets the metadata that got installed. Note that due to automatically generated metadata, there might have been
+     * more metadata installed than originally specified in the install request.
+     * 
+     * @return The installed metadata, never {@code null}.
+     */
+    public Collection<Metadata> getMetadata()
+    {
+        return metadata;
+    }
+
+    /**
+     * Sets the metadata that got installed.
+     * 
+     * @param metadata The installed metadata, may be {@code null}.
+     * @return This result for chaining, never {@code null}.
+     */
+    public InstallResult setMetadata( Collection<Metadata> metadata )
+    {
+        if ( metadata == null )
+        {
+            this.metadata = Collections.emptyList();
+        }
+        else
+        {
+            this.metadata = metadata;
+        }
+        return this;
+    }
+
+    /**
+     * Adds the specified metadata to this result.
+     * 
+     * @param metadata The installed metadata to add, may be {@code null}.
+     * @return This result for chaining, never {@code null}.
+     */
+    public InstallResult addMetadata( Metadata metadata )
+    {
+        if ( metadata != null )
+        {
+            if ( this.metadata.isEmpty() )
+            {
+                this.metadata = new ArrayList<Metadata>();
+            }
+            this.metadata.add( metadata );
+        }
+        return this;
+    }
+
+    @Override
+    public String toString()
+    {
+        return getArtifacts() + ", " + getMetadata();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/maven-resolver/blob/3a1b8ae0/maven-resolver-api/src/main/java/org/eclipse/aether/installation/InstallationException.java
----------------------------------------------------------------------
diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/installation/InstallationException.java b/maven-resolver-api/src/main/java/org/eclipse/aether/installation/InstallationException.java
new file mode 100644
index 0000000..9a556bb
--- /dev/null
+++ b/maven-resolver-api/src/main/java/org/eclipse/aether/installation/InstallationException.java
@@ -0,0 +1,52 @@
+package org.eclipse.aether.installation;
+
+/*
+ * 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;
+
+/**
+ * Thrown in case of an installation error like an IO error.
+ */
+public class InstallationException
+    extends RepositoryException
+{
+
+    /**
+     * Creates a new exception with the specified detail message.
+     * 
+     * @param message The detail message, may be {@code null}.
+     */
+    public InstallationException( String message )
+    {
+        super( message );
+    }
+
+    /**
+     * Creates a new exception with the specified detail message and cause.
+     * 
+     * @param message The detail message, may be {@code null}.
+     * @param cause The exception that caused this one, may be {@code null}.
+     */
+    public InstallationException( String message, Throwable cause )
+    {
+        super( message, cause );
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/maven-resolver/blob/3a1b8ae0/maven-resolver-api/src/main/java/org/eclipse/aether/installation/package-info.java
----------------------------------------------------------------------
diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/installation/package-info.java b/maven-resolver-api/src/main/java/org/eclipse/aether/installation/package-info.java
new file mode 100644
index 0000000..d4ac077
--- /dev/null
+++ b/maven-resolver-api/src/main/java/org/eclipse/aether/installation/package-info.java
@@ -0,0 +1,24 @@
+// CHECKSTYLE_OFF: RegexpHeader
+/*
+ * 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.
+ */
+/**
+ * The types supporting the publishing of artifacts to a local repository.
+ */
+package org.eclipse.aether.installation;
+

http://git-wip-us.apache.org/repos/asf/maven-resolver/blob/3a1b8ae0/maven-resolver-api/src/main/java/org/eclipse/aether/metadata/AbstractMetadata.java
----------------------------------------------------------------------
diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/metadata/AbstractMetadata.java b/maven-resolver-api/src/main/java/org/eclipse/aether/metadata/AbstractMetadata.java
new file mode 100644
index 0000000..49dab35
--- /dev/null
+++ b/maven-resolver-api/src/main/java/org/eclipse/aether/metadata/AbstractMetadata.java
@@ -0,0 +1,160 @@
+package org.eclipse.aether.metadata;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A skeleton class for metadata.
+ */
+public abstract class AbstractMetadata
+    implements Metadata
+{
+
+    private Metadata newInstance( Map<String, String> properties, File file )
+    {
+        return new DefaultMetadata( getGroupId(), getArtifactId(), getVersion(), getType(), getNature(), file,
+                                    properties );
+    }
+
+    public Metadata setFile( File file )
+    {
+        File current = getFile();
+        if ( ( current == null ) ? file == null : current.equals( file ) )
+        {
+            return this;
+        }
+        return newInstance( getProperties(), file );
+    }
+
+    public Metadata setProperties( Map<String, String> properties )
+    {
+        Map<String, String> current = getProperties();
+        if ( current.equals( properties ) || ( properties == null && current.isEmpty() ) )
+        {
+            return this;
+        }
+        return newInstance( copyProperties( properties ), getFile() );
+    }
+
+    public String getProperty( String key, String defaultValue )
+    {
+        String value = getProperties().get( key );
+        return ( value != null ) ? value : defaultValue;
+    }
+
+    /**
+     * Copies the specified metadata properties. This utility method should be used when creating new metadata instances
+     * with caller-supplied properties.
+     * 
+     * @param properties The properties to copy, may be {@code null}.
+     * @return The copied and read-only properties, never {@code null}.
+     */
+    protected static Map<String, String> copyProperties( Map<String, String> properties )
+    {
+        if ( properties != null && !properties.isEmpty() )
+        {
+            return Collections.unmodifiableMap( new HashMap<String, String>( properties ) );
+        }
+        else
+        {
+            return Collections.emptyMap();
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder buffer = new StringBuilder( 128 );
+        if ( getGroupId().length() > 0 )
+        {
+            buffer.append( getGroupId() );
+        }
+        if ( getArtifactId().length() > 0 )
+        {
+            buffer.append( ':' ).append( getArtifactId() );
+        }
+        if ( getVersion().length() > 0 )
+        {
+            buffer.append( ':' ).append( getVersion() );
+        }
+        buffer.append( '/' ).append( getType() );
+        return buffer.toString();
+    }
+
+    /**
+     * Compares this metadata with the specified object.
+     * 
+     * @param obj The object to compare this metadata against, may be {@code null}.
+     * @return {@code true} if and only if the specified object is another {@link Metadata} with equal coordinates,
+     *         type, nature, properties and file, {@code false} otherwise.
+     */
+    @Override
+    public boolean equals( Object obj )
+    {
+        if ( obj == this )
+        {
+            return true;
+        }
+        else if ( !( obj instanceof Metadata ) )
+        {
+            return false;
+        }
+
+        Metadata that = (Metadata) obj;
+
+        return getArtifactId().equals( that.getArtifactId() ) && getGroupId().equals( that.getGroupId() )
+            && getVersion().equals( that.getVersion() ) && getType().equals( that.getType() )
+            && getNature().equals( that.getNature() ) && eq( getFile(), that.getFile() )
+            && eq( getProperties(), that.getProperties() );
+    }
+
+    private static <T> boolean eq( T s1, T s2 )
+    {
+        return s1 != null ? s1.equals( s2 ) : s2 == null;
+    }
+
+    /**
+     * Returns a hash code for this metadata.
+     * 
+     * @return A hash code for the metadata.
+     */
+    @Override
+    public int hashCode()
+    {
+        int hash = 17;
+        hash = hash * 31 + getGroupId().hashCode();
+        hash = hash * 31 + getArtifactId().hashCode();
+        hash = hash * 31 + getType().hashCode();
+        hash = hash * 31 + getNature().hashCode();
+        hash = hash * 31 + getVersion().hashCode();
+        hash = hash * 31 + hash( getFile() );
+        return hash;
+    }
+
+    private static int hash( Object obj )
+    {
+        return ( obj != null ) ? obj.hashCode() : 0;
+    }
+
+}