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