/******************************************************************************* * 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. Note: 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 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 exclusions ) { this( artifact, scope, Exclusions.copy( exclusions ), optional ); } private Dependency( Artifact artifact, String scope, Set 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 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 exclusions ) { if ( hasEquivalentExclusions( exclusions ) ) { return this; } return new Dependency( artifact, scope, optional, exclusions ); } private boolean hasEquivalentExclusions( Collection 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 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 { private final Exclusion[] exclusions; public static Set copy( Collection exclusions ) { if ( exclusions == null || exclusions.isEmpty() ) { return Collections.emptySet(); } return new Exclusions( exclusions ); } private Exclusions( Collection exclusions ) { if ( exclusions.size() > 1 && !( exclusions instanceof Set ) ) { exclusions = new LinkedHashSet( exclusions ); } this.exclusions = exclusions.toArray( new Exclusion[exclusions.size()] ); } @Override public Iterator iterator() { return new Iterator() { 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; } } }