1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 package org.eclipse.jgit.fnmatch;
46
47 import java.util.ArrayList;
48 import java.util.Collections;
49 import java.util.List;
50 import java.util.ListIterator;
51 import java.util.regex.Matcher;
52 import java.util.regex.Pattern;
53
54 import org.eclipse.jgit.errors.InvalidPatternException;
55 import org.eclipse.jgit.errors.NoClosingBracketException;
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85 public class FileNameMatcher {
86 static final List<Head> EMPTY_HEAD_LIST = Collections.emptyList();
87
88 private static final Pattern characterClassStartPattern = Pattern
89 .compile("\\[[.:=]");
90
91 private List<Head> headsStartValue;
92
93 private List<Head> heads;
94
95
96
97
98
99
100
101 private List<Head> listForLocalUseage;
102
103
104
105
106
107
108 private FileNameMatcher(List<Head> headsStartValue) {
109 this(headsStartValue, headsStartValue);
110 }
111
112
113
114
115
116
117
118
119
120 private FileNameMatcher(final List<Head> headsStartValue,
121 final List<Head> heads) {
122 this.headsStartValue = headsStartValue;
123 this.heads = new ArrayList<>(heads.size());
124 this.heads.addAll(heads);
125 this.listForLocalUseage = new ArrayList<>(heads.size());
126 }
127
128
129
130
131
132
133
134
135
136
137
138
139 public FileNameMatcher(final String patternString,
140 final Character invalidWildgetCharacter)
141 throws InvalidPatternException {
142 this(createHeadsStartValues(patternString, invalidWildgetCharacter));
143 }
144
145
146
147
148
149
150
151
152
153
154 public FileNameMatcher(FileNameMatcher other) {
155 this(other.headsStartValue, other.heads);
156 }
157
158 private static List<Head> createHeadsStartValues(
159 final String patternString, final Character invalidWildgetCharacter)
160 throws InvalidPatternException {
161
162 final List<AbstractHead> allHeads = parseHeads(patternString,
163 invalidWildgetCharacter);
164
165 List<Head> nextHeadsSuggestion = new ArrayList<>(2);
166 nextHeadsSuggestion.add(LastHead.INSTANCE);
167 for (int i = allHeads.size() - 1; i >= 0; i--) {
168 final AbstractHead head = allHeads.get(i);
169
170
171
172
173
174 if (head.isStar()) {
175 nextHeadsSuggestion.add(head);
176 head.setNewHeads(nextHeadsSuggestion);
177 } else {
178 head.setNewHeads(nextHeadsSuggestion);
179 nextHeadsSuggestion = new ArrayList<>(2);
180 nextHeadsSuggestion.add(head);
181 }
182 }
183 return nextHeadsSuggestion;
184 }
185
186 private static int findGroupEnd(final int indexOfStartBracket,
187 final String pattern) throws InvalidPatternException {
188 int firstValidCharClassIndex = indexOfStartBracket + 1;
189 int firstValidEndBracketIndex = indexOfStartBracket + 2;
190
191 if (indexOfStartBracket + 1 >= pattern.length())
192 throw new NoClosingBracketException(indexOfStartBracket, "[", "]",
193 pattern);
194
195 if (pattern.charAt(firstValidCharClassIndex) == '!') {
196 firstValidCharClassIndex++;
197 firstValidEndBracketIndex++;
198 }
199
200 final Matcher charClassStartMatcher = characterClassStartPattern
201 .matcher(pattern);
202
203 int groupEnd = -1;
204 while (groupEnd == -1) {
205
206 final int possibleGroupEnd = indexOfUnescaped(pattern, ']',
207 firstValidEndBracketIndex);
208 if (possibleGroupEnd == -1)
209 throw new NoClosingBracketException(indexOfStartBracket, "[",
210 "]", pattern);
211
212 final boolean foundCharClass = charClassStartMatcher
213 .find(firstValidCharClassIndex);
214
215 if (foundCharClass
216 && charClassStartMatcher.start() < possibleGroupEnd) {
217
218 final String classStart = charClassStartMatcher.group(0);
219 final String classEnd = classStart.charAt(1) + "]";
220
221 final int classStartIndex = charClassStartMatcher.start();
222 final int classEndIndex = pattern.indexOf(classEnd,
223 classStartIndex + 2);
224
225 if (classEndIndex == -1)
226 throw new NoClosingBracketException(classStartIndex,
227 classStart, classEnd, pattern);
228
229 firstValidCharClassIndex = classEndIndex + 2;
230 firstValidEndBracketIndex = firstValidCharClassIndex;
231 } else {
232 groupEnd = possibleGroupEnd;
233 }
234 }
235 return groupEnd;
236 }
237
238 private static List<AbstractHead> parseHeads(final String pattern,
239 final Character invalidWildgetCharacter)
240 throws InvalidPatternException {
241
242 int currentIndex = 0;
243 List<AbstractHead> heads = new ArrayList<>();
244 while (currentIndex < pattern.length()) {
245 final int groupStart = indexOfUnescaped(pattern, '[', currentIndex);
246 if (groupStart == -1) {
247 final String patternPart = pattern.substring(currentIndex);
248 heads.addAll(createSimpleHeads(patternPart,
249 invalidWildgetCharacter));
250 currentIndex = pattern.length();
251 } else {
252 final String patternPart = pattern.substring(currentIndex,
253 groupStart);
254 heads.addAll(createSimpleHeads(patternPart,
255 invalidWildgetCharacter));
256
257 final int groupEnd = findGroupEnd(groupStart, pattern);
258 final String groupPart = pattern.substring(groupStart + 1,
259 groupEnd);
260 heads.add(new GroupHead(groupPart, pattern));
261 currentIndex = groupEnd + 1;
262 }
263 }
264 return heads;
265 }
266
267 private static List<AbstractHead> createSimpleHeads(
268 final String patternPart, final Character invalidWildgetCharacter) {
269 final List<AbstractHead> heads = new ArrayList<>(
270 patternPart.length());
271
272 boolean escaped = false;
273 for (int i = 0; i < patternPart.length(); i++) {
274 final char c = patternPart.charAt(i);
275 if (escaped) {
276 final CharacterHead head = new CharacterHead(c);
277 heads.add(head);
278 escaped = false;
279 } else {
280 switch (c) {
281 case '*': {
282 final AbstractHead head = createWildCardHead(
283 invalidWildgetCharacter, true);
284 heads.add(head);
285 break;
286 }
287 case '?': {
288 final AbstractHead head = createWildCardHead(
289 invalidWildgetCharacter, false);
290 heads.add(head);
291 break;
292 }
293 case '\\':
294 escaped = true;
295 break;
296 default:
297 final CharacterHead head = new CharacterHead(c);
298 heads.add(head);
299 }
300 }
301 }
302 return heads;
303 }
304
305 private static AbstractHead createWildCardHead(
306 final Character invalidWildgetCharacter, final boolean star) {
307 if (invalidWildgetCharacter != null)
308 return new RestrictedWildCardHead(invalidWildgetCharacter
309 .charValue(), star);
310 else
311 return new WildCardHead(star);
312 }
313
314
315
316
317
318 private boolean extendStringToMatchByOneCharacter(char c) {
319 final List<Head> newHeads = listForLocalUseage;
320 newHeads.clear();
321 List<Head> lastAddedHeads = null;
322 for (int i = 0; i < heads.size(); i++) {
323 final Head head = heads.get(i);
324 final List<Head> headsToAdd = head.getNextHeads(c);
325
326
327
328
329
330 if (headsToAdd != lastAddedHeads) {
331 if (!headsToAdd.isEmpty())
332 newHeads.addAll(headsToAdd);
333 lastAddedHeads = headsToAdd;
334 }
335 }
336 listForLocalUseage = heads;
337 heads = newHeads;
338 return !newHeads.isEmpty();
339 }
340
341 private static int indexOfUnescaped(final String searchString,
342 final char ch, final int fromIndex) {
343 for (int i = fromIndex; i < searchString.length(); i++) {
344 char current = searchString.charAt(i);
345 if (current == ch)
346 return i;
347 if (current == '\\')
348 i++;
349 }
350 return -1;
351 }
352
353
354
355
356
357
358
359
360 public void append(String stringToMatch) {
361 for (int i = 0; i < stringToMatch.length(); i++) {
362 final char c = stringToMatch.charAt(i);
363 if (!extendStringToMatchByOneCharacter(c))
364 break;
365 }
366 }
367
368
369
370
371 public void reset() {
372 heads.clear();
373 heads.addAll(headsStartValue);
374 }
375
376
377
378
379
380
381
382
383
384
385 public FileNameMatcher createMatcherForSuffix() {
386 final List<Head> copyOfHeads = new ArrayList<>(heads.size());
387 copyOfHeads.addAll(heads);
388 return new FileNameMatcher(copyOfHeads);
389 }
390
391
392
393
394
395
396 public boolean isMatch() {
397 if (heads.isEmpty())
398 return false;
399
400 final ListIterator<Head> headIterator = heads
401 .listIterator(heads.size());
402 while (headIterator.hasPrevious()) {
403 final Head head = headIterator.previous();
404 if (head == LastHead.INSTANCE) {
405 return true;
406 }
407 }
408 return false;
409 }
410
411
412
413
414
415
416 public boolean canAppendMatch() {
417 for (int i = 0; i < heads.size(); i++) {
418 if (heads.get(i) != LastHead.INSTANCE) {
419 return true;
420 }
421 }
422 return false;
423 }
424 }