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.Arrays;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.Iterator;
025import java.util.LinkedHashSet;
026import java.util.Set;
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 combines zero or more other selectors using a logical {@code AND}. The resulting selector
036 * selects a given dependency if and only if all constituent selectors do so.
037 */
038public final class AndDependencySelector implements DependencySelector {
039
040    private final Set<? extends DependencySelector> selectors;
041
042    private int hashCode;
043
044    /**
045     * Creates a new selector from the specified selectors. Prefer
046     * {@link #newInstance(DependencySelector, DependencySelector)} if any of the input selectors might be {@code null}.
047     *
048     * @param selectors The selectors to combine, may be {@code null} but must not contain {@code null} elements.
049     */
050    public AndDependencySelector(DependencySelector... selectors) {
051        if (selectors != null && selectors.length > 0) {
052            this.selectors = new LinkedHashSet<>(Arrays.asList(selectors));
053        } else {
054            this.selectors = Collections.emptySet();
055        }
056    }
057
058    /**
059     * Creates a new selector from the specified selectors.
060     *
061     * @param selectors The selectors to combine, may be {@code null} but must not contain {@code null} elements.
062     */
063    public AndDependencySelector(Collection<? extends DependencySelector> selectors) {
064        if (selectors != null && !selectors.isEmpty()) {
065            this.selectors = new LinkedHashSet<>(selectors);
066        } else {
067            this.selectors = Collections.emptySet();
068        }
069    }
070
071    private AndDependencySelector(Set<DependencySelector> selectors) {
072        if (selectors != null && !selectors.isEmpty()) {
073            this.selectors = selectors;
074        } else {
075            this.selectors = Collections.emptySet();
076        }
077    }
078
079    /**
080     * Creates a new selector from the specified selectors.
081     *
082     * @param selector1 The first selector to combine, may be {@code null}.
083     * @param selector2 The second selector to combine, may be {@code null}.
084     * @return The combined selector or {@code null} if both selectors were {@code null}.
085     */
086    public static DependencySelector newInstance(DependencySelector selector1, DependencySelector selector2) {
087        if (selector1 == null) {
088            return selector2;
089        } else if (selector2 == null || selector2.equals(selector1)) {
090            return selector1;
091        }
092        return new AndDependencySelector(selector1, selector2);
093    }
094
095    public boolean selectDependency(Dependency dependency) {
096        requireNonNull(dependency, "dependency cannot be null");
097        for (DependencySelector selector : selectors) {
098            if (!selector.selectDependency(dependency)) {
099                return false;
100            }
101        }
102        return true;
103    }
104
105    public DependencySelector deriveChildSelector(DependencyCollectionContext context) {
106        requireNonNull(context, "context cannot be null");
107        int seen = 0;
108        Set<DependencySelector> childSelectors = null;
109
110        for (DependencySelector selector : selectors) {
111            DependencySelector childSelector = selector.deriveChildSelector(context);
112            if (childSelectors != null) {
113                if (childSelector != null) {
114                    childSelectors.add(childSelector);
115                }
116            } else if (selector != childSelector) {
117                childSelectors = new LinkedHashSet<>();
118                if (seen > 0) {
119                    for (DependencySelector s : selectors) {
120                        if (childSelectors.size() >= seen) {
121                            break;
122                        }
123                        childSelectors.add(s);
124                    }
125                }
126                if (childSelector != null) {
127                    childSelectors.add(childSelector);
128                }
129            } else {
130                seen++;
131            }
132        }
133
134        if (childSelectors == null) {
135            return this;
136        }
137        if (childSelectors.size() <= 1) {
138            if (childSelectors.isEmpty()) {
139                return null;
140            }
141            return childSelectors.iterator().next();
142        }
143        return new AndDependencySelector(childSelectors);
144    }
145
146    @Override
147    public boolean equals(Object obj) {
148        if (this == obj) {
149            return true;
150        } else if (null == obj || !getClass().equals(obj.getClass())) {
151            return false;
152        }
153
154        AndDependencySelector that = (AndDependencySelector) obj;
155        return selectors.equals(that.selectors);
156    }
157
158    @Override
159    public int hashCode() {
160        if (hashCode == 0) {
161            int hash = 17;
162            hash = hash * 31 + selectors.hashCode();
163            hashCode = hash;
164        }
165        return hashCode;
166    }
167
168    @Override
169    public String toString() {
170        StringBuilder builder =
171                new StringBuilder().append(this.getClass().getSimpleName()).append('(');
172        Iterator<? extends DependencySelector> iterator = this.selectors.iterator();
173        while (iterator.hasNext()) {
174            final DependencySelector selector = iterator.next();
175            builder.append(selector.toString());
176            if (iterator.hasNext()) // not last
177            {
178                builder.append(" && ");
179            }
180        }
181        return builder.append(')').toString();
182    }
183}