001 /** 002 * Copyright 2008-2012 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.codehaus.mojo.wagon.shared; 017 018 /* 019 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license 020 * agreements. See the NOTICE file distributed with this work for additional information regarding 021 * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the 022 * "License"); you may not use this file except in compliance with the License. You may obtain a 023 * copy of the License at 024 * 025 * http://www.apache.org/licenses/LICENSE-2.0 026 * 027 * Unless required by applicable law or agreed to in writing, software distributed under the License 028 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 029 * or implied. See the License for the specific language governing permissions and limitations under 030 * the License. 031 */ 032 033 import java.util.StringTokenizer; 034 import java.util.Vector; 035 036 /** 037 * A copy of plexus-util's SelectorUtils to deal with unix file separator only. 038 */ 039 public final class SelectorUtils { 040 041 /** 042 * Tests whether or not a given path matches the start of a given pattern up to the first "**". 043 * <p> 044 * This is not a general purpose test and should only be used if you can live with false positives. For example, 045 * <code>pattern=**\a</code> and <code>str=b</code> will yield <code>true</code>. 046 * 047 * @param pattern 048 * The pattern to match against. Must not be <code>null</code>. 049 * @param str 050 * The path to match, as a String. Must not be <code>null</code>. 051 * 052 * @return whether or not a given path matches the start of a given pattern up to the first "**". 053 */ 054 public static boolean matchPatternStart(String pattern, String str) { 055 return matchPatternStart(pattern, str, true); 056 } 057 058 /** 059 * Tests whether or not a given path matches the start of a given pattern up to the first "**". 060 * <p> 061 * This is not a general purpose test and should only be used if you can live with false positives. For example, 062 * <code>pattern=**\a</code> and <code>str=b</code> will yield <code>true</code>. 063 * 064 * @param pattern 065 * The pattern to match against. Must not be <code>null</code>. 066 * @param str 067 * The path to match, as a String. Must not be <code>null</code>. 068 * @param isCaseSensitive 069 * Whether or not matching should be performed case sensitively. 070 * 071 * @return whether or not a given path matches the start of a given pattern up to the first "**". 072 */ 073 public static boolean matchPatternStart(String pattern, String str, boolean isCaseSensitive) { 074 // When str starts with a separator, pattern has to start with a 075 // separator. 076 // When pattern starts with a separator, str has to start with a 077 // separator. 078 if (str.startsWith("/") != pattern.startsWith("/")) { 079 return false; 080 } 081 082 Vector<?> patDirs = tokenizePath(pattern); 083 Vector<?> strDirs = tokenizePath(str); 084 085 int patIdxStart = 0; 086 int patIdxEnd = patDirs.size() - 1; 087 int strIdxStart = 0; 088 int strIdxEnd = strDirs.size() - 1; 089 090 // up to first '**' 091 while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) { 092 String patDir = (String) patDirs.elementAt(patIdxStart); 093 if (patDir.equals("**")) { 094 break; 095 } 096 if (!match(patDir, (String) strDirs.elementAt(strIdxStart), isCaseSensitive)) { 097 return false; 098 } 099 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 }