This is an automated email from the ASF dual-hosted git repository. slachiewicz pushed a commit to branch MSHARED-975-speed in repository https://gitbox.apache.org/repos/asf/maven-common-artifact-filters.git
commit 3272baaaed449f6cda94f8a9ee52291c289549c6 Author: Guillaume Nodet <gno...@gmail.com> AuthorDate: Wed Nov 18 20:52:42 2020 +0100 [MSHARED-975] Big speed improvements for patterns that do not contain any wildcard This can be a huge boost for dependency sets that contains lots of artifact ids like in Apache Camel project Closes #15 --- pom.xml | 12 + .../filter/PatternExcludesArtifactFilter.java | 2 +- .../filter/PatternIncludesArtifactFilter.java | 947 ++++++++++++++++----- .../filter/AbstractPatternArtifactFilterTest.java | 105 ++- .../filter/OldPatternIncludesArtifactFilter.java} | 18 +- .../artifact/filter/PatternFilterPerfTest.java | 131 +++ 6 files changed, 967 insertions(+), 248 deletions(-) diff --git a/pom.xml b/pom.xml index 6a40045..4350b2a 100644 --- a/pom.xml +++ b/pom.xml @@ -157,5 +157,17 @@ </exclusions> <scope>test</scope> </dependency> + <dependency> + <groupId>org.openjdk.jmh</groupId> + <artifactId>jmh-core</artifactId> + <version>1.26</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.openjdk.jmh</groupId> + <artifactId>jmh-generator-annprocess</artifactId> + <version>1.26</version> + <scope>test</scope> + </dependency> </dependencies> </project> diff --git a/src/main/java/org/apache/maven/shared/artifact/filter/PatternExcludesArtifactFilter.java b/src/main/java/org/apache/maven/shared/artifact/filter/PatternExcludesArtifactFilter.java index b0d7a1e..a55cb2a 100644 --- a/src/main/java/org/apache/maven/shared/artifact/filter/PatternExcludesArtifactFilter.java +++ b/src/main/java/org/apache/maven/shared/artifact/filter/PatternExcludesArtifactFilter.java @@ -56,7 +56,7 @@ public class PatternExcludesArtifactFilter if ( !shouldInclude ) { - addFilteredArtifactId( artifact.getId() ); + addFilteredArtifact( artifact ); } return shouldInclude; diff --git a/src/main/java/org/apache/maven/shared/artifact/filter/PatternIncludesArtifactFilter.java b/src/main/java/org/apache/maven/shared/artifact/filter/PatternIncludesArtifactFilter.java index c9aad45..6cfb68f 100644 --- a/src/main/java/org/apache/maven/shared/artifact/filter/PatternIncludesArtifactFilter.java +++ b/src/main/java/org/apache/maven/shared/artifact/filter/PatternIncludesArtifactFilter.java @@ -20,13 +20,18 @@ package org.apache.maven.shared.artifact.filter; */ import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; +import java.util.Objects; 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; @@ -42,15 +47,20 @@ import org.codehaus.plexus.logging.Logger; public class PatternIncludesArtifactFilter implements ArtifactFilter, StatisticsReportingArtifactFilter { - private final List<String> positivePatterns; + /** Holds the set of compiled patterns */ + private final Set<Pattern> patterns; - private final List<String> negativePatterns; + /** Holds simple patterns: those that can use direct matching */ + private final Map<Integer, Map<String, Pattern>> simplePatterns; + /** Whether the dependency trail should be checked */ private final boolean actTransitively; - private final Set<String> patternsTriggered = new HashSet<>(); + /** Set of patterns that have been triggered */ + private final Set<Pattern> patternsTriggered = new HashSet<>(); - private final List<String> filteredArtifactIds = new ArrayList<>(); + /** Set of artifacts that have been filtered out */ + private final List<Artifact> filteredArtifact = new ArrayList<>(); /** * @param patterns The pattern to be used. @@ -67,25 +77,55 @@ public class PatternIncludesArtifactFilter public PatternIncludesArtifactFilter( final Collection<String> patterns, final boolean actTransitively ) { this.actTransitively = actTransitively; - final List<String> pos = new ArrayList<>(); - final List<String> neg = new ArrayList<>(); + final Set<Pattern> pat = new LinkedHashSet<>(); + Map<Integer, Map<String, Pattern>> simplePat = null; + boolean allPos = true; if ( patterns != null && !patterns.isEmpty() ) { for ( String pattern : patterns ) { - if ( pattern.startsWith( "!" ) ) - { - neg.add( pattern.substring( 1 ) ); - } - else + + Pattern p = compile( pattern ); + allPos &= !( p instanceof NegativePattern ); + pat.add( p ); + } + } + // If all patterns are positive, we can check for simple patterns + // Simple patterns will match the first tokens and contain no wildcards, + // so we can put them in a map and check them using a simple map lookup. + if ( allPos ) + { + for ( Iterator<Pattern> it = pat.iterator(); it.hasNext(); ) + { + Pattern p = it.next(); + String peq = p.translateEquals(); + if ( peq != null ) { - pos.add( pattern ); + int nb = 0; + for ( char ch : peq.toCharArray() ) + { + if ( ch == ':' ) + { + nb++; + } + } + if ( simplePat == null ) + { + simplePat = new HashMap<>(); + } + Map<String, Pattern> peqm = simplePat.get( nb ); + if ( peqm == null ) + { + peqm = new HashMap<>(); + simplePat.put( nb, peqm ); + } + peqm.put( peq, p ); + it.remove(); } } } - - positivePatterns = pos; - negativePatterns = neg; + this.simplePatterns = simplePat; + this.patterns = pat; } /** {@inheritDoc} */ @@ -95,7 +135,7 @@ public class PatternIncludesArtifactFilter if ( !shouldInclude ) { - addFilteredArtifactId( artifact.getId() ); + addFilteredArtifact( artifact ); } return shouldInclude; @@ -107,322 +147,831 @@ public class PatternIncludesArtifactFilter */ protected boolean patternMatches( final Artifact artifact ) { - return positiveMatch( artifact ) == Boolean.TRUE || negativeMatch( artifact ) == Boolean.FALSE; + // Check if the main artifact matches + char[][] artifactGatvCharArray = new char[][] { + emptyOrChars( artifact.getGroupId() ), + emptyOrChars( artifact.getArtifactId() ), + emptyOrChars( artifact.getType() ), + emptyOrChars( artifact.getBaseVersion() ) + }; + Boolean match = match( artifactGatvCharArray ); + if ( match != null ) + { + return match; + } + + if ( actTransitively ) + { + final List<String> depTrail = artifact.getDependencyTrail(); + + if ( depTrail != null && depTrail.size() > 1 ) + { + for ( String trailItem : depTrail ) + { + char[][] depGatvCharArray = tokenizeAndSplit( trailItem ); + match = match( depGatvCharArray ); + if ( match != null ) + { + return match; + } + } + } + } + + return false; + } + + private Boolean match( char[][] gatvCharArray ) + { + if ( simplePatterns != null && simplePatterns.size() > 0 ) + { + // We add the parts one by one to the builder + StringBuilder sb = new StringBuilder(); + for ( int i = 0; i < 4; i++ ) + { + if ( i > 0 ) + { + sb.append( ":" ); + } + sb.append( gatvCharArray[i] ); + Map<String, Pattern> map = simplePatterns.get( i ); + if ( map != null ) + { + // Check if one of the pattern matches + Pattern p = map.get( sb.toString() ); + if ( p != null ) + { + patternsTriggered.add( p ); + return true; + } + } + } + } + // Check all other patterns + for ( Pattern pattern : patterns ) + { + if ( pattern.matches( gatvCharArray ) ) + { + patternsTriggered.add( pattern ); + return !( pattern instanceof NegativePattern ); + } + } + + return null; } /** - * @param artifactId add artifact to the filtered artifacts list. + * @param artifact add artifact to the filtered artifacts list. */ - protected void addFilteredArtifactId( final String artifactId ) + protected void addFilteredArtifact( final Artifact artifact ) { - filteredArtifactIds.add( artifactId ); + filteredArtifact.add( artifact ); } - private Boolean negativeMatch( final Artifact artifact ) + /** {@inheritDoc} */ + public void reportMissedCriteria( final Logger logger ) { - if ( negativePatterns == null || negativePatterns.isEmpty() ) + // if there are no patterns, there is nothing to report. + if ( !patterns.isEmpty() ) { - return null; + final List<Pattern> missed = new ArrayList<>( patterns ); + 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 ( Pattern pattern : missed ) + { + buffer.append( "\no '" ).append( pattern ).append( "'" ); + } + + buffer.append( "\n" ); + + logger.warn( buffer.toString() ); + } } - else + } + + @Override + public String toString() + { + return "Includes filter:" + getPatternsAsString(); + } + + /** + * @return pattern as a string. + */ + protected String getPatternsAsString() + { + final StringBuilder buffer = new StringBuilder(); + for ( Pattern pattern : patterns ) { - return match( artifact, negativePatterns ); + buffer.append( "\no '" ).append( pattern ).append( "'" ); } + + return buffer.toString(); } /** - * @param artifact check for positive match. - * @return true/false. + * @return description. */ - protected Boolean positiveMatch( final Artifact artifact ) + protected String getFilterDescription() + { + return "artifact inclusion filter"; + } + + /** {@inheritDoc} */ + public void reportFilteredArtifacts( final Logger logger ) { - if ( positivePatterns == null || positivePatterns.isEmpty() ) + if ( !filteredArtifact.isEmpty() && logger.isDebugEnabled() ) { - return null; + final StringBuilder buffer = + new StringBuilder( "The following artifacts were removed by this " + getFilterDescription() + ": " ); + + for ( Artifact artifactId : filteredArtifact ) + { + buffer.append( '\n' ).append( artifactId.getId() ); + } + + logger.debug( buffer.toString() ); } - else + } + + /** {@inheritDoc} */ + public boolean hasMissedCriteria() + { + // if there are no patterns, there is nothing to report. + if ( !patterns.isEmpty() ) { - return match( artifact, positivePatterns ); + final List<Pattern> missed = new ArrayList<>( patterns ); + missed.removeAll( patternsTriggered ); + + return !missed.isEmpty(); } + + return false; } - private boolean match( final Artifact artifact, final List<String> patterns ) + private static final char[] EMPTY = new char[0]; + + private static final char[] ANY = new char[] { '*' }; + + static char[] emptyOrChars( String str ) { - final String shortId = ArtifactUtils.versionlessKey( artifact ); - final String id = artifact.getDependencyConflictId(); - final String wholeId = artifact.getId(); + return str != null && str.length() > 0 ? str.toCharArray() : EMPTY; + } - if ( matchAgainst( wholeId, patterns, false ) ) - { - return true; - } + static char[] anyOrChars( char[] str ) + { + return str.length > 1 || ( str.length == 1 && str[0] != '*' ) ? str : ANY; + } - if ( matchAgainst( id, patterns, false ) ) + static char[][] tokenizeAndSplit( String pattern ) + { + String[] stokens = pattern.split( ":" ); + char[][] tokens = new char[ stokens.length ][]; + for ( int i = 0; i < stokens.length; i++ ) { - return true; + tokens[i] = emptyOrChars( stokens[i] ); } + return tokens; + } - if ( matchAgainst( shortId, patterns, false ) ) + @SuppressWarnings( "InnerAssignment" ) + static boolean match( char[] patArr, char[] strArr, boolean isVersion ) + { + int patIdxStart = 0; + int patIdxEnd = patArr.length - 1; + int strIdxStart = 0; + int strIdxEnd = strArr.length - 1; + char ch; + + boolean containsStar = false; + for ( char aPatArr : patArr ) { - return true; + if ( aPatArr == '*' ) + { + containsStar = true; + break; + } } - if ( actTransitively ) + if ( !containsStar ) { - final List<String> depTrail = artifact.getDependencyTrail(); - - if ( depTrail != null && depTrail.size() > 1 ) + if ( isVersion && ( patArr[0] == '[' || patArr[0] == '(' ) ) { - for ( String trailItem : depTrail ) + return isVersionIncludedInRange( String.valueOf( strArr ), String.valueOf( patArr ) ); + } + // No '*'s, so we make a shortcut + if ( patIdxEnd != strIdxEnd ) + { + return false; // Pattern and string do not have the same size + } + for ( int i = 0; i <= patIdxEnd; i++ ) + { + ch = patArr[i]; + if ( ch != '?' && ch != strArr[i] ) { - if ( matchAgainst( trailItem, patterns, true ) ) - { - return true; - } + return false; // Character mismatch } } + return true; // String matches against pattern } - return false; - } + if ( patIdxEnd == 0 ) + { + return true; // Pattern contains only '*', which matches anything + } - private boolean matchAgainst( final String value, final List<String> patterns, final boolean regionMatch ) - { - final String[] tokens = value.split( ":" ); - for ( String pattern : patterns ) + // Process characters before first star + while ( ( ch = patArr[patIdxStart] ) != '*' && strIdxStart <= strIdxEnd ) { - String[] patternTokens = pattern.split( ":" ); - - if ( patternTokens.length == 5 && tokens.length < 5 ) + if ( ch != '?' && ch != strArr[strIdxStart] ) { - // 4th element is the classifier - if ( !"*".equals( patternTokens[3] ) ) + return false; // Character mismatch + } + patIdxStart++; + strIdxStart++; + } + if ( strIdxStart > strIdxEnd ) + { + // All characters in the string are used. Check if only '*'s are + // left in the pattern. If so, we succeeded. Otherwise failure. + for ( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if ( patArr[i] != '*' ) { - // classifier required, cannot be a match return false; } - patternTokens = new String[] { patternTokens[0], patternTokens[1], patternTokens[2], patternTokens[4] }; } + return true; + } - // fail immediately if pattern tokens outnumber tokens to match - boolean matched = patternTokens.length <= tokens.length; - - for ( int i = 0; matched && i < patternTokens.length; i++ ) + // Process characters after last star + while ( ( ch = patArr[patIdxEnd] ) != '*' && strIdxStart <= strIdxEnd ) + { + if ( ch != '?' && ch != strArr[strIdxEnd] ) { - matched = matches( tokens[i], patternTokens[i] ); + return false; // Character mismatch } - - // case of starting '*' like '*:jar:*' - // This really only matches from the end instead..... - if ( !matched && patternTokens.length < tokens.length && isFirstPatternWildcard( patternTokens ) ) + patIdxEnd--; + strIdxEnd--; + } + if ( strIdxStart > strIdxEnd ) + { + // All characters in the string are used. Check if only '*'s are + // left in the pattern. If so, we succeeded. Otherwise failure. + for ( int i = patIdxStart; i <= patIdxEnd; i++ ) { - matched = true; - int tokenOffset = tokens.length - patternTokens.length; - for ( int i = 0; matched && i < patternTokens.length; i++ ) + if ( patArr[i] != '*' ) { - matched = matches( tokens[i + tokenOffset], patternTokens[i] ); + return false; } } + return true; + } - if ( matched ) + // process pattern between stars. padIdxStart and patIdxEnd point + // always to a '*'. + while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd ) + { + int patIdxTmp = -1; + for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ ) { - patternsTriggered.add( pattern ); - return true; + if ( patArr[i] == '*' ) + { + patIdxTmp = i; + break; + } + } + if ( patIdxTmp == patIdxStart + 1 ) + { + // Two stars next to each other, skip the first one. + patIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = ( patIdxTmp - patIdxStart - 1 ); + int strLength = ( strIdxEnd - strIdxStart + 1 ); + int foundIdx = -1; + strLoop: for ( int i = 0; i <= strLength - patLength; i++ ) + { + for ( int j = 0; j < patLength; j++ ) + { + ch = patArr[patIdxStart + j + 1]; + if ( ch != '?' && ch != strArr[strIdxStart + i + j] ) + { + continue strLoop; + } + } + + foundIdx = strIdxStart + i; + break; } - if ( regionMatch && value.contains( pattern ) ) + if ( foundIdx == -1 ) { - patternsTriggered.add( pattern ); - return true; + return false; } + patIdxStart = patIdxTmp; + strIdxStart = foundIdx + patLength; } - return false; + // All characters in the string are used. Check if only '*'s are left + // in the pattern. If so, we succeeded. Otherwise failure. + for ( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if ( patArr[i] != '*' ) + { + return false; + } + } + return true; } - private boolean isFirstPatternWildcard( String[] patternTokens ) + static boolean isVersionIncludedInRange( final String version, final String range ) { - 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 ) + try { - matches = true; + return VersionRange.createFromVersionSpec( range ).containsVersion( new DefaultArtifactVersion( version ) ); } - // support contains wildcard - else if ( pattern.startsWith( "*" ) && pattern.endsWith( "*" ) ) + catch ( final InvalidVersionSpecificationException e ) { - final String contains = pattern.substring( 1, pattern.length() - 1 ); - - matches = token.contains( contains ); + return false; } - // support leading wildcard - else if ( pattern.startsWith( "*" ) ) - { - final String suffix = pattern.substring( 1 ); + } - matches = token.endsWith( suffix ); - } - // support trailing wildcard - else if ( pattern.endsWith( "*" ) ) + static Pattern compile( String pattern ) + { + if ( pattern.startsWith( "!" ) ) { - final String prefix = pattern.substring( 0, pattern.length() - 1 ); - - matches = token.startsWith( prefix ); + return new NegativePattern( pattern, compile( pattern.substring( 1 ) ) ); } - // support wildcards in the middle of a pattern segment - else if ( pattern.indexOf( '*' ) > -1 ) + else { - String[] parts = pattern.split( "\\*" ); - int lastPartEnd = -1; - boolean match = true; - - for ( String part : parts ) + char[][] stokens = tokenizeAndSplit( pattern ); + char[][] tokens = new char[ stokens.length ][]; + for ( int i = 0; i < stokens.length; i++ ) + { + tokens[i] = anyOrChars( stokens[i] ); + } + if ( tokens.length > 5 ) + { + throw new IllegalArgumentException( "Invalid pattern: " + pattern ); + } + // we only accept 5 tokens if the classifier = '*' + if ( tokens.length == 5 ) { - int idx = token.indexOf( part ); - if ( idx <= lastPartEnd ) + if ( tokens[3] != ANY ) { - match = false; - break; + throw new IllegalArgumentException( "Invalid pattern: " + pattern ); + } + tokens = new char[][] { tokens[0], tokens[1], tokens[2], tokens[4] }; + } + // + // Check the 4 tokens and build an appropriate Pattern + // Special care needs to be taken if the first or the last part is '*' + // because this allows the '*' to match multiple tokens + // + if ( tokens.length == 1 ) + { + if ( tokens[0] == ANY ) + { + // * + return all( pattern ); + } + else + { + // [pat0] + return match( pattern, tokens[0], 0 ); } - - lastPartEnd = idx + part.length(); } + if ( tokens.length == 2 ) + { + if ( tokens[0] == ANY ) + { + if ( tokens[1] == ANY ) + { + // *:* + return all( pattern ); + } + else + { + // *:[pat1] + return match( pattern, tokens[1], 0, 3 ); + } + } + else + { + if ( tokens[1] == ANY ) + { + // [pat0]:* + return match( pattern, tokens[0], 0 ); + } + else + { + // [pat0]:[pat1] + Pattern m00 = match( tokens[0], 0 ); + Pattern m11 = match( tokens[1], 1 ); + return and( pattern, m00, m11 ); + } + } + } + if ( tokens.length == 3 ) + { + if ( tokens[0] == ANY ) + { + if ( tokens[1] == ANY ) + { + if ( tokens[2] == ANY ) + { + // *:*:* + return all( pattern ); + } + else + { + // *:*:[pat2] + return match( pattern, tokens[2], 2, 3 ); + } + } + else + { + if ( tokens[2] == ANY ) + { + // *:[pat1]:* + return match( pattern, tokens[1], 1, 2 ); + } + else + { + // *:[pat1]:[pat2] + Pattern m11 = match( tokens[1], 1 ); + Pattern m12 = match( tokens[1], 2 ); + Pattern m22 = match( tokens[2], 2 ); + Pattern m23 = match( tokens[2], 3 ); + return or( pattern, and( m11, m22 ), and( m12, m23 ) ); + } + } + } + else + { + if ( tokens[1] == ANY ) + { + if ( tokens[2] == ANY ) + { + // [pat0]:*:* + return match( pattern, tokens[0], 0, 1 ); + } + else + { + // [pat0]:*:[pat2] + Pattern m00 = match( tokens[0], 0 ); + Pattern m223 = match( tokens[2], 2, 3 ); + return and( pattern, m00, m223 ); + } + } + else + { + if ( tokens[2] == ANY ) + { + // [pat0]:[pat1]:* + Pattern m00 = match( tokens[0], 0 ); + Pattern m11 = match( tokens[1], 1 ); + return and( pattern, m00, m11 ); + } + else + { + // [pat0]:[pat1]:[pat2] + Pattern m00 = match( tokens[0], 0 ); + Pattern m11 = match( tokens[1], 1 ); + Pattern m22 = match( tokens[2], 2 ); + return and( pattern, m00, m11, m22 ); + } + } + } + } + if ( tokens.length == 4 ) + { + List<Pattern> patterns = new ArrayList<>(); + for ( int i = 0; i < 4; i++ ) + { + if ( tokens[i] != ANY ) + { + patterns.add( match( tokens[i], i ) ); + } + } + return and( pattern, patterns.toArray( new Pattern[0] ) ); + } + throw new IllegalStateException(); + } + } - matches = match; + /** Creates a positional matching pattern */ + private static Pattern match( String pattern, char[] token, int posVal ) + { + return match( pattern, token, posVal, posVal ); + } + + /** Creates a positional matching pattern */ + private static Pattern match( char[] token, int posVal ) + { + return match( "", token, posVal, posVal ); + } + + /** Creates a positional matching pattern */ + private static Pattern match( String pattern, char[] token, int posMin, int posMax ) + { + boolean hasWildcard = false; + for ( char ch : token ) + { + if ( ch == '*' || ch == '?' ) + { + hasWildcard = true; + break; + } } - // support versions range - else if ( pattern.startsWith( "[" ) || pattern.startsWith( "(" ) ) + if ( hasWildcard || posMax == 3 ) { - matches = isVersionIncludedInRange( token, pattern ); + return new PosPattern( pattern, token, posMin, posMax ); } - // support exact match else { - matches = token.equals( pattern ); + return new EqPattern( pattern, token, posMin, posMax ); } + } - return matches; + /** Creates a positional matching pattern */ + private static Pattern match( char[] token, int posMin, int posMax ) + { + return new PosPattern( "", token, posMin, posMax ); } - private boolean isVersionIncludedInRange( final String version, final String range ) + /** Creates an AND pattern */ + private static Pattern and( String pattern, Pattern... patterns ) { - try + return new AndPattern( pattern, patterns ); + } + + /** Creates an AND pattern */ + private static Pattern and( Pattern... patterns ) + { + return and( "", patterns ); + } + + /** Creates an OR pattern */ + private static Pattern or( String pattern, Pattern... patterns ) + { + return new OrPattern( pattern, patterns ); + } + + /** Creates an OR pattern */ + private static Pattern or( Pattern... patterns ) + { + return or( "", patterns ); + } + + /** Creates a match-all pattern */ + private static Pattern all( String pattern ) + { + return new MatchAllPattern( pattern ); + } + + /** + * Abstract class for patterns + */ + abstract static class Pattern + { + private final String pattern; + + Pattern( String pattern ) { - return VersionRange.createFromVersionSpec( range ).containsVersion( new DefaultArtifactVersion( version ) ); + this.pattern = Objects.requireNonNull( pattern ); } - catch ( final InvalidVersionSpecificationException e ) + + public abstract boolean matches( char[][] parts ); + + /** + * Returns a string containing a fixed artifact gatv coordinates + * or null if the pattern can not be translated. + */ + public String translateEquals() { - return false; + return null; } - } - /** {@inheritDoc} */ - public void reportMissedCriteria( final Logger logger ) - { - // if there are no patterns, there is nothing to report. - if ( !positivePatterns.isEmpty() || !negativePatterns.isEmpty() ) + /** + * Check if the this pattern is a fixed pattern on the specified pos. + */ + protected String translateEquals( int pos ) { - final List<String> missed = new ArrayList<>(); - missed.addAll( positivePatterns ); - missed.addAll( negativePatterns ); + return null; + } - missed.removeAll( patternsTriggered ); + @Override + public String toString() + { + return pattern; + } + } - if ( !missed.isEmpty() && logger.isWarnEnabled() ) - { - final StringBuilder buffer = new StringBuilder(); + /** + * Simple pattern which performs a logical AND between one or more patterns. + */ + static class AndPattern extends Pattern + { + private final Pattern[] patterns; - buffer.append( "The following patterns were never triggered in this " ); - buffer.append( getFilterDescription() ); - buffer.append( ':' ); + AndPattern( String pattern, Pattern[] patterns ) + { + super( pattern ); + this.patterns = patterns; + } - for ( String pattern : missed ) + @Override + public boolean matches( char[][] parts ) + { + for ( Pattern pattern : patterns ) + { + if ( !pattern.matches( parts ) ) { - buffer.append( "\no '" ).append( pattern ).append( "'" ); + return false; } + } + return true; + } - buffer.append( "\n" ); - - logger.warn( buffer.toString() ); + @Override + public String translateEquals() + { + String[] strings = new String[ patterns.length ]; + for ( int i = 0; i < patterns.length; i++ ) + { + strings[i] = patterns[i].translateEquals( i ); + if ( strings[i] == null ) + { + return null; + } } + StringBuilder sb = new StringBuilder(); + for ( int i = 0; i < strings.length; i++ ) + { + if ( i > 0 ) + { + sb.append( ":" ); + } + sb.append( strings[i] ); + } + return sb.toString(); } } - @Override - public String toString() + /** + * Simple pattern which performs a logical OR between one or more patterns. + */ + static class OrPattern extends Pattern { - return "Includes filter:" + getPatternsAsString(); + private final Pattern[] patterns; + + OrPattern( String pattern, Pattern[] patterns ) + { + super( pattern ); + this.patterns = patterns; + } + + @Override + public boolean matches( char[][] parts ) + { + for ( Pattern pattern : patterns ) + { + if ( pattern.matches( parts ) ) + { + return true; + } + } + return false; + } } /** - * @return pattern as a string. + * A positional matching pattern, to check if a token in the gatv coordinates + * having a position between posMin and posMax (both inclusives) can match + * the pattern. */ - protected String getPatternsAsString() + static class PosPattern extends Pattern { - final StringBuilder buffer = new StringBuilder(); - for ( String pattern : positivePatterns ) + private final char[] patternCharArray; + private final int posMin; + private final int posMax; + + PosPattern( String pattern, char[] patternCharArray, int posMin, int posMax ) { - buffer.append( "\no '" ).append( pattern ).append( "'" ); + super( pattern ); + this.patternCharArray = patternCharArray; + this.posMin = posMin; + this.posMax = posMax; } - return buffer.toString(); + @Override + public boolean matches( char[][] parts ) + { + for ( int i = posMin; i <= posMax; i++ ) + { + if ( match( patternCharArray, parts[i], i == 3 ) ) + { + return true; + } + } + return false; + } } /** - * @return description. + * Looks for an exact match in the gatv coordinates between + * posMin and posMax (both inclusives) */ - protected String getFilterDescription() + static class EqPattern extends Pattern { - return "artifact inclusion filter"; - } + private final char[] token; + private final int posMin; + private final int posMax; - /** {@inheritDoc} */ - public void reportFilteredArtifacts( final Logger logger ) - { - if ( !filteredArtifactIds.isEmpty() && logger.isDebugEnabled() ) + EqPattern( String pattern, char[] patternCharArray, int posMin, int posMax ) { - final StringBuilder buffer = - new StringBuilder( "The following artifacts were removed by this " + getFilterDescription() + ": " ); + super( pattern ); + this.token = patternCharArray; + this.posMin = posMin; + this.posMax = posMax; + } - for ( String artifactId : filteredArtifactIds ) + @Override + public boolean matches( char[][] parts ) + { + for ( int i = posMin; i <= posMax; i++ ) { - buffer.append( '\n' ).append( artifactId ); + if ( Arrays.equals( token, parts[i] ) ) + { + return true; + } } + return false; + } - logger.debug( buffer.toString() ); + @Override + public String translateEquals() + { + return translateEquals( 0 ); + } + + public String translateEquals( int pos ) + { + return posMin == pos && posMax == pos + && ( pos < 3 || ( token[0] != '[' && token[0] != '(' ) ) + ? String.valueOf( token ) : null; } + } - /** {@inheritDoc} */ - public boolean hasMissedCriteria() + /** + * Matches all input + */ + static class MatchAllPattern extends Pattern { - // if there are no patterns, there is nothing to report. - if ( !positivePatterns.isEmpty() || !negativePatterns.isEmpty() ) + MatchAllPattern( String pattern ) { - final List<String> missed = new ArrayList<>(); - missed.addAll( positivePatterns ); - missed.addAll( negativePatterns ); + super( pattern ); + } - missed.removeAll( patternsTriggered ); + @Override + public boolean matches( char[][] parts ) + { + return true; + } + } - return !missed.isEmpty(); + /** + * Negative pattern + */ + static class NegativePattern extends Pattern + { + private final Pattern inner; + + NegativePattern( String pattern, Pattern inner ) + { + super( pattern ); + this.inner = inner; } - return false; + @Override + public boolean matches( char[][] parts ) + { + return inner.matches( parts ); + } } } diff --git a/src/test/java/org/apache/maven/shared/artifact/filter/AbstractPatternArtifactFilterTest.java b/src/test/java/org/apache/maven/shared/artifact/filter/AbstractPatternArtifactFilterTest.java index bbd46f0..cb4ad8d 100644 --- a/src/test/java/org/apache/maven/shared/artifact/filter/AbstractPatternArtifactFilterTest.java +++ b/src/test/java/org/apache/maven/shared/artifact/filter/AbstractPatternArtifactFilterTest.java @@ -52,16 +52,16 @@ public abstract class AbstractPatternArtifactFilterTest final String artifactId2 = "artifact2"; Artifact artifact1 = mock( Artifact.class ); - when( artifact1.getDependencyConflictId() ).thenReturn( groupId1 + ":" + artifactId1 + ":jar" ); when( artifact1.getGroupId() ).thenReturn( groupId1 ); when( artifact1.getArtifactId() ).thenReturn( artifactId1 ); - when( artifact1.getId() ).thenReturn( groupId1 + ":" + artifactId1 + ":jar:version" ); + when( artifact1.getType() ).thenReturn( "jar" ); + when( artifact1.getBaseVersion() ).thenReturn( "version" ); Artifact artifact2 = mock( Artifact.class ); - when( artifact2.getDependencyConflictId() ).thenReturn( groupId2 + ":" + artifactId2 + ":jar" ); when( artifact2.getGroupId() ).thenReturn( groupId2 ); when( artifact2.getArtifactId() ).thenReturn( artifactId2 ); - when( artifact2.getId() ).thenReturn( groupId2 + ":" + artifactId2 + ":jar:version" ); + when( artifact2.getType() ).thenReturn( "jar" ); + when( artifact2.getBaseVersion() ).thenReturn( "version" ); final List<String> patterns = new ArrayList<>(); patterns.add( groupId1 + ":" + artifactId1 + ":*" ); @@ -91,16 +91,16 @@ public abstract class AbstractPatternArtifactFilterTest final String artifactId2 = "artifact2"; Artifact artifact1 = mock( Artifact.class ); - when( artifact1.getDependencyConflictId() ).thenReturn( groupId1 + ":" + artifactId1 + ":jar" ); when( artifact1.getGroupId() ).thenReturn( groupId1 ); when( artifact1.getArtifactId() ).thenReturn( artifactId1 ); - when( artifact1.getId() ).thenReturn( groupId1 + ":" + artifactId1 + ":jar:version" ); + when( artifact1.getType() ).thenReturn( "jar" ); + when( artifact1.getBaseVersion() ).thenReturn( "version" ); Artifact artifact2 = mock( Artifact.class ); - when( artifact2.getDependencyConflictId() ).thenReturn( groupId2 + ":" + artifactId2 + ":jar" ); when( artifact2.getGroupId() ).thenReturn( groupId2 ); when( artifact2.getArtifactId() ).thenReturn( artifactId2 ); - when( artifact2.getId() ).thenReturn( groupId2 + ":" + artifactId2 + ":jar:version" ); + when( artifact2.getType() ).thenReturn( "jar" ); + when( artifact2.getBaseVersion() ).thenReturn( "version" ); final List<String> patterns = new ArrayList<>(); patterns.add( groupId1 + "*" ); @@ -127,10 +127,10 @@ public abstract class AbstractPatternArtifactFilterTest final String artifactId = "artifact"; Artifact artifact = mock( Artifact.class ); - when( artifact.getDependencyConflictId() ).thenReturn( groupId + ":" + artifactId + ":jar" ); when( artifact.getGroupId() ).thenReturn( groupId ); when( artifact.getArtifactId() ).thenReturn( artifactId ); - when( artifact.getId() ).thenReturn( groupId + ":" + artifactId + ":jar:version" ); + when( artifact.getType() ).thenReturn( "jar" ); + when( artifact.getBaseVersion() ).thenReturn( "version" ); final ArtifactFilter filter = createFilter( Collections.singletonList( groupId + ":" + artifactId ) ); @@ -151,10 +151,10 @@ public abstract class AbstractPatternArtifactFilterTest final String artifactId = "artifact"; Artifact artifact = mock( Artifact.class ); - when( artifact.getDependencyConflictId() ).thenReturn( groupId + ":" + artifactId + ":jar" ); when( artifact.getGroupId() ).thenReturn( groupId ); when( artifact.getArtifactId() ).thenReturn( artifactId ); - when( artifact.getId() ).thenReturn( groupId + ":" + artifactId + ":jar:version" ); + when( artifact.getType() ).thenReturn( "jar" ); + when( artifact.getBaseVersion() ).thenReturn( "version" ); final ArtifactFilter filter = createFilter( Collections.singletonList( groupId + ":" + artifactId + ":jar" ) ); @@ -175,10 +175,10 @@ public abstract class AbstractPatternArtifactFilterTest final String artifactId = "artifact"; Artifact artifact = mock( Artifact.class ); - when( artifact.getDependencyConflictId() ).thenReturn( groupId + ":" + artifactId + ":jar" ); when( artifact.getGroupId() ).thenReturn( groupId ); when( artifact.getArtifactId() ).thenReturn( artifactId ); - when( artifact.getId() ).thenReturn( groupId + ":" + artifactId + ":jar:version" ); + when( artifact.getType() ).thenReturn( "jar" ); + when( artifact.getBaseVersion() ).thenReturn( "version" ); final List<String> patterns = new ArrayList<>(); patterns.add( "otherGroup:" + artifactId + ":jar" ); @@ -203,10 +203,10 @@ public abstract class AbstractPatternArtifactFilterTest final String artifactId = "artifact"; Artifact artifact = mock( Artifact.class ); - when( artifact.getDependencyConflictId() ).thenReturn( groupId + ":" + artifactId + ":jar" ); when( artifact.getGroupId() ).thenReturn( groupId ); when( artifact.getArtifactId() ).thenReturn( artifactId ); - when( artifact.getId() ).thenReturn( groupId + ":" + artifactId + ":jar:version" ); + when( artifact.getType() ).thenReturn( "jar" ); + when( artifact.getBaseVersion() ).thenReturn( "version" ); final List<String> patterns = new ArrayList<>(); patterns.add( groupId + "otherArtifact:jar" ); @@ -231,10 +231,10 @@ public abstract class AbstractPatternArtifactFilterTest final String artifactId = "artifact"; Artifact artifact = mock( Artifact.class ); - when( artifact.getDependencyConflictId() ).thenReturn( groupId + ":" + artifactId + ":jar" ); when( artifact.getGroupId() ).thenReturn( groupId ); when( artifact.getArtifactId() ).thenReturn( artifactId ); - when( artifact.getId() ).thenReturn( groupId + ":" + artifactId + ":jar:version" ); + when( artifact.getType() ).thenReturn( "jar" ); + when( artifact.getBaseVersion() ).thenReturn( "version" ); final List<String> patterns = new ArrayList<>(); patterns.add( "otherGroup:otherArtifact:jar" ); @@ -265,10 +265,10 @@ public abstract class AbstractPatternArtifactFilterTest final List<String> patterns = Collections.singletonList( depTrailItem ); Artifact artifact = mock( Artifact.class ); - when( artifact.getDependencyConflictId() ).thenReturn( groupId + ":" + artifactId + ":jar" ); when( artifact.getGroupId() ).thenReturn( groupId ); when( artifact.getArtifactId() ).thenReturn( artifactId ); - when( artifact.getId() ).thenReturn( groupId + ":" + artifactId + ":jar:version" ); + when( artifact.getType() ).thenReturn( "jar" ); + when( artifact.getBaseVersion() ).thenReturn( "version" ); when( artifact.getDependencyTrail() ).thenReturn( depTrail ); final ArtifactFilter filter = createFilter( patterns, true ); @@ -296,10 +296,10 @@ public abstract class AbstractPatternArtifactFilterTest final List<String> patterns = Collections.singletonList( "otherGroup*" ); Artifact artifact = mock( Artifact.class ); - when( artifact.getDependencyConflictId() ).thenReturn( groupId + ":" + artifactId + ":jar" ); when( artifact.getGroupId() ).thenReturn( groupId ); when( artifact.getArtifactId() ).thenReturn( artifactId ); - when( artifact.getId() ).thenReturn( groupId + ":" + artifactId + ":jar:version" ); + when( artifact.getType() ).thenReturn( "jar" ); + when( artifact.getBaseVersion() ).thenReturn( "version" ); when( artifact.getDependencyTrail() ).thenReturn( depTrail ); final ArtifactFilter filter = createFilter( patterns, true ); @@ -321,10 +321,10 @@ public abstract class AbstractPatternArtifactFilterTest final String artifactId = "artifact"; Artifact artifact = mock( Artifact.class ); - when( artifact.getDependencyConflictId() ).thenReturn( groupId + ":" + artifactId + ":jar" ); when( artifact.getGroupId() ).thenReturn( groupId ); when( artifact.getArtifactId() ).thenReturn( artifactId ); - when( artifact.getId() ).thenReturn( groupId + ":" + artifactId + ":jar:version" ); + when( artifact.getType() ).thenReturn( "jar" ); + when( artifact.getBaseVersion() ).thenReturn( "version" ); final List<String> patterns = new ArrayList<>(); patterns.add( "!group:artifact:jar" ); @@ -348,10 +348,10 @@ public abstract class AbstractPatternArtifactFilterTest final String artifactId = "artifact"; Artifact artifact = mock( Artifact.class ); - when( artifact.getDependencyConflictId() ).thenReturn( groupId + ":" + artifactId + ":jar" ); when( artifact.getGroupId() ).thenReturn( groupId ); when( artifact.getArtifactId() ).thenReturn( artifactId ); - when( artifact.getId() ).thenReturn( groupId + ":" + artifactId + ":jar:version" ); + when( artifact.getType() ).thenReturn( "jar" ); + when( artifact.getBaseVersion() ).thenReturn( "version" ); final List<String> patterns = new ArrayList<>(); patterns.add( "group:*:jar" ); @@ -376,10 +376,10 @@ public abstract class AbstractPatternArtifactFilterTest Artifact artifact = mock( Artifact.class ); - when( artifact.getDependencyConflictId() ).thenReturn( groupId + ":" + artifactId + ":jar" ); when( artifact.getGroupId() ).thenReturn( groupId ); when( artifact.getArtifactId() ).thenReturn( artifactId ); - when( artifact.getId() ).thenReturn( groupId + ":" + artifactId + ":jar:version" ); + when( artifact.getType() ).thenReturn( "jar" ); + when( artifact.getBaseVersion() ).thenReturn( "version" ); final List<String> patterns = new ArrayList<>(); patterns.add( "*:artifact:*" ); @@ -404,10 +404,10 @@ public abstract class AbstractPatternArtifactFilterTest Artifact artifact = mock( Artifact.class ); - when( artifact.getDependencyConflictId() ).thenReturn( groupId + ":" + artifactId + ":jar" ); when( artifact.getGroupId() ).thenReturn( groupId ); when( artifact.getArtifactId() ).thenReturn( artifactId ); - when( artifact.getId() ).thenReturn( groupId + ":" + artifactId + ":jar:version" ); + when( artifact.getType() ).thenReturn( "jar" ); + when( artifact.getBaseVersion() ).thenReturn( "version" ); final List<String> patterns = new ArrayList<>(); patterns.add( "group:some-*-id" ); @@ -432,10 +432,10 @@ public abstract class AbstractPatternArtifactFilterTest Artifact artifact = mock( Artifact.class ); - when( artifact.getDependencyConflictId() ).thenReturn( groupId + ":" + artifactId + ":jar" ); when( artifact.getGroupId() ).thenReturn( groupId ); when( artifact.getArtifactId() ).thenReturn( artifactId ); - when( artifact.getId() ).thenReturn( groupId + ":" + artifactId + ":jar:version" ); + when( artifact.getType() ).thenReturn( "jar" ); + when( artifact.getBaseVersion() ).thenReturn( "version" ); final List<String> patterns = new ArrayList<>(); patterns.add( "some.group*" ); @@ -465,16 +465,16 @@ public abstract class AbstractPatternArtifactFilterTest final List<String> patterns = Collections.singletonList( "*:jar:*" ); Artifact artifact1 = mock( Artifact.class ); - when( artifact1.getDependencyConflictId() ).thenReturn( groupId + ":" + artifactId + ":jar" ); when( artifact1.getGroupId() ).thenReturn( groupId ); when( artifact1.getArtifactId() ).thenReturn( artifactId ); - when( artifact1.getId() ).thenReturn( groupId + ":" + artifactId + ":jar:version" ); - + when( artifact1.getType() ).thenReturn( "jar" ); + when( artifact1.getBaseVersion() ).thenReturn( "version" ); + Artifact artifact2 = mock( Artifact.class ); - when( artifact2.getDependencyConflictId() ).thenReturn( otherGroup + ":" + otherArtifact + ":" + otherType ); when( artifact2.getGroupId() ).thenReturn( otherGroup ); when( artifact2.getArtifactId() ).thenReturn( otherArtifact ); - when( artifact2.getId() ).thenReturn( otherGroup + ":" + otherArtifact + ":" + otherType + ":version" ); + when( artifact2.getType() ).thenReturn( otherType ); + when( artifact2.getBaseVersion() ).thenReturn( "version" ); when( artifact2.getDependencyTrail() ).thenReturn( Collections.<String> emptyList() ); final ArtifactFilter filter = createFilter( patterns, true ); @@ -498,10 +498,10 @@ public abstract class AbstractPatternArtifactFilterTest final String artifactId = "some-artifact-id"; Artifact artifact = mock( Artifact.class ); - when( artifact.getDependencyConflictId() ).thenReturn( groupId + ":" + artifactId + ":jar" ); when( artifact.getGroupId() ).thenReturn( groupId ); when( artifact.getArtifactId() ).thenReturn( artifactId ); - when( artifact.getId() ).thenReturn( groupId + ":" + artifactId + ":jar:version" ); + when( artifact.getType() ).thenReturn( "jar" ); + when( artifact.getBaseVersion() ).thenReturn( "version" ); final List<String> patterns = new ArrayList<>(); patterns.add( "com.mycompany.*:*:jar:*:*" ); @@ -517,4 +517,31 @@ public abstract class AbstractPatternArtifactFilterTest assertTrue( filter.include( artifact ) ); } } + + @Test + public void testWithVersionRange() + { + final String groupId = "com.mycompany.myproject"; + final String artifactId = "some-artifact-id"; + + Artifact artifact = mock( Artifact.class ); + when( artifact.getGroupId() ).thenReturn( groupId ); + when( artifact.getArtifactId() ).thenReturn( artifactId ); + when( artifact.getType() ).thenReturn( "jar" ); + when( artifact.getBaseVersion() ).thenReturn( "1.1" ); + + final List<String> patterns = new ArrayList<>(); + patterns.add( "com.mycompany.myproject:some-artifact-id:jar:*:[1.0,2.0)" ); + + final ArtifactFilter filter = createFilter( patterns ); + + if ( isInclusionNotExpected() ) + { + assertFalse( filter.include( artifact ) ); + } + else + { + assertTrue( filter.include( artifact ) ); + } + } } diff --git a/src/main/java/org/apache/maven/shared/artifact/filter/PatternIncludesArtifactFilter.java b/src/test/java/org/apache/maven/shared/artifact/filter/OldPatternIncludesArtifactFilter.java similarity index 96% copy from src/main/java/org/apache/maven/shared/artifact/filter/PatternIncludesArtifactFilter.java copy to src/test/java/org/apache/maven/shared/artifact/filter/OldPatternIncludesArtifactFilter.java index c9aad45..df492bb 100644 --- a/src/main/java/org/apache/maven/shared/artifact/filter/PatternIncludesArtifactFilter.java +++ b/src/test/java/org/apache/maven/shared/artifact/filter/OldPatternIncludesArtifactFilter.java @@ -35,12 +35,12 @@ import org.codehaus.plexus.logging.Logger; /** * TODO: include in maven-artifact in future - * + * * @author <a href="mailto:br...@apache.org">Brett Porter</a> * @see StrictPatternIncludesArtifactFilter */ -public class PatternIncludesArtifactFilter - implements ArtifactFilter, StatisticsReportingArtifactFilter +public class OldPatternIncludesArtifactFilter + implements ArtifactFilter, StatisticsReportingArtifactFilter { private final List<String> positivePatterns; @@ -55,7 +55,7 @@ public class PatternIncludesArtifactFilter /** * @param patterns The pattern to be used. */ - public PatternIncludesArtifactFilter( final Collection<String> patterns ) + public OldPatternIncludesArtifactFilter( final Collection<String> patterns ) { this( patterns, false ); } @@ -64,7 +64,7 @@ public class PatternIncludesArtifactFilter * @param patterns The pattern to be used. * @param actTransitively transitive yes/no. */ - public PatternIncludesArtifactFilter( final Collection<String> patterns, final boolean actTransitively ) + public OldPatternIncludesArtifactFilter( final Collection<String> patterns, final boolean actTransitively ) { this.actTransitively = actTransitively; final List<String> pos = new ArrayList<>(); @@ -192,7 +192,7 @@ public class PatternIncludesArtifactFilter for ( String pattern : patterns ) { String[] patternTokens = pattern.split( ":" ); - + if ( patternTokens.length == 5 && tokens.length < 5 ) { // 4th element is the classifier @@ -248,7 +248,7 @@ public class PatternIncludesArtifactFilter /** * 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 @@ -396,7 +396,7 @@ public class PatternIncludesArtifactFilter if ( !filteredArtifactIds.isEmpty() && logger.isDebugEnabled() ) { final StringBuilder buffer = - new StringBuilder( "The following artifacts were removed by this " + getFilterDescription() + ": " ); + new StringBuilder( "The following artifacts were removed by this " + getFilterDescription() + ": " ); for ( String artifactId : filteredArtifactIds ) { @@ -425,4 +425,4 @@ public class PatternIncludesArtifactFilter return false; } -} +} \ No newline at end of file diff --git a/src/test/java/org/apache/maven/shared/artifact/filter/PatternFilterPerfTest.java b/src/test/java/org/apache/maven/shared/artifact/filter/PatternFilterPerfTest.java new file mode 100644 index 0000000..6cbe7a5 --- /dev/null +++ b/src/test/java/org/apache/maven/shared/artifact/filter/PatternFilterPerfTest.java @@ -0,0 +1,131 @@ +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.Arrays; +import java.util.concurrent.TimeUnit; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.DefaultArtifact; +import org.apache.maven.artifact.resolver.filter.ArtifactFilter; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; +import org.openjdk.jmh.runner.options.TimeValue; + +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 3, time = 3, timeUnit = TimeUnit.SECONDS) +public class PatternFilterPerfTest { + + @State(Scope.Benchmark) + static public class OldPatternState { + + @Param({ + "groupId:artifact-00,groupId:artifact-01,groupId:artifact-02,groupId:artifact-03,groupId:artifact-04,groupId:artifact-05,groupId:artifact-06,groupId:artifact-07,groupId:artifact-08,groupId:artifact-09", + "groupId:artifact-99", + "groupId:artifact-*", + "*:artifact-99", + "*:artifact-*", + "*:artifact-*:*", + "*:artifact-99:*", + }) + public String patterns; + + ArtifactFilter filter; + Artifact artifact; + + @Setup(Level.Invocation) + public void setup() + { + filter = new OldPatternIncludesArtifactFilter( Arrays.asList( patterns.split( "," ) ) ); + artifact = new DefaultArtifact( + "groupId", "artifact-99", "1.0", "runtime", + "jar", "", null + ); + } + + } + + @State(Scope.Benchmark) + static public class NewPatternState { + + @Param({ + "groupId:artifact-00,groupId:artifact-01,groupId:artifact-02,groupId:artifact-03,groupId:artifact-04,groupId:artifact-05,groupId:artifact-06,groupId:artifact-07,groupId:artifact-08,groupId:artifact-09", + "groupId:artifact-99", + "groupId:artifact-*", + "*:artifact-99", + "*:artifact-*", + "*:artifact-*:*", + "*:artifact-99:*", + }) + public String patterns; + + ArtifactFilter filter; + Artifact artifact; + + @Setup(Level.Invocation) + public void setup() + { + filter = new PatternIncludesArtifactFilter( Arrays.asList( patterns.split( "," ) ) ); + artifact = new DefaultArtifact( + "groupId", "artifact-99", "1.0", "runtime", + "jar", "", null + ); + } + + } + + + @Benchmark + public boolean newPatternTest(NewPatternState state ) + { + return state.filter.include( state.artifact ); + } + + @Benchmark + public boolean oldPatternTest(OldPatternState state ) + { + return state.filter.include( state.artifact ); + } + + public static void main( String... args ) + throws RunnerException + { + Options opts = new OptionsBuilder() + .measurementIterations( 3 ) + .measurementTime( TimeValue.milliseconds( 3000 ) ) + .forks( 1 ) + .include( "org.apache.maven.shared.artifact.filter.PatternFilterPerfTest" ) + .build(); + new Runner( opts ).run(); + } +}