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 final class ChainedVersionFilter implements VersionFilter {
033
034    private final VersionFilter[] filters;
035
036    private 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    }
090
091    public void filterVersions(VersionFilterContext context) throws RepositoryException {
092        for (int i = 0, n = filters.length; i < n && context.getCount() > 0; i++) {
093            filters[i].filterVersions(context);
094        }
095    }
096
097    public VersionFilter deriveChildFilter(DependencyCollectionContext context) {
098        VersionFilter[] children = null;
099        int removed = 0;
100        for (int i = 0, n = filters.length; i < n; i++) {
101            VersionFilter child = filters[i].deriveChildFilter(context);
102            if (children != null) {
103                children[i - removed] = child;
104            } else if (child != filters[i]) {
105                children = new VersionFilter[filters.length];
106                System.arraycopy(filters, 0, children, 0, i);
107                children[i - removed] = child;
108            }
109            if (child == null) {
110                removed++;
111            }
112        }
113        if (children == null) {
114            return this;
115        }
116        if (removed > 0) {
117            int count = filters.length - removed;
118            if (count <= 0) {
119                return null;
120            }
121            if (count == 1) {
122                return children[0];
123            }
124            VersionFilter[] tmp = new VersionFilter[count];
125            System.arraycopy(children, 0, tmp, 0, count);
126            children = tmp;
127        }
128        return new ChainedVersionFilter(children);
129    }
130
131    @Override
132    public boolean equals(Object obj) {
133        if (this == obj) {
134            return true;
135        } else if (null == obj || !getClass().equals(obj.getClass())) {
136            return false;
137        }
138
139        ChainedVersionFilter that = (ChainedVersionFilter) obj;
140        return Arrays.equals(filters, that.filters);
141    }
142
143    @Override
144    public int hashCode() {
145        if (hashCode == 0) {
146            int hash = getClass().hashCode();
147            hash = hash * 31 + Arrays.hashCode(filters);
148            hashCode = hash;
149        }
150        return hashCode;
151    }
152}