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
033import static java.util.Objects.requireNonNull;
034
035/**
036 * A dependency selector that applies exclusions based on artifact coordinates.
037 * 
038 * @see Dependency#getExclusions()
039 */
040public final class ExclusionDependencySelector
041    implements DependencySelector
042{
043
044    // sorted and dupe-free array, faster to iterate than LinkedHashSet
045    private final Exclusion[] exclusions;
046
047    private int hashCode;
048
049    /**
050     * Creates a new selector without any exclusions.
051     */
052    public ExclusionDependencySelector()
053    {
054        this.exclusions = new Exclusion[0];
055    }
056
057    /**
058     * Creates a new selector with the specified exclusions.
059     * 
060     * @param exclusions The exclusions, may be {@code null}.
061     */
062    public ExclusionDependencySelector( Collection<Exclusion> exclusions )
063    {
064        if ( exclusions != null && !exclusions.isEmpty() )
065        {
066            TreeSet<Exclusion> sorted = new TreeSet<>( ExclusionComparator.INSTANCE );
067            sorted.addAll( exclusions );
068            this.exclusions = sorted.toArray( new Exclusion[0] );
069        }
070        else
071        {
072            this.exclusions = new Exclusion[0];
073        }
074    }
075
076    private ExclusionDependencySelector( Exclusion[] exclusions )
077    {
078        this.exclusions = exclusions;
079    }
080
081    public boolean selectDependency( Dependency dependency )
082    {
083        requireNonNull( dependency, "dependency cannot be null" );
084        Artifact artifact = dependency.getArtifact();
085        for ( Exclusion exclusion : exclusions )
086        {
087            if ( matches( exclusion, artifact ) )
088            {
089                return false;
090            }
091        }
092        return true;
093    }
094
095    private boolean matches( Exclusion exclusion, Artifact artifact )
096    {
097        if ( !matches( exclusion.getArtifactId(), artifact.getArtifactId() ) )
098        {
099            return false;
100        }
101        if ( !matches( exclusion.getGroupId(), artifact.getGroupId() ) )
102        {
103            return false;
104        }
105        if ( !matches( exclusion.getExtension(), artifact.getExtension() ) )
106        {
107            return false;
108        }
109        if ( !matches( exclusion.getClassifier(), artifact.getClassifier() ) )
110        {
111            return false;
112        }
113        return true;
114    }
115
116    private boolean matches( String pattern, String value )
117    {
118        return "*".equals( pattern ) || pattern.equals( value );
119    }
120
121    public DependencySelector deriveChildSelector( DependencyCollectionContext context )
122    {
123        requireNonNull( context, "context cannot be null" );
124        Dependency dependency = context.getDependency();
125        Collection<Exclusion> exclusions = ( dependency != null ) ? dependency.getExclusions() : null;
126        if ( exclusions == null || exclusions.isEmpty() )
127        {
128            return this;
129        }
130
131        Exclusion[] merged = this.exclusions;
132        int count = merged.length;
133        for ( Exclusion exclusion : exclusions )
134        {
135            int index = Arrays.binarySearch( merged, exclusion, ExclusionComparator.INSTANCE );
136            if ( index < 0 )
137            {
138                index = -( index + 1 );
139                if ( count >= merged.length )
140                {
141                    Exclusion[] tmp = new Exclusion[merged.length + exclusions.size()];
142                    System.arraycopy( merged, 0, tmp, 0, index );
143                    tmp[index] = exclusion;
144                    System.arraycopy( merged, index, tmp, index + 1, count - index );
145                    merged = tmp;
146                }
147                else
148                {
149                    System.arraycopy( merged, index, merged, index + 1, count - index );
150                    merged[index] = exclusion;
151                }
152                count++;
153            }
154        }
155        if ( merged == this.exclusions )
156        {
157            return this;
158        }
159        if ( merged.length != count )
160        {
161            Exclusion[] tmp = new Exclusion[count];
162            System.arraycopy( merged, 0, tmp, 0, count );
163            merged = tmp;
164        }
165
166        return new ExclusionDependencySelector( merged );
167    }
168
169    @Override
170    public boolean equals( Object obj )
171    {
172        if ( this == obj )
173        {
174            return true;
175        }
176        else if ( null == obj || !getClass().equals( obj.getClass() ) )
177        {
178            return false;
179        }
180
181        ExclusionDependencySelector that = (ExclusionDependencySelector) obj;
182        return Arrays.equals( exclusions, that.exclusions );
183    }
184
185    @Override
186    public int hashCode()
187    {
188        if ( hashCode == 0 )
189        {
190            int hash = getClass().hashCode();
191            hash = hash * 31 + Arrays.hashCode( exclusions );
192            hashCode = hash;
193        }
194        return hashCode;
195    }
196
197    @Override
198    public String toString()
199    {
200        StringBuilder builder = new StringBuilder().append( this.getClass().getSimpleName() ).append( '(' );
201        for ( int i = 0; i < this.exclusions.length; i++ )
202        {
203            builder.append( this.exclusions[i] );
204            if ( i < this.exclusions.length - 1 )
205            {
206                builder.append( ", " );
207            }
208        }
209        return builder.append( ')' ).toString();
210    }
211
212    private static class ExclusionComparator
213        implements Comparator<Exclusion>
214    {
215
216        static final ExclusionComparator INSTANCE = new ExclusionComparator();
217
218        public int compare( Exclusion e1, Exclusion e2 )
219        {
220            if ( e1 == null )
221            {
222                return ( e2 == null ) ? 0 : 1;
223            }
224            else if ( e2 == null )
225            {
226                return -1;
227            }
228            int rel = e1.getArtifactId().compareTo( e2.getArtifactId() );
229            if ( rel == 0 )
230            {
231                rel = e1.getGroupId().compareTo( e2.getGroupId() );
232                if ( rel == 0 )
233                {
234                    rel = e1.getExtension().compareTo( e2.getExtension() );
235                    if ( rel == 0 )
236                    {
237                        rel = e1.getClassifier().compareTo( e2.getClassifier() );
238                    }
239                }
240            }
241            return rel;
242        }
243
244    }
245
246}