blob: 1a7d936757e45e992e59d9c68847dd3c31fe113f [file] [log] [blame]
package org.apache.maven.shared.artifact.filter;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.codehaus.plexus.logging.Logger;
/**
* TODO: include in maven-artifact in future
*
* @author <a href="mailto:brett@apache.org">Brett Porter</a>
* @see StrictPatternIncludesArtifactFilter
*/
public class PatternIncludesArtifactFilter
implements ArtifactFilter, StatisticsReportingArtifactFilter
{
private final List<String> positivePatterns;
private final List<String> negativePatterns;
private final boolean actTransitively;
private final Set<String> patternsTriggered = new HashSet<String>();
private final List<String> filteredArtifactIds = new ArrayList<String>();
public PatternIncludesArtifactFilter( final Collection<String> patterns )
{
this( patterns, false );
}
// for backwards compatibility
public PatternIncludesArtifactFilter( final List<String> patterns )
{
this( patterns, false );
}
public PatternIncludesArtifactFilter( final Collection<String> patterns, final boolean actTransitively )
{
this.actTransitively = actTransitively;
final List<String> pos = new ArrayList<String>();
final List<String> neg = new ArrayList<String>();
if ( ( patterns != null ) && !patterns.isEmpty() )
{
for ( String pattern : patterns )
{
if ( pattern.startsWith( "!" ) )
{
neg.add( pattern.substring( 1 ) );
}
else
{
pos.add( pattern );
}
}
}
positivePatterns = pos;
negativePatterns = neg;
}
public boolean include( final Artifact artifact )
{
final boolean shouldInclude = patternMatches( artifact );
if ( !shouldInclude )
{
addFilteredArtifactId( artifact.getId() );
}
return shouldInclude;
}
protected boolean patternMatches( final Artifact artifact )
{
return ( positiveMatch( artifact ) == Boolean.TRUE ) || ( negativeMatch( artifact ) == Boolean.FALSE );
}
protected void addFilteredArtifactId( final String artifactId )
{
filteredArtifactIds.add( artifactId );
}
private Boolean negativeMatch( final Artifact artifact )
{
if ( ( negativePatterns == null ) || negativePatterns.isEmpty() )
{
return null;
}
else
{
return match( artifact, negativePatterns );
}
}
protected Boolean positiveMatch( final Artifact artifact )
{
if ( ( positivePatterns == null ) || positivePatterns.isEmpty() )
{
return null;
}
else
{
return match( artifact, positivePatterns );
}
}
private boolean match( final Artifact artifact, final List<String> patterns )
{
final String shortId = ArtifactUtils.versionlessKey( artifact );
final String id = artifact.getDependencyConflictId();
final String wholeId = artifact.getId();
if ( matchAgainst( wholeId, patterns, false ) )
{
return true;
}
if ( matchAgainst( id, patterns, false ) )
{
return true;
}
if ( matchAgainst( shortId, patterns, false ) )
{
return true;
}
if ( actTransitively )
{
final List<String> depTrail = artifact.getDependencyTrail();
if ( ( depTrail != null ) && depTrail.size() > 1 )
{
for ( String trailItem : depTrail )
{
if ( matchAgainst( trailItem, patterns, true ) )
{
return true;
}
}
}
}
return false;
}
private boolean matchAgainst( final String value, final List<String> patterns, final boolean regionMatch )
{
final String[] tokens = value.split( ":" );
for ( String pattern : patterns )
{
final String[] patternTokens = pattern.split( ":" );
// fail immediately if pattern tokens outnumber tokens to match
boolean matched = ( patternTokens.length <= tokens.length );
for ( int i = 0; matched && i < patternTokens.length; i++ )
{
matched = matches( tokens[i], patternTokens[i] );
}
// case of starting '*' like '*:jar:*'
// This really only matches from the end instead.....
if ( !matched && patternTokens.length < tokens.length && isFirstPatternWildcard( patternTokens ) )
{
matched = true;
int tokenOffset = tokens.length - patternTokens.length;
for ( int i = 0; matched && i < patternTokens.length; i++ )
{
matched = matches( tokens[i + tokenOffset], patternTokens[i] );
}
}
if ( matched )
{
patternsTriggered.add( pattern );
return true;
}
if ( regionMatch && value.contains( pattern ) )
{
patternsTriggered.add( pattern );
return true;
}
}
return false;
}
private boolean isFirstPatternWildcard( String[] patternTokens )
{
return patternTokens.length > 0 && "*".equals( patternTokens[0] );
}
/**
* Gets whether the specified token matches the specified pattern segment.
*
* @param token the token to check
* @param pattern the pattern segment to match, as defined above
* @return <code>true</code> if the specified token is matched by the specified pattern segment
*/
private boolean matches( final String token, final String pattern )
{
boolean matches;
// support full wildcard and implied wildcard
if ( "*".equals( pattern ) || pattern.length() == 0 )
{
matches = true;
}
// support contains wildcard
else if ( pattern.startsWith( "*" ) && pattern.endsWith( "*" ) )
{
final String contains = pattern.substring( 1, pattern.length() - 1 );
matches = ( token.contains( contains ) );
}
// support leading wildcard
else if ( pattern.startsWith( "*" ) )
{
final String suffix = pattern.substring( 1, pattern.length() );
matches = token.endsWith( suffix );
}
// support trailing wildcard
else if ( pattern.endsWith( "*" ) )
{
final String prefix = pattern.substring( 0, pattern.length() - 1 );
matches = token.startsWith( prefix );
}
// support wildcards in the middle of a pattern segment
else if ( pattern.indexOf( '*' ) > -1 )
{
String[] parts = pattern.split( "\\*" );
int lastPartEnd = -1;
boolean match = true;
for ( String part : parts )
{
int idx = token.indexOf( part );
if ( idx <= lastPartEnd )
{
match = false;
break;
}
lastPartEnd = idx + part.length();
}
matches = match;
}
// support versions range
else if ( pattern.startsWith( "[" ) || pattern.startsWith( "(" ) )
{
matches = isVersionIncludedInRange( token, pattern );
}
// support exact match
else
{
matches = token.equals( pattern );
}
return matches;
}
private boolean isVersionIncludedInRange( final String version, final String range )
{
try
{
return VersionRange.createFromVersionSpec( range ).containsVersion( new DefaultArtifactVersion( version ) );
}
catch ( final InvalidVersionSpecificationException e )
{
return false;
}
}
public void reportMissedCriteria( final Logger logger )
{
// if there are no patterns, there is nothing to report.
if ( !positivePatterns.isEmpty() || !negativePatterns.isEmpty() )
{
final List<String> missed = new ArrayList<String>();
missed.addAll( positivePatterns );
missed.addAll( negativePatterns );
missed.removeAll( patternsTriggered );
if ( !missed.isEmpty() && logger.isWarnEnabled() )
{
final StringBuilder buffer = new StringBuilder();
buffer.append( "The following patterns were never triggered in this " );
buffer.append( getFilterDescription() );
buffer.append( ':' );
for ( String pattern : missed )
{
buffer.append( "\no \'" ).append( pattern ).append( "\'" );
}
buffer.append( "\n" );
logger.warn( buffer.toString() );
}
}
}
@Override
public String toString()
{
return "Includes filter:" + getPatternsAsString();
}
protected String getPatternsAsString()
{
final StringBuilder buffer = new StringBuilder();
for ( String pattern : positivePatterns )
{
buffer.append( "\no \'" ).append( pattern ).append( "\'" );
}
return buffer.toString();
}
protected String getFilterDescription()
{
return "artifact inclusion filter";
}
public void reportFilteredArtifacts( final Logger logger )
{
if ( !filteredArtifactIds.isEmpty() && logger.isDebugEnabled() )
{
final StringBuilder buffer =
new StringBuilder( "The following artifacts were removed by this " + getFilterDescription() + ": " );
for ( String artifactId : filteredArtifactIds )
{
buffer.append( '\n' ).append( artifactId );
}
logger.debug( buffer.toString() );
}
}
public boolean hasMissedCriteria()
{
// if there are no patterns, there is nothing to report.
if ( !positivePatterns.isEmpty() || !negativePatterns.isEmpty() )
{
final List<String> missed = new ArrayList<String>();
missed.addAll( positivePatterns );
missed.addAll( negativePatterns );
missed.removeAll( patternsTriggered );
return !missed.isEmpty();
}
return false;
}
}