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;
029
030import java.util.Objects;
031import java.util.Set;
032
033import org.eclipse.aether.artifact.Artifact;
034
035/**
036 * A dependency to some artifact. <em>Note:</em> Instances of this class are immutable and the exposed mutators return
037 * new objects rather than changing the current instance.
038 */
039public final class Dependency
040{
041
042    private final Artifact artifact;
043
044    private final String scope;
045
046    private final Boolean optional;
047
048    private final Set<Exclusion> exclusions;
049
050    /**
051     * Creates a mandatory dependency on the specified artifact with the given scope.
052     * 
053     * @param artifact The artifact being depended on, must not be {@code null}.
054     * @param scope The scope of the dependency, may be {@code null}.
055     */
056    public Dependency( Artifact artifact, String scope )
057    {
058        this( artifact, scope, false );
059    }
060
061    /**
062     * Creates a dependency on the specified artifact with the given scope.
063     * 
064     * @param artifact The artifact being depended on, must not be {@code null}.
065     * @param scope The scope of the dependency, may be {@code null}.
066     * @param optional A flag whether the dependency is optional or mandatory, may be {@code null}.
067     */
068    public Dependency( Artifact artifact, String scope, Boolean optional )
069    {
070        this( artifact, scope, optional, null );
071    }
072
073    /**
074     * Creates a dependency on the specified artifact with the given scope and exclusions.
075     * 
076     * @param artifact The artifact being depended on, must not be {@code null}.
077     * @param scope The scope of the dependency, may be {@code null}.
078     * @param optional A flag whether the dependency is optional or mandatory, may be {@code null}.
079     * @param exclusions The exclusions that apply to transitive dependencies, may be {@code null} if none.
080     */
081    public Dependency( Artifact artifact, String scope, Boolean optional, Collection<Exclusion> exclusions )
082    {
083        this( artifact, scope, Exclusions.copy( exclusions ), optional );
084    }
085
086    private Dependency( Artifact artifact, String scope, Set<Exclusion> exclusions, Boolean optional )
087    {
088        // NOTE: This constructor assumes immutability of the provided exclusion collection, for internal use only
089        this.artifact = requireNonNull( artifact, "artifact cannot be null" );
090        this.scope = ( scope != null ) ? scope : "";
091        this.optional = optional;
092        this.exclusions = exclusions;
093    }
094
095    /**
096     * Gets the artifact being depended on.
097     * 
098     * @return The artifact, never {@code null}.
099     */
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}