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