| 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.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.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.slf4j.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 |
| { |
| /** Holds the set of compiled patterns */ |
| private final Set<Pattern> patterns; |
| |
| /** 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; |
| |
| /** Set of patterns that have been triggered */ |
| private final Set<Pattern> patternsTriggered = new HashSet<>(); |
| |
| /** Set of artifacts that have been filtered out */ |
| private final List<Artifact> filteredArtifact = new ArrayList<>(); |
| |
| /** |
| * <p>Constructor for PatternIncludesArtifactFilter.</p> |
| * |
| * @param patterns The pattern to be used. |
| */ |
| public PatternIncludesArtifactFilter( final Collection<String> patterns ) |
| { |
| this( patterns, false ); |
| } |
| |
| /** |
| * <p>Constructor for PatternIncludesArtifactFilter.</p> |
| * |
| * @param patterns The pattern to be used. |
| * @param actTransitively transitive yes/no. |
| */ |
| public PatternIncludesArtifactFilter( final Collection<String> patterns, final boolean actTransitively ) |
| { |
| this.actTransitively = actTransitively; |
| 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 ) |
| { |
| |
| 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 ) |
| { |
| 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(); |
| } |
| } |
| } |
| this.simplePatterns = simplePat; |
| this.patterns = pat; |
| } |
| |
| /** {@inheritDoc} */ |
| public boolean include( final Artifact artifact ) |
| { |
| final boolean shouldInclude = patternMatches( artifact ); |
| |
| if ( !shouldInclude ) |
| { |
| addFilteredArtifact( artifact ); |
| } |
| |
| return shouldInclude; |
| } |
| |
| /** |
| * <p>patternMatches.</p> |
| * |
| * @param artifact to check for. |
| * @return true if the match is true false otherwise. |
| */ |
| protected boolean patternMatches( final Artifact artifact ) |
| { |
| // 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; |
| } |
| |
| /** |
| * <p>addFilteredArtifact.</p> |
| * |
| * @param artifact add artifact to the filtered artifacts list. |
| */ |
| protected void addFilteredArtifact( final Artifact artifact ) |
| { |
| filteredArtifact.add( artifact ); |
| } |
| |
| /** {@inheritDoc} */ |
| public void reportMissedCriteria( final Logger logger ) |
| { |
| // if there are no patterns, there is nothing to report. |
| if ( !patterns.isEmpty() ) |
| { |
| 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() ); |
| } |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public String toString() |
| { |
| return "Includes filter:" + getPatternsAsString(); |
| } |
| |
| /** |
| * <p>getPatternsAsString.</p> |
| * |
| * @return pattern as a string. |
| */ |
| protected String getPatternsAsString() |
| { |
| final StringBuilder buffer = new StringBuilder(); |
| for ( Pattern pattern : patterns ) |
| { |
| buffer.append( "\no '" ).append( pattern ).append( "'" ); |
| } |
| |
| return buffer.toString(); |
| } |
| |
| /** |
| * <p>getFilterDescription.</p> |
| * |
| * @return description. |
| */ |
| protected String getFilterDescription() |
| { |
| return "artifact inclusion filter"; |
| } |
| |
| /** {@inheritDoc} */ |
| public void reportFilteredArtifacts( final Logger logger ) |
| { |
| if ( !filteredArtifact.isEmpty() && logger.isDebugEnabled() ) |
| { |
| 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() ); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @return a boolean. |
| */ |
| public boolean hasMissedCriteria() |
| { |
| // if there are no patterns, there is nothing to report. |
| if ( !patterns.isEmpty() ) |
| { |
| final List<Pattern> missed = new ArrayList<>( patterns ); |
| missed.removeAll( patternsTriggered ); |
| |
| return !missed.isEmpty(); |
| } |
| |
| return false; |
| } |
| |
| private static final char[] EMPTY = new char[0]; |
| |
| private static final char[] ANY = new char[] { '*' }; |
| |
| static char[] emptyOrChars( String str ) |
| { |
| return str != null && str.length() > 0 ? str.toCharArray() : EMPTY; |
| } |
| |
| static char[] anyOrChars( char[] str ) |
| { |
| return str.length > 1 || ( str.length == 1 && str[0] != '*' ) ? str : ANY; |
| } |
| |
| static char[][] tokenizeAndSplit( String pattern ) |
| { |
| String[] stokens = pattern.split( ":" ); |
| char[][] tokens = new char[ stokens.length ][]; |
| for ( int i = 0; i < stokens.length; i++ ) |
| { |
| tokens[i] = emptyOrChars( stokens[i] ); |
| } |
| return tokens; |
| } |
| |
| @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 ) |
| { |
| if ( aPatArr == '*' ) |
| { |
| containsStar = true; |
| break; |
| } |
| } |
| |
| if ( !containsStar ) |
| { |
| if ( isVersion && ( patArr[0] == '[' || patArr[0] == '(' ) ) |
| { |
| 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] ) |
| { |
| return false; // Character mismatch |
| } |
| } |
| return true; // String matches against pattern |
| } |
| |
| if ( patIdxEnd == 0 ) |
| { |
| return true; // Pattern contains only '*', which matches anything |
| } |
| |
| // Process characters before first star |
| while ( ( ch = patArr[patIdxStart] ) != '*' && strIdxStart <= strIdxEnd ) |
| { |
| if ( ch != '?' && ch != strArr[strIdxStart] ) |
| { |
| 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] != '*' ) |
| { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // Process characters after last star |
| while ( ( ch = patArr[patIdxEnd] ) != '*' && strIdxStart <= strIdxEnd ) |
| { |
| if ( ch != '?' && ch != strArr[strIdxEnd] ) |
| { |
| return false; // Character mismatch |
| } |
| 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++ ) |
| { |
| if ( patArr[i] != '*' ) |
| { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // 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++ ) |
| { |
| 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 ( foundIdx == -1 ) |
| { |
| return false; |
| } |
| |
| patIdxStart = patIdxTmp; |
| strIdxStart = foundIdx + patLength; |
| } |
| |
| // 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; |
| } |
| |
| static boolean isVersionIncludedInRange( final String version, final String range ) |
| { |
| try |
| { |
| return VersionRange.createFromVersionSpec( range ).containsVersion( new DefaultArtifactVersion( version ) ); |
| } |
| catch ( final InvalidVersionSpecificationException e ) |
| { |
| return false; |
| } |
| } |
| |
| static Pattern compile( String pattern ) |
| { |
| if ( pattern.startsWith( "!" ) ) |
| { |
| return new NegativePattern( pattern, compile( pattern.substring( 1 ) ) ); |
| } |
| else |
| { |
| 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 ) |
| { |
| if ( tokens[3] != ANY ) |
| { |
| 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 ); |
| } |
| } |
| 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(); |
| } |
| } |
| |
| /** 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; |
| } |
| } |
| if ( hasWildcard || posMax == 3 ) |
| { |
| return new PosPattern( pattern, token, posMin, posMax ); |
| } |
| else |
| { |
| return new EqPattern( pattern, token, posMin, posMax ); |
| } |
| } |
| |
| /** Creates a positional matching pattern */ |
| private static Pattern match( char[] token, int posMin, int posMax ) |
| { |
| return new PosPattern( "", token, posMin, posMax ); |
| } |
| |
| /** Creates an AND pattern */ |
| private static Pattern and( String pattern, Pattern... patterns ) |
| { |
| 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 ) |
| { |
| this.pattern = Objects.requireNonNull( pattern ); |
| } |
| |
| 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 null; |
| } |
| |
| /** |
| * Check if the this pattern is a fixed pattern on the specified pos. |
| */ |
| protected String translateEquals( int pos ) |
| { |
| return null; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return pattern; |
| } |
| } |
| |
| /** |
| * Simple pattern which performs a logical AND between one or more patterns. |
| */ |
| static class AndPattern extends Pattern |
| { |
| private final Pattern[] patterns; |
| |
| AndPattern( String pattern, Pattern[] patterns ) |
| { |
| super( pattern ); |
| this.patterns = patterns; |
| } |
| |
| @Override |
| public boolean matches( char[][] parts ) |
| { |
| for ( Pattern pattern : patterns ) |
| { |
| if ( !pattern.matches( parts ) ) |
| { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| @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(); |
| } |
| } |
| |
| /** |
| * Simple pattern which performs a logical OR between one or more patterns. |
| */ |
| static class OrPattern extends Pattern |
| { |
| 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; |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| static class PosPattern extends Pattern |
| { |
| private final char[] patternCharArray; |
| private final int posMin; |
| private final int posMax; |
| |
| PosPattern( String pattern, char[] patternCharArray, int posMin, int posMax ) |
| { |
| super( pattern ); |
| this.patternCharArray = patternCharArray; |
| this.posMin = posMin; |
| this.posMax = posMax; |
| } |
| |
| @Override |
| public boolean matches( char[][] parts ) |
| { |
| for ( int i = posMin; i <= posMax; i++ ) |
| { |
| if ( match( patternCharArray, parts[i], i == 3 ) ) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Looks for an exact match in the gatv coordinates between |
| * posMin and posMax (both inclusives) |
| */ |
| static class EqPattern extends Pattern |
| { |
| private final char[] token; |
| private final int posMin; |
| private final int posMax; |
| |
| EqPattern( String pattern, char[] patternCharArray, int posMin, int posMax ) |
| { |
| super( pattern ); |
| this.token = patternCharArray; |
| this.posMin = posMin; |
| this.posMax = posMax; |
| } |
| |
| @Override |
| public boolean matches( char[][] parts ) |
| { |
| for ( int i = posMin; i <= posMax; i++ ) |
| { |
| if ( Arrays.equals( token, parts[i] ) ) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @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; |
| } |
| |
| } |
| |
| /** |
| * Matches all input |
| */ |
| static class MatchAllPattern extends Pattern |
| { |
| MatchAllPattern( String pattern ) |
| { |
| super( pattern ); |
| } |
| |
| @Override |
| public boolean matches( char[][] parts ) |
| { |
| return true; |
| } |
| } |
| |
| /** |
| * Negative pattern |
| */ |
| static class NegativePattern extends Pattern |
| { |
| private final Pattern inner; |
| |
| NegativePattern( String pattern, Pattern inner ) |
| { |
| super( pattern ); |
| this.inner = inner; |
| } |
| |
| @Override |
| public boolean matches( char[][] parts ) |
| { |
| return inner.matches( parts ); |
| } |
| } |
| |
| } |