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