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