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