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