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