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.apache.maven.shared.artifact.filter;
20  
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Set;
26  
27  import org.apache.maven.artifact.Artifact;
28  import org.apache.maven.artifact.ArtifactUtils;
29  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
30  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
31  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
32  import org.apache.maven.artifact.versioning.VersionRange;
33  import org.slf4j.Logger;
34  
35  /**
36   * TODO: include in maven-artifact in future
37   *
38   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
39   * @see StrictPatternIncludesArtifactFilter
40   */
41  public class OldPatternIncludesArtifactFilter implements ArtifactFilter, StatisticsReportingArtifactFilter {
42      private final List<String> positivePatterns;
43  
44      private final List<String> negativePatterns;
45  
46      private final boolean actTransitively;
47  
48      private final Set<String> patternsTriggered = new HashSet<>();
49  
50      private final List<String> filteredArtifactIds = new ArrayList<>();
51  
52      /**
53       * @param patterns The pattern to be used.
54       */
55      public OldPatternIncludesArtifactFilter(final Collection<String> patterns) {
56          this(patterns, false);
57      }
58  
59      /**
60       * @param patterns The pattern to be used.
61       * @param actTransitively transitive yes/no.
62       */
63      public OldPatternIncludesArtifactFilter(final Collection<String> patterns, final boolean actTransitively) {
64          this.actTransitively = actTransitively;
65          final List<String> pos = new ArrayList<>();
66          final List<String> neg = new ArrayList<>();
67          if (patterns != null && !patterns.isEmpty()) {
68              for (String pattern : patterns) {
69                  if (pattern.startsWith("!")) {
70                      neg.add(pattern.substring(1));
71                  } else {
72                      pos.add(pattern);
73                  }
74              }
75          }
76  
77          positivePatterns = pos;
78          negativePatterns = neg;
79      }
80  
81      /** {@inheritDoc} */
82      public boolean include(final Artifact artifact) {
83          final boolean shouldInclude = patternMatches(artifact);
84  
85          if (!shouldInclude) {
86              addFilteredArtifactId(artifact.getId());
87          }
88  
89          return shouldInclude;
90      }
91  
92      /**
93       * @param artifact to check for.
94       * @return true if the match is true false otherwise.
95       */
96      protected boolean patternMatches(final Artifact artifact) {
97          return positiveMatch(artifact) == Boolean.TRUE || negativeMatch(artifact) == Boolean.FALSE;
98      }
99  
100     /**
101      * @param artifactId add artifact to the filtered artifacts list.
102      */
103     protected void addFilteredArtifactId(final String artifactId) {
104         filteredArtifactIds.add(artifactId);
105     }
106 
107     private Boolean negativeMatch(final Artifact artifact) {
108         if (negativePatterns == null || negativePatterns.isEmpty()) {
109             return null;
110         } else {
111             return match(artifact, negativePatterns);
112         }
113     }
114 
115     /**
116      * @param artifact check for positive match.
117      * @return true/false.
118      */
119     protected Boolean positiveMatch(final Artifact artifact) {
120         if (positivePatterns == null || positivePatterns.isEmpty()) {
121             return null;
122         } else {
123             return match(artifact, positivePatterns);
124         }
125     }
126 
127     private boolean match(final Artifact artifact, final List<String> patterns) {
128         final String shortId = ArtifactUtils.versionlessKey(artifact);
129         final String id = artifact.getDependencyConflictId();
130         final String wholeId = artifact.getId();
131 
132         if (matchAgainst(wholeId, patterns, false)) {
133             return true;
134         }
135 
136         if (matchAgainst(id, patterns, false)) {
137             return true;
138         }
139 
140         if (matchAgainst(shortId, patterns, false)) {
141             return true;
142         }
143 
144         if (actTransitively) {
145             final List<String> depTrail = artifact.getDependencyTrail();
146 
147             if (depTrail != null && depTrail.size() > 1) {
148                 for (String trailItem : depTrail) {
149                     if (matchAgainst(trailItem, patterns, true)) {
150                         return true;
151                     }
152                 }
153             }
154         }
155 
156         return false;
157     }
158 
159     private boolean matchAgainst(final String value, final List<String> patterns, final boolean regionMatch) {
160         final String[] tokens = value.split(":");
161         for (String pattern : patterns) {
162             String[] patternTokens = pattern.split(":");
163 
164             if (patternTokens.length == 5 && tokens.length < 5) {
165                 // 4th element is the classifier
166                 if (!"*".equals(patternTokens[3])) {
167                     // classifier required, cannot be a match
168                     return false;
169                 }
170                 patternTokens = new String[] {patternTokens[0], patternTokens[1], patternTokens[2], patternTokens[4]};
171             }
172 
173             // fail immediately if pattern tokens outnumber tokens to match
174             boolean matched = patternTokens.length <= tokens.length;
175 
176             for (int i = 0; matched && i < patternTokens.length; i++) {
177                 matched = matches(tokens[i], patternTokens[i]);
178             }
179 
180             // case of starting '*' like '*:jar:*'
181             // This really only matches from the end instead.....
182             if (!matched && patternTokens.length < tokens.length && isFirstPatternWildcard(patternTokens)) {
183                 matched = true;
184                 int tokenOffset = tokens.length - patternTokens.length;
185                 for (int i = 0; matched && i < patternTokens.length; i++) {
186                     matched = matches(tokens[i + tokenOffset], patternTokens[i]);
187                 }
188             }
189 
190             if (matched) {
191                 patternsTriggered.add(pattern);
192                 return true;
193             }
194 
195             if (regionMatch && value.contains(pattern)) {
196                 patternsTriggered.add(pattern);
197                 return true;
198             }
199         }
200         return false;
201     }
202 
203     private boolean isFirstPatternWildcard(String[] patternTokens) {
204         return patternTokens.length > 0 && "*".equals(patternTokens[0]);
205     }
206 
207     /**
208      * Gets whether the specified token matches the specified pattern segment.
209      *
210      * @param token the token to check
211      * @param pattern the pattern segment to match, as defined above
212      * @return <code>true</code> if the specified token is matched by the specified pattern segment
213      */
214     private boolean matches(final String token, final String pattern) {
215         boolean matches;
216 
217         // support full wildcard and implied wildcard
218         if ("*".equals(pattern) || pattern.length() == 0) {
219             matches = true;
220         }
221         // support contains wildcard
222         else if (pattern.startsWith("*") && pattern.endsWith("*")) {
223             final String contains = pattern.substring(1, pattern.length() - 1);
224 
225             matches = token.contains(contains);
226         }
227         // support leading wildcard
228         else if (pattern.startsWith("*")) {
229             final String suffix = pattern.substring(1);
230 
231             matches = token.endsWith(suffix);
232         }
233         // support trailing wildcard
234         else if (pattern.endsWith("*")) {
235             final String prefix = pattern.substring(0, pattern.length() - 1);
236 
237             matches = token.startsWith(prefix);
238         }
239         // support wildcards in the middle of a pattern segment
240         else if (pattern.indexOf('*') > -1) {
241             String[] parts = pattern.split("\\*");
242             int lastPartEnd = -1;
243             boolean match = true;
244 
245             for (String part : parts) {
246                 int idx = token.indexOf(part);
247                 if (idx <= lastPartEnd) {
248                     match = false;
249                     break;
250                 }
251 
252                 lastPartEnd = idx + part.length();
253             }
254 
255             matches = match;
256         }
257         // support versions range
258         else if (pattern.startsWith("[") || pattern.startsWith("(")) {
259             matches = isVersionIncludedInRange(token, pattern);
260         }
261         // support exact match
262         else {
263             matches = token.equals(pattern);
264         }
265 
266         return matches;
267     }
268 
269     private boolean isVersionIncludedInRange(final String version, final String range) {
270         try {
271             return VersionRange.createFromVersionSpec(range).containsVersion(new DefaultArtifactVersion(version));
272         } catch (final InvalidVersionSpecificationException e) {
273             return false;
274         }
275     }
276 
277     /** {@inheritDoc} */
278     public void reportMissedCriteria(final Logger logger) {
279         // if there are no patterns, there is nothing to report.
280         if (!positivePatterns.isEmpty() || !negativePatterns.isEmpty()) {
281             final List<String> missed = new ArrayList<>();
282             missed.addAll(positivePatterns);
283             missed.addAll(negativePatterns);
284 
285             missed.removeAll(patternsTriggered);
286 
287             if (!missed.isEmpty() && logger.isWarnEnabled()) {
288                 final StringBuilder buffer = new StringBuilder();
289 
290                 buffer.append("The following patterns were never triggered in this ");
291                 buffer.append(getFilterDescription());
292                 buffer.append(':');
293 
294                 for (String pattern : missed) {
295                     buffer.append("\no  '").append(pattern).append("'");
296                 }
297 
298                 buffer.append("\n");
299 
300                 logger.warn(buffer.toString());
301             }
302         }
303     }
304 
305     @Override
306     public String toString() {
307         return "Includes filter:" + getPatternsAsString();
308     }
309 
310     /**
311      * @return pattern as a string.
312      */
313     protected String getPatternsAsString() {
314         final StringBuilder buffer = new StringBuilder();
315         for (String pattern : positivePatterns) {
316             buffer.append("\no '").append(pattern).append("'");
317         }
318 
319         return buffer.toString();
320     }
321 
322     /**
323      * @return description.
324      */
325     protected String getFilterDescription() {
326         return "artifact inclusion filter";
327     }
328 
329     /** {@inheritDoc} */
330     public void reportFilteredArtifacts(final Logger logger) {
331         if (!filteredArtifactIds.isEmpty() && logger.isDebugEnabled()) {
332             final StringBuilder buffer =
333                     new StringBuilder("The following artifacts were removed by this " + getFilterDescription() + ": ");
334 
335             for (String artifactId : filteredArtifactIds) {
336                 buffer.append('\n').append(artifactId);
337             }
338 
339             logger.debug(buffer.toString());
340         }
341     }
342 
343     /** {@inheritDoc} */
344     public boolean hasMissedCriteria() {
345         // if there are no patterns, there is nothing to report.
346         if (!positivePatterns.isEmpty() || !negativePatterns.isEmpty()) {
347             final List<String> missed = new ArrayList<>();
348             missed.addAll(positivePatterns);
349             missed.addAll(negativePatterns);
350 
351             missed.removeAll(patternsTriggered);
352 
353             return !missed.isEmpty();
354         }
355 
356         return false;
357     }
358 }