001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.util.graph.selector;
020
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.Collection;
024import java.util.HashSet;
025import java.util.Objects;
026import java.util.TreeSet;
027
028import org.eclipse.aether.collection.DependencyCollectionContext;
029import org.eclipse.aether.collection.DependencySelector;
030import org.eclipse.aether.graph.Dependency;
031
032import static java.util.Objects.requireNonNull;
033
034/**
035 * A dependency selector that filters transitive dependencies based on their scope. Direct dependencies are always
036 * included regardless of their scope. <em>Note:</em> This filter does not assume any relationships between the scopes.
037 * In particular, the filter is not aware of scopes that logically include other scopes.
038 *
039 * @see Dependency#getScope()
040 */
041public final class ScopeDependencySelector implements DependencySelector {
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        transitive = false;
057        this.included = clone(included);
058        this.excluded = clone(excluded);
059    }
060
061    private static Collection<String> clone(Collection<String> scopes) {
062        Collection<String> copy;
063        if (scopes == null || scopes.isEmpty()) {
064            // checking for null is faster than isEmpty()
065            copy = null;
066        } else {
067            copy = new HashSet<>(scopes);
068            if (copy.size() <= 2) {
069                // contains() is faster for smallish array (sorted for equals()!)
070                copy = new ArrayList<>(new TreeSet<>(copy));
071            }
072        }
073        return copy;
074    }
075
076    /**
077     * Creates a new selector using the specified excludes.
078     *
079     * @param excluded The set of scopes to exclude, may be {@code null} or empty to exclude no scope.
080     */
081    public ScopeDependencySelector(String... excluded) {
082        this(null, (excluded != null) ? Arrays.asList(excluded) : null);
083    }
084
085    private ScopeDependencySelector(boolean transitive, Collection<String> included, Collection<String> excluded) {
086        this.transitive = transitive;
087        this.included = included;
088        this.excluded = excluded;
089    }
090
091    public boolean selectDependency(Dependency dependency) {
092        requireNonNull(dependency, "dependency cannot be null");
093        if (!transitive) {
094            return true;
095        }
096
097        String scope = dependency.getScope();
098        return (included == null || included.contains(scope)) && (excluded == null || !excluded.contains(scope));
099    }
100
101    public DependencySelector deriveChildSelector(DependencyCollectionContext context) {
102        requireNonNull(context, "context cannot be null");
103        if (this.transitive || context.getDependency() == null) {
104            return this;
105        }
106
107        return new ScopeDependencySelector(true, included, excluded);
108    }
109
110    @Override
111    public boolean equals(Object obj) {
112        if (this == obj) {
113            return true;
114        } else if (null == obj || !getClass().equals(obj.getClass())) {
115            return false;
116        }
117
118        ScopeDependencySelector that = (ScopeDependencySelector) obj;
119        return transitive == that.transitive
120                && Objects.equals(included, that.included)
121                && Objects.equals(excluded, that.excluded);
122    }
123
124    @Override
125    public int hashCode() {
126        int hash = 17;
127        hash = hash * 31 + (transitive ? 1 : 0);
128        hash = hash * 31 + (included != null ? included.hashCode() : 0);
129        hash = hash * 31 + (excluded != null ? excluded.hashCode() : 0);
130        return hash;
131    }
132
133    @Override
134    public String toString() {
135        return String.format(
136                "%s(included: %s, excluded: %s, transitive: %s)",
137                getClass().getSimpleName(), included, excluded, transitive);
138    }
139}