66import java .io .File ;
77import java .util .ArrayList ;
88import java .util .List ;
9+ import java .util .regex .Pattern ;
910
1011import org .apache .commons .lang .StringUtils ;
1112
@@ -49,6 +50,8 @@ private GoLangFileMatch() {
4950
5051 public static final boolean IS_WINDOWS = File .separatorChar == '\\' ;
5152
53+ private static final String PATTERN_CHARS_TO_ESCAPE = "\\ .[]{}()*+-?^$|" ;
54+
5255 public static boolean match (List <String > patterns , File file ) {
5356 return !match (patterns , file .getPath ()).isEmpty ();
5457 }
@@ -71,196 +74,194 @@ public static List<String> match(List<String> patterns, String name) {
7174 }
7275
7376 public static boolean match (String pattern , String name ) {
74- Pattern : while (!pattern .isEmpty ()) {
75- ScanResult scanResult = scanChunk (pattern );
76- pattern = scanResult .pattern ;
77- if (scanResult .star && StringUtils .isEmpty (scanResult .chunk )) {
78- // Trailing * matches rest of string unless it has a /.
79- return name .indexOf (File .separatorChar ) < 0 ;
80- }
81- // Look for match at current position.
82- String matchResult = matchChunk (scanResult .chunk , name );
77+ return buildPattern (pattern ).matcher (name ).matches ();
78+ }
8379
84- // if we're the last chunk, make sure we've exhausted the name
85- // otherwise we'll give a false result even if we could still match
86- // using the star
87- if (matchResult != null && (matchResult .isEmpty () || !pattern .isEmpty ())) {
88- name = matchResult ;
89- continue ;
90- }
91- if (scanResult .star ) {
92- for (int i = 0 ; i < name .length () && name .charAt (i ) != File .separatorChar ; i ++) {
93- matchResult = matchChunk (scanResult .chunk , name .substring (i + 1 ));
94- if (matchResult != null ) {
95- // if we're the last chunk, make sure we exhausted the name
96- if (pattern .isEmpty () && !matchResult .isEmpty ()) {
97- continue ;
98- }
99- name = matchResult ;
100- continue Pattern ;
101- }
102- }
80+ private static Pattern buildPattern (String pattern ) {
81+ StringBuilder patternStringBuilder = new StringBuilder ("^" );
82+ while (!pattern .isEmpty ()) {
83+ pattern = appendChunkPattern (patternStringBuilder , pattern );
84+
85+ if (!pattern .isEmpty ()) {
86+ patternStringBuilder .append (quote (File .separatorChar ));
10387 }
104- return false ;
10588 }
106- return name .isEmpty ();
89+ patternStringBuilder .append ("(" ).append (quote (File .separatorChar )).append (".*" ).append (")?" );
90+ return Pattern .compile (patternStringBuilder .toString ());
10791 }
10892
109- static ScanResult scanChunk ( String pattern ) {
110- boolean star = false ;
111- if (! pattern . isEmpty () && pattern . charAt ( 0 ) == '*' ) {
112- pattern = pattern . substring ( 1 );
113- star = true ;
93+ private static String quote ( char separatorChar ) {
94+ if ( StringUtils . contains ( PATTERN_CHARS_TO_ESCAPE , separatorChar )) {
95+ return " \\ " + separatorChar ;
96+ } else {
97+ return String . valueOf ( separatorChar ) ;
11498 }
99+ }
100+
101+ private static String appendChunkPattern (StringBuilder patternStringBuilder , String pattern ) {
102+ if (pattern .equals ("**" ) || pattern .startsWith ("**" + File .separator )) {
103+ patternStringBuilder .append ("(" )
104+ .append ("[^" ).append (quote (File .separatorChar )).append ("]*" )
105+ .append ("(" )
106+ .append (quote (File .separatorChar )).append ("[^" ).append (quote (File .separatorChar )).append ("]*" )
107+ .append (")*" ).append (")?" );
108+ return pattern .substring (pattern .length () == 2 ? 2 : 3 );
109+ }
110+
115111 boolean inRange = false ;
112+ int rangeFrom = 0 ;
113+ RangeParseState rangeParseState = RangeParseState .CHAR_EXPECTED ;
114+ boolean isEsc = false ;
116115 int i ;
117- Scan : for (i = 0 ; i < pattern .length (); i ++) {
118- switch (pattern .charAt (i )) {
119- case '\\' : {
120- if (!IS_WINDOWS && i + 1 < pattern .length ()) {
121- i ++;
116+ for (i = 0 ; i < pattern .length (); i ++) {
117+ char c = pattern .charAt (i );
118+ switch (c ) {
119+ case '/' :
120+ if (!inRange ) {
121+ if (!IS_WINDOWS && !isEsc ) {
122+ // end of chunk
123+ return pattern .substring (i + 1 );
124+ } else {
125+ patternStringBuilder .append (quote (c ));
126+ }
127+ } else {
128+ rangeParseState = nextStateAfterChar (rangeParseState );
122129 }
130+ isEsc = false ;
123131 break ;
124- }
125- case '[' :
126- inRange = true ;
127- break ;
128- case ']' :
129- inRange = false ;
130- break ;
131- case '*' :
132+ case '\\' :
132133 if (!inRange ) {
133- break Scan ;
134+ if (!IS_WINDOWS ) {
135+ if (isEsc ) {
136+ patternStringBuilder .append (quote (c ));
137+ isEsc = false ;
138+ } else {
139+ isEsc = true ;
140+ }
141+ } else {
142+ // end of chunk
143+ return pattern .substring (i + 1 );
144+ }
145+ } else {
146+ if (IS_WINDOWS || isEsc ) {
147+ rangeParseState = nextStateAfterChar (rangeParseState );
148+ isEsc = false ;
149+ } else {
150+ isEsc = true ;
151+ }
134152 }
135- }
136- }
137- return new ScanResult (star , pattern .substring (0 , i ), pattern .substring (i ));
138- }
139-
140- static String matchChunk (String chunk , String s ) {
141- int chunkLength = chunk .length ();
142- int chunkOffset = 0 ;
143- int sLength = s .length ();
144- int sOffset = 0 ;
145- char r ;
146- while (chunkOffset < chunkLength ) {
147- if (sOffset == sLength ) {
148- return null ;
149- }
150- switch (chunk .charAt (chunkOffset )) {
153+ break ;
151154 case '[' :
152- r = s .charAt (sOffset );
153- sOffset ++;
154- chunkOffset ++;
155- // We can't end right after '[', we're expecting at least
156- // a closing bracket and possibly a caret.
157- if (chunkOffset == chunkLength ) {
158- throw new GoLangFileMatchException ();
159- }
160- // possibly negated
161- boolean negated = chunk .charAt (chunkOffset ) == '^' ;
162- if (negated ) {
163- chunkOffset ++;
155+ if (!isEsc ) {
156+ if (inRange ) {
157+ //"[ is not expected, ] had not reached"
158+ throw new GoLangFileMatchException ();
159+ }
160+ rangeFrom = i ;
161+ rangeParseState = RangeParseState .CHAR_EXPECTED ;
162+ inRange = true ;
163+ } else {
164+ if (!inRange ) {
165+ patternStringBuilder .append (c );
166+ } else {
167+ rangeParseState = nextStateAfterChar (rangeParseState );
168+ }
164169 }
165- // parse all ranges
166- boolean match = false ;
167- int nrange = 0 ;
168- while ( true ) {
169- if (chunkOffset < chunkLength && chunk . charAt ( chunkOffset ) == ']' && nrange > 0 ) {
170- chunkOffset ++;
171- break ;
170+ isEsc = false ;
171+ break ;
172+ case ']' :
173+ if (! isEsc ) {
174+ if (! inRange ) {
175+ //"] is not expected, [ was not met"
176+ throw new GoLangFileMatchException () ;
172177 }
173- GetEscResult result = getEsc (chunk , chunkOffset , chunkLength );
174- char lo = result .lo ;
175- char hi = lo ;
176- chunkOffset = result .chunkOffset ;
177- if (chunk .charAt (chunkOffset ) == '-' ) {
178- result = getEsc (chunk , ++chunkOffset , chunkLength );
179- chunkOffset = result .chunkOffset ;
180- hi = result .lo ;
178+ if (rangeParseState == RangeParseState .CHAR_EXPECTED_AFTER_DASH ) {
179+ // character range not finished
180+ throw new GoLangFileMatchException ();
181181 }
182- if (lo <= r && r <= hi ) {
183- match = true ;
182+ patternStringBuilder .append (pattern .substring (rangeFrom , i + 1 ));
183+ inRange = false ;
184+ } else {
185+ if (!inRange ) {
186+ patternStringBuilder .append (c );
187+ } else {
188+ rangeParseState = nextStateAfterChar (rangeParseState );
184189 }
185- nrange ++;
186190 }
187- if (match == negated ) {
188- return null ;
191+ isEsc = false ;
192+ break ;
193+ case '*' :
194+ if (!inRange ) {
195+ if (!isEsc ) {
196+ patternStringBuilder .append ("[^" ).append (quote (File .separatorChar )).append ("]*" );
197+ } else {
198+ patternStringBuilder .append (quote (c ));
199+ }
200+ } else {
201+ rangeParseState = nextStateAfterChar (rangeParseState );
189202 }
203+ isEsc = false ;
190204 break ;
191-
192205 case '?' :
193- if (s .charAt (sOffset ) == File .separatorChar ) {
194- return null ;
206+ if (!inRange ) {
207+ if (!isEsc ) {
208+ patternStringBuilder .append ("[^" ).append (quote (File .separatorChar )).append ("]" );
209+ } else {
210+ patternStringBuilder .append (quote (c ));
211+ }
212+ } else {
213+ rangeParseState = nextStateAfterChar (rangeParseState );
195214 }
196- sOffset ++;
197- chunkOffset ++;
215+ isEsc = false ;
198216 break ;
199- case '\\' :
200- if (!IS_WINDOWS ) {
201- chunkOffset ++;
202- if (chunkOffset == chunkLength ) {
203- throw new GoLangFileMatchException ();
217+ case '-' :
218+ if (!inRange ) {
219+ patternStringBuilder .append (quote (c ));
220+ } else {
221+ if (!isEsc ) {
222+ if (rangeParseState != RangeParseState .CHAR_OR_DASH_EXPECTED ) {
223+ // - not expected
224+ throw new GoLangFileMatchException ();
225+ }
226+ rangeParseState = RangeParseState .CHAR_EXPECTED_AFTER_DASH ;
227+ } else {
228+ rangeParseState = nextStateAfterChar (rangeParseState );
204229 }
205230 }
206- // fallthrough
231+ isEsc = false ;
232+ break ;
207233 default :
208- if (chunk .charAt (chunkOffset ) != s .charAt (sOffset )) {
209- return null ;
234+ if (!inRange ) {
235+ patternStringBuilder .append (quote (c ));
236+ } else {
237+ rangeParseState = nextStateAfterChar (rangeParseState );
210238 }
211- sOffset ++;
212- chunkOffset ++;
239+ isEsc = false ;
213240 }
214241 }
215- return s .substring (sOffset );
216- }
217-
218- static GetEscResult getEsc (String chunk , int chunkOffset , int chunkLength ) {
219- if (chunkOffset == chunkLength ) {
242+ if (isEsc ) {
243+ // "Escaped character missing"
220244 throw new GoLangFileMatchException ();
221245 }
222- char r = chunk . charAt ( chunkOffset );
223- if ( r == '-' || r == ']' ) {
246+ if ( inRange ) {
247+ // "Character range not finished"
224248 throw new GoLangFileMatchException ();
225249 }
226- if (r == '\\' && !IS_WINDOWS ) {
227- chunkOffset ++;
228- if (chunkOffset == chunkLength ) {
229- throw new GoLangFileMatchException ();
230- }
231-
232- }
233- r = chunk .charAt (chunkOffset );
234- chunkOffset ++;
235- if (chunkOffset == chunkLength ) {
236- throw new GoLangFileMatchException ();
237- }
238- return new GetEscResult (r , chunkOffset );
250+ return "" ;
239251 }
240252
241- private static final class ScanResult {
242- public boolean star ;
243-
244- public String chunk ;
245-
246- public String pattern ;
247-
248- ScanResult (boolean star , String chunk , String pattern ) {
249- this .star = star ;
250- this .chunk = chunk ;
251- this .pattern = pattern ;
253+ private static RangeParseState nextStateAfterChar (RangeParseState currentState ) {
254+ if (currentState == RangeParseState .CHAR_EXPECTED_AFTER_DASH ) {
255+ return RangeParseState .CHAR_EXPECTED ;
256+ } else {
257+ return RangeParseState .CHAR_OR_DASH_EXPECTED ;
252258 }
253259 }
254260
255- private static final class GetEscResult {
256- public char lo ;
257-
258- public int chunkOffset ;
259-
260- GetEscResult (char lo , int chunkOffset ) {
261- this .lo = lo ;
262- this .chunkOffset = chunkOffset ;
263- }
261+ private enum RangeParseState {
262+ CHAR_EXPECTED ,
263+ CHAR_OR_DASH_EXPECTED ,
264+ CHAR_EXPECTED_AFTER_DASH
264265 }
265266
266267}
0 commit comments