1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.common;
20
21 import java.lang.reflect.Constructor;
22 import java.lang.reflect.InvocationTargetException;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Map.Entry;
28 import java.util.Properties;
29 import java.util.Set;
30
31 import org.apache.logging.log4j.LogManager;
32 import org.apache.logging.log4j.Logger;
33
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 public class MCRTextResolver {
62
63 private static final Logger LOGGER = LogManager.getLogger(MCRTextResolver.class);
64
65 protected TermContainer termContainer;
66
67
68
69
70 protected Map<String, String> variablesMap;
71
72
73
74
75
76
77
78
79 protected boolean retainText;
80
81
82
83
84
85
86
87
88 protected ResolveDepth resolveDepth;
89
90 protected CircularDependencyTracker tracker;
91
92
93
94
95
96 protected void registerDefaultTerms() throws NoSuchMethodException, InvocationTargetException,
97 IllegalAccessException, InstantiationException {
98 registerTerm(Variable.class);
99 registerTerm(Condition.class);
100 registerTerm(EscapeCharacter.class);
101 }
102
103
104
105
106
107
108 public void registerTerm(Class<? extends Term> termClass) throws NoSuchMethodException, InvocationTargetException,
109 IllegalAccessException, InstantiationException {
110 this.termContainer.add(termClass);
111 }
112
113
114
115
116
117
118 public void unregisterTerm(Class<? extends Term> termClass) throws NoSuchMethodException,
119 InvocationTargetException, InstantiationException, IllegalAccessException {
120 this.termContainer.remove(termClass);
121 }
122
123
124
125
126
127
128
129
130 public enum ResolveDepth {
131 Deep, NoVariables
132 }
133
134
135
136
137 public MCRTextResolver() {
138 this.variablesMap = new HashMap<>();
139 this.setResolveDepth(ResolveDepth.Deep);
140 this.setRetainText(true);
141 this.tracker = new CircularDependencyTracker(this);
142 try {
143 this.termContainer = new TermContainer(this);
144 this.registerDefaultTerms();
145 } catch (Exception exc) {
146 throw new MCRException("Unable to register default terms", exc);
147 }
148 }
149
150
151
152
153
154
155 public MCRTextResolver(Map<String, String> variablesMap) {
156 this();
157 mixin(variablesMap);
158 }
159
160 public MCRTextResolver(Properties properties) {
161 this();
162 mixin(properties);
163 }
164
165 protected TermContainer getTermContainer() {
166 return this.termContainer;
167 }
168
169 protected CircularDependencyTracker getTracker() {
170 return this.tracker;
171 }
172
173 public void mixin(Map<String, String> variables) {
174 for (Entry<String, String> entrySet : variables.entrySet()) {
175 String key = entrySet.getKey();
176 String value = entrySet.getValue();
177 this.addVariable(key, value);
178 }
179 }
180
181 public void mixin(Properties properties) {
182 for (Entry<Object, Object> entrySet : properties.entrySet()) {
183 String key = entrySet.getKey().toString();
184 String value = entrySet.getValue().toString();
185 this.addVariable(key, value);
186 }
187 }
188
189
190
191
192
193
194
195
196
197
198 public void setRetainText(boolean retainText) {
199 this.retainText = retainText;
200 }
201
202
203
204
205
206 public boolean isRetainText() {
207 return this.retainText;
208 }
209
210
211
212
213
214
215
216
217
218
219 public String addVariable(String name, String value) {
220 return variablesMap.put(name, value);
221 }
222
223
224
225
226
227
228
229
230 public String removeVariable(String name) {
231 return variablesMap.remove(name);
232 }
233
234
235
236
237
238
239 public boolean containsVariable(String name) {
240 return variablesMap.containsKey(name);
241 }
242
243
244
245
246
247
248 public void setResolveDepth(ResolveDepth resolveDepth) {
249 this.resolveDepth = resolveDepth;
250 }
251
252
253
254
255
256
257 public ResolveDepth getResolveDepth() {
258 return this.resolveDepth;
259 }
260
261
262
263
264
265
266
267
268
269 public String resolve(String text) {
270 this.getTracker().clear();
271 Text textResolver = new Text(this);
272 textResolver.resolve(text, 0);
273 return textResolver.getValue();
274 }
275
276
277
278
279
280
281
282 public String getValue(String varName) {
283 return variablesMap.get(varName);
284 }
285
286
287
288
289
290
291 public Map<String, String> getVariables() {
292 return variablesMap;
293 }
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309 protected abstract static class Term {
310
311
312
313 protected StringBuffer termBuffer;
314
315
316
317
318
319 protected boolean resolved;
320
321
322
323
324 protected int position;
325
326 protected MCRTextResolver textResolver;
327
328 public Term(MCRTextResolver textResolver) {
329 this.textResolver = textResolver;
330 this.termBuffer = new StringBuffer();
331 this.resolved = true;
332 this.position = 0;
333 }
334
335
336
337
338
339
340
341
342
343 public String resolve(String text, int startPosition) {
344 for (position = startPosition; position < text.length(); position++) {
345 Term internalTerm = getTerm(text, position);
346 if (internalTerm != null) {
347 position += internalTerm.getStartEnclosingString().length();
348 internalTerm.resolve(text, position);
349 if (!internalTerm.resolved) {
350 resolved = false;
351 }
352 position = internalTerm.position;
353 termBuffer.append(internalTerm.getValue());
354 } else {
355 boolean complete = resolveInternal(text, position);
356 if (complete) {
357 int endEnclosingSize = getEndEnclosingString().length();
358 if (endEnclosingSize > 1) {
359 position += endEnclosingSize - 1;
360 }
361 break;
362 }
363 }
364 }
365 return getValue();
366 }
367
368
369
370
371
372
373
374 private Term getTerm(String text, int pos) {
375 TermContainer termContainer = this.getTextResolver().getTermContainer();
376 for (Entry<String, Class<? extends Term>> termEntry : termContainer.getTermSet()) {
377 String startEnclosingStringOfTerm = termEntry.getKey();
378 if (text.startsWith(startEnclosingStringOfTerm, pos)
379 && !startEnclosingStringOfTerm.equals(this.getEndEnclosingString())) {
380 try {
381 return termContainer.instantiate(termEntry.getValue());
382 } catch (Exception exc) {
383 LOGGER.error(exc);
384 }
385 }
386 }
387 return null;
388 }
389
390
391
392
393
394
395 protected abstract boolean resolveInternal(String text, int pos);
396
397
398
399
400
401
402
403 public String getValue() {
404 return termBuffer.toString();
405 }
406
407
408
409
410
411
412
413
414 public abstract String getStartEnclosingString();
415
416
417
418
419
420
421
422
423
424 public abstract String getEndEnclosingString();
425
426 public MCRTextResolver getTextResolver() {
427 return textResolver;
428 }
429
430 }
431
432
433
434
435
436
437
438 protected static class Variable extends Term {
439
440
441
442
443
444 private StringBuffer valueBuffer;
445
446 private boolean complete;
447
448 public Variable(MCRTextResolver textResolver) {
449 super(textResolver);
450 valueBuffer = new StringBuffer();
451 complete = false;
452 }
453
454 @Override
455 public boolean resolveInternal(String text, int pos) {
456 if (text.startsWith(getEndEnclosingString(), pos)) {
457 this.track();
458
459 String value = getTextResolver().getValue(termBuffer.toString());
460 if (value == null) {
461 resolved = false;
462 if (getTextResolver().isRetainText()) {
463 this.valueBuffer.append(getStartEnclosingString()).append(termBuffer)
464 .append(getEndEnclosingString());
465 }
466 this.untrack();
467 complete = true;
468 return true;
469 }
470
471
472 if (getTextResolver().getResolveDepth() != ResolveDepth.NoVariables) {
473 Text recursiveResolvedText = resolveText(value);
474 resolved = recursiveResolvedText.resolved;
475 value = recursiveResolvedText.getValue();
476 }
477
478 valueBuffer.append(value);
479 this.untrack();
480 complete = true;
481 return true;
482 }
483 termBuffer.append(text.charAt(pos));
484 return false;
485 }
486
487 @Override
488 public String getValue() {
489 if (!complete) {
490
491 return getStartEnclosingString() + termBuffer;
492 }
493 return valueBuffer.toString();
494 }
495
496 @Override
497 public String getStartEnclosingString() {
498 return "{";
499 }
500
501 @Override
502 public String getEndEnclosingString() {
503 return "}";
504 }
505
506
507
508
509 protected void track() {
510 this.getTextResolver().getTracker().track("var", getTrackID());
511 }
512
513 protected void untrack() {
514 this.getTextResolver().getTracker().untrack("var", getTrackID());
515 }
516
517 protected String getTrackID() {
518 return getStartEnclosingString() + termBuffer + getEndEnclosingString();
519 }
520
521
522
523
524
525
526
527
528
529 public Text resolveText(String text) {
530 Text textResolver = new Text(getTextResolver());
531 textResolver.resolve(text, 0);
532 return textResolver;
533 }
534
535 }
536
537
538
539
540
541
542
543
544 protected static class Condition extends Term {
545
546 public Condition(MCRTextResolver textResolver) {
547 super(textResolver);
548 }
549
550 @Override
551 protected boolean resolveInternal(String text, int pos) {
552 if (text.startsWith(getEndEnclosingString(), pos)) {
553 return true;
554 }
555 termBuffer.append(text.charAt(pos));
556 return false;
557 }
558
559 @Override
560 public String getValue() {
561 if (resolved) {
562 return super.getValue();
563 }
564 return "";
565 }
566
567 @Override
568 public String getStartEnclosingString() {
569 return "[";
570 }
571
572 @Override
573 public String getEndEnclosingString() {
574 return "]";
575 }
576 }
577
578
579
580
581
582 protected static class EscapeCharacter extends Term {
583
584 public EscapeCharacter(MCRTextResolver textResolver) {
585 super(textResolver);
586 }
587
588 @Override
589 public boolean resolveInternal(String text, int pos) {
590 return true;
591 }
592
593 @Override
594 public String resolve(String text, int startPos) {
595 position = startPos;
596 char c = text.charAt(position);
597 termBuffer.append(c);
598 return termBuffer.toString();
599 }
600
601 @Override
602 public String getStartEnclosingString() {
603 return "\\";
604 }
605
606 @Override
607 public String getEndEnclosingString() {
608 return "";
609 }
610 }
611
612
613
614
615
616 protected static class Text extends Term {
617 public Text(MCRTextResolver textResolver) {
618 super(textResolver);
619 }
620
621 @Override
622 public boolean resolveInternal(String text, int pos) {
623 termBuffer.append(text.charAt(pos));
624 return false;
625 }
626
627 @Override
628 public String getStartEnclosingString() {
629 return "";
630 }
631
632 @Override
633 public String getEndEnclosingString() {
634 return "";
635 }
636 }
637
638
639
640
641 protected static class TermContainer {
642
643 protected Map<String, Class<? extends Term>> termMap = new HashMap<>();
644
645 protected MCRTextResolver textResolver;
646
647 public TermContainer(MCRTextResolver textResolver) {
648 this.textResolver = textResolver;
649 }
650
651 public Term instantiate(Class<? extends Term> termClass) throws InvocationTargetException,
652 NoSuchMethodException, InstantiationException, IllegalAccessException {
653 Constructor<? extends Term> c = termClass.getConstructor(MCRTextResolver.class);
654 return c.newInstance(this.textResolver);
655 }
656
657 public void add(Class<? extends Term> termClass) throws InvocationTargetException, NoSuchMethodException,
658 InstantiationException, IllegalAccessException {
659 Term term = instantiate(termClass);
660 this.termMap.put(term.getStartEnclosingString(), termClass);
661 }
662
663 public void remove(Class<? extends Term> termClass) throws InvocationTargetException, NoSuchMethodException,
664 InstantiationException, IllegalAccessException {
665 Term term = instantiate(termClass);
666 this.termMap.remove(term.getStartEnclosingString());
667 }
668
669 public Set<Entry<String, Class<? extends Term>>> getTermSet() {
670 return this.termMap.entrySet();
671 }
672
673 }
674
675 protected static class CircularDependencyTracker {
676 protected MCRTextResolver textResolver;
677
678 protected Map<String, List<String>> trackMap;
679
680 public CircularDependencyTracker(MCRTextResolver textResolver) {
681 this.textResolver = textResolver;
682 this.trackMap = new HashMap<>();
683 }
684
685 public void track(String type, String id) throws CircularDependencyExecption {
686 List<String> idList = trackMap.computeIfAbsent(type, k -> new ArrayList<>());
687 if (idList.contains(id)) {
688 throw new CircularDependencyExecption(idList, id);
689 }
690 idList.add(id);
691 }
692
693 public void untrack(String type, String id) {
694 List<String> idList = trackMap.get(type);
695 if (idList == null) {
696 LOGGER.error("text resolver circular dependency tracking error: cannot get type {} of {}", type, id);
697 return;
698 }
699 idList.remove(id);
700 }
701
702 public void clear() {
703 this.trackMap.clear();
704 }
705
706 }
707
708 protected static class CircularDependencyExecption extends RuntimeException {
709
710 private static final long serialVersionUID = -2448797538275144448L;
711
712 private List<String> dependencyList;
713
714 private String id;
715
716 public CircularDependencyExecption(List<String> dependencyList, String id) {
717 this.dependencyList = dependencyList;
718 this.id = id;
719 }
720
721 @Override
722 public String getMessage() {
723 StringBuilder msg = new StringBuilder("A circular dependency exception occurred");
724 msg.append("\n").append("circular path: ");
725 for (String dep : dependencyList) {
726 msg.append(dep).append(" > ");
727 }
728 msg.append(id);
729 return msg.toString();
730 }
731
732 public String getId() {
733 return id;
734 }
735
736 public List<String> getDependencyList() {
737 return dependencyList;
738 }
739
740 }
741
742 }