1   package org.apache.maven.shared.utils.io;
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  import java.io.File;
23  import java.util.ArrayList;
24  import java.util.List;
25  import java.util.StringTokenizer;
26  
27  import javax.annotation.Nonnull;
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  public final class SelectorUtils
44  {
45  
46      
47  
48  
49      private static final String PATTERN_HANDLER_PREFIX = "[";
50  
51      
52  
53  
54      public static final String PATTERN_HANDLER_SUFFIX = "]";
55  
56      
57  
58  
59      public static final String REGEX_HANDLER_PREFIX = "%regex" + PATTERN_HANDLER_PREFIX;
60  
61      
62  
63  
64      public static final String ANT_HANDLER_PREFIX = "%ant" + PATTERN_HANDLER_PREFIX;
65  
66      
67  
68  
69      private SelectorUtils()
70      {
71      }
72  
73      
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88      public static boolean matchPatternStart( String pattern, String str )
89      {
90          return matchPatternStart( pattern, str, true );
91      }
92  
93      
94  
95  
96  
97  
98  
99  
100 
101 
102 
103 
104 
105 
106 
107 
108 
109 
110     public static boolean matchPatternStart( String pattern, String str, boolean isCaseSensitive )
111     {
112         if ( isRegexPrefixedPattern( pattern ) )
113         {
114             
115             
116             return true;
117         }
118         else
119         {
120             if ( isAntPrefixedPattern( pattern ) )
121             {
122                 pattern = pattern.substring( ANT_HANDLER_PREFIX.length(),
123                                              pattern.length() - PATTERN_HANDLER_SUFFIX.length() );
124             }
125 
126             String altPattern = pattern.replace( '\\', '/' );
127             String altStr = str.replace( '\\', '/' );
128 
129             return matchAntPathPatternStart( altPattern, altStr, "/", isCaseSensitive );
130         }
131     }
132 
133     private static boolean matchAntPathPatternStart( String pattern, String str, String separator,
134                                                      boolean isCaseSensitive )
135     {
136         
137         
138         
139         
140         if ( separatorPatternStartSlashMismatch( pattern, str, separator ) )
141         {
142             return false;
143         }
144 
145         List<String> patDirs = tokenizePath( pattern, separator );
146         List<String> strDirs = tokenizePath( str, separator );
147 
148         int patIdxStart = 0;
149         int patIdxEnd = patDirs.size() - 1;
150         int strIdxStart = 0;
151         int strIdxEnd = strDirs.size() - 1;
152 
153         
154         while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
155         {
156             String patDir = patDirs.get( patIdxStart );
157             if ( "**".equals( patDir ) )
158             {
159                 break;
160             }
161             if ( !match( patDir, strDirs.get( strIdxStart ), isCaseSensitive ) )
162             {
163                 return false;
164             }
165             patIdxStart++;
166             strIdxStart++;
167         }
168 
169         return strIdxStart > strIdxEnd || patIdxStart <= patIdxEnd;
170     }
171 
172     
173 
174 
175 
176 
177 
178 
179 
180 
181 
182     public static boolean matchPath( String pattern, String str )
183     {
184         return matchPath( pattern, str, true );
185     }
186 
187     
188 
189 
190 
191 
192 
193 
194 
195 
196 
197 
198 
199     public static boolean matchPath( String pattern, String str, boolean isCaseSensitive )
200     {
201         if ( pattern.length() > ( REGEX_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
202             && pattern.startsWith( REGEX_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX ) )
203         {
204             pattern =
205                 pattern.substring( REGEX_HANDLER_PREFIX.length(), pattern.length() - PATTERN_HANDLER_SUFFIX.length() );
206 
207             return str.matches( pattern );
208         }
209         else
210         {
211             if ( pattern.length() > ( ANT_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
212                 && pattern.startsWith( ANT_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX ) )
213             {
214                 pattern = pattern.substring( ANT_HANDLER_PREFIX.length(),
215                                              pattern.length() - PATTERN_HANDLER_SUFFIX.length() );
216             }
217 
218             return matchAntPathPattern( pattern, str, isCaseSensitive );
219         }
220     }
221 
222     private static boolean matchAntPathPattern( String pattern, String str, boolean isCaseSensitive )
223     {
224         
225         
226         
227         
228         if ( str.startsWith( File.separator ) != pattern.startsWith( File.separator ) )
229         {
230             return false;
231         }
232 
233         List<String> patDirs = tokenizePath( pattern, File.separator );
234         List<String> strDirs = tokenizePath( str, File.separator );
235 
236         int patIdxStart = 0;
237         int patIdxEnd = patDirs.size() - 1;
238         int strIdxStart = 0;
239         int strIdxEnd = strDirs.size() - 1;
240 
241         
242         while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
243         {
244             String patDir = patDirs.get( patIdxStart );
245             if ( "**".equals( patDir ) )
246             {
247                 break;
248             }
249             if ( !match( patDir, strDirs.get( strIdxStart ), isCaseSensitive ) )
250             {
251                 return false;
252             }
253             patIdxStart++;
254             strIdxStart++;
255         }
256         if ( strIdxStart > strIdxEnd )
257         {
258             
259             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
260             {
261                 if ( !"**".equals( patDirs.get( i ) ) )
262                 {
263                     return false;
264                 }
265             }
266             return true;
267         }
268         else
269         {
270             if ( patIdxStart > patIdxEnd )
271             {
272                 
273                 return false;
274             }
275         }
276 
277         
278         while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
279         {
280             String patDir = patDirs.get( patIdxEnd );
281             if ( "**".equals( patDir ) )
282             {
283                 break;
284             }
285             if ( !match( patDir, strDirs.get( strIdxEnd ), isCaseSensitive ) )
286             {
287                 return false;
288             }
289             patIdxEnd--;
290             strIdxEnd--;
291         }
292         if ( strIdxStart > strIdxEnd )
293         {
294             
295             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
296             {
297                 if ( !"**".equals( patDirs.get( i ) ) )
298                 {
299                     return false;
300                 }
301             }
302             return true;
303         }
304 
305         while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
306         {
307             int patIdxTmp = -1;
308             for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
309             {
310                 if ( "**".equals( patDirs.get( i ) ) )
311                 {
312                     patIdxTmp = i;
313                     break;
314                 }
315             }
316             if ( patIdxTmp == patIdxStart + 1 )
317             {
318                 
319                 patIdxStart++;
320                 continue;
321             }
322             
323             
324             int patLength = ( patIdxTmp - patIdxStart - 1 );
325             int strLength = ( strIdxEnd - strIdxStart + 1 );
326             int foundIdx = -1;
327             strLoop:
328             for ( int i = 0; i <= strLength - patLength; i++ )
329             {
330                 for ( int j = 0; j < patLength; j++ )
331                 {
332                     String subPat = patDirs.get( patIdxStart + j + 1 );
333                     String subStr = strDirs.get( strIdxStart + i + j );
334                     if ( !match( subPat, subStr, isCaseSensitive ) )
335                     {
336                         continue strLoop;
337                     }
338                 }
339 
340                 foundIdx = strIdxStart + i;
341                 break;
342             }
343 
344             if ( foundIdx == -1 )
345             {
346                 return false;
347             }
348 
349             patIdxStart = patIdxTmp;
350             strIdxStart = foundIdx + patLength;
351         }
352 
353         for ( int i = patIdxStart; i <= patIdxEnd; i++ )
354         {
355             if ( !"**".equals( patDirs.get( i ) ) )
356             {
357                 return false;
358             }
359         }
360 
361         return true;
362     }
363 
364     
365 
366 
367 
368 
369 
370 
371 
372 
373 
374 
375 
376 
377     public static boolean match( String pattern, String str )
378     {
379         return match( pattern, str, true );
380     }
381 
382     
383 
384 
385 
386 
387 
388 
389 
390 
391 
392 
393 
394 
395 
396 
397     public static boolean match( String pattern, String str, boolean isCaseSensitive )
398     {
399         char[] patArr = pattern.toCharArray();
400         char[] strArr = str.toCharArray();
401         int patIdxStart = 0;
402         int patIdxEnd = patArr.length - 1;
403         int strIdxStart = 0;
404         int strIdxEnd = strArr.length - 1;
405         char ch;
406 
407         boolean containsStar = false;
408         for ( char aPatArr : patArr )
409         {
410             if ( aPatArr == '*' )
411             {
412                 containsStar = true;
413                 break;
414             }
415         }
416 
417         if ( !containsStar )
418         {
419             
420             if ( patIdxEnd != strIdxEnd )
421             {
422                 return false; 
423             }
424             for ( int i = 0; i <= patIdxEnd; i++ )
425             {
426                 ch = patArr[i];
427                 if ( ch != '?' && !equals( ch, strArr[i], isCaseSensitive ) )
428                 {
429                     return false; 
430                 }
431             }
432             return true; 
433         }
434 
435         if ( patIdxEnd == 0 )
436         {
437             return true; 
438         }
439 
440         
441         
442         while ( ( ch = patArr[patIdxStart] ) != '*' && strIdxStart <= strIdxEnd )
443         
444         {
445             if ( ch != '?' && !equals( ch, strArr[strIdxStart], isCaseSensitive ) )
446             {
447                 return false; 
448             }
449             patIdxStart++;
450             strIdxStart++;
451         }
452         if ( strIdxStart > strIdxEnd )
453         {
454             
455             
456             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
457             {
458                 if ( patArr[i] != '*' )
459                 {
460                     return false;
461                 }
462             }
463             return true;
464         }
465 
466         
467         
468         while ( ( ch = patArr[patIdxEnd] ) != '*' && strIdxStart <= strIdxEnd )
469         
470         {
471             if ( ch != '?' && !equals( ch, strArr[strIdxEnd], isCaseSensitive ) )
472             {
473                 return false; 
474             }
475             patIdxEnd--;
476             strIdxEnd--;
477         }
478         if ( strIdxStart > strIdxEnd )
479         {
480             
481             
482             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
483             {
484                 if ( patArr[i] != '*' )
485                 {
486                     return false;
487                 }
488             }
489             return true;
490         }
491 
492         
493         
494         while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
495         {
496             int patIdxTmp = -1;
497             for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
498             {
499                 if ( patArr[i] == '*' )
500                 {
501                     patIdxTmp = i;
502                     break;
503                 }
504             }
505             if ( patIdxTmp == patIdxStart + 1 )
506             {
507                 
508                 patIdxStart++;
509                 continue;
510             }
511             
512             
513             int patLength = ( patIdxTmp - patIdxStart - 1 );
514             int strLength = ( strIdxEnd - strIdxStart + 1 );
515             int foundIdx = -1;
516             strLoop:
517             for ( int i = 0; i <= strLength - patLength; i++ )
518             {
519                 for ( int j = 0; j < patLength; j++ )
520                 {
521                     ch = patArr[patIdxStart + j + 1];
522                     if ( ch != '?' && !equals( ch, strArr[strIdxStart + i + j], isCaseSensitive ) )
523                     {
524                         continue strLoop;
525                     }
526                 }
527 
528                 foundIdx = strIdxStart + i;
529                 break;
530             }
531 
532             if ( foundIdx == -1 )
533             {
534                 return false;
535             }
536 
537             patIdxStart = patIdxTmp;
538             strIdxStart = foundIdx + patLength;
539         }
540 
541         
542         
543         for ( int i = patIdxStart; i <= patIdxEnd; i++ )
544         {
545             if ( patArr[i] != '*' )
546             {
547                 return false;
548             }
549         }
550         return true;
551     }
552 
553     
554 
555 
556     private static boolean equals( char c1, char c2, boolean isCaseSensitive )
557     {
558         if ( c1 == c2 )
559         {
560             return true;
561         }
562         if ( !isCaseSensitive )
563         {
564             
565             if ( Character.toUpperCase( c1 ) == Character.toUpperCase( c2 )
566                 || Character.toLowerCase( c1 ) == Character.toLowerCase( c2 ) )
567             {
568                 return true;
569             }
570         }
571         return false;
572     }
573 
574     
575 
576 
577 
578 
579 
580 
581 
582     private static List<String> tokenizePath( String path, String separator )
583     {
584         List<String> ret = new ArrayList<String>();
585         StringTokenizer st = new StringTokenizer( path, separator );
586         while ( st.hasMoreTokens() )
587         {
588             ret.add( st.nextToken() );
589         }
590         return ret;
591     }
592 
593 
594     static boolean matchAntPathPatternStart( @Nonnull MatchPattern pattern,
595                                              @Nonnull String str,
596                                              @Nonnull String separator,
597                                              boolean isCaseSensitive )
598     {
599         return !separatorPatternStartSlashMismatch( pattern, str, separator )
600             && matchAntPathPatternStart( pattern.getTokenizedPathString(), str, separator, isCaseSensitive );
601     }
602 
603     private static String[] tokenizePathToString( @Nonnull String path, @Nonnull String separator )
604     {
605         List<String> ret = new ArrayList<String>();
606         StringTokenizer st = new StringTokenizer( path, separator );
607         while ( st.hasMoreTokens() )
608         {
609             ret.add( st.nextToken() );
610         }
611         return ret.toArray( new String[ret.size()] );
612     }
613 
614     private static boolean matchAntPathPatternStart( @Nonnull String[] patDirs,
615                                                      @Nonnull String str,
616                                                      @Nonnull  String separator,
617                                                      boolean isCaseSensitive )
618     {
619         String[] strDirs = tokenizePathToString( str, separator );
620         return matchAntPathPatternStart( patDirs, strDirs, isCaseSensitive );
621     }
622 
623     private static boolean matchAntPathPatternStart( @Nonnull String[] patDirs,
624                                                      @Nonnull String[] tokenizedFileName,
625                                                      boolean isCaseSensitive )
626     {
627 
628         int patIdxStart = 0;
629         int patIdxEnd = patDirs.length - 1;
630         int strIdxStart = 0;
631         int strIdxEnd = tokenizedFileName.length - 1;
632 
633         
634         while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
635         {
636             String patDir = patDirs[patIdxStart];
637             if ( patDir.equals( "**" ) )
638             {
639                 break;
640             }
641             if ( !match( patDir, tokenizedFileName[strIdxStart], isCaseSensitive ) )
642             {
643                 return false;
644             }
645             patIdxStart++;
646             strIdxStart++;
647         }
648 
649         return strIdxStart > strIdxEnd || patIdxStart <= patIdxEnd;
650     }
651 
652     private static boolean separatorPatternStartSlashMismatch( @Nonnull MatchPattern matchPattern, @Nonnull String str,
653                                                                @Nonnull String separator )
654     {
655         return str.startsWith( separator ) != matchPattern.startsWith( separator );
656     }
657 
658     private static boolean separatorPatternStartSlashMismatch( String pattern, String str, String separator )
659     {
660         return str.startsWith( separator ) != pattern.startsWith( separator );
661     }
662 
663 
664     static boolean matchAntPathPattern( String[] patDirs, String[] strDirs, boolean isCaseSensitive )
665     {
666         int patIdxStart = 0;
667         int patIdxEnd = patDirs.length - 1;
668         int strIdxStart = 0;
669         int strIdxEnd = strDirs.length - 1;
670 
671         
672         while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
673         {
674             String patDir = patDirs[patIdxStart];
675             if ( patDir.equals( "**" ) )
676             {
677                 break;
678             }
679             if ( !match( patDir, strDirs[strIdxStart], isCaseSensitive ) )
680             {
681                 return false;
682             }
683             patIdxStart++;
684             strIdxStart++;
685         }
686         if ( strIdxStart > strIdxEnd )
687         {
688             
689             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
690             {
691                 if ( !patDirs[i].equals( "**" ) )
692                 {
693                     return false;
694                 }
695             }
696             return true;
697         }
698         else
699         {
700             if ( patIdxStart > patIdxEnd )
701             {
702                 
703                 return false;
704             }
705         }
706 
707         
708         while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
709         {
710             String patDir = patDirs[patIdxEnd];
711             if ( patDir.equals( "**" ) )
712             {
713                 break;
714             }
715             if ( !match( patDir, strDirs[strIdxEnd], isCaseSensitive ) )
716             {
717                 return false;
718             }
719             patIdxEnd--;
720             strIdxEnd--;
721         }
722         if ( strIdxStart > strIdxEnd )
723         {
724             
725             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
726             {
727                 if ( !patDirs[i].equals( "**" ) )
728                 {
729                     return false;
730                 }
731             }
732             return true;
733         }
734 
735         while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
736         {
737             int patIdxTmp = -1;
738             for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
739             {
740                 if ( patDirs[i].equals( "**" ) )
741                 {
742                     patIdxTmp = i;
743                     break;
744                 }
745             }
746             if ( patIdxTmp == patIdxStart + 1 )
747             {
748                 
749                 patIdxStart++;
750                 continue;
751             }
752             
753             
754             int patLength = ( patIdxTmp - patIdxStart - 1 );
755             int strLength = ( strIdxEnd - strIdxStart + 1 );
756             int foundIdx = -1;
757             strLoop:
758             for ( int i = 0; i <= strLength - patLength; i++ )
759             {
760                 for ( int j = 0; j < patLength; j++ )
761                 {
762                     String subPat = patDirs[patIdxStart + j + 1];
763                     String subStr = strDirs[strIdxStart + i + j];
764                     if ( !match( subPat, subStr, isCaseSensitive ) )
765                     {
766                         continue strLoop;
767                     }
768                 }
769 
770                 foundIdx = strIdxStart + i;
771                 break;
772             }
773 
774             if ( foundIdx == -1 )
775             {
776                 return false;
777             }
778 
779             patIdxStart = patIdxTmp;
780             strIdxStart = foundIdx + patLength;
781         }
782 
783         for ( int i = patIdxStart; i <= patIdxEnd; i++ )
784         {
785             if ( !patDirs[i].equals( "**" ) )
786             {
787                 return false;
788             }
789         }
790 
791         return true;
792     }
793 
794     static boolean isRegexPrefixedPattern( String pattern )
795     {
796         return pattern.length() > ( REGEX_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
797             && pattern.startsWith( REGEX_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX );
798     }
799 
800     static boolean isAntPrefixedPattern( String pattern )
801     {
802         return pattern.length() > ( ANT_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
803             && pattern.startsWith( ANT_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX );
804     }
805 
806     static boolean matchAntPathPattern( @Nonnull MatchPattern matchPattern, @Nonnull String str,
807                                         @Nonnull String separator, boolean isCaseSensitive )
808     {
809         if ( separatorPatternStartSlashMismatch( matchPattern, str, separator ) )
810         {
811             return false;
812         }
813         String[] patDirs = matchPattern.getTokenizedPathString();
814         String[] strDirs = tokenizePathToString( str, separator );
815         return matchAntPathPattern( patDirs, strDirs, isCaseSensitive );
816     }
817 }