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.traverser;
020
021import java.util.Arrays;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.LinkedHashSet;
025import java.util.Set;
026
027import org.eclipse.aether.collection.DependencyCollectionContext;
028import org.eclipse.aether.collection.DependencyTraverser;
029import org.eclipse.aether.graph.Dependency;
030
031import static java.util.Objects.requireNonNull;
032
033/**
034 * A dependency traverser that combines zero or more other traversers using a logical {@code AND}. The resulting
035 * traverser enables processing of child dependencies if and only if all constituent traversers request traversal.
036 */
037public final class AndDependencyTraverser implements DependencyTraverser {
038
039    private final Set<? extends DependencyTraverser> traversers;
040
041    private final int hashCode;
042
043    /**
044     * Creates a new traverser from the specified traversers. Prefer
045     * {@link #newInstance(DependencyTraverser, DependencyTraverser)} if any of the input traversers might be
046     * {@code null}.
047     *
048     * @param traversers the traversers to combine, may be {@code null} but must not contain {@code null} elements
049     */
050    public AndDependencyTraverser(DependencyTraverser... traversers) {
051        if (traversers != null && traversers.length > 0) {
052            this.traversers = new LinkedHashSet<>(Arrays.asList(traversers));
053        } else {
054            this.traversers = Collections.emptySet();
055        }
056        this.hashCode = 17 * 31 + this.traversers.hashCode();
057    }
058
059    /**
060     * Creates a new traverser from the specified traversers.
061     *
062     * @param traversers the traversers to combine, may be {@code null} but must not contain {@code null} elements
063     */
064    public AndDependencyTraverser(Collection<? extends DependencyTraverser> traversers) {
065        if (traversers != null && !traversers.isEmpty()) {
066            this.traversers = new LinkedHashSet<>(traversers);
067        } else {
068            this.traversers = Collections.emptySet();
069        }
070        this.hashCode = 17 * 31 + this.traversers.hashCode();
071    }
072
073    private AndDependencyTraverser(Set<DependencyTraverser> traversers) {
074        if (traversers != null && !traversers.isEmpty()) {
075            this.traversers = traversers;
076        } else {
077            this.traversers = Collections.emptySet();
078        }
079        this.hashCode = 17 * 31 + this.traversers.hashCode();
080    }
081
082    /**
083     * Creates a new traverser from the specified traversers.
084     *
085     * @param traverser1 the first traverser to combine, may be {@code null}
086     * @param traverser2 the second traverser to combine, may be {@code null}
087     * @return the combined traverser or {@code null} if both traversers were {@code null}
088     */
089    public static DependencyTraverser newInstance(DependencyTraverser traverser1, DependencyTraverser traverser2) {
090        if (traverser1 == null) {
091            return traverser2;
092        } else if (traverser2 == null || traverser2.equals(traverser1)) {
093            return traverser1;
094        }
095        return new AndDependencyTraverser(traverser1, traverser2);
096    }
097
098    @Override
099    public boolean traverseDependency(Dependency dependency) {
100        requireNonNull(dependency, "dependency cannot be null");
101        for (DependencyTraverser traverser : traversers) {
102            if (!traverser.traverseDependency(dependency)) {
103                return false;
104            }
105        }
106        return true;
107    }
108
109    @Override
110    public DependencyTraverser deriveChildTraverser(DependencyCollectionContext context) {
111        requireNonNull(context, "context cannot be null");
112        int seen = 0;
113        Set<DependencyTraverser> childTraversers = null;
114
115        for (DependencyTraverser traverser : traversers) {
116            DependencyTraverser childTraverser = traverser.deriveChildTraverser(context);
117            if (childTraversers != null) {
118                if (childTraverser != null) {
119                    childTraversers.add(childTraverser);
120                }
121            } else if (traverser != childTraverser) {
122                childTraversers = new LinkedHashSet<>();
123                if (seen > 0) {
124                    for (DependencyTraverser s : traversers) {
125                        if (childTraversers.size() >= seen) {
126                            break;
127                        }
128                        childTraversers.add(s);
129                    }
130                }
131                if (childTraverser != null) {
132                    childTraversers.add(childTraverser);
133                }
134            } else {
135                seen++;
136            }
137        }
138
139        if (childTraversers == null) {
140            return this;
141        }
142        if (childTraversers.size() <= 1) {
143            if (childTraversers.isEmpty()) {
144                return null;
145            }
146            return childTraversers.iterator().next();
147        }
148        return new AndDependencyTraverser(childTraversers);
149    }
150
151    @Override
152    public boolean equals(Object obj) {
153        if (this == obj) {
154            return true;
155        } else if (null == obj || !getClass().equals(obj.getClass())) {
156            return false;
157        }
158
159        AndDependencyTraverser that = (AndDependencyTraverser) obj;
160        return traversers.equals(that.traversers);
161    }
162
163    @Override
164    public int hashCode() {
165        return hashCode;
166    }
167}