1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package org.modeshape.graph.property;
25
26 import java.io.Serializable;
27 import java.util.regex.Pattern;
28 import java.util.regex.PatternSyntaxException;
29 import net.jcip.annotations.Immutable;
30 import org.modeshape.common.util.CheckArg;
31 import org.modeshape.common.util.HashCode;
32 import org.modeshape.common.util.ObjectUtil;
33 import org.modeshape.graph.GraphI18n;
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141 @Immutable
142 public class PathExpression implements Serializable {
143
144
145
146
147 private static final long serialVersionUID = 1L;
148
149
150
151
152
153
154
155
156
157 public static final PathExpression compile( String expression ) throws InvalidPathExpressionException {
158 return new PathExpression(expression);
159 }
160
161 private static final String SEQUENCE_PATTERN_STRING = "\\[(\\d+(?:,\\d+)*)\\]";
162 private static final Pattern SEQUENCE_PATTERN = Pattern.compile(SEQUENCE_PATTERN_STRING);
163
164
165
166
167
168
169
170
171
172
173
174
175
176 private static final String UNUSABLE_PREDICATE_PATTERN_STRING = "\\[(?:(?:\\d+(?:,\\d+)*)|\\*)\\]|(?:\\[[^\\]\\+\\-\\*=\\!><'\"\\s]+\\])$|(\\[[^\\]]+\\])";
177 private static final Pattern UNUSABLE_PREDICATE_PATTERN = Pattern.compile(UNUSABLE_PREDICATE_PATTERN_STRING);
178
179
180
181
182
183
184 private static final String NON_INDEX_PREDICATE_PATTERN_STRING = "\\[(?:(?:\\d+(?:,\\d+)*)|\\*)\\]|(\\[[^\\]]+\\])";
185 private static final Pattern NON_INDEX_PREDICATE_PATTERN = Pattern.compile(NON_INDEX_PREDICATE_PATTERN_STRING);
186
187
188
189
190
191
192 private static final String REPOSITORY_AND_WORKSPACE_AND_PATH_PATTERN_STRING = "((([^:/]*):)?(([^:/]*):))?(.*)";
193 private static final Pattern REPOSITORY_AND_WORKSPACE_AND_PATH_PATTERN = Pattern.compile(REPOSITORY_AND_WORKSPACE_AND_PATH_PATTERN_STRING);
194
195 private final String expression;
196
197
198
199
200
201 private final Pattern repositoryPattern;
202
203
204
205
206
207 private final Pattern workspacePattern;
208
209
210
211 private final Pattern matchPattern;
212
213
214
215
216 private final Pattern selectPattern;
217
218
219
220
221
222
223
224
225 public PathExpression( String expression ) throws InvalidPathExpressionException {
226 CheckArg.isNotNull(expression, "path expression");
227 this.expression = expression.trim();
228 if (this.expression.length() == 0) {
229 throw new InvalidPathExpressionException(GraphI18n.pathExpressionMayNotBeBlank.text());
230 }
231
232
233 RepositoryPath repoPath = parseRepositoryPath(this.expression);
234 if (repoPath == null) {
235 throw new InvalidPathExpressionException(GraphI18n.pathExpressionHasInvalidMatch.text(this.expression,
236 this.expression));
237 }
238 String repoPatternStr = repoPath.repositoryName != null ? repoPath.repositoryName : ".*";
239 String workPatternStr = repoPath.workspaceName != null ? repoPath.workspaceName : ".*";
240 String pathPatternStr = repoPath.path;
241 this.repositoryPattern = Pattern.compile(repoPatternStr);
242 this.workspacePattern = Pattern.compile(workPatternStr);
243
244
245
246
247 String matchString = pathPatternStr;
248 try {
249 matchString = removeUnusedPredicates(matchString);
250 matchString = replaceXPathPatterns(matchString);
251 this.matchPattern = Pattern.compile(matchString, Pattern.CASE_INSENSITIVE);
252 } catch (PatternSyntaxException e) {
253 String msg = GraphI18n.pathExpressionHasInvalidMatch.text(matchString, this.expression);
254 throw new InvalidPathExpressionException(msg, e);
255 }
256
257 String selectString = pathPatternStr;
258 try {
259 selectString = removeAllPredicatesExceptIndexes(selectString);
260 selectString = replaceXPathPatterns(selectString);
261 selectString = "(" + selectString + ").*";
262 this.selectPattern = Pattern.compile(selectString, Pattern.CASE_INSENSITIVE);
263 } catch (PatternSyntaxException e) {
264 String msg = GraphI18n.pathExpressionHasInvalidSelect.text(selectString, this.expression);
265 throw new InvalidPathExpressionException(msg, e);
266 }
267 }
268
269
270
271
272 public String getExpression() {
273 return expression;
274 }
275
276
277
278
279
280
281
282 protected String removeUnusedPredicates( String expression ) {
283 assert expression != null;
284 java.util.regex.Matcher matcher = UNUSABLE_PREDICATE_PATTERN.matcher(expression);
285 StringBuffer sb = new StringBuffer();
286 if (matcher.find()) {
287 do {
288
289 String predicateStr = matcher.group(0);
290 String unusablePredicateStr = matcher.group(1);
291 if (unusablePredicateStr != null) {
292 predicateStr = "";
293 }
294 matcher.appendReplacement(sb, predicateStr);
295 } while (matcher.find());
296 matcher.appendTail(sb);
297 expression = sb.toString();
298 }
299 return expression;
300 }
301
302
303
304
305
306
307
308 protected String removeAllPredicatesExceptIndexes( String expression ) {
309 assert expression != null;
310 java.util.regex.Matcher matcher = NON_INDEX_PREDICATE_PATTERN.matcher(expression);
311 StringBuffer sb = new StringBuffer();
312 if (matcher.find()) {
313 do {
314
315 String predicateStr = matcher.group(0);
316 String unusablePredicateStr = matcher.group(1);
317 if (unusablePredicateStr != null) {
318 predicateStr = "";
319 }
320 matcher.appendReplacement(sb, predicateStr);
321 } while (matcher.find());
322 matcher.appendTail(sb);
323 expression = sb.toString();
324 }
325 return expression;
326 }
327
328
329
330
331
332
333
334 protected String replaceXPathPatterns( String expression ) {
335 assert expression != null;
336
337 expression = expression.replaceAll("[\\|]{2,}", "|");
338
339
340 expression = expression.replaceAll("/(\\([^|]+)(\\|){2,}([^)]+\\))", "(/$1$2$3)?");
341 expression = expression.replaceAll("/\\(\\|+([^)]+)\\)", "(?:/($1))?");
342 expression = expression.replaceAll("/\\((([^|]+)(\\|[^|]+)*)\\|+\\)", "(?:/($1))?");
343
344
345
346
347
348
349
350 expression = expression.replaceAll("\\[\\]", "(?:\\\\[\\\\d+\\\\])?");
351
352 expression = expression.replaceAll("\\[[*]\\]", "(?:\\\\[\\\\d+\\\\])?");
353
354 expression = expression.replaceAll("\\[0\\]", "(?:\\\\[0\\\\])?");
355
356 expression = expression.replaceAll("\\[([1-9]\\d*)\\]", "\\\\[$1\\\\]");
357
358
359
360 expression = expression.replaceAll("(?<!\\\\)\\[([^\\]]*)\\]$", "/$1");
361
362
363 java.util.regex.Matcher matcher = SEQUENCE_PATTERN.matcher(expression);
364 StringBuffer sb = new StringBuffer();
365 boolean result = matcher.find();
366 if (result) {
367 do {
368 String sequenceStr = matcher.group(1);
369 boolean optional = false;
370 if (sequenceStr.startsWith("0,")) {
371 sequenceStr = sequenceStr.replaceFirst("^0,", "");
372 optional = true;
373 }
374 if (sequenceStr.endsWith(",0")) {
375 sequenceStr = sequenceStr.replaceFirst(",0$", "");
376 optional = true;
377 }
378 if (sequenceStr.contains(",0,")) {
379 sequenceStr = sequenceStr.replaceAll(",0,", ",");
380 optional = true;
381 }
382 sequenceStr = sequenceStr.replaceAll(",", "|");
383 String replacement = "\\\\[(?:" + sequenceStr + ")\\\\]";
384 if (optional) {
385 replacement = "(?:" + replacement + ")?";
386 }
387 matcher.appendReplacement(sb, replacement);
388 result = matcher.find();
389 } while (result);
390 matcher.appendTail(sb);
391 expression = sb.toString();
392 }
393
394
395 expression = expression.replaceAll("[*]([^/(\\\\])", "[^/$1]*$1");
396 expression = expression.replaceAll("(?<!\\[\\^/\\])[*]", "[^/]*");
397 expression = expression.replaceAll("[/]{2,}$", "(?:/[^/]*)*");
398 expression = expression.replaceAll("[/]{2,}", "(?:/[^/]*)*/");
399 return expression;
400 }
401
402
403
404
405 public String getSelectExpression() {
406 return this.expression;
407 }
408
409
410
411
412 @Override
413 public int hashCode() {
414 return this.expression.hashCode();
415 }
416
417
418
419
420 @Override
421 public boolean equals( Object obj ) {
422 if (obj == this) return true;
423 if (obj instanceof PathExpression) {
424 PathExpression that = (PathExpression)obj;
425 if (!this.expression.equalsIgnoreCase(that.expression)) return false;
426 return true;
427 }
428 return false;
429 }
430
431
432
433
434 @Override
435 public String toString() {
436 return this.expression;
437 }
438
439
440
441
442
443
444
445
446
447 public Matcher matcher( String absolutePath ) {
448
449 RepositoryPath repoPath = parseRepositoryPath(absolutePath);
450 if (repoPath == null) {
451
452 return new Matcher(null, absolutePath, null, null, null);
453 }
454 String repoName = repoPath.repositoryName != null ? repoPath.repositoryName : "";
455 String workspaceName = repoPath.workspaceName != null ? repoPath.workspaceName : "";
456 String path = repoPath.path;
457
458
459 if (!repositoryPattern.matcher(repoName).matches() || !workspacePattern.matcher(workspaceName).matches()) {
460
461 return new Matcher(null, path, null, null, null);
462 }
463
464
465 String originalAbsolutePath = path;
466
467
468 path = path.replaceAll("/+$", "");
469
470
471 final java.util.regex.Matcher matcher = this.matchPattern.matcher(path);
472 if (!matcher.matches()) {
473
474 return new Matcher(matcher, originalAbsolutePath, null, null, null);
475 }
476
477
478 final java.util.regex.Matcher selectMatcher = this.selectPattern.matcher(path);
479 if (!selectMatcher.matches()) {
480
481 return new Matcher(matcher, null, null, null, null);
482 }
483
484 String selectedPath = selectMatcher.group(1);
485
486
487 selectedPath = selectedPath.replaceAll("/@[^/\\[\\]]+$", "");
488
489 return new Matcher(matcher, originalAbsolutePath, repoName, workspaceName, selectedPath);
490 }
491
492 @Immutable
493 public static class Matcher {
494
495 private final String inputPath;
496 private final String selectedRepository;
497 private final String selectedWorkspace;
498 private final String selectedPath;
499 private final java.util.regex.Matcher inputMatcher;
500 private final int hc;
501
502 protected Matcher( java.util.regex.Matcher inputMatcher,
503 String inputPath,
504 String selectedRepository,
505 String selectedWorkspace,
506 String selectedPath ) {
507 this.inputMatcher = inputMatcher;
508 this.inputPath = inputPath;
509 this.selectedRepository = selectedRepository == null || selectedRepository.length() == 0 ? null : selectedRepository;
510 this.selectedWorkspace = selectedWorkspace == null || selectedWorkspace.length() == 0 ? null : selectedWorkspace;
511 this.selectedPath = selectedPath;
512 this.hc = HashCode.compute(this.inputPath, this.selectedPath);
513 }
514
515 public boolean matches() {
516 return this.inputMatcher != null && this.selectedPath != null;
517 }
518
519
520
521
522 public String getInputPath() {
523 return this.inputPath;
524 }
525
526
527
528
529 public String getSelectedNodePath() {
530 return this.selectedPath;
531 }
532
533
534
535
536
537
538 public String getSelectedRepositoryName() {
539 return this.selectedRepository;
540 }
541
542
543
544
545
546
547 public String getSelectedWorkspaceName() {
548 return this.selectedWorkspace;
549 }
550
551 public int groupCount() {
552 if (this.inputMatcher == null) return 0;
553 return this.inputMatcher.groupCount();
554 }
555
556 public String group( int groupNumber ) {
557 return this.inputMatcher.group(groupNumber);
558 }
559
560
561
562
563 @Override
564 public int hashCode() {
565 return this.hc;
566 }
567
568
569
570
571 @Override
572 public boolean equals( Object obj ) {
573 if (obj == this) return true;
574 if (obj instanceof PathExpression.Matcher) {
575 PathExpression.Matcher that = (PathExpression.Matcher)obj;
576 if (!this.inputPath.equalsIgnoreCase(that.inputPath)) return false;
577 if (!this.selectedPath.equalsIgnoreCase(that.selectedPath)) return false;
578 return true;
579 }
580 return false;
581 }
582
583
584
585
586 @Override
587 public String toString() {
588 return this.selectedPath;
589 }
590 }
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607 public boolean matchesAnything() {
608 return ANYTHING_PATTERN.matcher(expression).matches();
609 }
610
611 public static PathExpression all() {
612 return ALL_PATHS_EXPRESSION;
613 }
614
615 private static final PathExpression ALL_PATHS_EXPRESSION = PathExpression.compile("//");
616
617
618
619
620
621
622
623 public static RepositoryPath parseRepositoryPath( String path ) {
624
625 java.util.regex.Matcher pathMatcher = REPOSITORY_AND_WORKSPACE_AND_PATH_PATTERN.matcher(path);
626 if (!pathMatcher.matches()) {
627
628 return null;
629 }
630 String repoName = pathMatcher.group(3);
631 String workspaceName = pathMatcher.group(5);
632 String absolutePath = pathMatcher.group(6);
633 if (repoName == null || repoName.length() == 0 || repoName.trim().length() == 0) repoName = null;
634 if (workspaceName == null || workspaceName.length() == 0 || workspaceName.trim().length() == 0) workspaceName = null;
635 return new RepositoryPath(repoName, workspaceName, absolutePath);
636 }
637
638 @Immutable
639 public static class RepositoryPath {
640 public final String repositoryName;
641 public final String workspaceName;
642 public final String path;
643
644 public RepositoryPath( String repositoryName,
645 String workspaceName,
646 String path ) {
647 this.repositoryName = repositoryName;
648 this.workspaceName = workspaceName;
649 this.path = path;
650 }
651
652
653
654
655
656
657 @Override
658 public int hashCode() {
659 return path.hashCode();
660 }
661
662
663
664
665
666
667 @Override
668 public boolean equals( Object obj ) {
669 if (obj == this) return true;
670 if (obj instanceof RepositoryPath) {
671 RepositoryPath that = (RepositoryPath)obj;
672 if (!ObjectUtil.isEqualWithNulls(this.repositoryName, that.repositoryName)) return false;
673 if (!ObjectUtil.isEqualWithNulls(this.workspaceName, that.workspaceName)) return false;
674 return this.path.equals(that.path);
675 }
676 return false;
677 }
678
679
680
681
682
683
684 @Override
685 public String toString() {
686 return (repositoryName != null ? repositoryName : "") + ":" + (workspaceName != null ? workspaceName : "") + ":"
687 + path;
688 }
689
690 public RepositoryPath withRepositoryName( String repositoryName ) {
691 return new RepositoryPath(repositoryName, workspaceName, path);
692 }
693
694 public RepositoryPath withWorkspaceName( String workspaceName ) {
695 return new RepositoryPath(repositoryName, workspaceName, path);
696 }
697
698 public RepositoryPath withPath( String path ) {
699 return new RepositoryPath(repositoryName, workspaceName, path);
700 }
701 }
702
703 }