You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PathMatcher.java 8.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. /*
  2. * Copyright (C) 2014, Andrey Loskutov <loskutov@gmx.de>
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.ignore.internal;
  44. import static org.eclipse.jgit.ignore.internal.Strings.checkWildCards;
  45. import static org.eclipse.jgit.ignore.internal.Strings.count;
  46. import static org.eclipse.jgit.ignore.internal.Strings.getPathSeparator;
  47. import static org.eclipse.jgit.ignore.internal.Strings.isWildCard;
  48. import static org.eclipse.jgit.ignore.internal.Strings.split;
  49. import java.util.ArrayList;
  50. import java.util.List;
  51. import org.eclipse.jgit.errors.InvalidPatternException;
  52. import org.eclipse.jgit.ignore.FastIgnoreRule;
  53. import org.eclipse.jgit.ignore.internal.Strings.PatternState;
  54. /**
  55. * Matcher built by patterns consists of multiple path segments.
  56. * <p>
  57. * This class is immutable and thread safe.
  58. *
  59. * @since 3.6
  60. */
  61. public class PathMatcher extends AbstractMatcher {
  62. private static final WildMatcher WILD = WildMatcher.INSTANCE;
  63. private final List<IMatcher> matchers;
  64. private final char slash;
  65. private boolean beginning;
  66. PathMatcher(String pattern, Character pathSeparator, boolean dirOnly)
  67. throws InvalidPatternException {
  68. super(pattern, dirOnly);
  69. slash = getPathSeparator(pathSeparator);
  70. beginning = pattern.indexOf(slash) == 0;
  71. if (isSimplePathWithSegments(pattern))
  72. matchers = null;
  73. else
  74. matchers = createMatchers(split(pattern, slash), pathSeparator,
  75. dirOnly);
  76. }
  77. private boolean isSimplePathWithSegments(String path) {
  78. return !isWildCard(path) && count(path, slash, true) > 0;
  79. }
  80. static private List<IMatcher> createMatchers(List<String> segments,
  81. Character pathSeparator, boolean dirOnly)
  82. throws InvalidPatternException {
  83. List<IMatcher> matchers = new ArrayList<IMatcher>(segments.size());
  84. for (int i = 0; i < segments.size(); i++) {
  85. String segment = segments.get(i);
  86. IMatcher matcher = createNameMatcher0(segment, pathSeparator,
  87. dirOnly);
  88. if (matcher == WILD && i > 0
  89. && matchers.get(matchers.size() - 1) == WILD)
  90. // collapse wildmatchers **/** is same as **
  91. continue;
  92. matchers.add(matcher);
  93. }
  94. return matchers;
  95. }
  96. /**
  97. *
  98. * @param pattern
  99. * @param pathSeparator
  100. * if this parameter isn't null then this character will not
  101. * match at wildcards(* and ? are wildcards).
  102. * @param dirOnly
  103. * @return never null
  104. * @throws InvalidPatternException
  105. */
  106. public static IMatcher createPathMatcher(String pattern,
  107. Character pathSeparator, boolean dirOnly)
  108. throws InvalidPatternException {
  109. pattern = pattern.trim();
  110. char slash = Strings.getPathSeparator(pathSeparator);
  111. // ignore possible leading and trailing slash
  112. int slashIdx = pattern.indexOf(slash, 1);
  113. if (slashIdx > 0 && slashIdx < pattern.length() - 1)
  114. return new PathMatcher(pattern, pathSeparator, dirOnly);
  115. return createNameMatcher0(pattern, pathSeparator, dirOnly);
  116. }
  117. private static IMatcher createNameMatcher0(String segment,
  118. Character pathSeparator, boolean dirOnly)
  119. throws InvalidPatternException {
  120. // check if we see /** or ** segments => double star pattern
  121. if (WildMatcher.WILDMATCH.equals(segment)
  122. || WildMatcher.WILDMATCH2.equals(segment))
  123. return WILD;
  124. PatternState state = checkWildCards(segment);
  125. switch (state) {
  126. case LEADING_ASTERISK_ONLY:
  127. return new LeadingAsteriskMatcher(segment, pathSeparator, dirOnly);
  128. case TRAILING_ASTERISK_ONLY:
  129. return new TrailingAsteriskMatcher(segment, pathSeparator, dirOnly);
  130. case COMPLEX:
  131. return new WildCardMatcher(segment, pathSeparator, dirOnly);
  132. default:
  133. return new NameMatcher(segment, pathSeparator, dirOnly);
  134. }
  135. }
  136. public boolean matches(String path, boolean assumeDirectory) {
  137. if (matchers == null)
  138. return simpleMatch(path, assumeDirectory);
  139. return iterate(path, 0, path.length(), assumeDirectory);
  140. }
  141. /*
  142. * Stupid but fast string comparison: the case where we don't have to match
  143. * wildcards or single segments (mean: this is multi-segment path which must
  144. * be at the beginning of the another string)
  145. */
  146. private boolean simpleMatch(String path, boolean assumeDirectory) {
  147. boolean hasSlash = path.indexOf(slash) == 0;
  148. if (beginning && !hasSlash)
  149. path = slash + path;
  150. if (!beginning && hasSlash)
  151. path = path.substring(1);
  152. if (path.equals(pattern))
  153. // Exact match
  154. if (dirOnly && !assumeDirectory)
  155. // Directory expectations not met
  156. return false;
  157. else
  158. // Directory expectations met
  159. return true;
  160. /*
  161. * Add slashes for startsWith check. This avoids matching e.g.
  162. * "/src/new" to /src/newfile" but allows "/src/new" to match
  163. * "/src/new/newfile", as is the git standard
  164. */
  165. if (path.startsWith(pattern + FastIgnoreRule.PATH_SEPARATOR))
  166. return true;
  167. return false;
  168. }
  169. public boolean matches(String segment, int startIncl, int endExcl,
  170. boolean assumeDirectory) {
  171. throw new UnsupportedOperationException(
  172. "Path matcher works only on entire paths"); //$NON-NLS-1$
  173. }
  174. boolean iterate(final String path, final int startIncl, final int endExcl,
  175. boolean assumeDirectory) {
  176. int matcher = 0;
  177. int right = startIncl;
  178. boolean match = false;
  179. int lastWildmatch = -1;
  180. while (true) {
  181. int left = right;
  182. right = path.indexOf(slash, right);
  183. if (right == -1) {
  184. if (left < endExcl)
  185. match = matches(matcher, path, left, endExcl,
  186. assumeDirectory);
  187. if (match) {
  188. if (matcher == matchers.size() - 2
  189. && matchers.get(matcher + 1) == WILD)
  190. // ** can match *nothing*: a/b/** match also a/b
  191. return true;
  192. if (matcher < matchers.size() - 1
  193. && matchers.get(matcher) == WILD) {
  194. // ** can match *nothing*: a/**/b match also a/b
  195. matcher++;
  196. match = matches(matcher, path, left, endExcl,
  197. assumeDirectory);
  198. } else if (dirOnly && !assumeDirectory)
  199. // Directory expectations not met
  200. return false;
  201. }
  202. return match && matcher + 1 == matchers.size();
  203. }
  204. if (right - left > 0)
  205. match = matches(matcher, path, left, right, assumeDirectory);
  206. else {
  207. // path starts with slash???
  208. right++;
  209. continue;
  210. }
  211. if (match) {
  212. if (matchers.get(matcher) == WILD) {
  213. lastWildmatch = matcher;
  214. // ** can match *nothing*: a/**/b match also a/b
  215. right = left - 1;
  216. }
  217. matcher++;
  218. if (matcher == matchers.size())
  219. return true;
  220. } else if (lastWildmatch != -1)
  221. matcher = lastWildmatch + 1;
  222. else
  223. return false;
  224. right++;
  225. }
  226. }
  227. boolean matches(int matcherIdx, String path, int startIncl, int endExcl,
  228. boolean assumeDirectory) {
  229. IMatcher matcher = matchers.get(matcherIdx);
  230. return matcher.matches(path, startIncl, endExcl, assumeDirectory);
  231. }
  232. }