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.internal.impl.scope;
020
021import java.util.Collection;
022import java.util.Objects;
023
024import org.eclipse.aether.collection.DependencyCollectionContext;
025import org.eclipse.aether.collection.DependencySelector;
026import org.eclipse.aether.graph.Dependency;
027
028import static java.util.Objects.requireNonNull;
029
030/**
031 * A dependency selector that filters transitive dependencies based on their scope. It is configurable from which level
032 * applies, as it depend on "as project" and "as dependency" use cases.
033 * <p>
034 * <em>Important note:</em> equals/hashCode must factor in starting state, as instances of this class
035 * (potentially differentially configured) are used now in session, but are kept in a set.
036 * <p>
037 * <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 implements DependencySelector {
043    /**
044     * This enables "legacy" mode (proper): in Resolver 1.x "transitive" state depended on the presence or
045     * absence of the root node.
046     */
047    public static ScopeDependencySelector legacy(Collection<String> included, Collection<String> excluded) {
048        return new ScopeDependencySelector(
049                Objects.hash(true, 0, 1, Integer.MAX_VALUE, included, excluded),
050                true,
051                0,
052                1,
053                Integer.MAX_VALUE,
054                included,
055                excluded);
056    }
057
058    /**
059     * Selects dependencies by scope always (from root).
060     */
061    public static ScopeDependencySelector fromRoot(Collection<String> included, Collection<String> excluded) {
062        return from(1, included, excluded);
063    }
064
065    /**
066     * Selects dependencies by scope starting from direct dependencies.
067     */
068    public static ScopeDependencySelector fromDirect(Collection<String> included, Collection<String> excluded) {
069        return from(2, included, excluded);
070    }
071
072    /**
073     * Selects dependencies by scope starting from given depth (1=root, 2=direct, 3=transitives of direct ones...).
074     */
075    public static ScopeDependencySelector from(
076            int applyFrom, Collection<String> included, Collection<String> excluded) {
077        return fromTo(applyFrom, Integer.MAX_VALUE, included, excluded);
078    }
079
080    /**
081     * Selects dependencies by scope starting from given depth (1=root, 2=direct, 3=transitives of direct ones...) to
082     * given depth.
083     */
084    public static ScopeDependencySelector fromTo(
085            int applyFrom, int applyTo, Collection<String> included, Collection<String> excluded) {
086        if (applyFrom < 1) {
087            throw new IllegalArgumentException("applyFrom must be non-zero and positive");
088        }
089        if (applyFrom > applyTo) {
090            throw new IllegalArgumentException("applyTo must be greater or equal than applyFrom");
091        }
092        return new ScopeDependencySelector(
093                Objects.hash(false, 0, applyFrom, applyTo, included, excluded),
094                false,
095                0,
096                applyFrom,
097                applyTo,
098                included,
099                excluded);
100    }
101
102    private final int seed;
103    private final boolean shiftIfRootNull;
104    private final int depth;
105    private final int applyFrom;
106    private final int applyTo;
107    private final Collection<String> included;
108    private final Collection<String> excluded;
109
110    private ScopeDependencySelector(
111            int seed,
112            boolean shiftIfRootNull,
113            int depth,
114            int applyFrom,
115            int applyTo,
116            Collection<String> included,
117            Collection<String> excluded) {
118        this.seed = seed;
119        this.shiftIfRootNull = shiftIfRootNull;
120        this.depth = depth;
121        this.applyFrom = applyFrom;
122        this.applyTo = applyTo;
123        this.included = included;
124        this.excluded = excluded;
125    }
126
127    @Override
128    public boolean selectDependency(Dependency dependency) {
129        requireNonNull(dependency, "dependency cannot be null");
130        if (depth < applyFrom || depth > applyTo) {
131            return true;
132        }
133
134        String scope = dependency.getScope();
135        return (included == null || included.contains(scope)) && (excluded == null || !excluded.contains(scope));
136    }
137
138    @Override
139    public DependencySelector deriveChildSelector(DependencyCollectionContext context) {
140        requireNonNull(context, "context cannot be null");
141        if (depth == 0 && shiftIfRootNull && context.getDependency() == null) {
142            return new ScopeDependencySelector(
143                    seed, shiftIfRootNull, depth + 1, applyFrom + 1, applyTo, included, excluded);
144        } else {
145            return new ScopeDependencySelector(
146                    seed, shiftIfRootNull, depth + 1, applyFrom, applyTo, included, excluded);
147        }
148    }
149
150    @Override
151    public boolean equals(Object obj) {
152        if (this == obj) {
153            return true;
154        } else if (null == obj || !getClass().equals(obj.getClass())) {
155            return false;
156        }
157
158        ScopeDependencySelector that = (ScopeDependencySelector) obj;
159        return seed == that.seed
160                && shiftIfRootNull == that.shiftIfRootNull
161                && depth == that.depth
162                && applyFrom == that.applyFrom
163                && applyTo == that.applyTo
164                && Objects.equals(included, that.included)
165                && Objects.equals(excluded, that.excluded);
166    }
167
168    @Override
169    public int hashCode() {
170        int hash = 17;
171        hash = hash * 31 + seed;
172        hash = hash * 31 + (shiftIfRootNull ? 0 : 1);
173        hash = hash * 31 + depth;
174        hash = hash * 31 + applyFrom;
175        hash = hash * 31 + applyTo;
176        hash = hash * 31 + (included != null ? included.hashCode() : 0);
177        hash = hash * 31 + (excluded != null ? excluded.hashCode() : 0);
178        return hash;
179    }
180
181    @Override
182    public String toString() {
183        return String.format(
184                "%s(included: %s, excluded: %s, applied: %s)",
185                getClass().getSimpleName(), included, excluded, depth >= applyFrom);
186    }
187}