1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.jexl3.internal;
18
19 import java.io.BufferedReader;
20 import java.io.IOException;
21 import java.io.Reader;
22 import java.io.StringReader;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Objects;
28 import java.util.Set;
29
30 import org.apache.commons.jexl3.JexlCache;
31 import org.apache.commons.jexl3.JexlContext;
32 import org.apache.commons.jexl3.JexlEngine;
33 import org.apache.commons.jexl3.JexlException;
34 import org.apache.commons.jexl3.JexlFeatures;
35 import org.apache.commons.jexl3.JexlInfo;
36 import org.apache.commons.jexl3.JexlOptions;
37 import org.apache.commons.jexl3.JxltEngine;
38 import org.apache.commons.jexl3.parser.ASTJexlScript;
39 import org.apache.commons.jexl3.parser.JexlNode;
40 import org.apache.commons.jexl3.parser.StringParser;
41 import org.apache.commons.logging.Log;
42
43
44
45
46
47
48 public final class TemplateEngine extends JxltEngine {
49
50
51
52 static final class Block {
53
54 private final BlockType type;
55
56
57 private final int line;
58
59
60 private final String body;
61
62
63
64
65
66
67
68
69 Block(final BlockType theType, final int theLine, final String theBlock) {
70 type = theType;
71 line = theLine;
72 body = theBlock;
73 }
74
75
76
77
78 String getBody() {
79 return body;
80 }
81
82
83
84
85 int getLine() {
86 return line;
87 }
88
89
90
91
92 BlockType getType() {
93 return type;
94 }
95
96
97
98
99
100
101
102 void toString(final StringBuilder strb, final String prefix) {
103 if (BlockType.VERBATIM == type) {
104 strb.append(body);
105 } else {
106 final Iterator<CharSequence> lines = readLines(new StringReader(body));
107 while (lines.hasNext()) {
108 strb.append(prefix).append(lines.next());
109 }
110 }
111 }
112 }
113
114
115
116
117 enum BlockType {
118
119 VERBATIM,
120
121
122 DIRECTIVE
123 }
124
125
126 final class CompositeExpression extends TemplateExpression {
127
128 private final int meta;
129
130
131 final TemplateExpression[] exprs;
132
133
134
135
136
137
138
139
140 CompositeExpression(final int[] counters, final List<TemplateExpression> list, final TemplateExpression src) {
141 super(src);
142 this.exprs = list.toArray(new TemplateExpression[0]);
143 this.meta = (counters[ExpressionType.DEFERRED.getIndex()] > 0 ? 2 : 0)
144 | (counters[ExpressionType.IMMEDIATE.getIndex()] > 0 ? 1 : 0);
145 }
146
147 @Override
148 public StringBuilder asString(final StringBuilder strb) {
149 for (final TemplateExpression e : exprs) {
150 e.asString(strb);
151 }
152 return strb;
153 }
154
155 @Override
156 protected Object evaluate(final Interpreter interpreter) {
157 Object value;
158
159 final StringBuilder strb = new StringBuilder();
160 for (final TemplateExpression expr : exprs) {
161 value = expr.evaluate(interpreter);
162 if (value != null) {
163 strb.append(value);
164 }
165 }
166 return strb.toString();
167 }
168
169 @Override
170 ExpressionType getType() {
171 return ExpressionType.COMPOSITE;
172 }
173
174 @Override
175 public Set<List<String>> getVariables() {
176 final Engine.VarCollector collector = jexl.varCollector();
177 for (final TemplateExpression expr : exprs) {
178 expr.getVariables(collector);
179 }
180 return collector.collected();
181 }
182
183
184
185
186
187
188 @Override
189 protected void getVariables(final Engine.VarCollector collector) {
190 for (final TemplateExpression expr : exprs) {
191 expr.getVariables(collector);
192 }
193 }
194
195 @Override
196 public boolean isImmediate() {
197
198 return (meta & 2) == 0;
199 }
200
201 @Override
202 protected TemplateExpression prepare(final Interpreter interpreter) {
203
204 if (source != this) {
205 return this;
206 }
207
208 final int size = exprs.length;
209 final ExpressionBuilder builder = new ExpressionBuilder(size);
210
211 boolean eq = true;
212 for (final TemplateExpression expr : exprs) {
213 final TemplateExpression prepared = expr.prepare(interpreter);
214
215 if (prepared != null) {
216 builder.add(prepared);
217 }
218
219 eq &= expr == prepared;
220 }
221 return eq ? this : builder.build(TemplateEngine.this, this);
222 }
223 }
224
225
226 final class ConstantExpression extends TemplateExpression {
227
228 private final Object value;
229
230
231
232
233
234
235
236 ConstantExpression(final Object val, final TemplateExpression source) {
237 super(source);
238 Objects.requireNonNull(val, "val");
239 this.value = val instanceof String
240 ? StringParser.buildTemplate((String) val, false)
241 : val;
242 }
243
244 @Override
245 public StringBuilder asString(final StringBuilder strb) {
246 if (value != null) {
247 strb.append(value);
248 }
249 return strb;
250 }
251
252 @Override
253 protected Object evaluate(final Interpreter interpreter) {
254 return value;
255 }
256
257 @Override
258 ExpressionType getType() {
259 return ExpressionType.CONSTANT;
260 }
261 }
262
263
264 final class DeferredExpression extends JexlBasedExpression {
265
266
267
268
269
270
271 DeferredExpression(final CharSequence expr, final JexlNode node) {
272 super(expr, node, null);
273 }
274
275 @Override
276 ExpressionType getType() {
277 return ExpressionType.DEFERRED;
278 }
279
280 @Override
281 protected void getVariables(final Engine.VarCollector collector) {
282
283 }
284
285 @Override
286 public boolean isImmediate() {
287 return false;
288 }
289
290 @Override
291 protected TemplateExpression prepare(final Interpreter interpreter) {
292 return new ImmediateExpression(expr, node, source);
293 }
294 }
295
296
297
298
299
300 static final class ExpressionBuilder {
301
302 private final int[] counts;
303
304
305 private final List<TemplateExpression> expressions;
306
307
308
309
310
311
312 ExpressionBuilder(final int size) {
313 counts = new int[]{0, 0, 0};
314 expressions = new ArrayList<>(size <= 0 ? 3 : size);
315 }
316
317
318
319
320
321
322 void add(final TemplateExpression expr) {
323 counts[expr.getType().getIndex()] += 1;
324 expressions.add(expr);
325 }
326
327
328
329
330
331
332
333
334 TemplateExpression build(final TemplateEngine el, final TemplateExpression source) {
335 int sum = 0;
336 for (final int count : counts) {
337 sum += count;
338 }
339 if (expressions.size() != sum) {
340 final StringBuilder error = new StringBuilder("parsing algorithm error: ");
341 throw new IllegalStateException(toString(error).toString());
342 }
343
344 if (expressions.size() == 1) {
345 return expressions.get(0);
346 }
347 return el.new CompositeExpression(counts, expressions, source);
348 }
349
350 @Override
351 public String toString() {
352 return toString(new StringBuilder()).toString();
353 }
354
355
356
357
358
359
360
361 StringBuilder toString(final StringBuilder error) {
362 error.append("exprs{");
363 error.append(expressions.size());
364 error.append(", constant:");
365 error.append(counts[ExpressionType.CONSTANT.getIndex()]);
366 error.append(", immediate:");
367 error.append(counts[ExpressionType.IMMEDIATE.getIndex()]);
368 error.append(", deferred:");
369 error.append(counts[ExpressionType.DEFERRED.getIndex()]);
370 error.append("}");
371 return error;
372 }
373 }
374
375
376
377
378
379
380
381 enum ExpressionType {
382
383 CONSTANT(0),
384
385
386 IMMEDIATE(1),
387
388
389 DEFERRED(2),
390
391
392
393 NESTED(2),
394
395
396 COMPOSITE(-1);
397
398
399 private final int index;
400
401
402
403
404
405
406 ExpressionType(final int idx) {
407 this.index = idx;
408 }
409
410
411
412
413 int getIndex() {
414 return index;
415 }
416 }
417
418
419 final class ImmediateExpression extends JexlBasedExpression {
420
421
422
423
424
425
426
427 ImmediateExpression(final CharSequence expr, final JexlNode node, final TemplateExpression source) {
428 super(expr, node, source);
429 }
430
431 @Override
432 ExpressionType getType() {
433 return ExpressionType.IMMEDIATE;
434 }
435
436 @Override
437 protected TemplateExpression prepare(final Interpreter interpreter) {
438
439 final Object value = evaluate(interpreter);
440 return value != null ? new ConstantExpression(value, source) : null;
441 }
442 }
443
444
445 abstract class JexlBasedExpression extends TemplateExpression {
446
447 protected final CharSequence expr;
448
449
450 protected final JexlNode node;
451
452
453
454
455
456
457
458
459 protected JexlBasedExpression(final CharSequence theExpr, final JexlNode theNode, final TemplateExpression theSource) {
460 super(theSource);
461 this.expr = theExpr;
462 this.node = theNode;
463 }
464
465 @Override
466 public StringBuilder asString(final StringBuilder strb) {
467 strb.append(isImmediate() ? immediateChar : deferredChar);
468 strb.append("{");
469 strb.append(expr);
470 strb.append("}");
471 return strb;
472 }
473
474 @Override
475 protected Object evaluate(final Interpreter interpreter) {
476 return interpreter.interpret(node);
477 }
478
479 @Override
480 JexlInfo getInfo() {
481 return node.jexlInfo();
482 }
483
484 @Override
485 public Set<List<String>> getVariables() {
486 final Engine.VarCollector collector = jexl.varCollector();
487 getVariables(collector);
488 return collector.collected();
489 }
490
491 @Override
492 protected void getVariables(final Engine.VarCollector collector) {
493 jexl.getVariables(node instanceof ASTJexlScript? (ASTJexlScript) node : null, node, collector);
494 }
495
496 @Override
497 protected JexlOptions options(final JexlContext context) {
498 return jexl.evalOptions(node instanceof ASTJexlScript? (ASTJexlScript) node : null, context);
499 }
500 }
501
502
503
504
505
506
507 final class NestedExpression extends JexlBasedExpression {
508 private final Scope scope;
509
510
511
512
513
514
515 NestedExpression(final CharSequence expr, final JexlNode node, final Scope sc) {
516 super(expr, node, null);
517 this.scope = sc;
518 if (source != this) {
519 throw new IllegalArgumentException("Nested TemplateExpression cannot have a source");
520 }
521 }
522
523 @Override
524 public StringBuilder asString(final StringBuilder strb) {
525 strb.append(expr);
526 return strb;
527 }
528
529 @Override
530 protected Object evaluate(final Interpreter interpreter) {
531 return prepare(interpreter).evaluate(interpreter);
532 }
533
534 @Override
535 ExpressionType getType() {
536 return ExpressionType.NESTED;
537 }
538
539 @Override
540 public boolean isImmediate() {
541 return false;
542 }
543
544 @Override
545 protected TemplateExpression prepare(final Interpreter interpreter) {
546 final String value = interpreter.interpret(node).toString();
547 final JexlNode dnode = jexl.jxltParse(node.jexlInfo(), noscript, value, scope);
548 return new ImmediateExpression(value, dnode, this);
549 }
550 }
551
552
553 private enum ParseState {
554
555
556 CONST,
557
558
559 IMMEDIATE0,
560
561
562 DEFERRED0,
563
564
565 IMMEDIATE1,
566
567
568 DEFERRED1,
569
570
571 ESCAPE
572 }
573
574
575
576
577 abstract class TemplateExpression implements Expression {
578
579 protected final TemplateExpression source;
580
581
582
583
584
585
586 TemplateExpression(final TemplateExpression src) {
587 this.source = src != null ? src : this;
588 }
589
590 @Override
591 public String asString() {
592 final StringBuilder strb = new StringBuilder();
593 asString(strb);
594 return strb.toString();
595 }
596
597
598
599
600
601
602
603
604 protected abstract Object evaluate(Interpreter interpreter);
605
606 @Override
607 public final Object evaluate(final JexlContext context) {
608 return evaluate(context, null, null);
609 }
610
611
612
613
614
615
616
617
618
619
620 protected final Object evaluate(final JexlContext context, final Frame frame, final JexlOptions options) {
621 try {
622 final TemplateInterpreter.Arguments args = new TemplateInterpreter.Arguments(jexl).context(context)
623 .options(options != null ? options : options(context)).frame(frame);
624 final Interpreter interpreter = jexl.createTemplateInterpreter(args);
625 return evaluate(interpreter);
626 } catch (final JexlException xjexl) {
627 final JexlException xuel = createException(xjexl.getInfo(), "evaluate", this, xjexl);
628 if (jexl.isSilent()) {
629 if (logger.isWarnEnabled()) {
630 logger.warn(xuel.getMessage(), xuel.getCause());
631 }
632 return null;
633 }
634 throw xuel;
635 }
636 }
637
638
639 JexlInfo getInfo() {
640 return null;
641 }
642
643 @Override
644 public final TemplateExpression getSource() {
645 return source;
646 }
647
648
649
650
651
652
653 abstract ExpressionType getType();
654
655 @Override
656 public Set<List<String>> getVariables() {
657 return Collections.emptySet();
658 }
659
660
661
662
663
664
665 protected void getVariables(final Engine.VarCollector collector) {
666
667 }
668
669 @Override
670 public final boolean isDeferred() {
671 return !isImmediate();
672 }
673
674 @Override
675 public boolean isImmediate() {
676 return true;
677 }
678
679
680
681
682
683
684
685 protected JexlOptions options(final JexlContext context) {
686 return jexl.evalOptions(null, context);
687 }
688
689
690
691
692
693
694
695
696 protected TemplateExpression prepare(final Interpreter interpreter) {
697 return this;
698 }
699
700 @Override
701 public final TemplateExpression prepare(final JexlContext context) {
702 return prepare(context, null, null);
703 }
704
705
706
707
708
709
710
711
712
713
714 protected final TemplateExpression prepare(final JexlContext context, final Frame frame, final JexlOptions options) {
715 try {
716 final TemplateInterpreter.Arguments args = new TemplateInterpreter.Arguments(jexl).context(context)
717 .options(options != null ? options : options(context)).frame(frame);
718 final Interpreter interpreter = jexl.createTemplateInterpreter(args);
719 return prepare(interpreter);
720 } catch (final JexlException xjexl) {
721 final JexlException xuel = createException(xjexl.getInfo(), "prepare", this, xjexl);
722 if (jexl.isSilent()) {
723 if (logger.isWarnEnabled()) {
724 logger.warn(xuel.getMessage(), xuel.getCause());
725 }
726 return null;
727 }
728 throw xuel;
729 }
730 }
731
732 @Override
733 public final String toString() {
734 final StringBuilder strb = new StringBuilder();
735 asString(strb);
736 if (source != this) {
737 strb.append(" /*= ");
738 strb.append(source);
739 strb.append(" */");
740 }
741 return strb.toString();
742 }
743 }
744
745
746
747
748
749
750
751
752
753
754 private static int append(final StringBuilder strb, final CharSequence expr, final int position, final char c) {
755 strb.append(c);
756 if (c != '"' && c != '\'') {
757 return position;
758 }
759
760 final int end = expr.length();
761 boolean escape= false;
762 int index = position + 1;
763 for (; index < end; ++index) {
764 final char ec = expr.charAt(index);
765 strb.append(ec);
766 if (ec == '\\') {
767 escape = !escape;
768 } else if (escape) {
769 escape = false;
770 } else if (ec == c) {
771 break;
772 }
773 }
774 return index;
775 }
776
777
778
779
780
781
782
783
784
785
786 static Exception createException(final JexlInfo info,
787 final String action,
788 final TemplateExpression expr,
789 final java.lang.Exception xany) {
790 final StringBuilder strb = new StringBuilder("failed to ");
791 strb.append(action);
792 if (expr != null) {
793 strb.append(" '");
794 strb.append(expr);
795 strb.append("'");
796 }
797 final Throwable cause = xany.getCause();
798 if (cause != null) {
799 final String causeMsg = cause.getMessage();
800 if (causeMsg != null) {
801 strb.append(", ");
802 strb.append(causeMsg);
803 }
804 }
805 return new Exception(info, strb.toString(), xany);
806 }
807
808
809
810
811
812
813
814 static Iterator<CharSequence> readLines(final Reader reader) {
815 if (!reader.markSupported()) {
816 throw new IllegalArgumentException("mark support in reader required");
817 }
818 return new Iterator<CharSequence>() {
819 private CharSequence next = doNext();
820
821 private CharSequence doNext() {
822 final StringBuilder strb = new StringBuilder(64);
823 int c;
824 boolean eol = false;
825 try {
826 while ((c = reader.read()) >= 0) {
827 if (eol) {
828 reader.reset();
829 break;
830 }
831 if (c == '\n') {
832 eol = true;
833 }
834 strb.append((char) c);
835 reader.mark(1);
836 }
837 } catch (final IOException xio) {
838 return null;
839 }
840 return strb.length() > 0 ? strb : null;
841 }
842
843 @Override
844 public boolean hasNext() {
845 return next != null;
846 }
847
848 @Override
849 public CharSequence next() {
850 final CharSequence current = next;
851 if (current != null) {
852 next = doNext();
853 }
854 return current;
855 }
856 };
857 }
858
859
860 final JexlCache<Source, Object> cache;
861
862
863 final Engine jexl;
864
865
866 final Log logger;
867
868
869 final char immediateChar;
870
871
872 final char deferredChar;
873
874
875 final boolean noscript;
876
877
878
879
880
881
882
883
884
885
886 public TemplateEngine(final Engine jexl,
887 final boolean noScript,
888 final int cacheSize,
889 final char immediate,
890 final char deferred) {
891 this.jexl = jexl;
892 this.logger = jexl.logger;
893 this.cache = jexl.createCache(cacheSize);
894 immediateChar = immediate;
895 deferredChar = deferred;
896 noscript = noScript;
897 }
898
899
900
901
902 @Override
903 public void clearCache() {
904 synchronized (cache) {
905 cache.clear();
906 }
907 }
908
909 @Override
910 public JxltEngine.Expression createExpression(final JexlInfo jexlInfo, final String expression) {
911 return createExpression(jexlInfo, expression, null);
912 }
913
914 public JxltEngine.Expression createExpression(final JexlInfo jexlInfo, final String expression, final Scope scope) {
915 final JexlInfo info = jexlInfo == null ? jexl.createInfo() : jexlInfo;
916 Exception xuel = null;
917 TemplateExpression stmt = null;
918 final JexlFeatures features = noscript ? jexl.expressionFeatures : jexl.scriptFeatures;
919
920 final boolean cached = cache != null && expression.length() < jexl.cacheThreshold;
921 final JexlEngine je = Engine.putThreadEngine(jexl);
922 try {
923 if (!cached) {
924 stmt = parseExpression(info, expression, scope);
925 } else {
926 final Source source = new Source(features, Scope.getSymbolsMap(scope), expression);
927 final Object c = cache.get(source);
928 stmt = c instanceof TemplateExpression ? (TemplateExpression) c : null;
929 if (stmt != null) {
930 return stmt;
931 }
932 stmt = parseExpression(info, expression, scope);
933 cache.put(source, stmt);
934 }
935 } catch (final JexlException xjexl) {
936 xuel = new Exception(xjexl.getInfo(), "failed to parse '" + expression + "'", xjexl);
937 } finally {
938 Engine.putThreadEngine(je);
939 }
940 if (xuel != null) {
941 if (!jexl.isSilent()) {
942 throw xuel;
943 }
944 if (logger.isWarnEnabled()) {
945 logger.warn(xuel.getMessage(), xuel.getCause());
946 }
947 stmt = null;
948 }
949 return stmt;
950 }
951
952 @Override
953 public TemplateScript createTemplate(final JexlInfo info, final String prefix, final Reader source, final String... parms) {
954 return new TemplateScript(this, info, prefix, source, parms);
955 }
956
957
958
959
960 char getDeferredChar() {
961 return deferredChar;
962 }
963
964
965
966
967
968
969 @Override
970 public Engine getEngine() {
971 return jexl;
972 }
973
974
975
976
977 char getImmediateChar() {
978 return immediateChar;
979 }
980
981
982
983
984
985
986
987
988
989
990 TemplateExpression parseExpression(final JexlInfo info, final String expr, final Scope scope) {
991 final int size = expr.length();
992 final ExpressionBuilder builder = new ExpressionBuilder(0);
993 final StringBuilder strb = new StringBuilder(size);
994 ParseState state = ParseState.CONST;
995 int immediate1 = 0;
996 int deferred1 = 0;
997 int inner1 = 0;
998 boolean nested = false;
999 int inested = -1;
1000 int lineno = info.getLine();
1001 for (int column = 0; column < size; ++column) {
1002 final char c = expr.charAt(column);
1003 switch (state) {
1004 case CONST:
1005 if (c == immediateChar) {
1006 state = ParseState.IMMEDIATE0;
1007 } else if (c == deferredChar) {
1008 inested = column;
1009 state = ParseState.DEFERRED0;
1010 } else if (c == '\\') {
1011 state = ParseState.ESCAPE;
1012 } else {
1013
1014 strb.append(c);
1015 }
1016 break;
1017 case IMMEDIATE0:
1018 if (c != '{') {
1019
1020 strb.append(immediateChar);
1021 state = ParseState.CONST;
1022
1023 column -= 1;
1024 continue;
1025 }
1026 state = ParseState.IMMEDIATE1;
1027
1028 if (strb.length() > 0) {
1029 final TemplateExpression cexpr = new ConstantExpression(strb.toString(), null);
1030 builder.add(cexpr);
1031 strb.delete(0, Integer.MAX_VALUE);
1032 }
1033 break;
1034 case DEFERRED0:
1035 if (c != '{') {
1036
1037 strb.append(deferredChar);
1038 state = ParseState.CONST;
1039
1040 column -= 1;
1041 continue;
1042 }
1043 state = ParseState.DEFERRED1;
1044
1045 if (strb.length() > 0) {
1046 final TemplateExpression cexpr = new ConstantExpression(strb.toString(), null);
1047 builder.add(cexpr);
1048 strb.delete(0, Integer.MAX_VALUE);
1049 }
1050 break;
1051 case IMMEDIATE1:
1052 if (c == '}') {
1053 if (immediate1 > 0) {
1054 immediate1 -= 1;
1055 strb.append(c);
1056 } else {
1057
1058 final String src = escapeString(strb);
1059 final JexlInfo srcInfo = info.at(lineno, column);
1060 final TemplateExpression iexpr = new ImmediateExpression(src,
1061 jexl.jxltParse(srcInfo, noscript, src, scope), null);
1062 builder.add(iexpr);
1063 strb.delete(0, Integer.MAX_VALUE);
1064 state = ParseState.CONST;
1065 }
1066 } else if (!isIgnorable(c)) {
1067 if (c == '{') {
1068 immediate1 += 1;
1069 }
1070
1071 column = append(strb, expr, column, c);
1072 }
1073 break;
1074 case DEFERRED1:
1075
1076
1077
1078 switch (c) {
1079 case '"':
1080 case '\'':
1081 strb.append(c);
1082 column = StringParser.readString(strb, expr, column + 1, c);
1083 continue;
1084 case '{':
1085 if (expr.charAt(column - 1) == immediateChar) {
1086 inner1 += 1;
1087 strb.deleteCharAt(strb.length() - 1);
1088 nested = true;
1089 } else {
1090 deferred1 += 1;
1091 strb.append(c);
1092 }
1093 continue;
1094 case '}':
1095
1096 if (deferred1 > 0) {
1097 deferred1 -= 1;
1098 strb.append(c);
1099 } else if (inner1 > 0) {
1100 inner1 -= 1;
1101 } else {
1102
1103 final String src = escapeString(strb);
1104 final JexlInfo srcInfo = info.at(lineno, column);
1105 TemplateExpression dexpr;
1106 if (nested) {
1107 dexpr = new NestedExpression(
1108 escapeString(expr.substring(inested, column + 1)),
1109 jexl.jxltParse(srcInfo, noscript, src, scope),
1110 scope);
1111 } else {
1112 dexpr = new DeferredExpression(
1113 src,
1114 jexl.jxltParse(srcInfo, noscript, src, scope));
1115 }
1116 builder.add(dexpr);
1117 strb.delete(0, Integer.MAX_VALUE);
1118 nested = false;
1119 state = ParseState.CONST;
1120 }
1121 break;
1122 default:
1123 if (!isIgnorable(c)) {
1124
1125 column = append(strb, expr, column, c);
1126 }
1127 break;
1128 }
1129 break;
1130 case ESCAPE:
1131 if (c == deferredChar) {
1132 strb.append(deferredChar);
1133 } else if (c == immediateChar) {
1134 strb.append(immediateChar);
1135 } else {
1136 strb.append('\\');
1137 strb.append(c);
1138 }
1139 state = ParseState.CONST;
1140 break;
1141 default:
1142 throw new UnsupportedOperationException("unexpected unified expression type");
1143 }
1144 if (c == '\n') {
1145 lineno += 1;
1146 }
1147 }
1148
1149 if (state != ParseState.CONST) {
1150
1151 switch (state) {
1152 case ESCAPE:
1153 strb.append('\\');
1154 strb.append('\\');
1155 break;
1156 case DEFERRED0:
1157 strb.append(deferredChar);
1158 break;
1159 case IMMEDIATE0:
1160 strb.append(immediateChar);
1161 break;
1162 default:
1163 throw new Exception(info.at(lineno, 0), "malformed expression: " + expr, null);
1164 }
1165 }
1166
1167 if (strb.length() > 0) {
1168 final TemplateExpression cexpr = new ConstantExpression(strb.toString(), null);
1169 builder.add(cexpr);
1170 }
1171 return builder.build(this, null);
1172 }
1173
1174 private String escapeString(final CharSequence str) {
1175 return StringParser.escapeString(str, (char) 0);
1176 }
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193 private static boolean isIgnorable(final char c) {
1194 return c == '\n' || c == '\r' || c == '\t' || c == '\f';
1195 }
1196
1197
1198
1199
1200
1201
1202
1203
1204 List<Block> readTemplate(final String prefix, final Reader source) {
1205 final ArrayList<Block> blocks = new ArrayList<>();
1206 final BufferedReader reader;
1207 if (source instanceof BufferedReader) {
1208 reader = (BufferedReader) source;
1209 } else {
1210 reader = new BufferedReader(source);
1211 }
1212 final StringBuilder strb = new StringBuilder();
1213 BlockType type = null;
1214 int prefixLen;
1215 final Iterator<CharSequence> lines = readLines(reader);
1216 int lineno = 1;
1217 int start = 0;
1218 while (lines.hasNext()) {
1219 final CharSequence line = lines.next();
1220 if (line == null) {
1221 break;
1222 }
1223 if (type == null) {
1224
1225 prefixLen = startsWith(line, prefix);
1226 if (prefixLen >= 0) {
1227 type = BlockType.DIRECTIVE;
1228 strb.append(line.subSequence(prefixLen, line.length()));
1229 } else {
1230 type = BlockType.VERBATIM;
1231 strb.append(line.subSequence(0, line.length()));
1232 }
1233 start = lineno;
1234 } else if (type == BlockType.DIRECTIVE) {
1235
1236 prefixLen = startsWith(line, prefix);
1237 if (prefixLen < 0) {
1238 final Block directive = new Block(BlockType.DIRECTIVE, start, strb.toString());
1239 strb.delete(0, Integer.MAX_VALUE);
1240 blocks.add(directive);
1241 type = BlockType.VERBATIM;
1242 strb.append(line.subSequence(0, line.length()));
1243 start = lineno;
1244 } else {
1245
1246 strb.append(line.subSequence(prefixLen, line.length()));
1247 }
1248 } else {
1249
1250 prefixLen = startsWith(line, prefix);
1251 if (prefixLen >= 0) {
1252 final Block verbatim = new Block(BlockType.VERBATIM, start, strb.toString());
1253 strb.delete(0, Integer.MAX_VALUE);
1254 blocks.add(verbatim);
1255 type = BlockType.DIRECTIVE;
1256 strb.append(line.subSequence(prefixLen, line.length()));
1257 start = lineno;
1258 } else {
1259 strb.append(line.subSequence(0, line.length()));
1260 }
1261 }
1262 lineno += 1;
1263 }
1264
1265 if (type != null && strb.length() > 0) {
1266 final Block block = new Block(type, start, strb.toString());
1267 blocks.add(block);
1268 }
1269 blocks.trimToSize();
1270 return blocks;
1271 }
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281 int startsWith(final CharSequence sequence, final CharSequence pattern) {
1282 final int length = sequence.length();
1283 int s = 0;
1284 while (s < length && Character.isSpaceChar(sequence.charAt(s))) {
1285 s += 1;
1286 }
1287 if (s < length && pattern.length() <= length - s) {
1288 final CharSequence subSequence = sequence.subSequence(s, length);
1289 if (subSequence.subSequence(0, pattern.length()).equals(pattern)) {
1290 return s + pattern.length();
1291 }
1292 }
1293 return -1;
1294 }
1295 }