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