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