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