--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2010, 2013 Sonatype, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Sonatype, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.aether.graph;
+
+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;
+ }
+
+ }
+
+}