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 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  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.*;
  45. import java.util.*;
  46. import org.eclipse.jgit.errors.InvalidPatternException;
  47. import org.eclipse.jgit.ignore.FastIgnoreRule;
  48. /**
  49. * Matcher built by patterns consists of multiple path segments.
  50. * <p>
  51. * This class is immutable and thread safe.
  52. *
  53. * @since 3.6
  54. */
  55. public class PathMatcher extends AbstractMatcher {
  56. private static final WildMatcher WILD = WildMatcher.INSTANCE;
  57. private final List<IMatcher> matchers;
  58. private final char slash;
  59. private boolean beginning;
  60. PathMatcher(String pattern, Character pathSeparator, boolean dirOnly)
  61. throws InvalidPatternException {
  62. super(pattern, dirOnly);
  63. slash = getPathSeparator(pathSeparator);
  64. beginning = pattern.indexOf(slash) == 0;
  65. if (isSimplePathWithSegments(pattern))
  66. matchers = null;
  67. else
  68. matchers = createMatchers(split(pattern, slash), pathSeparator,
  69. dirOnly);
  70. }
  71. private boolean isSimplePathWithSegments(String path) {
  72. return !isWildCard(path) && count(path, slash, true) > 0;
  73. }
  74. static private List<IMatcher> createMatchers(List<String> segments,
  75. Character pathSeparator, boolean dirOnly)
  76. throws InvalidPatternException {
  77. List<IMatcher> matchers = new ArrayList<IMatcher>(segments.size());
  78. for (int i = 0; i < segments.size(); i++) {
  79. String segment = segments.get(i);
  80. IMatcher matcher = createNameMatcher0(segment, pathSeparator,
  81. dirOnly);
  82. if (matcher == WILD && i > 0
  83. && matchers.get(matchers.size() - 1) == WILD)
  84. // collapse wildmatchers **/** is same as **
  85. continue;
  86. matchers.add(matcher);
  87. }
  88. return matchers;
  89. }
  90. /**
  91. *
  92. * @param pattern
  93. * @param pathSeparator
  94. * if this parameter isn't null then this character will not
  95. * match at wildcards(* and ? are wildcards).
  96. * @param dirOnly
  97. * @return never null
  98. * @throws InvalidPatternException
  99. */
  100. public static IMatcher createPathMatcher(String pattern,
  101. Character pathSeparator, boolean dirOnly)
  102. throws InvalidPatternException {
  103. pattern = pattern.trim();
  104. char slash = Strings.getPathSeparator(pathSeparator);
  105. // ignore possible leading and trailing slash
  106. int slashIdx = pattern.indexOf(slash, 1);
  107. if (slashIdx > 0 && slashIdx < pattern.length() - 1)
  108. return new PathMatcher(pattern, pathSeparator, dirOnly);
  109. return createNameMatcher0(pattern, pathSeparator, dirOnly);
  110. }
  111. private static IMatcher createNameMatcher0(String segment,
  112. Character pathSeparator, boolean dirOnly)
  113. throws InvalidPatternException {
  114. // check if we see /** or ** segments => double star pattern
  115. if (WildMatcher.WILDMATCH.equals(segment)
  116. || WildMatcher.WILDMATCH2.equals(segment))
  117. return WILD;
  118. if (isWildCard(segment))
  119. return new WildCardMatcher(segment, pathSeparator, dirOnly);
  120. return new NameMatcher(segment, pathSeparator, dirOnly);
  121. }
  122. public boolean matches(String path, boolean assumeDirectory) {
  123. if (matchers == null)
  124. return simpleMatch(path, assumeDirectory);
  125. return iterate(path, 0, path.length(), assumeDirectory);
  126. }
  127. /*
  128. * Stupid but fast string comparison: the case where we don't have to match
  129. * wildcards or single segments (mean: this is multi-segment path which must
  130. * be at the beginning of the another string)
  131. */
  132. private boolean simpleMatch(String path, boolean assumeDirectory) {
  133. boolean hasSlash = path.indexOf(slash) == 0;
  134. if (beginning && !hasSlash)
  135. path = slash + path;
  136. if (!beginning && hasSlash)
  137. path = path.substring(1);
  138. if (path.equals(pattern))
  139. // Exact match
  140. if (dirOnly && !assumeDirectory)
  141. // Directory expectations not met
  142. return false;
  143. else
  144. // Directory expectations met
  145. return true;
  146. /*
  147. * Add slashes for startsWith check. This avoids matching e.g.
  148. * "/src/new" to /src/newfile" but allows "/src/new" to match
  149. * "/src/new/newfile", as is the git standard
  150. */
  151. if (path.startsWith(pattern + FastIgnoreRule.PATH_SEPARATOR))
  152. return true;
  153. return false;
  154. }
  155. public boolean matches(String segment, int startIncl, int endExcl,
  156. boolean assumeDirectory) {
  157. throw new UnsupportedOperationException(
  158. "Path matcher works only on entire paths"); //$NON-NLS-1$
  159. }
  160. boolean iterate(final String path, final int startIncl, final int endExcl,
  161. boolean assumeDirectory) {
  162. int matcher = 0;
  163. int right = startIncl;
  164. boolean match = false;
  165. int lastWildmatch = -1;
  166. while (true) {
  167. int left = right;
  168. right = path.indexOf(slash, right);
  169. if (right == -1) {
  170. if (left < endExcl)
  171. match = matches(matcher, path, left, endExcl,
  172. assumeDirectory);
  173. if (match) {
  174. if (matcher == matchers.size() - 2
  175. && matchers.get(matcher + 1) == WILD)
  176. // ** can match *nothing*: a/b/** match also a/b
  177. return true;
  178. if (matcher < matchers.size() - 1
  179. && matchers.get(matcher) == WILD) {
  180. // ** can match *nothing*: a/**/b match also a/b
  181. matcher++;
  182. match = matches(matcher, path, left, endExcl,
  183. assumeDirectory);
  184. } else if (dirOnly)
  185. return false;
  186. }
  187. return match && matcher + 1 == matchers.size();
  188. }
  189. if (right - left > 0)
  190. match = matches(matcher, path, left, right, assumeDirectory);
  191. else {
  192. // path starts with slash???
  193. right++;
  194. continue;
  195. }
  196. if (match) {
  197. if (matchers.get(matcher) == WILD) {
  198. lastWildmatch = matcher;
  199. // ** can match *nothing*: a/**/b match also a/b
  200. right = left - 1;
  201. }
  202. matcher++;
  203. if (matcher == matchers.size())
  204. return true;
  205. } else if (lastWildmatch != -1)
  206. matcher = lastWildmatch + 1;
  207. else
  208. return false;
  209. right++;
  210. }
  211. }
  212. boolean matches(int matcherIdx, String path, int startIncl, int endExcl,
  213. boolean assumeDirectory) {
  214. IMatcher matcher = matchers.get(matcherIdx);
  215. return matcher.matches(path, startIncl, endExcl, assumeDirectory);
  216. }
  217. }