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 }