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(final 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<Head>(heads.size());
124 this.heads.addAll(heads);
125 this.listForLocalUseage = new ArrayList<Head>(heads.size());
126 }
127
128
129
130
131
132
133
134
135
136
137 public FileNameMatcher(final String patternString,
138 final Character invalidWildgetCharacter)
139 throws InvalidPatternException {
140 this(createHeadsStartValues(patternString, invalidWildgetCharacter));
141 }
142
143
144
145
146
147
148
149
150 public FileNameMatcher(FileNameMatcher other) {
151 this(other.headsStartValue, other.heads);
152 }
153
154 private static List<Head> createHeadsStartValues(
155 final String patternString, final Character invalidWildgetCharacter)
156 throws InvalidPatternException {
157
158 final List<AbstractHead> allHeads = parseHeads(patternString,
159 invalidWildgetCharacter);
160
161 List<Head> nextHeadsSuggestion = new ArrayList<Head>(2);
162 nextHeadsSuggestion.add(LastHead.INSTANCE);
163 for (int i = allHeads.size() - 1; i >= 0; i--) {
164 final AbstractHead head = allHeads.get(i);
165
166
167
168
169
170 if (head.isStar()) {
171 nextHeadsSuggestion.add(head);
172 head.setNewHeads(nextHeadsSuggestion);
173 } else {
174 head.setNewHeads(nextHeadsSuggestion);
175 nextHeadsSuggestion = new ArrayList<Head>(2);
176 nextHeadsSuggestion.add(head);
177 }
178 }
179 return nextHeadsSuggestion;
180 }
181
182 private static int findGroupEnd(final int indexOfStartBracket,
183 final String pattern) throws InvalidPatternException {
184 int firstValidCharClassIndex = indexOfStartBracket + 1;
185 int firstValidEndBracketIndex = indexOfStartBracket + 2;
186
187 if (indexOfStartBracket + 1 >= pattern.length())
188 throw new NoClosingBracketException(indexOfStartBracket, "[", "]",
189 pattern);
190
191 if (pattern.charAt(firstValidCharClassIndex) == '!') {
192 firstValidCharClassIndex++;
193 firstValidEndBracketIndex++;
194 }
195
196 final Matcher charClassStartMatcher = characterClassStartPattern
197 .matcher(pattern);
198
199 int groupEnd = -1;
200 while (groupEnd == -1) {
201
202 final int possibleGroupEnd = indexOfUnescaped(pattern, ']',
203 firstValidEndBracketIndex);
204 if (possibleGroupEnd == -1)
205 throw new NoClosingBracketException(indexOfStartBracket, "[",
206 "]", pattern);
207
208 final boolean foundCharClass = charClassStartMatcher
209 .find(firstValidCharClassIndex);
210
211 if (foundCharClass
212 && charClassStartMatcher.start() < possibleGroupEnd) {
213
214 final String classStart = charClassStartMatcher.group(0);
215 final String classEnd = classStart.charAt(1) + "]";
216
217 final int classStartIndex = charClassStartMatcher.start();
218 final int classEndIndex = pattern.indexOf(classEnd,
219 classStartIndex + 2);
220
221 if (classEndIndex == -1)
222 throw new NoClosingBracketException(classStartIndex,
223 classStart, classEnd, pattern);
224
225 firstValidCharClassIndex = classEndIndex + 2;
226 firstValidEndBracketIndex = firstValidCharClassIndex;
227 } else {
228 groupEnd = possibleGroupEnd;
229 }
230 }
231 return groupEnd;
232 }
233
234 private static List<AbstractHead> parseHeads(final String pattern,
235 final Character invalidWildgetCharacter)
236 throws InvalidPatternException {
237
238 int currentIndex = 0;
239 List<AbstractHead> heads = new ArrayList<AbstractHead>();
240 while (currentIndex < pattern.length()) {
241 final int groupStart = indexOfUnescaped(pattern, '[', currentIndex);
242 if (groupStart == -1) {
243 final String patternPart = pattern.substring(currentIndex);
244 heads.addAll(createSimpleHeads(patternPart,
245 invalidWildgetCharacter));
246 currentIndex = pattern.length();
247 } else {
248 final String patternPart = pattern.substring(currentIndex,
249 groupStart);
250 heads.addAll(createSimpleHeads(patternPart,
251 invalidWildgetCharacter));
252
253 final int groupEnd = findGroupEnd(groupStart, pattern);
254 final String groupPart = pattern.substring(groupStart + 1,
255 groupEnd);
256 heads.add(new GroupHead(groupPart, pattern));
257 currentIndex = groupEnd + 1;
258 }
259 }
260 return heads;
261 }
262
263 private static List<AbstractHead> createSimpleHeads(
264 final String patternPart, final Character invalidWildgetCharacter) {
265 final List<AbstractHead> heads = new ArrayList<AbstractHead>(
266 patternPart.length());
267
268 boolean escaped = false;
269 for (int i = 0; i < patternPart.length(); i++) {
270 final char c = patternPart.charAt(i);
271 if (escaped) {
272 final CharacterHead head = new CharacterHead(c);
273 heads.add(head);
274 escaped = false;
275 } else {
276 switch (c) {
277 case '*': {
278 final AbstractHead head = createWildCardHead(
279 invalidWildgetCharacter, true);
280 heads.add(head);
281 break;
282 }
283 case '?': {
284 final AbstractHead head = createWildCardHead(
285 invalidWildgetCharacter, false);
286 heads.add(head);
287 break;
288 }
289 case '\\':
290 escaped = true;
291 break;
292 default:
293 final CharacterHead head = new CharacterHead(c);
294 heads.add(head);
295 }
296 }
297 }
298 return heads;
299 }
300
301 private static AbstractHead createWildCardHead(
302 final Character invalidWildgetCharacter, final boolean star) {
303 if (invalidWildgetCharacter != null)
304 return new RestrictedWildCardHead(invalidWildgetCharacter
305 .charValue(), star);
306 else
307 return new WildCardHead(star);
308 }
309
310
311
312
313
314 private boolean extendStringToMatchByOneCharacter(final char c) {
315 final List<Head> newHeads = listForLocalUseage;
316 newHeads.clear();
317 List<Head> lastAddedHeads = null;
318 for (int i = 0; i < heads.size(); i++) {
319 final Head head = heads.get(i);
320 final List<Head> headsToAdd = head.getNextHeads(c);
321
322
323
324
325
326 if (headsToAdd != lastAddedHeads) {
327 if (!headsToAdd.isEmpty())
328 newHeads.addAll(headsToAdd);
329 lastAddedHeads = headsToAdd;
330 }
331 }
332 listForLocalUseage = heads;
333 heads = newHeads;
334 return !newHeads.isEmpty();
335 }
336
337 private static int indexOfUnescaped(final String searchString,
338 final char ch, final int fromIndex) {
339 for (int i = fromIndex; i < searchString.length(); i++) {
340 char current = searchString.charAt(i);
341 if (current == ch)
342 return i;
343 if (current == '\\')
344 i++;
345 }
346 return -1;
347 }
348
349
350
351
352
353
354
355 public void append(final String stringToMatch) {
356 for (int i = 0; i < stringToMatch.length(); i++) {
357 final char c = stringToMatch.charAt(i);
358 if (!extendStringToMatchByOneCharacter(c))
359 break;
360 }
361 }
362
363
364
365
366 public void reset() {
367 heads.clear();
368 heads.addAll(headsStartValue);
369 }
370
371
372
373
374
375
376
377 public FileNameMatcher createMatcherForSuffix() {
378 final List<Head> copyOfHeads = new ArrayList<Head>(heads.size());
379 copyOfHeads.addAll(heads);
380 return new FileNameMatcher(copyOfHeads);
381 }
382
383
384
385
386
387 public boolean isMatch() {
388 if (heads.isEmpty())
389 return false;
390
391 final ListIterator<Head> headIterator = heads
392 .listIterator(heads.size());
393 while (headIterator.hasPrevious()) {
394 final Head head = headIterator.previous();
395 if (head == LastHead.INSTANCE) {
396 return true;
397 }
398 }
399 return false;
400 }
401
402
403
404
405
406
407 public boolean canAppendMatch() {
408 for (int i = 0; i < heads.size(); i++) {
409 if (heads.get(i) != LastHead.INSTANCE) {
410 return true;
411 }
412 }
413 return false;
414 }
415 }