001package org.eclipse.aether.util.graph.selector;
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.ArrayList;
023import java.util.Arrays;
024import java.util.Collection;
025import java.util.HashSet;
026import java.util.TreeSet;
027
028import org.eclipse.aether.collection.DependencyCollectionContext;
029import org.eclipse.aether.collection.DependencySelector;
030import org.eclipse.aether.graph.Dependency;
031
032/**
033 * A dependency selector that filters transitive dependencies based on their scope. Direct dependencies are always
034 * included regardless of their scope. <em>Note:</em> This filter does not assume any relationships between the scopes.
035 * In particular, the filter is not aware of scopes that logically include other scopes.
036 * 
037 * @see Dependency#getScope()
038 */
039public final class ScopeDependencySelector
040    implements DependencySelector
041{
042
043    private final boolean transitive;
044
045    private final Collection<String> included;
046
047    private final Collection<String> excluded;
048
049    /**
050     * Creates a new selector using the specified includes and excludes.
051     * 
052     * @param included The set of scopes to include, may be {@code null} or empty to include any scope.
053     * @param excluded The set of scopes to exclude, may be {@code null} or empty to exclude no scope.
054     */
055    public ScopeDependencySelector( Collection<String> included, Collection<String> excluded )
056    {
057        transitive = false;
058        this.included = clone( included );
059        this.excluded = clone( excluded );
060    }
061
062    private static Collection<String> clone( Collection<String> scopes )
063    {
064        Collection<String> copy;
065        if ( scopes == null || scopes.isEmpty() )
066        {
067            // checking for null is faster than isEmpty()
068            copy = null;
069        }
070        else
071        {
072            copy = new HashSet<String>( scopes );
073            if ( copy.size() <= 2 )
074            {
075                // contains() is faster for smallish array (sorted for equals()!)
076                copy = new ArrayList<String>( new TreeSet<String>( copy ) );
077            }
078        }
079        return copy;
080    }
081
082    /**
083     * Creates a new selector using the specified excludes.
084     * 
085     * @param excluded The set of scopes to exclude, may be {@code null} or empty to exclude no scope.
086     */
087    public ScopeDependencySelector( String... excluded )
088    {
089        this( null, ( excluded != null ) ? Arrays.asList( excluded ) : null );
090    }
091
092    private ScopeDependencySelector( boolean transitive, Collection<String> included, Collection<String> excluded )
093    {
094        this.transitive = transitive;
095        this.included = included;
096        this.excluded = excluded;
097    }
098
099    public boolean selectDependency( Dependency dependency )
100    {
101        if ( !transitive )
102        {
103            return true;
104        }
105
106        String scope = dependency.getScope();
107        return ( included == null || included.contains( scope ) ) && ( excluded == null || !excluded.contains( scope ) );
108    }
109
110    public DependencySelector deriveChildSelector( DependencyCollectionContext context )
111    {
112        if ( this.transitive || context.getDependency() == null )
113        {
114            return this;
115        }
116
117        return new ScopeDependencySelector( true, included, excluded );
118    }
119
120    @Override
121    public boolean equals( Object obj )
122    {
123        if ( this == obj )
124        {
125            return true;
126        }
127        else if ( null == obj || !getClass().equals( obj.getClass() ) )
128        {
129            return false;
130        }
131
132        ScopeDependencySelector that = (ScopeDependencySelector) obj;
133        return transitive == that.transitive && eq( included, that.included ) && eq( excluded, that.excluded );
134    }
135
136    private static <T> boolean eq( T o1, T o2 )
137    {
138        return ( o1 != null ) ? o1.equals( o2 ) : o2 == null;
139    }
140
141    @Override
142    public int hashCode()
143    {
144        int hash = 17;
145        hash = hash * 31 + ( transitive ? 1 : 0 );
146        hash = hash * 31 + ( included != null ? included.hashCode() : 0 );
147        hash = hash * 31 + ( excluded != null ? excluded.hashCode() : 0 );
148        return hash;
149    }
150
151}