All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.jexl3.internal.TemplateEngine Maven / Gradle / Ivy

Go to download

The Apache Commons JEXL library is an implementation of the JSTL Expression Language with extensions.

There is a newer version: 3.4.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.jexl3.internal;

import org.apache.commons.jexl3.JexlContext;
import org.apache.commons.jexl3.JexlException;
import org.apache.commons.jexl3.JexlInfo;
import org.apache.commons.jexl3.JxltEngine;
import org.apache.commons.jexl3.parser.ASTJexlScript;
import org.apache.commons.jexl3.parser.JexlNode;
import org.apache.commons.jexl3.parser.StringParser;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * A JxltEngine implementation.
 * @since 3.0
 */
public final class TemplateEngine extends JxltEngine {
    /** The TemplateExpression cache. */
    private final SoftCache cache;
    /** The JEXL engine instance. */
    private final Engine jexl;
    /** The first character for immediate expressions. */
    private final char immediateChar;
    /** The first character for deferred expressions. */
    private final char deferredChar;
    /** Whether expressions can use JEXL script or only expressions (ie, no for, var, etc). */
    private boolean noscript = true;

    /**
     * Creates a new instance of {@link JxltEngine} creating a local cache.
     * @param aJexl     the JexlEngine to use.
     * @param noScript  whether this engine only allows JEXL expressions or scripts
     * @param cacheSize the number of expressions in this cache, default is 256
     * @param immediate the immediate template expression character, default is '$'
     * @param deferred  the deferred template expression character, default is '#'
     */
    public TemplateEngine(Engine aJexl, boolean noScript, int cacheSize, char immediate, char deferred) {
        this.jexl = aJexl;
        this.cache = new SoftCache(cacheSize);
        immediateChar = immediate;
        deferredChar = deferred;
        noscript = noScript;
    }

    /**
     * @return the immediate character
     */
    char getImmediateChar() {
        return immediateChar;
    }

    /**
     * @return the deferred character
     */
    char getDeferredChar() {
        return deferredChar;
    }

    /**
     * Types of expressions.
     * Each instance carries a counter index per (composite sub-) template expression type.
     * @see ExpressionBuilder
     */
    enum ExpressionType {
        /** Constant TemplateExpression, count index 0. */
        CONSTANT(0),
        /** Immediate TemplateExpression, count index 1. */
        IMMEDIATE(1),
        /** Deferred TemplateExpression, count index 2. */
        DEFERRED(2),
        /** Nested (which are deferred) expressions, count
         * index 2. */
        NESTED(2),
        /** Composite expressions are not counted, index -1. */
        COMPOSITE(-1);
        /** The index in arrays of TemplateExpression counters for composite expressions. */
        private final int index;

        /**
         * Creates an ExpressionType.
         * @param idx the index for this type in counters arrays.
         */
        ExpressionType(int idx) {
            this.index = idx;
        }
    }

    /**
     * A helper class to build expressions.
     * Keeps count of sub-expressions by type.
     */
    static final class ExpressionBuilder {
        /** Per TemplateExpression type counters. */
        private final int[] counts;
        /** The list of expressions. */
        private final ArrayList expressions;

        /**
         * Creates a builder.
         * @param size the initial TemplateExpression array size
         */
        private ExpressionBuilder(int size) {
            counts = new int[]{0, 0, 0};
            expressions = new ArrayList(size <= 0 ? 3 : size);
        }

        /**
         * Adds an TemplateExpression to the list of expressions, maintain per-type counts.
         * @param expr the TemplateExpression to add
         */
        private void add(TemplateExpression expr) {
            counts[expr.getType().index] += 1;
            expressions.add(expr);
        }

        @Override
        public String toString() {
            return toString(new StringBuilder()).toString();
        }

