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