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