001package org.eclipse.aether.util.graph.selector;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 * 
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 * 
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.util.Arrays;
023import java.util.Collection;
024import java.util.Comparator;
025import java.util.TreeSet;
026
027import org.eclipse.aether.artifact.Artifact;
028import org.eclipse.aether.collection.DependencyCollectionContext;
029import org.eclipse.aether.collection.DependencySelector;
030import org.eclipse.aether.graph.Dependency;
031import org.eclipse.aether.graph.Exclusion;
032
033/**
034 * A dependency selector that applies exclusions based on artifact coordinates.
035 * 
036 * @see Dependency#getExclusions()
037 */
038public final class ExclusionDependencySelector
039    implements DependencySelector
040{
041
042    // sorted and dupe-free array, faster to iterate than LinkedHashSet
043    private final Exclusion[] exclusions;
044
045    private int hashCode;
046
047    /**
048     * Creates a new selector without any exclusions.
049     */
050    public ExclusionDependencySelector()
051    {
052        this.exclusions = new Exclusion[0];
053    }
054
055    /**
056     * Creates a new selector with the specified exclusions.
057     * 
058     * @param exclusions The exclusions, may be {@code null}.
059     */
060    public ExclusionDependencySelector( Collection<Exclusion> exclusions )
061    {
062        if ( exclusions != null && !exclusions.isEmpty() )
063        {
064            TreeSet<Exclusion> sorted = new TreeSet<Exclusion>( ExclusionComparator.INSTANCE );
065            sorted.addAll( exclusions );
066            this.exclusions = sorted.toArray( new Exclusion[sorted.size()] );
067        }
068        else
069        {
070            this.exclusions = new Exclusion[0];
071        }
072    }
073
074    private ExclusionDependencySelector( Exclusion[] exclusions )
075    {
076        this.exclusions = exclusions;
077    }
078
079    public boolean selectDependency( Dependency dependency )
080    {
081        Artifact artifact = dependency.getArtifact();
082        for ( Exclusion exclusion : exclusions )
083        {
084            if ( matches( exclusion, artifact ) )
085            {
086                return false;
087            }
088        }
089        return true;
090    }
091
092    private boolean matches( Exclusion exclusion, Artifact artifact )
093    {
094        if ( !matches( exclusion.getArtifactId(), artifact.getArtifactId() ) )
095        {
096            return false;
097        }
098        if ( !matches( exclusion.getGroupId(), artifact.getGroupId() ) )
099        {
100            return false;
101        }
102        if ( !matches( exclusion.getExtension(), artifact.getExtension() ) )
103        {
104            return false;
105        }
106        if ( !matches( exclusion.getClassifier(), artifact.getClassifier() ) )
107        {
108            return false;
109        }
110        return true;
111    }
112
113    private boolean matches( String pattern, String value )
114    {
115        return "*".equals( pattern ) || pattern.equals( value );
116    }
117
118    public DependencySelector deriveChildSelector( DependencyCollectionContext context )
119    {
120        Dependency dependency = context.getDependency();
121        Collection<Exclusion> exclusions = ( dependency != null ) ? dependency.getExclusions() : null;
122        if ( exclusions == null || exclusions.isEmpty() )
123        {
124            return this;
125        }
126
127        Exclusion[] merged = this.exclusions;
128        int count = merged.length;
129        for ( Exclusion exclusion : exclusions )
130        {
131            int index = Arrays.binarySearch( merged, exclusion, ExclusionComparator.INSTANCE );
132            if ( index < 0 )
133            {
134                index = -( index + 1 );
135                if ( count >= merged.length )
136                {
137                    Exclusion[] tmp = new Exclusion[merged.length + exclusions.size()];
138                    System.arraycopy( merged, 0, tmp, 0, index );
139                    tmp[index] = exclusion;
140                    System.arraycopy( merged, index, tmp, index + 1, count - index );
141                    merged = tmp;
142                }
143                else
144                {
145                    System.arraycopy( merged, index, merged, index + 1, count - index );
146                    merged[index] = exclusion;
147                }
148                count++;
149            }
150        }
151        if ( merged == this.exclusions )
152        {
153            return this;
154        }
155        if ( merged.length != count )
156        {
157            Exclusion[] tmp = new Exclusion[count];
158            System.arraycopy( merged, 0, tmp, 0, count );
159            merged = tmp;
160        }
161
162        return new ExclusionDependencySelector( merged );
163    }
164
165    @Override
166    public boolean equals( Object obj )
167    {
168        if ( this == obj )
169        {
170            return true;
171        }
172        else if ( null == obj || !getClass().equals( obj.getClass() ) )
173        {
174            return false;
175        }
176
177        ExclusionDependencySelector that = (ExclusionDependencySelector) obj;
178        return Arrays.equals( exclusions, that.exclusions );
179    }
180
181    @Override
182    public int hashCode()
183    {
184        if ( hashCode == 0 )
185        {
186            int hash = getClass().hashCode();
187            hash = hash * 31 + Arrays.hashCode( exclusions );
188            hashCode = hash;
189        }
190        return hashCode;
191    }
192
193    private static class ExclusionComparator
194        implements Comparator<Exclusion>
195    {
196
197        static final ExclusionComparator INSTANCE = new ExclusionComparator();
198
199        public int compare( Exclusion e1, Exclusion e2 )
200        {
201            if ( e1 == null )
202            {
203                return ( e2 == null ) ? 0 : 1;
204            }
205            else if ( e2 == null )
206            {
207                return -1;
208            }
209            int rel = e1.getArtifactId().compareTo( e2.getArtifactId() );
210            if ( rel == 0 )
211            {
212                rel = e1.getGroupId().compareTo( e2.getGroupId() );
213                if ( rel == 0 )
214                {
215                    rel = e1.getExtension().compareTo( e2.getExtension() );
216                    if ( rel == 0 )
217                    {
218                        rel = e1.getClassifier().compareTo( e2.getClassifier() );
219                    }
220                }
221            }
222            return rel;
223        }
224
225    }
226
227}