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    public boolean traverseDependency(Dependency dependency) {
096        requireNonNull(dependency, "dependency cannot be null");
097        for (DependencyTraverser traverser : traversers) {
098            if (!traverser.traverseDependency(dependency)) {
099                return false;
100            }
101        }
102        return true;
103    }
104
105    public DependencyTraverser deriveChildTraverser(DependencyCollectionContext context) {
106        requireNonNull(context, "context cannot be null");
107        int seen = 0;
108        Set<DependencyTraverser> childTraversers = null;
109
110        for (DependencyTraverser traverser : traversers) {
111            DependencyTraverser childTraverser = traverser.deriveChildTraverser(context);
112            if (childTraversers != null) {
113                if (childTraverser != null) {
114                    childTraversers.add(childTraverser);
115                }
116            } else if (traverser != childTraverser) {
117                childTraversers = new LinkedHashSet<>();
118                if (seen > 0) {
119                    for (DependencyTraverser s : traversers) {
120                        if (childTraversers.size() >= seen) {
121                            break;
122                        }
123                        childTraversers.add(s);
124                    }
125                }
126                if (childTraverser != null) {
127                    childTraversers.add(childTraverser);
128                }
129            } else {
130                seen++;
131            }
132        }
133
134        if (childTraversers == null) {
135            return this;
136        }
137        if (childTraversers.size() <= 1) {
138            if (childTraversers.isEmpty()) {
139                return null;
140            }
141            return childTraversers.iterator().next();
142        }
143        return new AndDependencyTraverser(childTraversers);
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        AndDependencyTraverser that = (AndDependencyTraverser) obj;
155        return traversers.equals(that.traversers);
156    }
157
158    @Override
159    public int hashCode() {
160        if (hashCode == 0) {
161            int hash = 17;
162            hash = hash * 31 + traversers.hashCode();
163            hashCode = hash;
164        }
165        return hashCode;
166    }
167}