001package org.eclipse.aether.graph;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 * 
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 * 
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.util.AbstractSet;
023import java.util.Collection;
024import java.util.Collections;
025import java.util.Iterator;
026import java.util.LinkedHashSet;
027import java.util.NoSuchElementException;
028import static java.util.Objects.requireNonNull;
029import java.util.Set;
030
031import org.eclipse.aether.artifact.Artifact;
032
033/**
034 * A dependency to some artifact. <em>Note:</em> Instances of this class are immutable and the exposed mutators return
035 * new objects rather than changing the current instance.
036 */
037public final class Dependency
038{
039
040    private final Artifact artifact;
041
042    private final String scope;
043
044    private final Boolean optional;
045
046    private final Set<Exclusion> exclusions;
047
048    /**
049     * Creates a mandatory dependency on the specified artifact with the given scope.
050     * 
051     * @param artifact The artifact being depended on, must not be {@code null}.
052     * @param scope The scope of the dependency, may be {@code null}.
053     */
054    public Dependency( Artifact artifact, String scope )
055    {
056        this( artifact, scope, false );
057    }
058
059    /**
060     * Creates a dependency on the specified artifact with the given scope.
061     * 
062     * @param artifact The artifact being depended on, must not be {@code null}.
063     * @param scope The scope of the dependency, may be {@code null}.
064     * @param optional A flag whether the dependency is optional or mandatory, may be {@code null}.
065     */
066    public Dependency( Artifact artifact, String scope, Boolean optional )
067    {
068        this( artifact, scope, optional, null );
069    }
070
071    /**
072     * Creates a dependency on the specified artifact with the given scope and exclusions.
073     * 
074     * @param artifact The artifact being depended on, must not be {@code null}.
075     * @param scope The scope of the dependency, may be {@code null}.
076     * @param optional A flag whether the dependency is optional or mandatory, may be {@code null}.
077     * @param exclusions The exclusions that apply to transitive dependencies, may be {@code null} if none.
078     */
079    public Dependency( Artifact artifact, String scope, Boolean optional, Collection<Exclusion> exclusions )
080    {
081        this( artifact, scope, Exclusions.copy( exclusions ), optional );
082    }
083
084    private Dependency( Artifact artifact, String scope, Set<Exclusion> exclusions, Boolean optional )
085    {
086        // NOTE: This constructor assumes immutability of the provided exclusion collection, for internal use only
087        this.artifact = requireNonNull( artifact, "artifact cannot be null" );
088        this.scope = ( scope != null ) ? scope : "";
089        this.optional = optional;
090        this.exclusions = exclusions;
091    }
092
093    /**
094     * Gets the artifact being depended on.
095     * 
096     * @return The artifact, never {@code null}.
097     */
098    public Artifact getArtifact()
099    {
100        return artifact;
101    }
102
103    /**
104     * Sets the artifact being depended on.
105     * 
106     * @param artifact The artifact, must not be {@code null}.
107     * @return The new dependency, never {@code null}.
108     */
109    public Dependency setArtifact( Artifact artifact )
110    {
111        if ( this.artifact.equals( artifact ) )
112        {
113            return this;
114        }
115        return new Dependency( artifact, scope, exclusions, optional );
116    }
117
118    /**
119     * Gets the scope of the dependency. The scope defines in which context this dependency is relevant.
120     * 
121     * @return The scope or an empty string if not set, never {@code null}.
122     */
123    public String getScope()
124    {
125        return scope;
126    }
127
128    /**
129     * Sets the scope of the dependency, e.g. "compile".
130     * 
131     * @param scope The scope of the dependency, may be {@code null}.
132     * @return The new dependency, never {@code null}.
133     */
134    public Dependency setScope( String scope )
135    {
136        if ( this.scope.equals( scope ) || ( scope == null && this.scope.length() <= 0 ) )
137        {
138            return this;
139        }
140        return new Dependency( artifact, scope, exclusions, optional );
141    }
142
143    /**
144     * Indicates whether this dependency is optional or not. Optional dependencies can be ignored in some contexts.
145     * 
146     * @return {@code true} if the dependency is (definitively) optional, {@code false} otherwise.
147     */
148    public boolean isOptional()
149    {
150        return Boolean.TRUE.equals( optional );
151    }
152
153    /**
154     * Gets the optional flag for the dependency. Note: Most clients will usually call {@link #isOptional()} to
155     * determine the optional flag, this method is for advanced use cases where three-valued logic is required.
156     * 
157     * @return The optional flag or {@code null} if unspecified.
158     */
159    public Boolean getOptional()
160    {
161        return optional;
162    }
163
164    /**
165     * Sets the optional flag for the dependency.
166     * 
167     * @param optional {@code true} if the dependency is optional, {@code false} if the dependency is mandatory, may be
168     *            {@code null} if unspecified.
169     * @return The new dependency, never {@code null}.
170     */
171    public Dependency setOptional( Boolean optional )
172    {
173        if ( eq( this.optional, optional ) )
174        {
175            return this;
176        }
177        return new Dependency( artifact, scope, exclusions, optional );
178    }
179
180    /**
181     * Gets the exclusions for this dependency. Exclusions can be used to remove transitive dependencies during
182     * resolution.
183     * 
184     * @return The (read-only) exclusions, never {@code null}.
185     */
186    public Collection<Exclusion> getExclusions()
187    {
188        return exclusions;
189    }
190
191    /**
192     * Sets the exclusions for the dependency.
193     * 
194     * @param exclusions The exclusions, may be {@code null}.
195     * @return The new dependency, never {@code null}.
196     */
197    public Dependency setExclusions( Collection<Exclusion> exclusions )
198    {
199        if ( hasEquivalentExclusions( exclusions ) )
200        {
201            return this;
202        }
203        return new Dependency( artifact, scope, optional, exclusions );
204    }
205
206    private boolean hasEquivalentExclusions( Collection<Exclusion> exclusions )
207    {
208        if ( exclusions == null || exclusions.isEmpty() )
209        {
210            return this.exclusions.isEmpty();
211        }
212        if ( exclusions instanceof Set )
213        {
214            return this.exclusions.equals( exclusions );
215        }
216        return exclusions.size() >= this.exclusions.size() && this.exclusions.containsAll( exclusions )
217            && exclusions.containsAll( this.exclusions );
218    }
219
220    @Override
221    public String toString()
222    {
223        return String.valueOf( getArtifact() ) + " (" + getScope() + ( isOptional() ? "?" : "" ) + ")";
224    }
225
226    @Override
227    public boolean equals( Object obj )
228    {
229        if ( obj == this )
230        {
231            return true;
232        }
233        else if ( obj == null || !getClass().equals( obj.getClass() ) )
234        {
235            return false;
236        }
237
238        Dependency that = (Dependency) obj;
239
240        return artifact.equals( that.artifact ) && scope.equals( that.scope ) && eq( optional, that.optional )
241            && exclusions.equals( that.exclusions );
242    }
243
244    private static <T> boolean eq( T o1, T o2 )
245    {
246        return ( o1 != null ) ? o1.equals( o2 ) : o2 == null;
247    }
248
249    @Override
250    public int hashCode()
251    {
252        int hash = 17;
253        hash = hash * 31 + artifact.hashCode();
254        hash = hash * 31 + scope.hashCode();
255        hash = hash * 31 + ( optional != null ? optional.hashCode() : 0 );
256        hash = hash * 31 + exclusions.size();
257        return hash;
258    }
259
260    private static class Exclusions
261        extends AbstractSet<Exclusion>
262    {
263
264        private final Exclusion[] exclusions;
265
266        public static Set<Exclusion> copy( Collection<Exclusion> exclusions )
267        {
268            if ( exclusions == null || exclusions.isEmpty() )
269            {
270                return Collections.emptySet();
271            }
272            return new Exclusions( exclusions );
273        }
274
275        private Exclusions( Collection<Exclusion> exclusions )
276        {
277            if ( exclusions.size() > 1 && !( exclusions instanceof Set ) )
278            {
279                exclusions = new LinkedHashSet<Exclusion>( exclusions );
280            }
281            this.exclusions = exclusions.toArray( new Exclusion[exclusions.size()] );
282        }
283
284        @Override
285        public Iterator<Exclusion> iterator()
286        {
287            return new Iterator<Exclusion>()
288            {
289
290                private int cursor = 0;
291
292                public boolean hasNext()
293                {
294                    return cursor < exclusions.length;
295                }
296
297                public Exclusion next()
298                {
299                    try
300                    {
301                        Exclusion exclusion = exclusions[cursor];
302                        cursor++;
303                        return exclusion;
304                    }
305                    catch ( IndexOutOfBoundsException e )
306                    {
307                        throw new NoSuchElementException();
308                    }
309                }
310
311                public void remove()
312                {
313                    throw new UnsupportedOperationException();
314                }
315
316            };
317        }
318
319        @Override
320        public int size()
321        {
322            return exclusions.length;
323        }
324
325    }
326
327}