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