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.version;
020
021import java.util.Arrays;
022import java.util.Collection;
023
024import org.eclipse.aether.RepositoryException;
025import org.eclipse.aether.collection.DependencyCollectionContext;
026import org.eclipse.aether.collection.VersionFilter;
027
028/**
029 * A version filter that combines multiple version filters into a chain where each filter gets invoked one after the
030 * other, thereby accumulating their filtering effects.
031 */
032public class ChainedVersionFilter implements VersionFilter {
033
034    private final VersionFilter[] filters;
035
036    private final int hashCode;
037
038    /**
039     * Chains the specified version filters.
040     *
041     * @param filter1 the first version filter, may be {@code null}
042     * @param filter2 the second version filter, may be {@code null}
043     * @return the chained version filter or {@code null} if both input filters are {@code null}
044     */
045    public static VersionFilter newInstance(VersionFilter filter1, VersionFilter filter2) {
046        if (filter1 == null) {
047            return filter2;
048        }
049        if (filter2 == null) {
050            return filter1;
051        }
052        return new ChainedVersionFilter(new VersionFilter[] {filter1, filter2});
053    }
054
055    /**
056     * Chains the specified version filters.
057     *
058     * @param filters the version filters to chain, must not be {@code null} or contain {@code null}
059     * @return the chained version filter or {@code null} if the input array is empty
060     */
061    public static VersionFilter newInstance(VersionFilter... filters) {
062        if (filters.length <= 1) {
063            if (filters.length <= 0) {
064                return null;
065            }
066            return filters[0];
067        }
068        return new ChainedVersionFilter(filters.clone());
069    }
070
071    /**
072     * Chains the specified version filters.
073     *
074     * @param filters the version filters to chain, must not be {@code null} or contain {@code null}
075     * @return the chained version filter or {@code null} if the input collection is empty
076     */
077    public static VersionFilter newInstance(Collection<? extends VersionFilter> filters) {
078        if (filters.size() <= 1) {
079            if (filters.isEmpty()) {
080                return null;
081            }
082            return filters.iterator().next();
083        }
084        return new ChainedVersionFilter(filters.toArray(new VersionFilter[0]));
085    }
086
087    private ChainedVersionFilter(VersionFilter[] filters) {
088        this.filters = filters;
089        this.hashCode = getClass().hashCode() * 31 + Arrays.hashCode(filters);
090    }
091
092    @Override
093    public void filterVersions(VersionFilterContext context) throws RepositoryException {
094        for (int i = 0, n = filters.length; i < n && context.getCount() > 0; i++) {
095            filters[i].filterVersions(context);
096        }
097    }
098
099    @Override
100    public VersionFilter deriveChildFilter(DependencyCollectionContext context) {
101        VersionFilter[] children = null;
102        int removed = 0;
103        for (int i = 0, n = filters.length; i < n; i++) {
104            VersionFilter child = filters[i].deriveChildFilter(context);
105            if (children != null) {
106                children[i - removed] = child;
107            } else if (child != filters[i]) {
108                children = new VersionFilter[filters.length];
109                System.arraycopy(filters, 0, children, 0, i);
110                children[i - removed] = child;
111            }
112            if (child == null) {
113                removed++;
114            }
115        }
116        if (children == null) {
117            return this;
118        }
119        if (removed > 0) {
120            int count = filters.length - removed;
121            if (count <= 0) {
122                return null;
123            }
124            if (count == 1) {
125                return children[0];
126            }
127            VersionFilter[] tmp = new VersionFilter[count];
128            System.arraycopy(children, 0, tmp, 0, count);
129            children = tmp;
130        }
131        return new ChainedVersionFilter(children);
132    }
133
134    @Override
135    public boolean equals(Object obj) {
136        if (this == obj) {
137            return true;
138        } else if (null == obj || !getClass().equals(obj.getClass())) {
139            return false;
140        }
141
142        ChainedVersionFilter that = (ChainedVersionFilter) obj;
143        return Arrays.equals(filters, that.filters);
144    }
145
146    @Override
147    public int hashCode() {
148        return hashCode;
149    }
150}