View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.util.graph.selector;
20  
21  import java.util.Arrays;
22  import java.util.Collection;
23  import java.util.Comparator;
24  import java.util.TreeSet;
25  
26  import org.eclipse.aether.artifact.Artifact;
27  import org.eclipse.aether.collection.DependencyCollectionContext;
28  import org.eclipse.aether.collection.DependencySelector;
29  import org.eclipse.aether.graph.Dependency;
30  import org.eclipse.aether.graph.Exclusion;
31  
32  import static java.util.Objects.requireNonNull;
33  
34  /**
35   * A dependency selector that applies exclusions based on artifact coordinates.
36   *
37   * @see Dependency#getExclusions()
38   */
39  public final class ExclusionDependencySelector implements DependencySelector {
40  
41      // sorted and dupe-free array, faster to iterate than LinkedHashSet
42      private final Exclusion[] exclusions;
43  
44      private final int hashCode;
45  
46      /**
47       * Creates a new selector without any exclusions.
48       */
49      public ExclusionDependencySelector() {
50          this.exclusions = new Exclusion[0];
51          this.hashCode = getClass().hashCode() * 31 + Arrays.hashCode(exclusions);
52      }
53  
54      /**
55       * Creates a new selector with the specified exclusions.
56       *
57       * @param exclusions the exclusions, may be {@code null}
58       */
59      public ExclusionDependencySelector(Collection<Exclusion> exclusions) {
60          if (exclusions != null && !exclusions.isEmpty()) {
61              TreeSet<Exclusion> sorted = new TreeSet<>(ExclusionComparator.INSTANCE);
62              sorted.addAll(exclusions);
63              this.exclusions = sorted.toArray(new Exclusion[0]);
64          } else {
65              this.exclusions = new Exclusion[0];
66          }
67          this.hashCode = getClass().hashCode() * 31 + Arrays.hashCode(this.exclusions);
68      }
69  
70      private ExclusionDependencySelector(Exclusion[] exclusions) {
71          this.exclusions = exclusions;
72          this.hashCode = getClass().hashCode() * 31 + Arrays.hashCode(exclusions);
73      }
74  
75      public boolean selectDependency(Dependency dependency) {
76          requireNonNull(dependency, "dependency cannot be null");
77          Artifact artifact = dependency.getArtifact();
78          for (Exclusion exclusion : exclusions) {
79              if (matches(exclusion, artifact)) {
80                  return false;
81              }
82          }
83          return true;
84      }
85  
86      private boolean matches(Exclusion exclusion, Artifact artifact) {
87          if (!matches(exclusion.getArtifactId(), artifact.getArtifactId())) {
88              return false;
89          }
90          if (!matches(exclusion.getGroupId(), artifact.getGroupId())) {
91              return false;
92          }
93          if (!matches(exclusion.getExtension(), artifact.getExtension())) {
94              return false;
95          }
96          return matches(exclusion.getClassifier(), artifact.getClassifier());
97      }
98  
99      private boolean matches(String pattern, String value) {
100         return "*".equals(pattern) || pattern.equals(value);
101     }
102 
103     public DependencySelector deriveChildSelector(DependencyCollectionContext context) {
104         requireNonNull(context, "context cannot be null");
105         Dependency dependency = context.getDependency();
106         Collection<Exclusion> exclusions = (dependency != null) ? dependency.getExclusions() : null;
107         if (exclusions == null || exclusions.isEmpty()) {
108             return this;
109         }
110 
111         Exclusion[] merged = this.exclusions;
112         int count = merged.length;
113         for (Exclusion exclusion : exclusions) {
114             int index = Arrays.binarySearch(merged, exclusion, ExclusionComparator.INSTANCE);
115             if (index < 0) {
116                 index = -(index + 1);
117                 if (count >= merged.length) {
118                     Exclusion[] tmp = new Exclusion[merged.length + exclusions.size()];
119                     System.arraycopy(merged, 0, tmp, 0, index);
120                     tmp[index] = exclusion;
121                     System.arraycopy(merged, index, tmp, index + 1, count - index);
122                     merged = tmp;
123                 } else {
124                     System.arraycopy(merged, index, merged, index + 1, count - index);
125                     merged[index] = exclusion;
126                 }
127                 count++;
128             }
129         }
130         if (merged == this.exclusions) {
131             return this;
132         }
133         if (merged.length != count) {
134             Exclusion[] tmp = new Exclusion[count];
135             System.arraycopy(merged, 0, tmp, 0, count);
136             merged = tmp;
137         }
138 
139         return new ExclusionDependencySelector(merged);
140     }
141 
142     @Override
143     public boolean equals(Object obj) {
144         if (this == obj) {
145             return true;
146         } else if (null == obj || !getClass().equals(obj.getClass())) {
147             return false;
148         }
149 
150         ExclusionDependencySelector that = (ExclusionDependencySelector) obj;
151         return Arrays.equals(exclusions, that.exclusions);
152     }
153 
154     @Override
155     public int hashCode() {
156         return hashCode;
157     }
158 
159     @Override
160     public String toString() {
161         StringBuilder builder =
162                 new StringBuilder().append(this.getClass().getSimpleName()).append('(');
163         for (int i = 0; i < this.exclusions.length; i++) {
164             builder.append(this.exclusions[i]);
165             if (i < this.exclusions.length - 1) {
166                 builder.append(", ");
167             }
168         }
169         return builder.append(')').toString();
170     }
171 
172     private static class ExclusionComparator implements Comparator<Exclusion> {
173 
174         static final ExclusionComparator INSTANCE = new ExclusionComparator();
175 
176         public int compare(Exclusion e1, Exclusion e2) {
177             if (e1 == null) {
178                 return (e2 == null) ? 0 : 1;
179             } else if (e2 == null) {
180                 return -1;
181             }
182             int rel = e1.getArtifactId().compareTo(e2.getArtifactId());
183             if (rel == 0) {
184                 rel = e1.getGroupId().compareTo(e2.getGroupId());
185                 if (rel == 0) {
186                     rel = e1.getExtension().compareTo(e2.getExtension());
187                     if (rel == 0) {
188                         rel = e1.getClassifier().compareTo(e2.getClassifier());
189                     }
190                 }
191             }
192             return rel;
193         }
194     }
195 }