        /**
         * Base for to-string.
         * @param error the builder to fill
         * @return the builder
         */
        private StringBuilder toString(StringBuilder error) {
            error.append("exprs{");
            error.append(expressions.size());
            error.append(", constant:");
            error.append(counts[ExpressionType.CONSTANT.index]);
            error.append(", immediate:");
            error.append(counts[ExpressionType.IMMEDIATE.index]);
            error.append(", deferred:");
            error.append(counts[ExpressionType.DEFERRED.index]);
            error.append("}");
            return error;
        }

        /**
         * Builds an TemplateExpression from a source, performs checks.
         * @param el     the unified el instance
         * @param source the source TemplateExpression
         * @return an TemplateExpression
         */
        private TemplateExpression build(TemplateEngine el, TemplateExpression source) {
            int sum = 0;
            for (int count : counts) {
                sum += count;
            }
            if (expressions.size() != sum) {
                StringBuilder error = new StringBuilder("parsing algorithm error: ");
                throw new IllegalStateException(toString(error).toString());
            }
            // if only one sub-expr, no need to create a composite
            if (expressions.size() == 1) {
                return expressions.get(0);
            } else {
                return el.new CompositeExpression(counts, expressions, source);
            }
        }
    }

    /**
     * Gets the JexlEngine underlying this JxltEngine.
     * @return the JexlEngine
     */
    @Override
    public Engine getEngine() {
        return jexl;
    }

    /**
     * Clears the cache.
     */
    @Override
    public void clearCache() {
        synchronized (cache) {
            cache.clear();
        }
    }

    /**
     * The abstract base class for all unified expressions, immediate '${...}' and deferred '#{...}'.
     */
    abstract class TemplateExpression implements Expression {
        /** The source of this template expression(see {@link TemplateEngine.TemplateExpression#prepare}). */
        protected final TemplateExpression source;

        /**
         * Creates an TemplateExpression.
         * @param src the source TemplateExpression if any
         */
        TemplateExpression(TemplateExpression src) {
            this.source = src != null ? src : this;
        }

        @Override
        public boolean isImmediate() {
            return true;
        }

        @Override
        public final boolean isDeferred() {
            return !isImmediate();
        }

        /**
         * Gets this TemplateExpression type.
         * @return its type
         */
        abstract ExpressionType getType();

        /** @return the info */
        JexlInfo getInfo() {
            return null;
        }

        @Override
        public final String toString() {
            StringBuilder strb = new StringBuilder();
            asString(strb);
            if (source != this) {
                strb.append(" /*= ");
                strb.append(source.toString());
                strb.append(" */");
            }
            return strb.toString();
        }

        @Override
        public String asString() {
            StringBuilder strb = new StringBuilder();
            asString(strb);
            return strb.toString();
        }

        @Override
        public Set> getVariables() {
            return Collections.emptySet();
        }

        @Override
        public final TemplateExpression getSource() {
            return source;
        }

        /**
         * Fills up the list of variables accessed by this unified expression.
         * @param collector the variable collector
         */
        protected void getVariables(Engine.VarCollector collector) {
            // nothing to do
        }

        @Override
        public final TemplateExpression prepare(JexlContext context) {
                return prepare(null, context);
        }

        /**
         * Prepares this expression.
         * @param frame the frame storing parameters and local variables
         * @param context the context storing global variables
         * @return the expression value
         * @throws JexlException
         */
        protected final TemplateExpression prepare(Scope.Frame frame, JexlContext context) {
            try {
                Interpreter interpreter = new TemplateInterpreter(jexl, context, frame, null, null);
                return prepare(interpreter);
            } catch (JexlException xjexl) {
                JexlException xuel = createException(xjexl.getInfo(), "prepare", this, xjexl);
                if (jexl.isSilent()) {
                    jexl.logger.warn(xuel.getMessage(), xuel.getCause());
                    return null;
                }
                throw xuel;
            }
        }

        /**
         * Prepares a sub-expression for interpretation.
         * @param interpreter a JEXL interpreter
         * @return a prepared unified expression
         * @throws JexlException (only for nested and composite)
         */
        protected TemplateExpression prepare(Interpreter interpreter) {
            return this;
        }

