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