View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.graph;
20  
21  import java.util.AbstractSet;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.Iterator;
25  import java.util.LinkedHashSet;
26  import java.util.NoSuchElementException;
27  import java.util.Objects;
28  import java.util.Set;
29  
30  import org.eclipse.aether.artifact.Artifact;
31  
32  import static java.util.Objects.requireNonNull;
33  
34  /**
35   * A dependency to some artifact. <em>Note:</em> Instances of this class are immutable and the exposed mutators return
36   * new objects rather than changing the current instance.
37   */
38  public final class Dependency {
39  
40      private final Artifact artifact;
41  
42      private final String scope;
43  
44      private final Boolean optional;
45  
46      private final Set<Exclusion> exclusions;
47  
48      /**
49       * Creates a mandatory dependency on the specified artifact with the given scope.
50       *
51       * @param artifact The artifact being depended on, must not be {@code null}.
52       * @param scope The scope of the dependency, may be {@code null}.
53       */
54      public Dependency(Artifact artifact, String scope) {
55          this(artifact, scope, false);
56      }
57  
58      /**
59       * Creates a dependency on the specified artifact with the given scope.
60       *
61       * @param artifact The artifact being depended on, must not be {@code null}.
62       * @param scope The scope of the dependency, may be {@code null}.
63       * @param optional A flag whether the dependency is optional or mandatory, may be {@code null}.
64       */
65      public Dependency(Artifact artifact, String scope, Boolean optional) {
66          this(artifact, scope, optional, null);
67      }
68  
69      /**
70       * Creates a dependency on the specified artifact with the given scope and exclusions.
71       *
72       * @param artifact The artifact being depended on, must not be {@code null}.
73       * @param scope The scope of the dependency, may be {@code null}.
74       * @param optional A flag whether the dependency is optional or mandatory, may be {@code null}.
75       * @param exclusions The exclusions that apply to transitive dependencies, may be {@code null} if none.
76       */
77      public Dependency(Artifact artifact, String scope, Boolean optional, Collection<Exclusion> exclusions) {
78          this(artifact, scope, Exclusions.copy(exclusions), optional);
79      }
80  
81      private Dependency(Artifact artifact, String scope, Set<Exclusion> exclusions, Boolean optional) {
82          // NOTE: This constructor assumes immutability of the provided exclusion collection, for internal use only
83          this.artifact = requireNonNull(artifact, "artifact cannot be null");
84          this.scope = (scope != null) ? scope : "";
85          this.optional = optional;
86          this.exclusions = exclusions;
87      }
88  
89      /**
90       * Gets the artifact being depended on.
91       *
92       * @return The artifact, never {@code null}.
93       */
94      public Artifact getArtifact() {
95          return artifact;
96      }
97  
98      /**
99       * Sets the artifact being depended on.
100      *
101      * @param artifact The artifact, must not be {@code null}.
102      * @return The new dependency, never {@code null}.
103      */
104     public Dependency setArtifact(Artifact artifact) {
105         if (this.artifact.equals(artifact)) {
106             return this;
107         }
108         return new Dependency(artifact, scope, exclusions, optional);
109     }
110 
111     /**
112      * Gets the scope of the dependency. The scope defines in which context this dependency is relevant.
113      *
114      * @return The scope or an empty string if not set, never {@code null}.
115      */
116     public String getScope() {
117         return scope;
118     }
119 
120     /**
121      * Sets the scope of the dependency, e.g. "compile".
122      *
123      * @param scope The scope of the dependency, may be {@code null}.
124      * @return The new dependency, never {@code null}.
125      */
126     public Dependency setScope(String scope) {
127         if (this.scope.equals(scope) || (scope == null && this.scope.isEmpty())) {
128             return this;
129         }
130         return new Dependency(artifact, scope, exclusions, optional);
131     }
132 
133     /**
134      * Indicates whether this dependency is optional or not. Optional dependencies can be ignored in some contexts.
135      *
136      * @return {@code true} if the dependency is (definitively) optional, {@code false} otherwise.
137      */
138     public boolean isOptional() {
139         return Boolean.TRUE.equals(optional);
140     }
141 
142     /**
143      * Gets the optional flag for the dependency. Note: Most clients will usually call {@link #isOptional()} to
144      * determine the optional flag, this method is for advanced use cases where three-valued logic is required.
145      *
146      * @return The optional flag or {@code null} if unspecified.
147      */
148     public Boolean getOptional() {
149         return optional;
150     }
151 
152     /**
153      * Sets the optional flag for the dependency.
154      *
155      * @param optional {@code true} if the dependency is optional, {@code false} if the dependency is mandatory, may be
156      *            {@code null} if unspecified.
157      * @return The new dependency, never {@code null}.
158      */
159     public Dependency setOptional(Boolean optional) {
160         if (Objects.equals(this.optional, optional)) {
161             return this;
162         }
163         return new Dependency(artifact, scope, exclusions, optional);
164     }
165 
166     /**
167      * Gets the exclusions for this dependency. Exclusions can be used to remove transitive dependencies during
168      * resolution.
169      *
170      * @return The (read-only) exclusions, never {@code null}.
171      */
172     public Collection<Exclusion> getExclusions() {
173         return exclusions;
174     }
175 
176     /**
177      * Sets the exclusions for the dependency.
178      *
179      * @param exclusions The exclusions, may be {@code null}.
180      * @return The new dependency, never {@code null}.
181      */
182     public Dependency setExclusions(Collection<Exclusion> exclusions) {
183         if (hasEquivalentExclusions(exclusions)) {
184             return this;
185         }
186         return new Dependency(artifact, scope, optional, exclusions);
187     }
188 
189     private boolean hasEquivalentExclusions(Collection<Exclusion> exclusions) {
190         if (exclusions == null || exclusions.isEmpty()) {
191             return this.exclusions.isEmpty();
192         }
193         if (exclusions instanceof Set) {
194             return this.exclusions.equals(exclusions);
195         }
196         return exclusions.size() >= this.exclusions.size()
197                 && this.exclusions.containsAll(exclusions)
198                 && exclusions.containsAll(this.exclusions);
199     }
200 
201     @Override
202     public String toString() {
203         return getArtifact() + " (" + getScope() + (isOptional() ? "?" : "") + ")";
204     }
205 
206     @Override
207     public boolean equals(Object obj) {
208         if (obj == this) {
209             return true;
210         } else if (obj == null || !getClass().equals(obj.getClass())) {
211             return false;
212         }
213 
214         Dependency that = (Dependency) obj;
215 
216         return Objects.equals(artifact, that.artifact)
217                 && Objects.equals(scope, that.scope)
218                 && Objects.equals(optional, that.optional)
219                 && Objects.equals(exclusions, that.exclusions);
220     }
221 
222     @Override
223     public int hashCode() {
224         int hash = 17;
225         hash = hash * 31 + artifact.hashCode();
226         hash = hash * 31 + scope.hashCode();
227         hash = hash * 31 + (optional != null ? optional.hashCode() : 0);
228         hash = hash * 31 + exclusions.size();
229         return hash;
230     }
231 
232     private static class Exclusions extends AbstractSet<Exclusion> {
233 
234         private final Exclusion[] exclusions;
235 
236         public static Set<Exclusion> copy(Collection<Exclusion> exclusions) {
237             if (exclusions == null || exclusions.isEmpty()) {
238                 return Collections.emptySet();
239             }
240             return new Exclusions(exclusions);
241         }
242 
243         private Exclusions(Collection<Exclusion> exclusions) {
244             if (exclusions.size() > 1 && !(exclusions instanceof Set)) {
245                 exclusions = new LinkedHashSet<>(exclusions);
246             }
247             this.exclusions = exclusions.toArray(new Exclusion[0]);
248         }
249 
250         @Override
251         public Iterator<Exclusion> iterator() {
252             return new Iterator<Exclusion>() {
253 
254                 private int cursor = 0;
255 
256                 public boolean hasNext() {
257                     return cursor < exclusions.length;
258                 }
259 
260                 public Exclusion next() {
261                     try {
262                         Exclusion exclusion = exclusions[cursor];
263                         cursor++;
264                         return exclusion;
265                     } catch (IndexOutOfBoundsException e) {
266                         throw new NoSuchElementException();
267                     }
268                 }
269 
270                 public void remove() {
271                     throw new UnsupportedOperationException();
272                 }
273             };
274         }
275 
276         @Override
277         public int size() {
278             return exclusions.length;
279         }
280     }
281 }