        @Override
        public final Object evaluate(JexlContext context) {
            return evaluate(null, context);
        }

        /**
         * Evaluates this expression.
         * @param frame the frame storing parameters and local variables
         * @param context the context storing global variables
         * @return the expression value
         * @throws JexlException
         */
        protected final Object evaluate(Scope.Frame frame, JexlContext context) {
            try {
                Interpreter interpreter = new TemplateInterpreter(jexl, context, frame, null, null);
                return evaluate(interpreter);
            } catch (JexlException xjexl) {
                JexlException xuel = createException(xjexl.getInfo(), "evaluate", this, xjexl);
                if (jexl.isSilent()) {
                    jexl.logger.warn(xuel.getMessage(), xuel.getCause());
                    return null;
                }
                throw xuel;
            }
        }

        /**
         * Interprets a sub-expression.
         * @param interpreter a JEXL interpreter
         * @return the result of interpretation
         * @throws JexlException (only for nested and composite)
         */
        protected abstract Object evaluate(Interpreter interpreter);

    }

    /** A constant unified expression. */
    class ConstantExpression extends TemplateExpression {
        /** The constant held by this unified expression. */
        private final Object value;

        /**
         * Creates a constant unified expression.
         * 

* If the wrapped constant is a string, it is treated * as a JEXL strings with respect to escaping. *

* @param val the constant value * @param source the source TemplateExpression if any */ ConstantExpression(Object val, TemplateExpression source) { super(source); if (val == null) { throw new NullPointerException("constant can not be null"); } if (val instanceof String) { val = StringParser.buildString((String) val, false); } this.value = val; } @Override ExpressionType getType() { return ExpressionType.CONSTANT; } @Override public StringBuilder asString(StringBuilder strb) { if (value != null) { strb.append(value.toString()); } return strb; } @Override protected Object evaluate(Interpreter interpreter) { return value; } } /** The base for JEXL based unified expressions. */ abstract class JexlBasedExpression extends TemplateExpression { /** The JEXL string for this unified expression. */ protected final CharSequence expr; /** The JEXL node for this unified expression. */ protected final JexlNode node; /** * Creates a JEXL interpretable unified expression. * @param theExpr the unified expression as a string * @param theNode the unified expression as an AST * @param theSource the source unified expression if any */ protected JexlBasedExpression(CharSequence theExpr, JexlNode theNode, TemplateExpression theSource) { super(theSource); this.expr = theExpr; this.node = theNode; } @Override public StringBuilder asString(StringBuilder strb) { strb.append(isImmediate() ? immediateChar : deferredChar); strb.append("{"); strb.append(expr); strb.append("}"); return strb; } @Override protected Object evaluate(Interpreter interpreter) { return interpreter.interpret(node); } @Override public Set> getVariables() { Engine.VarCollector collector = new Engine.VarCollector(); getVariables(collector); return collector.collected(); } @Override protected void getVariables(Engine.VarCollector collector) { jexl.getVariables(node instanceof ASTJexlScript? (ASTJexlScript) node : null, node, collector); } @Override JexlInfo getInfo() { return node.jexlInfo(); } } /** An immediate unified expression: ${jexl}. */ class ImmediateExpression extends JexlBasedExpression { /** * Creates an immediate unified expression. * @param expr the unified expression as a string * @param node the unified expression as an AST * @param source the source unified expression if any */ ImmediateExpression(CharSequence expr, JexlNode node, TemplateExpression source) { super(expr, node, source); } @Override ExpressionType getType() { return ExpressionType.IMMEDIATE; } @Override protected TemplateExpression prepare(Interpreter interpreter) { // evaluate immediate as constant Object value = evaluate(interpreter); return value != null ? new ConstantExpression(value, source) : null; } } /** A deferred unified expression: #{jexl}. */ class DeferredExpression extends JexlBasedExpression { /** * Creates a deferred unified expression. * @param expr the unified expression as a string * @param node the unified expression as an AST * @param source the source unified expression if any */ DeferredExpression(CharSequence expr, JexlNode node, TemplateExpression source) { super(expr, node, source); } @Override public boolean isImmediate() { return false; } @Override ExpressionType getType() { return ExpressionType.DEFERRED; } @Override protected TemplateExpression prepare(Interpreter interpreter) { return new ImmediateExpression(expr, node, source); } @Override protected void getVariables(Engine.VarCollector collector) { // noop } } /** * An immediate unified expression nested into a deferred unified expression. * #{...${jexl}...} * Note that the deferred syntax is JEXL's. */ class NestedExpression extends JexlBasedExpression { /** * Creates a nested unified expression. * @param expr the unified expression as a string * @param node the unified expression as an AST * @param source the source unified expression if any */ NestedExpression(CharSequence expr, JexlNode node, TemplateExpression source) { super(expr, node, source); if (this.source != this) { throw new IllegalArgumentException("Nested TemplateExpression can not have a source"); } } @Override public StringBuilder asString(StringBuilder strb) { strb.append(expr); return strb; } @Override public boolean isImmediate() { return false; } @Override ExpressionType getType() { return ExpressionType.NESTED; } @Override protected TemplateExpression prepare(Interpreter interpreter) { String value = interpreter.interpret(node).toString(); JexlNode dnode = jexl.parse(node.jexlInfo(), value, null, false, noscript); return new ImmediateExpression(value, dnode, this); } @Override protected Object evaluate(Interpreter interpreter) { return prepare(interpreter).evaluate(interpreter); } } /** A composite unified expression: "... ${...} ... #{...} ...". */ class CompositeExpression extends TemplateExpression { /** Bit encoded (deferred count > 0) bit 1, (immediate count > 0) bit 0. */ private final int meta; /** The list of sub-expression resulting from parsing. */ protected final TemplateExpression[] exprs; /** * Creates a composite expression. * @param counters counters of expressions per type * @param list the sub-expressions * @param src the source for this expresion if any */ CompositeExpression(int[] counters, ArrayList list, TemplateExpression src) { super(src); this.exprs = list.toArray(new TemplateExpression[list.size()]); this.meta = (counters[ExpressionType.DEFERRED.index] > 0 ? 2 : 0) | (counters[ExpressionType.IMMEDIATE.index] > 0 ? 1 : 0); } @Override public boolean isImmediate() { // immediate if no deferred return (meta & 2) == 0; } @Override ExpressionType getType() { return ExpressionType.COMPOSITE; } @Override public StringBuilder asString(StringBuilder strb) { for (TemplateExpression e : exprs) { e.asString(strb); } return strb; } @Override public Set> getVariables() { Engine.VarCollector collector = new Engine.VarCollector(); for (TemplateExpression expr : exprs) { expr.getVariables(collector); } return collector.collected(); } /** * Fills up the list of variables accessed by this unified expression. * @param collector the variable collector */ @Override protected void getVariables(Engine.VarCollector collector) { for (TemplateExpression expr : exprs) { expr.getVariables(collector); } } @Override protected TemplateExpression prepare(Interpreter interpreter) { // if this composite is not its own source, it is already prepared if (source != this) { return this; } // we need to prepare all sub-expressions final int size = exprs.length; final ExpressionBuilder builder = new ExpressionBuilder(size); // tracking whether prepare will return a different expression boolean eq = true; for (int e = 0; e < size; ++e) { TemplateExpression expr = exprs[e]; TemplateExpression prepared = expr.prepare(interpreter); // add it if not null if (prepared != null) { builder.add(prepared); } // keep track of TemplateExpression equivalence eq &= expr == prepared; } return eq ? this : builder.build(TemplateEngine.this, this); } @Override protected Object evaluate(Interpreter interpreter) { final int size = exprs.length; Object value; // common case: evaluate all expressions & concatenate them as a string StringBuilder strb = new StringBuilder(); for (int e = 0; e < size; ++e) { value = exprs[e].evaluate(interpreter); if (value != null) { strb.append(value.toString()); } } value = strb.toString(); return value; } } @Override public JxltEngine.Expression createExpression(JexlInfo info, String expression) { if (info == null) { info = jexl.createInfo(); } Exception xuel = null; TemplateExpression stmt = null; try { stmt = cache.get(expression); if (stmt == null) { stmt = parseExpression(info, expression, null); cache.put(expression, stmt); } } catch (JexlException xjexl) { xuel = new Exception(xjexl.getInfo(), "failed to parse '" + expression + "'", xjexl); } if (xuel != null) { if (jexl.isSilent()) { jexl.logger.warn(xuel.getMessage(), xuel.getCause()); stmt = null; } else { throw xuel; } } return stmt; } /** * Creates a JxltEngine.Exception from a JexlException. * @param info the source info * @param action createExpression, prepare, evaluate * @param expr the template expression * @param xany the exception * @return an exception containing an explicit error message */ static Exception createException(JexlInfo info, String action, TemplateExpression expr, java.lang.Exception xany) { StringBuilder strb = new StringBuilder("failed to "); strb.append(action); if (expr != null) { strb.append(" '"); strb.append(expr.toString()); strb.append("'"); } Throwable cause = xany.getCause(); if (cause != null) { String causeMsg = cause.getMessage(); if (causeMsg != null) { strb.append(", "); strb.append(causeMsg); } } return new Exception(info, strb.toString(), xany); } /** The different parsing states. */ private enum ParseState { /** Parsing a constant. */ CONST, /** Parsing after $ . */ IMMEDIATE0, /** Parsing after # . */ DEFERRED0, /** Parsing after ${ . */ IMMEDIATE1, /** Parsing after #{ . */ DEFERRED1, /** Parsing after \ . */ ESCAPE } /** * Parses a unified expression. * @param info the source info * @param expr the string expression * @param scope the template scope * @return the unified expression instance * @throws JexlException if an error occur during parsing */ TemplateExpression parseExpression(JexlInfo info, String expr, Scope scope) { // CSOFF: MethodLength final int size = expr.length(); final ExpressionBuilder builder = new ExpressionBuilder(0); final StringBuilder strb = new StringBuilder(size); ParseState state = ParseState.CONST; int immediate1 = 0; int deferred1 = 0; int inner1 = 0; boolean nested = false; int inested = -1; int lineno = info.getLine(); for (int column = 0; column < size; ++column) { char c = expr.charAt(column); switch (state) { default: // in case we ever add new unified expresssion type throw new UnsupportedOperationException("unexpected unified expression type"); case CONST: if (c == immediateChar) { state = ParseState.IMMEDIATE0; } else if (c == deferredChar) { inested = column; state = ParseState.DEFERRED0; } else if (c == '\\') { state = ParseState.ESCAPE; } else { // do buildup expr strb.append(c); } break; case IMMEDIATE0: // $ if (c == '{') { state = ParseState.IMMEDIATE1; // if chars in buffer, create constant if (strb.length() > 0) { TemplateExpression cexpr = new ConstantExpression(strb.toString(), null); builder.add(cexpr); strb.delete(0, Integer.MAX_VALUE); } } else { // revert to CONST strb.append(immediateChar); strb.append(c); state = ParseState.CONST; } break; case DEFERRED0: // # if (c == '{') { state = ParseState.DEFERRED1; // if chars in buffer, create constant if (strb.length() > 0) { TemplateExpression cexpr = new ConstantExpression(strb.toString(), null); builder.add(cexpr); strb.delete(0, Integer.MAX_VALUE); } } else { // revert to CONST strb.append(deferredChar); strb.append(c); state = ParseState.CONST; } break; case IMMEDIATE1: // ${... if (c == '}') { if (immediate1 > 0) { immediate1 -= 1; strb.append(c); } else { // materialize the immediate expr String src = strb.toString(); TemplateExpression iexpr = new ImmediateExpression( src, jexl.parse(info.at(lineno, column), src, scope, false, noscript), null); builder.add(iexpr); strb.delete(0, Integer.MAX_VALUE); state = ParseState.CONST; } } else { if (c == '{') { immediate1 += 1; } // do buildup expr strb.append(c); } break; case DEFERRED1: // #{... // skip inner strings (for '}') if (c == '"' || c == '\'') { strb.append(c); column = StringParser.readString(strb, expr, column + 1, c); continue; } // nested immediate in deferred; need to balance count of '{' & '}' if (c == '{') { if (expr.charAt(column - 1) == immediateChar) { inner1 += 1; strb.deleteCharAt(strb.length() - 1); nested = true; } else { deferred1 += 1; strb.append(c); } continue; } // closing '}' if (c == '}') { // balance nested immediate if (deferred1 > 0) { deferred1 -= 1; strb.append(c); } else if (inner1 > 0) { inner1 -= 1; } else { // materialize the nested/deferred expr String src = strb.toString(); TemplateExpression dexpr; if (nested) { dexpr = new NestedExpression( expr.substring(inested, column + 1), jexl.parse(info.at(lineno, column), src, scope, false, noscript), null); } else { dexpr = new DeferredExpression( strb.toString(), jexl.parse(info.at(lineno, column), src, scope, false, noscript), null); } builder.add(dexpr); strb.delete(0, Integer.MAX_VALUE); nested = false; state = ParseState.CONST; } } else { // do buildup expr strb.append(c); } break; case ESCAPE: if (c == deferredChar) { strb.append(deferredChar); } else if (c == immediateChar) { strb.append(immediateChar); } else { strb.append('\\'); strb.append(c); } state = ParseState.CONST; } if (c == '\n') { lineno += 1; } } // we should be in that state if (state != ParseState.CONST) { throw new Exception(info.at(lineno, 0), "malformed expression: " + expr, null); } // if any chars were buffered, add them as a constant if (strb.length() > 0) { TemplateExpression cexpr = new ConstantExpression(strb.toString(), null); builder.add(cexpr); } return builder.build(this, null); } /** * The enum capturing the difference between verbatim and code source fragments. */ enum BlockType { /** Block is to be output "as is" but may be a unified expression. */ VERBATIM, /** Block is a directive, ie a fragment of JEXL code. */ DIRECTIVE } /** * Abstract the source fragments, verbatim or immediate typed text blocks. */ static final class Block { /** The type of block, verbatim or directive. */ private final BlockType type; /** The block start line info. */ private final int line; /** The actual content. */ private final String body; /** * Creates a new block. * @param theType the block type * @param theLine the line number * @param theBlock the content */ Block(BlockType theType, int theLine, String theBlock) { type = theType; line = theLine; body = theBlock; } /** * @return type */ BlockType getType() { return type; } /** * @return line */ int getLine() { return line; } /** * @return body */ String getBody() { return body; } @Override public String toString() { if (BlockType.VERBATIM.equals(type)) { return body; } else { // CHECKSTYLE:OFF StringBuilder strb = new StringBuilder(64); // CSOFF: MagicNumber // CHECKSTYLE:ON Iterator lines = readLines(new StringReader(body)); while (lines.hasNext()) { strb.append("$$").append(lines.next()); } return strb.toString(); } } /** * Appends this block string representation to a builder. * @param strb the string builder to append to * @param prefix the line prefix (immediate or deferred) */ protected void toString(StringBuilder strb, String prefix) { if (BlockType.VERBATIM.equals(type)) { strb.append(body); } else { Iterator lines = readLines(new StringReader(body)); while (lines.hasNext()) { strb.append(prefix).append(lines.next()); } } } } /** * Whether a sequence starts with a given set of characters (following spaces). *

Space characters at beginning of line before the pattern are discarded.

* @param sequence the sequence * @param pattern the pattern to match at start of sequence * @return the first position after end of pattern if it matches, -1 otherwise */ protected int startsWith(CharSequence sequence, CharSequence pattern) { int length = sequence.length(); int s = 0; while (s < length && Character.isSpaceChar(sequence.charAt(s))) { s += 1; } if (s < length && pattern.length() <= (length - s)) { sequence = sequence.subSequence(s, length); if (sequence.subSequence(0, pattern.length()).equals(pattern)) { return s + pattern.length(); } } return -1; } /** * Read lines from a (buffered / mark-able) reader keeping all new-lines and line-feeds. * @param reader the reader * @return the line iterator */ protected static Iterator readLines(final Reader reader) { if (!reader.markSupported()) { throw new IllegalArgumentException("mark support in reader required"); } return new Iterator() { private CharSequence next = doNext(); private CharSequence doNext() { StringBuffer strb = new StringBuffer(64); // CSOFF: MagicNumber int c; boolean eol = false; try { while ((c = reader.read()) >= 0) { if (eol) {// && (c != '\n' && c != '\r')) { reader.reset(); break; } if (c == '\n') { eol = true; } strb.append((char) c); reader.mark(1); } } catch (IOException xio) { return null; } return strb.length() > 0 ? strb : null; } @Override public boolean hasNext() { return next != null; } @Override public CharSequence next() { CharSequence current = next; if (current != null) { next = doNext(); } return current; } @Override public void remove() { throw new UnsupportedOperationException("Not supported."); } }; } /** * Reads lines of a template grouping them by typed blocks. * @param prefix the directive prefix * @param source the source reader * @return the list of blocks */ protected List readTemplate(final String prefix, Reader source) { final ArrayList blocks = new ArrayList(); final BufferedReader reader; if (source instanceof BufferedReader) { reader = (BufferedReader) source; } else { reader = new BufferedReader(source); } final StringBuilder strb = new StringBuilder(); BlockType type = null; int prefixLen; Iterator lines = readLines(reader); int lineno = 0; int start = 0; while (lines.hasNext()) { CharSequence line = lines.next(); if (line == null) { break; } else if (type == null) { // determine starting type if not known yet prefixLen = startsWith(line, prefix); if (prefixLen >= 0) { type = BlockType.DIRECTIVE; strb.append(line.subSequence(prefixLen, line.length())); } else { type = BlockType.VERBATIM; strb.append(line.subSequence(0, line.length())); } start = lineno; } else if (type == BlockType.DIRECTIVE) { // switch to verbatim if necessary prefixLen = startsWith(line, prefix); if (prefixLen < 0) { Block directive = new Block(BlockType.DIRECTIVE, start, strb.toString()); strb.delete(0, Integer.MAX_VALUE); blocks.add(directive); type = BlockType.VERBATIM; strb.append(line.subSequence(0, line.length())); start = lineno; } else { // still a directive strb.append(line.subSequence(prefixLen, line.length())); } } else if (type == BlockType.VERBATIM) { // switch to directive if necessary prefixLen = startsWith(line, prefix); if (prefixLen >= 0) { Block verbatim = new Block(BlockType.VERBATIM, start, strb.toString()); strb.delete(0, Integer.MAX_VALUE); blocks.add(verbatim); type = BlockType.DIRECTIVE; strb.append(line.subSequence(prefixLen, line.length())); start = lineno; } else { strb.append(line.subSequence(0, line.length())); } } lineno += 1; } // input may be null if (type != null && strb.length() > 0) { Block block = new Block(type, start, strb.toString()); blocks.add(block); } blocks.trimToSize(); return blocks; } @Override public TemplateScript createTemplate(JexlInfo info, String prefix, Reader source, String... parms) { return new TemplateScript(this, info, prefix, source, parms); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy