View Javadoc
1   /*******************************************************************************
2    * Copyright (c) 2010, 2013 Sonatype, Inc.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v10.html
7    *
8    * Contributors:
9    *    Sonatype, Inc. - initial API and implementation
10   *******************************************************************************/
11  package org.eclipse.aether.graph;
12  
13  import java.util.AbstractSet;
14  import java.util.Collection;
15  import java.util.Collections;
16  import java.util.Iterator;
17  import java.util.LinkedHashSet;
18  import java.util.NoSuchElementException;
19  import java.util.Set;
20  
21  import org.eclipse.aether.artifact.Artifact;
22  
23  /**
24   * A dependency to some artifact. <em>Note:</em> Instances of this class are immutable and the exposed mutators return
25   * new objects rather than changing the current instance.
26   */
27  public final class Dependency
28  {
29  
30      private final Artifact artifact;
31  
32      private final String scope;
33  
34      private final Boolean optional;
35  
36      private final Set<Exclusion> exclusions;
37  
38      /**
39       * Creates a mandatory dependency on the specified artifact with the given scope.
40       * 
41       * @param artifact The artifact being depended on, must not be {@code null}.
42       * @param scope The scope of the dependency, may be {@code null}.
43       */
44      public Dependency( Artifact artifact, String scope )
45      {
46          this( artifact, scope, false );
47      }
48  
49      /**
50       * Creates a dependency on the specified artifact with the given scope.
51       * 
52       * @param artifact The artifact being depended on, must not be {@code null}.
53       * @param scope The scope of the dependency, may be {@code null}.
54       * @param optional A flag whether the dependency is optional or mandatory, may be {@code null}.
55       */
56      public Dependency( Artifact artifact, String scope, Boolean optional )
57      {
58          this( artifact, scope, optional, null );
59      }
60  
61      /**
62       * Creates a dependency on the specified artifact with the given scope and exclusions.
63       * 
64       * @param artifact The artifact being depended on, must not be {@code null}.
65       * @param scope The scope of the dependency, may be {@code null}.
66       * @param optional A flag whether the dependency is optional or mandatory, may be {@code null}.
67       * @param exclusions The exclusions that apply to transitive dependencies, may be {@code null} if none.
68       */
69      public Dependency( Artifact artifact, String scope, Boolean optional, Collection<Exclusion> exclusions )
70      {
71          this( artifact, scope, Exclusions.copy( exclusions ), optional );
72      }
73  
74      private Dependency( Artifact artifact, String scope, Set<Exclusion> exclusions, Boolean optional )
75      {
76          // NOTE: This constructor assumes immutability of the provided exclusion collection, for internal use only
77          if ( artifact == null )
78          {
79              throw new IllegalArgumentException( "no artifact specified for dependency" );
80          }
81          this.artifact = artifact;
82          this.scope = ( scope != null ) ? scope : "";
83          this.optional = optional;
84          this.exclusions = exclusions;
85      }
86  
87      /**
88       * Gets the artifact being depended on.
89       * 
90       * @return The artifact, never {@code null}.
91       */
92      public Artifact getArtifact()
93      {
94          return artifact;
95      }
96  
97      /**
98       * Sets the artifact being depended on.
99       * 
100      * @param artifact The artifact, must not be {@code null}.
101      * @return The new dependency, never {@code null}.
102      */
103     public Dependency setArtifact( Artifact artifact )
104     {
105         if ( this.artifact.equals( artifact ) )
106         {
107             return this;
108         }
109         return new Dependency( artifact, scope, exclusions, optional );
110     }
111 
112     /**
113      * Gets the scope of the dependency. The scope defines in which context this dependency is relevant.
114      * 
115      * @return The scope or an empty string if not set, never {@code null}.
116      */
117     public String getScope()
118     {
119         return scope;
120     }
121 
122     /**
123      * Sets the scope of the dependency, e.g. "compile".
124      * 
125      * @param scope The scope of the dependency, may be {@code null}.
126      * @return The new dependency, never {@code null}.
127      */
128     public Dependency setScope( String scope )
129     {
130         if ( this.scope.equals( scope ) || ( scope == null && this.scope.length() <= 0 ) )
131         {
132             return this;
133         }
134         return new Dependency( artifact, scope, exclusions, optional );
135     }
136 
137     /**
138      * Indicates whether this dependency is optional or not. Optional dependencies can be ignored in some contexts.
139      * 
140      * @return {@code true} if the dependency is (definitively) optional, {@code false} otherwise.
141      */
142     public boolean isOptional()
143     {
144         return Boolean.TRUE.equals( optional );
145     }
146 
147     /**
148      * Gets the optional flag for the dependency. Note: Most clients will usually call {@link #isOptional()} to
149      * determine the optional flag, this method is for advanced use cases where three-valued logic is required.
150      * 
151      * @return The optional flag or {@code null} if unspecified.
152      */
153     public Boolean getOptional()
154     {
155         return optional;
156     }
157 
158     /**
159      * Sets the optional flag for the dependency.
160      * 
161      * @param optional {@code true} if the dependency is optional, {@code false} if the dependency is mandatory, may be
162      *            {@code null} if unspecified.
163      * @return The new dependency, never {@code null}.
164      */
165     public Dependency setOptional( Boolean optional )
166     {
167         if ( eq( this.optional, optional ) )
168         {
169             return this;
170         }
171         return new Dependency( artifact, scope, exclusions, optional );
172     }
173 
174     /**
175      * Gets the exclusions for this dependency. Exclusions can be used to remove transitive dependencies during
176      * resolution.
177      * 
178      * @return The (read-only) exclusions, never {@code null}.
179      */
180     public Collection<Exclusion> getExclusions()
181     {
182         return exclusions;
183     }
184 
185     /**
186      * Sets the exclusions for the dependency.
187      * 
188      * @param exclusions The exclusions, may be {@code null}.
189      * @return The new dependency, never {@code null}.
190      */
191     public Dependency setExclusions( Collection<Exclusion> exclusions )
192     {
193         if ( hasEquivalentExclusions( exclusions ) )
194         {
195             return this;
196         }
197         return new Dependency( artifact, scope, optional, exclusions );
198     }
199 
200     private boolean hasEquivalentExclusions( Collection<Exclusion> exclusions )
201     {
202         if ( exclusions == null || exclusions.isEmpty() )
203         {
204             return this.exclusions.isEmpty();
205         }
206         if ( exclusions instanceof Set )
207         {
208             return this.exclusions.equals( exclusions );
209         }
210         return exclusions.size() >= this.exclusions.size() && this.exclusions.containsAll( exclusions )
211             && exclusions.containsAll( this.exclusions );
212     }
213 
214     @Override
215     public String toString()
216     {
217         return String.valueOf( getArtifact() ) + " (" + getScope() + ( isOptional() ? "?" : "" ) + ")";
218     }
219 
220     @Override
221     public boolean equals( Object obj )
222     {
223         if ( obj == this )
224         {
225             return true;
226         }
227         else if ( obj == null || !getClass().equals( obj.getClass() ) )
228         {
229             return false;
230         }
231 
232         Dependency that = (Dependency) obj;
233 
234         return artifact.equals( that.artifact ) && scope.equals( that.scope ) && eq( optional, that.optional )
235             && exclusions.equals( that.exclusions );
236     }
237 
238     private static <T> boolean eq( T o1, T o2 )
239     {
240         return ( o1 != null ) ? o1.equals( o2 ) : o2 == null;
241     }
242 
243     @Override
244     public int hashCode()
245     {
246         int hash = 17;
247         hash = hash * 31 + artifact.hashCode();
248         hash = hash * 31 + scope.hashCode();
249         hash = hash * 31 + ( optional != null ? optional.hashCode() : 0 );
250         hash = hash * 31 + exclusions.size();
251         return hash;
252     }
253 
254     private static class Exclusions
255         extends AbstractSet<Exclusion>
256     {
257 
258         private final Exclusion[] exclusions;
259 
260         public static Set<Exclusion> copy( Collection<Exclusion> exclusions )
261         {
262             if ( exclusions == null || exclusions.isEmpty() )
263             {
264                 return Collections.emptySet();
265             }
266             return new Exclusions( exclusions );
267         }
268 
269         private Exclusions( Collection<Exclusion> exclusions )
270         {
271             if ( exclusions.size() > 1 && !( exclusions instanceof Set ) )
272             {
273                 exclusions = new LinkedHashSet<Exclusion>( exclusions );
274             }
275             this.exclusions = exclusions.toArray( new Exclusion[exclusions.size()] );
276         }
277 
278         @Override
279         public Iterator<Exclusion> iterator()
280         {
281             return new Iterator<Exclusion>()
282             {
283 
284                 private int cursor = 0;
285 
286                 public boolean hasNext()
287                 {
288                     return cursor < exclusions.length;
289                 }
290 
291                 public Exclusion next()
292                 {
293                     try
294                     {
295                         Exclusion exclusion = exclusions[cursor];
296                         cursor++;
297                         return exclusion;
298                     }
299                     catch ( IndexOutOfBoundsException e )
300                     {
301                         throw new NoSuchElementException();
302                     }
303                 }
304 
305                 public void remove()
306                 {
307                     throw new UnsupportedOperationException();
308                 }
309 
310             };
311         }
312 
313         @Override
314         public int size()
315         {
316             return exclusions.length;
317         }
318 
319     }
320 
321 }