001 package org.codehaus.mojo.wagon.shared;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
005 * agreements. See the NOTICE file distributed with this work for additional information regarding
006 * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance with the License. You may obtain a
008 * copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed under the License
013 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
014 * or implied. See the License for the specific language governing permissions and limitations under
015 * the License.
016 */
017
018 import java.util.StringTokenizer;
019 import java.util.Vector;
020
021 /**
022 * A copy of plexus-util's SelectorUtils to deal with unix file separator only.
023 */
024 public final class SelectorUtils {
025
026 /**
027 * Tests whether or not a given path matches the start of a given pattern up to the first "**".
028 * <p>
029 * This is not a general purpose test and should only be used if you can live with false positives. For example,
030 * <code>pattern=**\a</code> and <code>str=b</code> will yield <code>true</code>.
031 *
032 * @param pattern
033 * The pattern to match against. Must not be <code>null</code>.
034 * @param str
035 * The path to match, as a String. Must not be <code>null</code>.
036 *
037 * @return whether or not a given path matches the start of a given pattern up to the first "**".
038 */
039 public static boolean matchPatternStart(String pattern, String str) {
040 return matchPatternStart(pattern, str, true);
041 }
042
043 /**
044 * Tests whether or not a given path matches the start of a given pattern up to the first "**".
045 * <p>
046 * This is not a general purpose test and should only be used if you can live with false positives. For example,
047 * <code>pattern=**\a</code> and <code>str=b</code> will yield <code>true</code>.
048 *
049 * @param pattern
050 * The pattern to match against. Must not be <code>null</code>.
051 * @param str
052 * The path to match, as a String. Must not be <code>null</code>.
053 * @param isCaseSensitive
054 * Whether or not matching should be performed case sensitively.
055 *
056 * @return whether or not a given path matches the start of a given pattern up to the first "**".
057 */
058 public static boolean matchPatternStart(String pattern, String str, boolean isCaseSensitive) {
059 // When str starts with a separator, pattern has to start with a
060 // separator.
061 // When pattern starts with a separator, str has to start with a
062 // separator.
063 if (str.startsWith("/") != pattern.startsWith("/")) {
064 return false;
065 }
066
067 Vector<?> patDirs = tokenizePath(pattern);
068 Vector<?> strDirs = tokenizePath(str);
069
070 int patIdxStart = 0;
071 int patIdxEnd = patDirs.size() - 1;
072 int strIdxStart = 0;
073 int strIdxEnd = strDirs.size() - 1;
074
075 // up to first '**'
076 while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
077 String patDir = (String) patDirs.elementAt(patIdxStart);
078 if (patDir.equals("**")) {
079 break;
080 }
081 if (!match(patDir, (String) strDirs.elementAt(strIdxStart), isCaseSensitive)) {
082 return false;
083 }
084 patIdxStart++;
085 strIdxStart++;
086 }
087
088 if (strIdxStart > strIdxEnd) {
089 // String is exhausted
090 return true;
091 } else if (patIdxStart > patIdxEnd) {
092 // String not exhausted, but pattern is. Failure.
093 return false;
094 } else {
095 // pattern now holds ** while string is not exhausted
096 // this will generate false positives but we can live with that.
097 return true;
098 }
099 }
100
101 /**
102 * Tests whether or not a given path matches a given pattern.
103 *
104 * @param pattern
105 * The pattern to match against. Must not be <code>null</code>.
106 * @param str
107 * The path to match, as a String. Must not be <code>null</code>.
108 *
109 * @return <code>true</code> if the pattern matches against the string, or <code>false</code> otherwise.
110 */
111 public static boolean matchPath(String pattern, String str) {
112 return matchPath(pattern, str, true);
113 }
114
115 /**
116 * Tests whether or not a given path matches a given pattern.
117 *
118 * @param pattern
119 * The pattern to match against. Must not be <code>null</code>.
120 * @param str
121 * The path to match, as a String. Must not be <code>null</code>.
122 * @param isCaseSensitive
123 * Whether or not matching should be performed case sensitively.
124 *
125 * @return <code>true</code> if the pattern matches against the string, or <code>false</code> otherwise.
126 */
127 public static boolean matchPath(String pattern, String str, boolean isCaseSensitive) {
128 // When str starts with a separator, pattern has to start with a
129 // separator.
130 // When pattern starts with a separator, str has to start with a
131 // separator.
132 if (str.startsWith("/") != pattern.startsWith("/")) {
133 return false;
134 }
135
136 Vector<?> patDirs = tokenizePath(pattern);
137 Vector<?> strDirs = tokenizePath(str);
138
139 int patIdxStart = 0;
140 int patIdxEnd = patDirs.size() - 1;
141 int strIdxStart = 0;
142 int strIdxEnd = strDirs.size() - 1;
143
144 // up to first '**'
145 while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
146 String patDir = (String) patDirs.elementAt(patIdxStart);
147 if (patDir.equals("**")) {
148 break;
149 }
150 if (!match(patDir, (String) strDirs.elementAt(strIdxStart), isCaseSensitive)) {
151 patDirs = null;
152 strDirs = null;
153 return false;
154 }
155 patIdxStart++;
156 strIdxStart++;
157 }
158 if (strIdxStart > strIdxEnd) {
159 // String is exhausted
160 for (int i = patIdxStart; i <= patIdxEnd; i++) {
161 if (!patDirs.elementAt(i).equals("**")) {
162 patDirs = null;
163 strDirs = null;
164 return false;
165 }
166 }
167 return true;
168 } else {
169 if (patIdxStart > patIdxEnd) {
170 // String not exhausted, but pattern is. Failure.
171 patDirs = null;
172 strDirs = null;
173 return false;
174 }
175 }
176
177 // up to last '**'
178 while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
179 String patDir = (String) patDirs.elementAt(patIdxEnd);
180 if (patDir.equals("**")) {
181 break;
182 }
183 if (!match(patDir, (String) strDirs.elementAt(strIdxEnd), isCaseSensitive)) {
184 patDirs = null;
185 strDirs = null;
186 return false;
187 }
188 patIdxEnd--;
189 strIdxEnd--;
190 }
191 if (strIdxStart > strIdxEnd) {
192 // String is exhausted
193 for (int i = patIdxStart; i <= patIdxEnd; i++) {
194 if (!patDirs.elementAt(i).equals("**")) {
195 patDirs = null;
196 strDirs = null;
197 return false;
198 }
199 }
200 return true;
201 }
202
203 while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
204 int patIdxTmp = -1;
205 for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
206 if (patDirs.elementAt(i).equals("**")) {
207 patIdxTmp = i;
208 break;
209 }
210 }
211 if (patIdxTmp == patIdxStart + 1) {
212 // '**/**' situation, so skip one
213 patIdxStart++;
214 continue;
215 }
216 // Find the pattern between padIdxStart & padIdxTmp in str between
217 // strIdxStart & strIdxEnd
218 int patLength = (patIdxTmp - patIdxStart - 1);
219 int strLength = (strIdxEnd - strIdxStart + 1);
220 int foundIdx = -1;
221 strLoop: for (int i = 0; i <= strLength - patLength; i++) {
222 for (int j = 0; j < patLength; j++) {
223 String subPat = (String) patDirs.elementAt(patIdxStart + j + 1);
224 String subStr = (String) strDirs.elementAt(strIdxStart + i + j);
225 if (!match(subPat, subStr, isCaseSensitive)) {
226 continue strLoop;
227 }
228 }
229
230 foundIdx = strIdxStart + i;
231 break;
232 }
233
234 if (foundIdx == -1) {
235 patDirs = null;
236 strDirs = null;
237 return false;
238 }
239
240 patIdxStart = patIdxTmp;
241 strIdxStart = foundIdx + patLength;
242 }
243
244 for (int i = patIdxStart; i <= patIdxEnd; i++) {
245 if (!patDirs.elementAt(i).equals("**")) {
246 patDirs = null;
247 strDirs = null;
248 return false;
249 }
250 }
251
252 return true;
253 }
254
255 /**
256 * Tests whether or not a string matches against a pattern. The pattern may contain two special characters:<br>
257 * '*' means zero or more characters<br>
258 * '?' means one and only one character
259 *
260 * @param pattern
261 * The pattern to match against. Must not be <code>null</code>.
262 * @param str
263 * The string which must be matched against the pattern. Must not be <code>null</code>.
264 *
265 * @return <code>true</code> if the string matches against the pattern, or <code>false</code> otherwise.
266 */
267 public static boolean match(String pattern, String str) {
268 return match(pattern, str, true);
269 }
270
271 /**
272 * Tests whether or not a string matches against a pattern. The pattern may contain two special characters:<br>
273 * '*' means zero or more characters<br>
274 * '?' means one and only one character
275 *
276 * @param pattern
277 * The pattern to match against. Must not be <code>null</code>.
278 * @param str
279 * The string which must be matched against the pattern. Must not be <code>null</code>.
280 * @param isCaseSensitive
281 * Whether or not matching should be performed case sensitively.
282 *
283 *
284 * @return <code>true</code> if the string matches against the pattern, or <code>false</code> otherwise.
285 */
286 public static boolean match(String pattern, String str, boolean isCaseSensitive) {
287 char[] patArr = pattern.toCharArray();
288 char[] strArr = str.toCharArray();
289 int patIdxStart = 0;
290 int patIdxEnd = patArr.length - 1;
291 int strIdxStart = 0;
292 int strIdxEnd = strArr.length - 1;
293 char ch;
294
295 boolean containsStar = false;
296 for (int i = 0; i < patArr.length; i++) {
297 if (patArr[i] == '*') {
298 containsStar = true;
299 break;
300 }
301 }
302
303 if (!containsStar) {
304 // No '*'s, so we make a shortcut
305 if (patIdxEnd != strIdxEnd) {
306 return false; // Pattern and string do not have the same size
307 }
308 for (int i = 0; i <= patIdxEnd; i++) {
309 ch = patArr[i];
310 if (ch != '?' && !equals(ch, strArr[i], isCaseSensitive)) {
311 return false; // Character mismatch
312 }
313 }
314 return true; // String matches against pattern
315 }
316
317 if (patIdxEnd == 0) {
318 return true; // Pattern contains only '*', which matches anything
319 }
320
321 // Process characters before first star
322 while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
323 if (ch != '?' && !equals(ch, strArr[strIdxStart], isCaseSensitive)) {
324 return false; // Character mismatch
325 }
326 patIdxStart++;
327 strIdxStart++;
328 }
329 if (strIdxStart > strIdxEnd) {
330 // All characters in the string are used. Check if only '*'s are
331 // left in the pattern. If so, we succeeded. Otherwise failure.
332 for (int i = patIdxStart; i <= patIdxEnd; i++) {
333 if (patArr[i] != '*') {
334 return false;
335 }
336 }
337 return true;
338 }
339
340 // Process characters after last star
341 while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
342 if (ch != '?' && !equals(ch, strArr[strIdxEnd], isCaseSensitive)) {
343 return false; // Character mismatch
344 }
345 patIdxEnd--;
346 strIdxEnd--;
347 }
348 if (strIdxStart > strIdxEnd) {
349 // All characters in the string are used. Check if only '*'s are
350 // left in the pattern. If so, we succeeded. Otherwise failure.
351 for (int i = patIdxStart; i <= patIdxEnd; i++) {
352 if (patArr[i] != '*') {
353 return false;
354 }
355 }
356 return true;
357 }
358
359 // process pattern between stars. padIdxStart and patIdxEnd point
360 // always to a '*'.
361 while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
362 int patIdxTmp = -1;
363 for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
364 if (patArr[i] == '*') {
365 patIdxTmp = i;
366 break;
367 }
368 }
369 if (patIdxTmp == patIdxStart + 1) {
370 // Two stars next to each other, skip the first one.
371 patIdxStart++;
372 continue;
373 }
374 // Find the pattern between padIdxStart & padIdxTmp in str between
375 // strIdxStart & strIdxEnd
376 int patLength = (patIdxTmp - patIdxStart - 1);
377 int strLength = (strIdxEnd - strIdxStart + 1);
378 int foundIdx = -1;
379 strLoop: for (int i = 0; i <= strLength - patLength; i++) {
380 for (int j = 0; j < patLength; j++) {
381 ch = patArr[patIdxStart + j + 1];
382 if (ch != '?' && !equals(ch, strArr[strIdxStart + i + j], isCaseSensitive)) {
383 continue strLoop;
384 }
385 }
386
387 foundIdx = strIdxStart + i;
388 break;
389 }
390
391 if (foundIdx == -1) {
392 return false;
393 }
394
395 patIdxStart = patIdxTmp;
396 strIdxStart = foundIdx + patLength;
397 }
398
399 // All characters in the string are used. Check if only '*'s are left
400 // in the pattern. If so, we succeeded. Otherwise failure.
401 for (int i = patIdxStart; i <= patIdxEnd; i++) {
402 if (patArr[i] != '*') {
403 return false;
404 }
405 }
406 return true;
407 }
408
409 /**
410 * Tests whether two characters are equal.
411 */
412 private static boolean equals(char c1, char c2, boolean isCaseSensitive) {
413 if (c1 == c2) {
414 return true;
415 }
416 if (!isCaseSensitive) {
417 // NOTE: Try both upper case and lower case as done by String.equalsIgnoreCase()
418 if (Character.toUpperCase(c1) == Character.toUpperCase(c2)
419 || Character.toLowerCase(c1) == Character.toLowerCase(c2)) {
420 return true;
421 }
422 }
423 return false;
424 }
425
426 /**
427 * Breaks a path up into a Vector of path elements, tokenizing on <code>File.separator</code>.
428 *
429 * @param path
430 * Path to tokenize. Must not be <code>null</code>.
431 *
432 * @return a Vector of path elements from the tokenized path
433 */
434 public static Vector<String> tokenizePath(String path) {
435 Vector<String> ret = new Vector<String>();
436 StringTokenizer st = new StringTokenizer(path, "/");
437 while (st.hasMoreTokens()) {
438 ret.addElement(st.nextToken());
439 }
440 return ret;
441 }
442
443 }