View Javadoc
1   package org.apache.maven.shared.artifact.filter;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collection;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.Iterator;
28  import java.util.LinkedHashSet;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Objects;
32  import java.util.Set;
33  
34  import org.apache.maven.artifact.Artifact;
35  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
36  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
37  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
38  import org.apache.maven.artifact.versioning.VersionRange;
39  import org.codehaus.plexus.logging.Logger;
40  
41  /**
42   * TODO: include in maven-artifact in future
43   *
44   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
45   * @see StrictPatternIncludesArtifactFilter
46   */
47  public class PatternIncludesArtifactFilter
48      implements ArtifactFilter, StatisticsReportingArtifactFilter
49  {
50      /** Holds the set of compiled patterns */
51      private final Set<Pattern> patterns;
52  
53      /** Holds simple patterns: those that can use direct matching */
54      private final Map<Integer, Map<String, Pattern>> simplePatterns;
55  
56      /** Whether the dependency trail should be checked */
57      private final boolean actTransitively;
58  
59      /** Set of patterns that have been triggered */
60      private final Set<Pattern> patternsTriggered = new HashSet<>();
61  
62      /** Set of artifacts that have been filtered out */
63      private final List<Artifact> filteredArtifact = new ArrayList<>();
64  
65      /**
66       * <p>Constructor for PatternIncludesArtifactFilter.</p>
67       *
68       * @param patterns The pattern to be used.
69       */
70      public PatternIncludesArtifactFilter( final Collection<String> patterns )
71      {
72          this( patterns, false );
73      }
74  
75      /**
76       * <p>Constructor for PatternIncludesArtifactFilter.</p>
77       *
78       * @param patterns The pattern to be used.
79       * @param actTransitively transitive yes/no.
80       */
81      public PatternIncludesArtifactFilter( final Collection<String> patterns, final boolean actTransitively )
82      {
83          this.actTransitively = actTransitively;
84          final Set<Pattern> pat = new LinkedHashSet<>();
85          Map<Integer, Map<String, Pattern>> simplePat = null;
86          boolean allPos = true;
87          if ( patterns != null && !patterns.isEmpty() )
88          {
89              for ( String pattern : patterns )
90              {
91  
92                  Pattern p = compile( pattern );
93                  allPos &= !( p instanceof NegativePattern );
94                  pat.add( p );
95              }
96          }
97          // If all patterns are positive, we can check for simple patterns
98          // Simple patterns will match the first tokens and contain no wildcards,
99          // so we can put them in a map and check them using a simple map lookup.
100         if ( allPos )
101         {
102             for ( Iterator<Pattern> it = pat.iterator(); it.hasNext(); )
103             {
104                 Pattern p = it.next();
105                 String peq = p.translateEquals();
106                 if ( peq != null )
107                 {
108                     int nb = 0;
109                     for ( char ch : peq.toCharArray() )
110                     {
111                         if ( ch == ':' )
112                         {
113                             nb++;
114                         }
115                     }
116                     if ( simplePat == null )
117                     {
118                         simplePat = new HashMap<>();
119                     }
120                     Map<String, Pattern> peqm = simplePat.get( nb );
121                     if ( peqm == null )
122                     {
123                         peqm = new HashMap<>();
124                         simplePat.put( nb, peqm );
125                     }
126                     peqm.put( peq, p );
127                     it.remove();
128                 }
129             }
130         }
131         this.simplePatterns = simplePat;
132         this.patterns = pat;
133     }
134 
135     /** {@inheritDoc} */
136     public boolean include( final Artifact artifact )
137     {
138         final boolean shouldInclude = patternMatches( artifact );
139 
140         if ( !shouldInclude )
141         {
142             addFilteredArtifact( artifact );
143         }
144 
145         return shouldInclude;
146     }
147 
148     /**
149      * <p>patternMatches.</p>
150      *
151      * @param artifact to check for.
152      * @return true if the match is true false otherwise.
153      */
154     protected boolean patternMatches( final Artifact artifact )
155     {
156         // Check if the main artifact matches
157         char[][] artifactGatvCharArray = new char[][] {
158                             emptyOrChars( artifact.getGroupId() ),
159                             emptyOrChars( artifact.getArtifactId() ),
160                             emptyOrChars( artifact.getType() ),
161                             emptyOrChars( artifact.getBaseVersion() )
162                     };
163         Boolean match = match( artifactGatvCharArray );
164         if ( match != null )
165         {
166             return match;
167         }
168 
169         if ( actTransitively )
170         {
171             final List<String> depTrail = artifact.getDependencyTrail();
172 
173             if ( depTrail != null && depTrail.size() > 1 )
174             {
175                 for ( String trailItem : depTrail )
176                 {
177                     char[][] depGatvCharArray = tokenizeAndSplit( trailItem );
178                     match = match( depGatvCharArray );
179                     if ( match != null )
180                     {
181                         return match;
182                     }
183                 }
184             }
185         }
186 
187         return false;
188     }
189 
190     private Boolean match( char[][] gatvCharArray )
191     {
192         if ( simplePatterns != null && simplePatterns.size() > 0 )
193         {
194             // We add the parts one by one to the builder
195             StringBuilder sb = new StringBuilder();
196             for ( int i = 0; i < 4; i++ )
197             {
198                 if ( i > 0 )
199                 {
200                     sb.append( ":" );
201                 }
202                 sb.append( gatvCharArray[i] );
203                 Map<String, Pattern> map = simplePatterns.get( i );
204                 if ( map != null )
205                 {
206                     // Check if one of the pattern matches
207                     Pattern p = map.get( sb.toString() );
208                     if ( p != null )
209                     {
210                         patternsTriggered.add( p );
211                         return true;
212                     }
213                 }
214             }
215         }
216         // Check all other patterns
217         for ( Pattern pattern : patterns )
218         {
219             if ( pattern.matches( gatvCharArray ) )
220             {
221                 patternsTriggered.add( pattern );
222                 return !( pattern instanceof NegativePattern );
223             }
224         }
225 
226         return null;
227     }
228 
229     /**
230      * <p>addFilteredArtifact.</p>
231      *
232      * @param artifact add artifact to the filtered artifacts list.
233      */
234     protected void addFilteredArtifact( final Artifact artifact )
235     {
236         filteredArtifact.add( artifact );
237     }
238 
239     /** {@inheritDoc} */
240     public void reportMissedCriteria( final Logger logger )
241     {
242         // if there are no patterns, there is nothing to report.
243         if ( !patterns.isEmpty() )
244         {
245             final List<Pattern> missed = new ArrayList<>( patterns );
246             missed.removeAll( patternsTriggered );
247 
248             if ( !missed.isEmpty() && logger.isWarnEnabled() )
249             {
250                 final StringBuilder buffer = new StringBuilder();
251 
252                 buffer.append( "The following patterns were never triggered in this " );
253                 buffer.append( getFilterDescription() );
254                 buffer.append( ':' );
255 
256                 for ( Pattern pattern : missed )
257                 {
258                     buffer.append( "\no  '" ).append( pattern ).append( "'" );
259                 }
260 
261                 buffer.append( "\n" );
262 
263                 logger.warn( buffer.toString() );
264             }
265         }
266     }
267 
268     /** {@inheritDoc} */
269     @Override
270     public String toString()
271     {
272         return "Includes filter:" + getPatternsAsString();
273     }
274 
275     /**
276      * <p>getPatternsAsString.</p>
277      *
278      * @return pattern as a string.
279      */
280     protected String getPatternsAsString()
281     {
282         final StringBuilder buffer = new StringBuilder();
283         for ( Pattern pattern : patterns )
284         {
285             buffer.append( "\no '" ).append( pattern ).append( "'" );
286         }
287 
288         return buffer.toString();
289     }
290 
291     /**
292      * <p>getFilterDescription.</p>
293      *
294      * @return description.
295      */
296     protected String getFilterDescription()
297     {
298         return "artifact inclusion filter";
299     }
300 
301     /** {@inheritDoc} */
302     public void reportFilteredArtifacts( final Logger logger )
303     {
304         if ( !filteredArtifact.isEmpty() && logger.isDebugEnabled() )
305         {
306             final StringBuilder buffer =
307                 new StringBuilder( "The following artifacts were removed by this " + getFilterDescription() + ": " );
308 
309             for ( Artifact artifactId : filteredArtifact )
310             {
311                 buffer.append( '\n' ).append( artifactId.getId() );
312             }
313 
314             logger.debug( buffer.toString() );
315         }
316     }
317 
318     /**
319      * {@inheritDoc}
320      *
321      * @return a boolean.
322      */
323     public boolean hasMissedCriteria()
324     {
325         // if there are no patterns, there is nothing to report.
326         if ( !patterns.isEmpty() )
327         {
328             final List<Pattern> missed = new ArrayList<>( patterns );
329             missed.removeAll( patternsTriggered );
330 
331             return !missed.isEmpty();
332         }
333 
334         return false;
335     }
336 
337     private static final char[] EMPTY = new char[0];
338 
339     private static final char[] ANY = new char[] { '*' };
340 
341     static char[] emptyOrChars( String str )
342     {
343         return str != null && str.length() > 0 ? str.toCharArray() : EMPTY;
344     }
345 
346     static char[] anyOrChars( char[] str )
347     {
348         return str.length > 1 || ( str.length == 1 && str[0] != '*' ) ? str : ANY;
349     }
350 
351     static char[][] tokenizeAndSplit( String pattern )
352     {
353         String[] stokens = pattern.split( ":" );
354         char[][] tokens = new char[ stokens.length ][];
355         for ( int i = 0; i < stokens.length; i++ )
356         {
357             tokens[i] = emptyOrChars( stokens[i] );
358         }
359         return tokens;
360     }
361 
362     @SuppressWarnings( "InnerAssignment" )
363     static boolean match( char[] patArr, char[] strArr, boolean isVersion )
364     {
365         int patIdxStart = 0;
366         int patIdxEnd = patArr.length - 1;
367         int strIdxStart = 0;
368         int strIdxEnd = strArr.length - 1;
369         char ch;
370 
371         boolean containsStar = false;
372         for ( char aPatArr : patArr )
373         {
374             if ( aPatArr == '*' )
375             {
376                 containsStar = true;
377                 break;
378             }
379         }
380 
381         if ( !containsStar )
382         {
383             if ( isVersion && ( patArr[0] == '[' || patArr[0] == '(' ) )
384             {
385                 return isVersionIncludedInRange( String.valueOf( strArr ), String.valueOf( patArr ) );
386             }
387             // No '*'s, so we make a shortcut
388             if ( patIdxEnd != strIdxEnd )
389             {
390                 return false; // Pattern and string do not have the same size
391             }
392             for ( int i = 0; i <= patIdxEnd; i++ )
393             {
394                 ch = patArr[i];
395                 if ( ch != '?' && ch != strArr[i] )
396                 {
397                     return false; // Character mismatch
398                 }
399             }
400             return true; // String matches against pattern
401         }
402 
403         if ( patIdxEnd == 0 )
404         {
405             return true; // Pattern contains only '*', which matches anything
406         }
407 
408         // Process characters before first star
409         while ( ( ch = patArr[patIdxStart] ) != '*' && strIdxStart <= strIdxEnd )
410         {
411             if ( ch != '?' && ch != strArr[strIdxStart] )
412             {
413                 return false; // Character mismatch
414             }
415             patIdxStart++;
416             strIdxStart++;
417         }
418         if ( strIdxStart > strIdxEnd )
419         {
420             // All characters in the string are used. Check if only '*'s are
421             // left in the pattern. If so, we succeeded. Otherwise failure.
422             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
423             {
424                 if ( patArr[i] != '*' )
425                 {
426                     return false;
427                 }
428             }
429             return true;
430         }
431 
432         // Process characters after last star
433         while ( ( ch = patArr[patIdxEnd] ) != '*' && strIdxStart <= strIdxEnd )
434         {
435             if ( ch != '?' && ch != strArr[strIdxEnd] )
436             {
437                 return false; // Character mismatch
438             }
439             patIdxEnd--;
440             strIdxEnd--;
441         }
442         if ( strIdxStart > strIdxEnd )
443         {
444             // All characters in the string are used. Check if only '*'s are
445             // left in the pattern. If so, we succeeded. Otherwise failure.
446             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
447             {
448                 if ( patArr[i] != '*' )
449                 {
450                     return false;
451                 }
452             }
453             return true;
454         }
455 
456         // process pattern between stars. padIdxStart and patIdxEnd point
457         // always to a '*'.
458         while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
459         {
460             int patIdxTmp = -1;
461             for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
462             {
463                 if ( patArr[i] == '*' )
464                 {
465                     patIdxTmp = i;
466                     break;
467                 }
468             }
469             if ( patIdxTmp == patIdxStart + 1 )
470             {
471                 // Two stars next to each other, skip the first one.
472                 patIdxStart++;
473                 continue;
474             }
475             // Find the pattern between padIdxStart & padIdxTmp in str between
476             // strIdxStart & strIdxEnd
477             int patLength = ( patIdxTmp - patIdxStart - 1 );
478             int strLength = ( strIdxEnd - strIdxStart + 1 );
479             int foundIdx = -1;
480             strLoop: for ( int i = 0; i <= strLength - patLength; i++ )
481             {
482                 for ( int j = 0; j < patLength; j++ )
483                 {
484                     ch = patArr[patIdxStart + j + 1];
485                     if ( ch != '?' && ch != strArr[strIdxStart + i + j] )
486                     {
487                         continue strLoop;
488                     }
489                 }
490 
491                 foundIdx = strIdxStart + i;
492                 break;
493             }
494 
495             if ( foundIdx == -1 )
496             {
497                 return false;
498             }
499 
500             patIdxStart = patIdxTmp;
501             strIdxStart = foundIdx + patLength;
502         }
503 
504         // All characters in the string are used. Check if only '*'s are left
505         // in the pattern. If so, we succeeded. Otherwise failure.
506         for ( int i = patIdxStart; i <= patIdxEnd; i++ )
507         {
508             if ( patArr[i] != '*' )
509             {
510                 return false;
511             }
512         }
513         return true;
514     }
515 
516     static boolean isVersionIncludedInRange( final String version, final String range )
517     {
518         try
519         {
520             return VersionRange.createFromVersionSpec( range ).containsVersion( new DefaultArtifactVersion( version ) );
521         }
522         catch ( final InvalidVersionSpecificationException e )
523         {
524             return false;
525         }
526     }
527 
528     static Pattern compile( String pattern )
529     {
530         if ( pattern.startsWith( "!" ) )
531         {
532             return new NegativePattern( pattern, compile( pattern.substring( 1 ) ) );
533         }
534         else
535         {
536             char[][] stokens = tokenizeAndSplit( pattern );
537             char[][] tokens = new char[ stokens.length ][];
538             for ( int i = 0; i < stokens.length; i++ )
539             {
540                 tokens[i] = anyOrChars( stokens[i] );
541             }
542             if ( tokens.length > 5 )
543             {
544                 throw new IllegalArgumentException( "Invalid pattern: " + pattern );
545             }
546             // we only accept 5 tokens if the classifier = '*'
547             if ( tokens.length == 5 )
548             {
549                 if ( tokens[3] != ANY )
550                 {
551                     throw new IllegalArgumentException( "Invalid pattern: " + pattern );
552                 }
553                 tokens = new char[][] { tokens[0], tokens[1], tokens[2], tokens[4] };
554             }
555             //
556             // Check the 4 tokens and build an appropriate Pattern
557             // Special care needs to be taken if the first or the last part is '*'
558             // because this allows the '*' to match multiple tokens
559             //
560             if ( tokens.length == 1 )
561             {
562                 if ( tokens[0] == ANY )
563                 {
564                     // *
565                     return all( pattern );
566                 }
567                 else
568                 {
569                     // [pat0]
570                     return match( pattern, tokens[0], 0 );
571                 }
572             }
573             if ( tokens.length == 2 )
574             {
575                 if ( tokens[0] == ANY )
576                 {
577                     if ( tokens[1] == ANY )
578                     {
579                         // *:*
580                         return all( pattern );
581                     }
582                     else
583                     {
584                         // *:[pat1]
585                         return match( pattern, tokens[1], 0, 3 );
586                     }
587                 }
588                 else
589                 {
590                     if ( tokens[1] == ANY )
591                     {
592                         // [pat0]:*
593                         return match( pattern, tokens[0], 0 );
594                     }
595                     else
596                     {
597                         // [pat0]:[pat1]
598                         Pattern m00 = match( tokens[0], 0 );
599                         Pattern m11 = match( tokens[1], 1 );
600                         return and( pattern, m00, m11 );
601                     }
602                 }
603             }
604             if ( tokens.length == 3 )
605             {
606                 if ( tokens[0] == ANY )
607                 {
608                     if ( tokens[1] == ANY )
609                     {
610                         if ( tokens[2] == ANY )
611                         {
612                             // *:*:*
613                             return all( pattern );
614                         }
615                         else
616                         {
617                             // *:*:[pat2]
618                             return match( pattern, tokens[2], 2, 3 );
619                         }
620                     }
621                     else
622                     {
623                         if ( tokens[2] == ANY )
624                         {
625                             // *:[pat1]:*
626                             return match( pattern, tokens[1], 1, 2 );
627                         }
628                         else
629                         {
630                             // *:[pat1]:[pat2]
631                             Pattern m11 = match( tokens[1], 1 );
632                             Pattern m12 = match( tokens[1], 2 );
633                             Pattern m22 = match( tokens[2], 2 );
634                             Pattern m23 = match( tokens[2], 3 );
635                             return or( pattern, and( m11, m22 ), and( m12, m23 ) );
636                         }
637                     }
638                 }
639                 else
640                 {
641                     if ( tokens[1] == ANY )
642                     {
643                         if ( tokens[2] == ANY )
644                         {
645                             // [pat0]:*:*
646                             return match( pattern, tokens[0], 0, 1 );
647                         }
648                         else
649                         {
650                             // [pat0]:*:[pat2]
651                             Pattern m00 = match( tokens[0], 0 );
652                             Pattern m223 = match( tokens[2], 2, 3 );
653                             return and( pattern, m00, m223 );
654                         }
655                     }
656                     else
657                     {
658                         if ( tokens[2] == ANY )
659                         {
660                             // [pat0]:[pat1]:*
661                             Pattern m00 = match( tokens[0], 0 );
662                             Pattern m11 = match( tokens[1], 1 );
663                             return and( pattern, m00, m11 );
664                         }
665                         else
666                         {
667                             // [pat0]:[pat1]:[pat2]
668                             Pattern m00 = match( tokens[0], 0 );
669                             Pattern m11 = match( tokens[1], 1 );
670                             Pattern m22 = match( tokens[2], 2 );
671                             return and( pattern, m00, m11, m22 );
672                         }
673                     }
674                 }
675             }
676             if ( tokens.length == 4 )
677             {
678                 List<Pattern> patterns = new ArrayList<>();
679                 for ( int i = 0; i < 4; i++ )
680                 {
681                     if ( tokens[i] != ANY )
682                     {
683                         patterns.add( match( tokens[i], i ) );
684                     }
685                 }
686                 return and( pattern, patterns.toArray( new Pattern[0] ) );
687             }
688             throw new IllegalStateException();
689         }
690     }
691 
692     /** Creates a positional matching pattern */
693     private static Pattern match( String pattern, char[] token, int posVal )
694     {
695         return match( pattern, token, posVal, posVal );
696     }
697 
698     /** Creates a positional matching pattern */
699     private static Pattern match( char[] token, int posVal )
700     {
701         return match( "", token, posVal, posVal );
702     }
703 
704     /** Creates a positional matching pattern */
705     private static Pattern match( String pattern, char[] token, int posMin, int posMax )
706     {
707         boolean hasWildcard = false;
708         for ( char ch : token )
709         {
710             if ( ch == '*' || ch == '?' )
711             {
712                 hasWildcard = true;
713                 break;
714             }
715         }
716         if ( hasWildcard || posMax == 3 )
717         {
718             return new PosPattern( pattern, token, posMin, posMax );
719         }
720         else
721         {
722             return new EqPattern( pattern, token, posMin, posMax );
723         }
724     }
725 
726     /** Creates a positional matching pattern */
727     private static Pattern match( char[] token, int posMin, int posMax )
728     {
729         return new PosPattern( "", token, posMin, posMax );
730     }
731 
732     /** Creates an AND pattern */
733     private static Pattern and( String pattern, Pattern... patterns )
734     {
735         return new AndPattern( pattern, patterns );
736     }
737 
738     /** Creates an AND pattern */
739     private static Pattern and( Pattern... patterns )
740     {
741         return and( "", patterns );
742     }
743 
744     /** Creates an OR pattern */
745     private static Pattern or( String pattern, Pattern... patterns )
746     {
747         return new OrPattern( pattern, patterns );
748     }
749 
750     /** Creates an OR pattern */
751     private static Pattern or( Pattern... patterns )
752     {
753         return or( "", patterns );
754     }
755 
756     /** Creates a match-all pattern */
757     private static Pattern all( String pattern )
758     {
759         return new MatchAllPattern( pattern );
760     }
761 
762     /**
763      * Abstract class for patterns
764      */
765     abstract static class Pattern
766     {
767         private final String pattern;
768 
769         Pattern( String pattern )
770         {
771             this.pattern = Objects.requireNonNull( pattern );
772         }
773 
774         public abstract boolean matches( char[][] parts );
775 
776         /**
777          * Returns a string containing a fixed artifact gatv coordinates
778          * or null if the pattern can not be translated.
779          */
780         public String translateEquals()
781         {
782             return null;
783         }
784 
785         /**
786          * Check if the this pattern is a fixed pattern on the specified pos.
787          */
788         protected String translateEquals( int pos )
789         {
790             return null;
791         }
792 
793         @Override
794         public String toString()
795         {
796             return pattern;
797         }
798     }
799 
800     /**
801      * Simple pattern which performs a logical AND between one or more patterns.
802      */
803     static class AndPattern extends Pattern
804     {
805         private final Pattern[] patterns;
806 
807         AndPattern( String pattern, Pattern[] patterns )
808         {
809             super( pattern );
810             this.patterns = patterns;
811         }
812 
813         @Override
814         public boolean matches( char[][] parts )
815         {
816             for ( Pattern pattern : patterns )
817             {
818                 if ( !pattern.matches( parts ) )
819                 {
820                     return false;
821                 }
822             }
823             return true;
824         }
825 
826         @Override
827         public String translateEquals()
828         {
829             String[] strings = new String[ patterns.length ];
830             for ( int i = 0; i < patterns.length; i++ )
831             {
832                 strings[i] = patterns[i].translateEquals( i );
833                 if ( strings[i] == null )
834                 {
835                     return null;
836                 }
837             }
838             StringBuilder sb = new StringBuilder();
839             for ( int i = 0; i < strings.length; i++ )
840             {
841                 if ( i > 0 )
842                 {
843                     sb.append( ":" );
844                 }
845                 sb.append( strings[i] );
846             }
847             return sb.toString();
848         }
849     }
850 
851     /**
852      * Simple pattern which performs a logical OR between one or more patterns.
853      */
854     static class OrPattern extends Pattern
855     {
856         private final Pattern[] patterns;
857 
858         OrPattern( String pattern, Pattern[] patterns )
859         {
860             super( pattern );
861             this.patterns = patterns;
862         }
863 
864         @Override
865         public boolean matches( char[][] parts )
866         {
867             for ( Pattern pattern : patterns )
868             {
869                 if ( pattern.matches( parts ) )
870                 {
871                     return true;
872                 }
873             }
874             return false;
875         }
876     }
877 
878     /**
879      * A positional matching pattern, to check if a token in the gatv coordinates
880      * having a position between posMin and posMax (both inclusives) can match
881      * the pattern.
882      */
883     static class PosPattern extends Pattern
884     {
885         private final char[] patternCharArray;
886         private final int posMin;
887         private final int posMax;
888 
889         PosPattern( String pattern, char[] patternCharArray, int posMin, int posMax )
890         {
891             super( pattern );
892             this.patternCharArray = patternCharArray;
893             this.posMin = posMin;
894             this.posMax = posMax;
895         }
896 
897         @Override
898         public boolean matches( char[][] parts )
899         {
900             for ( int i = posMin; i <= posMax; i++ )
901             {
902                 if ( match( patternCharArray, parts[i], i == 3 ) )
903                 {
904                     return true;
905                 }
906             }
907             return false;
908         }
909     }
910 
911     /**
912      * Looks for an exact match in the gatv coordinates between
913      * posMin and posMax (both inclusives)
914      */
915     static class EqPattern extends Pattern
916     {
917         private final char[] token;
918         private final int posMin;
919         private final int posMax;
920 
921         EqPattern( String pattern, char[] patternCharArray, int posMin, int posMax )
922         {
923             super( pattern );
924             this.token = patternCharArray;
925             this.posMin = posMin;
926             this.posMax = posMax;
927         }
928 
929         @Override
930         public boolean matches( char[][] parts )
931         {
932             for ( int i = posMin; i <= posMax; i++ )
933             {
934                 if ( Arrays.equals( token, parts[i] ) )
935                 {
936                     return true;
937                 }
938             }
939             return false;
940         }
941 
942         @Override
943         public String translateEquals()
944         {
945             return translateEquals( 0 );
946         }
947 
948         public String translateEquals( int pos )
949         {
950             return posMin == pos && posMax == pos
951                 && ( pos < 3 || ( token[0] != '[' && token[0] != '(' ) )
952                     ? String.valueOf( token ) : null;
953         }
954 
955     }
956 
957     /**
958      * Matches all input
959      */
960     static class MatchAllPattern extends Pattern
961     {
962         MatchAllPattern( String pattern )
963         {
964             super( pattern );
965         }
966 
967         @Override
968         public boolean matches( char[][] parts )
969         {
970             return true;
971         }
972     }
973 
974     /**
975      * Negative pattern
976      */
977     static class NegativePattern extends Pattern
978     {
979         private final Pattern inner;
980 
981         NegativePattern( String pattern, Pattern inner )
982         {
983             super( pattern );
984             this.inner = inner;
985         }
986 
987         @Override
988         public boolean matches( char[][] parts )
989         {
990             return inner.matches( parts );
991         }
992     }
993 
994 }