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 final 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        this.hashCode = 17 * 31 + this.selectors.hashCode();
057    }
058
059    /**
060     * Creates a new selector from the specified selectors.
061     *
062     * @param selectors the selectors to combine, may be {@code null} but must not contain {@code null} elements
063     */
064    public AndDependencySelector(Collection<? extends DependencySelector> selectors) {
065        if (selectors != null && !selectors.isEmpty()) {
066            this.selectors = new LinkedHashSet<>(selectors);
067        } else {
068            this.selectors = Collections.emptySet();
069        }
070        this.hashCode = 17 * 31 + this.selectors.hashCode();
071    }
072
073    private AndDependencySelector(Set<DependencySelector> selectors) {
074        if (selectors != null && !selectors.isEmpty()) {
075            this.selectors = selectors;
076        } else {
077            this.selectors = Collections.emptySet();
078        }
079        this.hashCode = 17 * 31 + this.selectors.hashCode();
080    }
081
082    /**
083     * Creates a new selector from the specified selectors.
084     *
085     * @param selector1 the first selector to combine, may be {@code null}
086     * @param selector2 the second selector to combine, may be {@code null}
087     * @return the combined selector or {@code null} if both selectors were {@code null}
088     */
089    public static DependencySelector newInstance(DependencySelector selector1, DependencySelector selector2) {
090        if (selector1 == null) {
091            return selector2;
092        } else if (selector2 == null || selector2.equals(selector1)) {
093            return selector1;
094        }
095        return new AndDependencySelector(selector1, selector2);
096    }
097
098    public boolean selectDependency(Dependency dependency) {
099        requireNonNull(dependency, "dependency cannot be null");
100        for (DependencySelector selector : selectors) {
101            if (!selector.selectDependency(dependency)) {
102                return false;
103            }
104        }
105        return true;
106    }
107
108    public DependencySelector deriveChildSelector(DependencyCollectionContext context) {
109        requireNonNull(context, "context cannot be null");
110        int seen = 0;
111        Set<DependencySelector> childSelectors = null;
112
113        for (DependencySelector selector : selectors) {
114            DependencySelector childSelector = selector.deriveChildSelector(context);
115            if (childSelectors != null) {
116                if (childSelector != null) {
117                    childSelectors.add(childSelector);
118                }
119            } else if (selector != childSelector) {
120                childSelectors = new LinkedHashSet<>();
121                if (seen > 0) {
122                    for (DependencySelector s : selectors) {
123                        if (childSelectors.size() >= seen) {
124                            break;
125                        }
126                        childSelectors.add(s);
127                    }
128                }
129                if (childSelector != null) {
130                    childSelectors.add(childSelector);
131                }
132            } else {
133                seen++;
134            }
135        }
136
137        if (childSelectors == null) {
138            return this;
139        }
140        if (childSelectors.size() <= 1) {
141            if (childSelectors.isEmpty()) {
142                return null;
143            }
144            return childSelectors.iterator().next();
145        }
146        return new AndDependencySelector(childSelectors);
147    }
148
149    @Override
150    public boolean equals(Object obj) {
151        if (this == obj) {
152            return true;
153        } else if (null == obj || !getClass().equals(obj.getClass())) {
154            return false;
155        }
156
157        AndDependencySelector that = (AndDependencySelector) obj;
158        return selectors.equals(that.selectors);
159    }
160
161    @Override
162    public int hashCode() {
163        return hashCode;
164    }
165
166    @Override
167    public String toString() {
168        StringBuilder builder =
169                new StringBuilder().append(this.getClass().getSimpleName()).append('(');
170        Iterator<? extends DependencySelector> iterator = this.selectors.iterator();
171        while (iterator.hasNext()) {
172            final DependencySelector selector = iterator.next();
173            builder.append(selector.toString());
174            if (iterator.hasNext()) // not last
175            {
176                builder.append(" && ");
177            }
178        }
179        return builder.append(')').toString();
180    }
181}