1 /**
2 * Copyright 2008-2012 The Kuali Foundation
3 *
4 * Licensed under the Educational Community License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.opensource.org/licenses/ecl2.php
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.codehaus.mojo.wagon.shared;
17
18 /*
19 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
20 * agreements. See the NOTICE file distributed with this work for additional information regarding
21 * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
22 * "License"); you may not use this file except in compliance with the License. You may obtain a
23 * copy of the License at
24 *
25 * http://www.apache.org/licenses/LICENSE-2.0
26 *
27 * Unless required by applicable law or agreed to in writing, software distributed under the License
28 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
29 * or implied. See the License for the specific language governing permissions and limitations under
30 * the License.
31 */
32
33 import java.util.StringTokenizer;
34 import java.util.Vector;
35
36 /**
37 * A copy of plexus-util's SelectorUtils to deal with unix file separator only.
38 */
39 public final class SelectorUtils {
40
41 /**
42 * Tests whether or not a given path matches the start of a given pattern up to the first "**".
43 * <p>
44 * This is not a general purpose test and should only be used if you can live with false positives. For example,
45 * <code>pattern=**\a</code> and <code>str=b</code> will yield <code>true</code>.
46 *
47 * @param pattern
48 * The pattern to match against. Must not be <code>null</code>.
49 * @param str
50 * The path to match, as a String. Must not be <code>null</code>.
51 *
52 * @return whether or not a given path matches the start of a given pattern up to the first "**".
53 */
54 public static boolean matchPatternStart(String pattern, String str) {
55 return matchPatternStart(pattern, str, true);
56 }
57
58 /**
59 * Tests whether or not a given path matches the start of a given pattern up to the first "**".
60 * <p>
61 * This is not a general purpose test and should only be used if you can live with false positives. For example,
62 * <code>pattern=**\a</code> and <code>str=b</code> will yield <code>true</code>.
63 *
64 * @param pattern
65 * The pattern to match against. Must not be <code>null</code>.
66 * @param str
67 * The path to match, as a String. Must not be <code>null</code>.
68 * @param isCaseSensitive
69 * Whether or not matching should be performed case sensitively.
70 *
71 * @return whether or not a given path matches the start of a given pattern up to the first "**".
72 */
73 public static boolean matchPatternStart(String pattern, String str, boolean isCaseSensitive) {
74 // When str starts with a separator, pattern has to start with a
75 // separator.
76 // When pattern starts with a separator, str has to start with a
77 // separator.
78 if (str.startsWith("/") != pattern.startsWith("/")) {
79 return false;
80 }
81
82 Vector<?> patDirs = tokenizePath(pattern);
83 Vector<?> strDirs = tokenizePath(str);
84
85 int patIdxStart = 0;
86 int patIdxEnd = patDirs.size() - 1;
87 int strIdxStart = 0;
88 int strIdxEnd = strDirs.size() - 1;
89
90 // up to first '**'
91 while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
92 String patDir = (String) patDirs.elementAt(patIdxStart);
93 if (patDir.equals("**")) {
94 break;
95 }
96 if (!match(patDir, (String) strDirs.elementAt(strIdxStart), isCaseSensitive)) {
97 return false;
98 }
99 patIdxStart++;
100 strIdxStart++;
101 }
102
103 if (strIdxStart > strIdxEnd) {
104 // String is exhausted
105 return true;
106 } else if (patIdxStart > patIdxEnd) {
107 // String not exhausted, but pattern is. Failure.
108 return false;
109 } else {
110 // pattern now holds ** while string is not exhausted
111 // this will generate false positives but we can live with that.
112 return true;
113 }
114 }
115
116 /**
117 * Tests whether or not a given path matches a given pattern.
118 *
119 * @param pattern
120 * The pattern to match against. Must not be <code>null</code>.
121 * @param str
122 * The path to match, as a String. Must not be <code>null</code>.
123 *
124 * @return <code>true</code> if the pattern matches against the string, or <code>false</code> otherwise.
125 */
126 public static boolean matchPath(String pattern, String str) {
127 return matchPath(pattern, str, true);
128 }
129
130 /**
131 * Tests whether or not a given path matches a given pattern.
132 *
133 * @param pattern
134 * The pattern to match against. Must not be <code>null</code>.
135 * @param str
136 * The path to match, as a String. Must not be <code>null</code>.
137 * @param isCaseSensitive
138 * Whether or not matching should be performed case sensitively.
139 *
140 * @return <code>true</code> if the pattern matches against the string, or <code>false</code> otherwise.
141 */
142 public static boolean matchPath(String pattern, String str, boolean isCaseSensitive) {
143 // When str starts with a separator, pattern has to start with a
144 // separator.
145 // When pattern starts with a separator, str has to start with a
146 // separator.
147 if (str.startsWith("/") != pattern.startsWith("/")) {
148 return false;
149 }
150
151 Vector<?> patDirs = tokenizePath(pattern);
152 Vector<?> strDirs = tokenizePath(str);
153
154 int patIdxStart = 0;
155 int patIdxEnd = patDirs.size() - 1;
156 int strIdxStart = 0;
157 int strIdxEnd = strDirs.size() - 1;
158
159 // up to first '**'
160 while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
161 String patDir = (String) patDirs.elementAt(patIdxStart);
162 if (patDir.equals("**")) {
163 break;
164 }
165 if (!match(patDir, (String) strDirs.elementAt(strIdxStart), isCaseSensitive)) {
166 patDirs = null;
167 strDirs = null;
168 return false;
169 }
170 patIdxStart++;
171 strIdxStart++;
172 }
173 if (strIdxStart > strIdxEnd) {
174 // String is exhausted
175 for (int i = patIdxStart; i <= patIdxEnd; i++) {
176 if (!patDirs.elementAt(i).equals("**")) {
177 patDirs = null;
178 strDirs = null;
179 return false;
180 }
181 }
182 return true;
183 } else {
184 if (patIdxStart > patIdxEnd) {
185 // String not exhausted, but pattern is. Failure.
186 patDirs = null;
187 strDirs = null;
188 return false;
189 }
190 }
191
192 // up to last '**'
193 while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
194 String patDir = (String) patDirs.elementAt(patIdxEnd);
195 if (patDir.equals("**")) {
196 break;
197 }
198 if (!match(patDir, (String) strDirs.elementAt(strIdxEnd), isCaseSensitive)) {
199 patDirs = null;
200 strDirs = null;
201 return false;
202 }
203 patIdxEnd--;
204 strIdxEnd--;
205 }
206 if (strIdxStart > strIdxEnd) {
207 // String is exhausted
208 for (int i = patIdxStart; i <= patIdxEnd; i++) {
209 if (!patDirs.elementAt(i).equals("**")) {
210 patDirs = null;
211 strDirs = null;
212 return false;
213 }
214 }
215 return true;
216 }
217
218 while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
219 int patIdxTmp = -1;
220 for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
221 if (patDirs.elementAt(i).equals("**")) {
222 patIdxTmp = i;
223 break;
224 }
225 }
226 if (patIdxTmp == patIdxStart + 1) {
227 // '**/**' situation, so skip one
228 patIdxStart++;
229 continue;
230 }
231 // Find the pattern between padIdxStart & padIdxTmp in str between
232 // strIdxStart & strIdxEnd
233 int patLength = (patIdxTmp - patIdxStart - 1);
234 int strLength = (strIdxEnd - strIdxStart + 1);
235 int foundIdx = -1;
236 strLoop: for (int i = 0; i <= strLength - patLength; i++) {
237 for (int j = 0; j < patLength; j++) {
238 String subPat = (String) patDirs.elementAt(patIdxStart + j + 1);
239 String subStr = (String) strDirs.elementAt(strIdxStart + i + j);
240 if (!match(subPat, subStr, isCaseSensitive)) {
241 continue strLoop;
242 }
243 }
244
245 foundIdx = strIdxStart + i;
246 break;
247 }
248
249 if (foundIdx == -1) {
250 patDirs = null;
251 strDirs = null;
252 return false;
253 }
254
255 patIdxStart = patIdxTmp;
256 strIdxStart = foundIdx + patLength;
257 }
258
259 for (int i = patIdxStart; i <= patIdxEnd; i++) {
260 if (!patDirs.elementAt(i).equals("**")) {
261 patDirs = null;
262 strDirs = null;
263 return false;
264 }
265 }
266
267 return true;
268 }
269
270 /**
271 * Tests whether or not a string matches against a pattern. The pattern may contain two special characters:<br>
272 * '*' means zero or more characters<br>
273 * '?' means one and only one character
274 *
275 * @param pattern
276 * The pattern to match against. Must not be <code>null</code>.
277 * @param str
278 * The string which must be matched against the pattern. Must not be <code>null</code>.
279 *
280 * @return <code>true</code> if the string matches against the pattern, or <code>false</code> otherwise.
281 */
282 public static boolean match(String pattern, String str) {
283 return match(pattern, str, true);
284 }
285
286 /**
287 * Tests whether or not a string matches against a pattern. The pattern may contain two special characters:<br>
288 * '*' means zero or more characters<br>
289 * '?' means one and only one character
290 *
291 * @param pattern
292 * The pattern to match against. Must not be <code>null</code>.
293 * @param str
294 * The string which must be matched against the pattern. Must not be <code>null</code>.
295 * @param isCaseSensitive
296 * Whether or not matching should be performed case sensitively.
297 *
298 *
299 * @return <code>true</code> if the string matches against the pattern, or <code>false</code> otherwise.
300 */
301 public static boolean match(String pattern, String str, boolean isCaseSensitive) {
302 char[] patArr = pattern.toCharArray();
303 char[] strArr = str.toCharArray();
304 int patIdxStart = 0;
305 int patIdxEnd = patArr.length - 1;
306 int strIdxStart = 0;
307 int strIdxEnd = strArr.length - 1;
308 char ch;
309
310 boolean containsStar = false;
311 for (int i = 0; i < patArr.length; i++) {
312 if (patArr[i] == '*') {
313 containsStar = true;
314 break;
315 }
316 }
317
318 if (!containsStar) {
319 // No '*'s, so we make a shortcut
320 if (patIdxEnd != strIdxEnd) {
321 return false; // Pattern and string do not have the same size
322 }
323 for (int i = 0; i <= patIdxEnd; i++) {
324 ch = patArr[i];
325 if (ch != '?' && !equals(ch, strArr[i], isCaseSensitive)) {
326 return false; // Character mismatch
327 }
328 }
329 return true; // String matches against pattern
330 }
331
332 if (patIdxEnd == 0) {
333 return true; // Pattern contains only '*', which matches anything
334 }
335
336 // Process characters before first star
337 while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
338 if (ch != '?' && !equals(ch, strArr[strIdxStart], isCaseSensitive)) {
339 return false; // Character mismatch
340 }
341 patIdxStart++;
342 strIdxStart++;
343 }
344 if (strIdxStart > strIdxEnd) {
345 // All characters in the string are used. Check if only '*'s are
346 // left in the pattern. If so, we succeeded. Otherwise failure.
347 for (int i = patIdxStart; i <= patIdxEnd; i++) {
348 if (patArr[i] != '*') {
349 return false;
350 }
351 }
352 return true;
353 }
354
355 // Process characters after last star
356 while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
357 if (ch != '?' && !equals(ch, strArr[strIdxEnd], isCaseSensitive)) {
358 return false; // Character mismatch
359 }
360 patIdxEnd--;
361 strIdxEnd--;
362 }
363 if (strIdxStart > strIdxEnd) {
364 // All characters in the string are used. Check if only '*'s are
365 // left in the pattern. If so, we succeeded. Otherwise failure.
366 for (int i = patIdxStart; i <= patIdxEnd; i++) {
367 if (patArr[i] != '*') {
368 return false;
369 }
370 }
371 return true;
372 }
373
374 // process pattern between stars. padIdxStart and patIdxEnd point
375 // always to a '*'.
376 while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
377 int patIdxTmp = -1;
378 for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
379 if (patArr[i] == '*') {
380 patIdxTmp = i;
381 break;
382 }
383 }
384 if (patIdxTmp == patIdxStart + 1) {
385 // Two stars next to each other, skip the first one.
386 patIdxStart++;
387 continue;
388 }
389 // Find the pattern between padIdxStart & padIdxTmp in str between
390 // strIdxStart & strIdxEnd
391 int patLength = (patIdxTmp - patIdxStart - 1);
392 int strLength = (strIdxEnd - strIdxStart + 1);
393 int foundIdx = -1;
394 strLoop: for (int i = 0; i <= strLength - patLength; i++) {
395 for (int j = 0; j < patLength; j++) {
396 ch = patArr[patIdxStart + j + 1];
397 if (ch != '?' && !equals(ch, strArr[strIdxStart + i + j], isCaseSensitive)) {
398 continue strLoop;
399 }
400 }
401
402 foundIdx = strIdxStart + i;
403 break;
404 }
405
406 if (foundIdx == -1) {
407 return false;
408 }
409
410 patIdxStart = patIdxTmp;
411 strIdxStart = foundIdx + patLength;
412 }
413
414 // All characters in the string are used. Check if only '*'s are left
415 // in the pattern. If so, we succeeded. Otherwise failure.
416 for (int i = patIdxStart; i <= patIdxEnd; i++) {
417 if (patArr[i] != '*') {
418 return false;
419 }
420 }
421 return true;
422 }
423
424 /**
425 * Tests whether two characters are equal.
426 */
427 private static boolean equals(char c1, char c2, boolean isCaseSensitive) {
428 if (c1 == c2) {
429 return true;
430 }
431 if (!isCaseSensitive) {
432 // NOTE: Try both upper case and lower case as done by String.equalsIgnoreCase()
433 if (Character.toUpperCase(c1) == Character.toUpperCase(c2)
434 || Character.toLowerCase(c1) == Character.toLowerCase(c2)) {
435 return true;
436 }
437 }
438 return false;
439 }
440
441 /**
442 * Breaks a path up into a Vector of path elements, tokenizing on <code>File.separator</code>.
443 *
444 * @param path
445 * Path to tokenize. Must not be <code>null</code>.
446 *
447 * @return a Vector of path elements from the tokenized path
448 */
449 public static Vector<String> tokenizePath(String path) {
450 Vector<String> ret = new Vector<String>();
451 StringTokenizer st = new StringTokenizer(path, "/");
452 while (st.hasMoreTokens()) {
453 ret.addElement(st.nextToken());
454 }
455 return ret;
456 }
457
458 }