Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. package org.apache.archiva.configuration.util;
  2. /*
  3. * Licensed to the Apache Software Foundation (ASF) under one
  4. * or more contributor license agreements. See the NOTICE file
  5. * distributed with this work for additional information
  6. * regarding copyright ownership. The ASF licenses this file
  7. * to you under the Apache License, Version 2.0 (the
  8. * "License"); you may not use this file except in compliance
  9. * with the License. You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing,
  14. * software distributed under the License is distributed on an
  15. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  16. * KIND, either express or implied. See the License for the
  17. * specific language governing permissions and limitations
  18. * under the License.
  19. */
  20. import java.io.File;
  21. import java.nio.file.InvalidPathException;
  22. import java.nio.file.Path;
  23. import java.nio.file.Paths;
  24. /**
  25. *
  26. * This provides a path matcher like in the SelectorUtils of the ant project.
  27. *
  28. * Using code from apache ant org.apache.tools.ant.types.selectors.SelectorUtils to remove the ant dependency for code
  29. * compilation.
  30. * See https://github.com/apache/ant/blob/master/src/main/org/apache/tools/ant/types/selectors/SelectorUtils.java
  31. *
  32. * @author Martin Stockhammer <martin_s@apache.org>
  33. *
  34. *
  35. */
  36. public class PathMatcher
  37. {
  38. private static final String DEEP_TREE_MATCH = "**";
  39. /**
  40. * Tests whether or not a string matches against a pattern.
  41. * The pattern may contain two special characters:<br>
  42. * '*' means zero or more characters<br>
  43. * '?' means one and only one character
  44. *
  45. * @param pattern The pattern to match against.
  46. * Must not be <code>null</code>.
  47. * @param str The string which must be matched against the pattern.
  48. * Must not be <code>null</code>.
  49. *
  50. * @return <code>true</code> if the string matches against the pattern,
  51. * or <code>false</code> otherwise.
  52. */
  53. public static boolean match(String pattern, String str) {
  54. return match(pattern, str, true);
  55. }
  56. /**
  57. * Tests whether or not a string matches against a pattern.
  58. * The pattern may contain two special characters:<br>
  59. * '*' means zero or more characters<br>
  60. * '?' means one and only one character
  61. *
  62. * @param pattern The pattern to match against.
  63. * Must not be <code>null</code>.
  64. * @param str The string which must be matched against the pattern.
  65. * Must not be <code>null</code>.
  66. * @param caseSensitive Whether or not matching should be performed
  67. * case sensitively.
  68. *
  69. *
  70. * @return <code>true</code> if the string matches against the pattern,
  71. * or <code>false</code> otherwise.
  72. */
  73. public static boolean match(String pattern, String str,
  74. boolean caseSensitive) {
  75. char[] patArr = pattern.toCharArray();
  76. char[] strArr = str.toCharArray();
  77. int patIdxStart = 0;
  78. int patIdxEnd = patArr.length - 1;
  79. int strIdxStart = 0;
  80. int strIdxEnd = strArr.length - 1;
  81. boolean containsStar = false;
  82. for (char ch : patArr) {
  83. if (ch == '*') {
  84. containsStar = true;
  85. break;
  86. }
  87. }
  88. if (!containsStar) {
  89. // No '*'s, so we make a shortcut
  90. if (patIdxEnd != strIdxEnd) {
  91. return false; // Pattern and string do not have the same size
  92. }
  93. for (int i = 0; i <= patIdxEnd; i++) {
  94. char ch = patArr[i];
  95. if (ch != '?' && different(caseSensitive, ch, strArr[i])) {
  96. return false; // Character mismatch
  97. }
  98. }
  99. return true; // String matches against pattern
  100. }
  101. if (patIdxEnd == 0) {
  102. return true; // Pattern contains only '*', which matches anything
  103. }
  104. // Process characters before first star
  105. while (true) {
  106. char ch = patArr[patIdxStart];
  107. if (ch == '*' || strIdxStart > strIdxEnd) {
  108. break;
  109. }
  110. if (ch != '?'
  111. && different(caseSensitive, ch, strArr[strIdxStart])) {
  112. return false; // Character mismatch
  113. }
  114. patIdxStart++;
  115. strIdxStart++;
  116. }
  117. if (strIdxStart > strIdxEnd) {
  118. // All characters in the string are used. Check if only '*'s are
  119. // left in the pattern. If so, we succeeded. Otherwise failure.
  120. return allStars(patArr, patIdxStart, patIdxEnd);
  121. }
  122. // Process characters after last star
  123. while (true) {
  124. char ch = patArr[patIdxEnd];
  125. if (ch == '*' || strIdxStart > strIdxEnd) {
  126. break;
  127. }
  128. if (ch != '?' && different(caseSensitive, ch, strArr[strIdxEnd])) {
  129. return false; // Character mismatch
  130. }
  131. patIdxEnd--;
  132. strIdxEnd--;
  133. }
  134. if (strIdxStart > strIdxEnd) {
  135. // All characters in the string are used. Check if only '*'s are
  136. // left in the pattern. If so, we succeeded. Otherwise failure.
  137. return allStars(patArr, patIdxStart, patIdxEnd);
  138. }
  139. // process pattern between stars. padIdxStart and patIdxEnd point
  140. // always to a '*'.
  141. while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
  142. int patIdxTmp = -1;
  143. for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
  144. if (patArr[i] == '*') {
  145. patIdxTmp = i;
  146. break;
  147. }
  148. }
  149. if (patIdxTmp == patIdxStart + 1) {
  150. // Two stars next to each other, skip the first one.
  151. patIdxStart++;
  152. continue;
  153. }
  154. // Find the pattern between padIdxStart & padIdxTmp in str between
  155. // strIdxStart & strIdxEnd
  156. int patLength = (patIdxTmp - patIdxStart - 1);
  157. int strLength = (strIdxEnd - strIdxStart + 1);
  158. int foundIdx = -1;
  159. strLoop:
  160. for (int i = 0; i <= strLength - patLength; i++) {
  161. for (int j = 0; j < patLength; j++) {
  162. char ch = patArr[patIdxStart + j + 1];
  163. if (ch != '?' && different(caseSensitive, ch,
  164. strArr[strIdxStart + i + j])) {
  165. continue strLoop;
  166. }
  167. }
  168. foundIdx = strIdxStart + i;
  169. break;
  170. }
  171. if (foundIdx == -1) {
  172. return false;
  173. }
  174. patIdxStart = patIdxTmp;
  175. strIdxStart = foundIdx + patLength;
  176. }
  177. // All characters in the string are used. Check if only '*'s are left
  178. // in the pattern. If so, we succeeded. Otherwise failure.
  179. return allStars(patArr, patIdxStart, patIdxEnd);
  180. }
  181. private static boolean allStars(char[] chars, int start, int end) {
  182. for (int i = start; i <= end; ++i) {
  183. if (chars[i] != '*') {
  184. return false;
  185. }
  186. }
  187. return true;
  188. }
  189. private static boolean different(
  190. boolean caseSensitive, char ch, char other) {
  191. return caseSensitive
  192. ? ch != other
  193. : Character.toUpperCase(ch) != Character.toUpperCase(other);
  194. }
  195. /**
  196. * Tests whether or not a given path matches a given pattern.
  197. *
  198. * If you need to call this method multiple times with the same
  199. * pattern you should rather use TokenizedPath
  200. *
  201. *
  202. * @param pattern The pattern to match against. Must not be
  203. * <code>null</code>.
  204. * @param str The path to match, as a String. Must not be
  205. * <code>null</code>.
  206. *
  207. * @return <code>true</code> if the pattern matches against the string,
  208. * or <code>false</code> otherwise.
  209. */
  210. public static boolean matchPath(String pattern, String str) {
  211. String[] patDirs = tokenizePathAsArray( pattern, false );
  212. return matchPath(patDirs, tokenizePathAsArray( str, true ), true);
  213. }
  214. /**
  215. * Tests whether or not a given path matches a given pattern.
  216. *
  217. * If you need to call this method multiple times with the same
  218. * pattern you should rather use TokenizedPattern
  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. * @param isCaseSensitive Whether or not matching should be performed
  225. * case sensitively.
  226. *
  227. * @return <code>true</code> if the pattern matches against the string,
  228. * or <code>false</code> otherwise.
  229. */
  230. public static boolean matchPath(String pattern, String str,
  231. boolean isCaseSensitive) {
  232. String[] patDirs = tokenizePathAsArray( pattern, false );
  233. return matchPath(patDirs, tokenizePathAsArray( str, false ), isCaseSensitive);
  234. }
  235. /**
  236. *
  237. * @param path
  238. * @param osspecific
  239. * @return
  240. */
  241. static String[] tokenizePathAsArray(String path, boolean osSpecific) {
  242. Path root = null;
  243. try
  244. {
  245. Path fsPath = Paths.get( path );
  246. if ( fsPath.isAbsolute() ) {
  247. root = fsPath.getRoot();
  248. path = root.relativize( fsPath ).toString();
  249. }
  250. } catch (InvalidPathException ipe )
  251. {
  252. // invalid path, windauze hate **/*
  253. }
  254. char sep = osSpecific ? File.separatorChar : '/';
  255. int start = 0;
  256. int len = path.length();
  257. int count = 0;
  258. for (int pos = 0; pos < len; pos++) {
  259. if (path.charAt(pos) == sep) {
  260. if (pos != start) {
  261. count++;
  262. }
  263. start = pos + 1;
  264. }
  265. }
  266. if (len != start) {
  267. count++;
  268. }
  269. String[] l = new String[count + ((root == null) ? 0 : 1)];
  270. if (root != null) {
  271. l[0] = root.toString();
  272. count = 1;
  273. } else {
  274. count = 0;
  275. }
  276. start = 0;
  277. for (int pos = 0; pos < len; pos++) {
  278. if (path.charAt(pos) == sep) {
  279. if (pos != start) {
  280. String tok = path.substring(start, pos);
  281. l[count++] = tok;
  282. }
  283. start = pos + 1;
  284. }
  285. }
  286. if (len != start) {
  287. String tok = path.substring(start);
  288. l[count/*++*/] = tok;
  289. }
  290. return l;
  291. }
  292. /**
  293. * Core implementation of matchPath. It is isolated so that it
  294. * can be called from TokenizedPattern.
  295. */
  296. public static boolean matchPath( String[] tokenizedPattern, String[] strDirs,
  297. boolean isCaseSensitive ) {
  298. int patIdxStart = 0;
  299. int patIdxEnd = tokenizedPattern.length - 1;
  300. int strIdxStart = 0;
  301. int strIdxEnd = strDirs.length - 1;
  302. // up to first '**'
  303. while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
  304. String patDir = tokenizedPattern[patIdxStart];
  305. if (patDir.equals(DEEP_TREE_MATCH)) {
  306. break;
  307. }
  308. if (!match(patDir, strDirs[strIdxStart], isCaseSensitive)) {
  309. return false;
  310. }
  311. patIdxStart++;
  312. strIdxStart++;
  313. }
  314. if (strIdxStart > strIdxEnd) {
  315. // String is exhausted
  316. for (int i = patIdxStart; i <= patIdxEnd; i++) {
  317. if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) {
  318. return false;
  319. }
  320. }
  321. return true;
  322. }
  323. if (patIdxStart > patIdxEnd) {
  324. // String not exhausted, but pattern is. Failure.
  325. return false;
  326. }
  327. // up to last '**'
  328. while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
  329. String patDir = tokenizedPattern[patIdxEnd];
  330. if (patDir.equals(DEEP_TREE_MATCH)) {
  331. break;
  332. }
  333. if (!match(patDir, strDirs[strIdxEnd], isCaseSensitive)) {
  334. return false;
  335. }
  336. patIdxEnd--;
  337. strIdxEnd--;
  338. }
  339. if (strIdxStart > strIdxEnd) {
  340. // String is exhausted
  341. for (int i = patIdxStart; i <= patIdxEnd; i++) {
  342. if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) {
  343. return false;
  344. }
  345. }
  346. return true;
  347. }
  348. while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
  349. int patIdxTmp = -1;
  350. for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
  351. if (tokenizedPattern[i].equals(DEEP_TREE_MATCH)) {
  352. patIdxTmp = i;
  353. break;
  354. }
  355. }
  356. if (patIdxTmp == patIdxStart + 1) {
  357. // '**/**' situation, so skip one
  358. patIdxStart++;
  359. continue;
  360. }
  361. // Find the pattern between padIdxStart & padIdxTmp in str between
  362. // strIdxStart & strIdxEnd
  363. int patLength = (patIdxTmp - patIdxStart - 1);
  364. int strLength = (strIdxEnd - strIdxStart + 1);
  365. int foundIdx = -1;
  366. strLoop:
  367. for (int i = 0; i <= strLength - patLength; i++) {
  368. for (int j = 0; j < patLength; j++) {
  369. String subPat = tokenizedPattern[patIdxStart + j + 1];
  370. String subStr = strDirs[strIdxStart + i + j];
  371. if (!match(subPat, subStr, isCaseSensitive)) {
  372. continue strLoop;
  373. }
  374. }
  375. foundIdx = strIdxStart + i;
  376. break;
  377. }
  378. if (foundIdx == -1) {
  379. return false;
  380. }
  381. patIdxStart = patIdxTmp;
  382. strIdxStart = foundIdx + patLength;
  383. }
  384. for (int i = patIdxStart; i <= patIdxEnd; i++) {
  385. if (!DEEP_TREE_MATCH.equals(tokenizedPattern[i])) {
  386. return false;
  387. }
  388. }
  389. return true;
  390. }
  391. }