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