    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.jexl3;
    019
    020import java.io.Reader;
    021import java.io.StringReader;
    022import java.io.Writer;
    023import java.util.List;
    024import java.util.Map;
    025import java.util.Set;
    026
    027/**
    028 * A simple "JeXL Template" engine.
    029 *
    030 * <p>At the base is an evaluator similar to the Unified EL evaluator used in JSP/JSF based on JEXL.
    031 * At the top is a template engine inspired by Velocity that uses JEXL (instead of OGNL/VTL) as the scripting
    032 * language.</p>
    033 *
    034 * <p>The evaluator is intended to be used in configuration modules, XML based frameworks or JSP taglibs
    035 * and facilitate the implementation of expression evaluation.</p>
    036 *
    037 * <p>The template engine is intended to output any form of text; html, XML, CSV...</p>
    038 *
    039 * @since 3.0
    040 */
    041public abstract class JxltEngine {
    042
    043    /**
    044     * The sole type of (runtime) exception the JxltEngine can throw.
    045     */
    046    public static class Exception extends JexlException {
    047
    048        /** Serial version UID. */
    049        private static final long serialVersionUID = 201112030113L;
    050
    051        /**
    052         * Creates an Exception.
    053         *
    054         * @param info the contextual information
    055         * @param msg the exception message
    056         * @param cause the exception cause
    057         */
    058        public Exception(final JexlInfo info, final String msg, final Throwable cause) {
    059            super(info, msg, cause);
    060        }
    061    }
    062
    063    /**
    064     * A unified expression that can mix immediate, deferred and nested sub-expressions as well as string constants;
    065     * <ul>
    066     *   <li>The "immediate" syntax is of the form <code>"...${jexl-expr}..."</code></li>
    067     *   <li>The "deferred" syntax is of the form <code>"...#{jexl-expr}..."</code></li>
    068     *   <li>The "nested" syntax is of the form <code>"...#{...${jexl-expr0}...}..."</code></li>
    069     *   <li>The "composite" syntax is of the form <code>"...${jexl-expr0}... #{jexl-expr1}..."</code></li>
    070     * </ul>
    071     *
    072     * <p>Deferred and immediate expression carry different intentions:</p>
    073     *
    074     * <ul>
    075     *   <li>An immediate expression indicate that evaluation is intended to be performed close to
    076     *       the definition/parsing point.</li>
    077     *   <li>A deferred expression indicate that evaluation is intended to occur at a later stage.</li>
    078     * </ul>
    079     *
    080     * <p>For instance: <code>"Hello ${name}, now is #{time}"</code> is a composite "deferred" expression since one
    081     * of its subexpressions is deferred. Furthermore, this (composite) expression intent is
    082     * to perform two evaluations; one close to its definition and another one in a later
    083     * phase.</p>
    084     *
    085     * <p>The API reflects this feature in 2 methods, prepare and evaluate. The prepare method
    086     * will evaluate the immediate subexpression and return an expression that contains only
    087     * the deferred subexpressions (and constants), a prepared expression. Such a prepared expression
    088     * is suitable for a later phase evaluation that may occur with a different JexlContext.
    089     * Note that it is valid to call evaluate without prepare in which case the same JexlContext
    090     * is used for the 2 evaluation phases.</p>
    091     *
    092     * <p>In the most common use-case where deferred expressions are to be kept around as properties of objects,
    093     * one should createExpression and prepare an expression before storing it and evaluate it each time
    094     * the property storing it is accessed.</p>
    095     *
    096     * <p>Note that nested expression use the JEXL syntax as in:</p>
    097     *
    098     * <blockquote><code>"#{${bar}+'.charAt(2)'}"</code></blockquote>
    099     *
    100     * <p>The most common mistake leading to an invalid expression being the following:</p>
    101     *
    102     * <blockquote><code>"#{${bar}charAt(2)}"</code></blockquote>
    103     *
    104     * <p>Also note that methods that createExpression evaluate expressions may throw <em>unchecked</em> exceptions;
    105     * The {@link JxltEngine.Exception} are thrown when the engine instance is in "non-silent" mode
    106     * but since these are RuntimeException, user-code <em>should</em> catch them where appropriate.</p>
    107     *
    108     * @since 2.0
    109     */
    110    public interface Expression {
    111
    112        /**
    113         * Generates this expression's string representation.
    114         *
    115         * @return the string representation
    116         */
    117        String asString();
    118
    119        /**
    120         * Adds this expression's string representation to a StringBuilder.
    121         *
    122         * @param strb the builder to fill
    123         * @return the builder argument
    124         */
    125        StringBuilder asString(StringBuilder strb);
    126
    127        /**
    128         * Evaluates this expression.
    129         *
    130         * <p>If the underlying JEXL engine is silent, errors will be logged through its logger as warning.</p>
    131         *
    132         * @param context the variable context
    133         * @return the result of this expression evaluation or null if an error occurs and the {@link JexlEngine} is
    134         * running in silent mode
    135         * @throws Exception if an error occurs and the {@link JexlEngine}
    136         * is not silent
    137         */
    138        Object evaluate(JexlContext context);
    139
    140        /**
    141         * Retrieves this expression's source expression.
    142         * <p>
    143         * If this expression was prepared, this allows to retrieve the
    144         * original expression that lead to it.</p>
    145         * <p>Other expressions return themselves.</p>
    146         *
    147         * @return the source expression
    148         */
    149        Expression getSource();
    150
    151        /**
    152         * Gets the list of variables accessed by this expression.
    153         * <p>This method will visit all nodes of the sub-expressions and extract all variables whether they
    154         * are written in 'dot' or 'bracketed' notation. (a.b is equivalent to a['b']).</p>
    155         *
    156         * @return the set of variables, each as a list of strings (ant-ish variables use more than 1 string)
    157         * or the empty set if no variables are used
    158         */
    159        Set<List<String>> getVariables();
    160
    161        /**
    162         * Checks whether this expression is deferred.
    163         *
    164         * @return true if deferred, false otherwise
    165         */
    166        boolean isDeferred();
    167
    168        /**
    169         * Checks whether this expression is immediate.
    170         *
    171         * @return true if immediate, false otherwise
    172         */
    173        boolean isImmediate();
    174
    175        /**
    176         * Evaluates the immediate sub-expressions.
    177         *
    178         * <p>When the expression is dependant upon immediate and deferred sub-expressions,
    179         * evaluates the immediate sub-expressions with the context passed as parameter
    180         * and returns this expression deferred form.</p>
    181         *
    182         * <p>In effect, this binds the result of the immediate sub-expressions evaluation in the
    183         * context, allowing to differ evaluation of the remaining (deferred) expression within another context.
    184         * This only has an effect to nested and composite expressions that contain differed and
    185         * immediate sub-expressions.</p>
    186         *
    187         * <p>If the underlying JEXL engine is silent, errors will be logged through its logger as warning.* </p>
    188         *
    189         * @param context the context to use for immediate expression evaluations
    190         * @return an {@link Expression} or null if an error occurs and the {@link JexlEngine} is running
    191         * in silent mode
    192         * @throws Exception if an error occurs and the {@link JexlEngine} is not in silent mode
    193         */
    194        Expression prepare(JexlContext context);
    195
    196        /**
    197         * Formats this expression, adding its source string representation in
    198         * comments if available: 'expression /*= source *\/'' .
    199         *
    200         * @return the formatted expression string
    201         */
    202        @Override
    203        String toString();
    204    }
    205
    206    /**
    207     * A template is a JEXL script that evaluates by writing its content through a Writer.
    208     * <p>
    209     * The source text is parsed considering each line beginning with '$$' (as default pattern) as JEXL script code
    210     * and all others as Unified JEXL expressions; those expressions will be invoked from the script during
    211     * evaluation and their output gathered through a writer.
    212     * It is thus possible to use looping or conditional construct "around" expressions generating output.
    213     * </p>
    214     * For instance:
    215     * <blockquote><pre>
    216     * $$ for(var x : [1, 3, 5, 42, 169]) {
    217     * $$   if (x == 42) {
    218     * Life, the universe, and everything
    219     * $$   } else if (x &gt; 42) {
    220     * The value $(x} is over forty-two
    221     * $$   } else {
    222     * The value ${x} is under forty-two
    223     * $$   }
    224     * $$ }
    225     * </pre></blockquote>
    226     *
    227     * <p>Will evaluate as:</p>
    228     *
    229     * <blockquote><pre>
    230     * The value 1 is under forty-two
    231     * The value 3 is under forty-two
    232     * The value 5 is under forty-two
    233     * Life, the universe, and everything
    234     * The value 169 is over forty-two
    235     * </pre></blockquote>
    236     *
    237     * <p>During evaluation, the template context exposes its writer as '$jexl' which is safe to use in this case.
    238     * This allows writing directly through the writer without adding new-lines as in:</p>
    239     *
    240     * <blockquote><pre>
    241     * $$ for(var cell : cells) { $jexl.print(cell); $jexl.print(';') }
    242     * </pre></blockquote>
    243     *
    244     * <p>A template is expanded as one JEXL script and a list of template expressions; each template expression is
    245     * being replaced in the script by a call to jexl:print(expr) (the expr is in fact the expr number in the template).
    246     * This integration uses a specialized JexlContext (TemplateContext) that serves as a namespace (for jexl:)
    247     * and stores the template expression array and the writer (java.io.Writer) that the 'jexl:print(...)'
    248     * delegates the output generation to.</p>
    249     *
    250     * @since 3.0
    251     */
    252    public interface Template {
    253
    254        /**
    255         * Recreate the template source from its inner components.
    256         *
    257         * @return the template source rewritten
    258         */
    259        String asString();
    260
    261        /**
    262         * Evaluates this template.
    263         *
    264         * @param context the context to use during evaluation
    265         * @param writer the writer to use for output
    266         */
    267        void evaluate(JexlContext context, Writer writer);
    268
    269        /**
    270         * Evaluates this template.
    271         *
    272         * @param context the context to use during evaluation
    273         * @param writer the writer to use for output
    274         * @param args the arguments
    275         */
    276        void evaluate(JexlContext context, Writer writer, Object... args);
    277
    278        /**
    279         * Gets the list of parameters expected by this template.
    280         *
    281         * @return the parameter names array
    282         */
    283        String[] getParameters();
    284
    285        /**
    286         * Gets this script pragmas.
    287         *
    288         * @return the (non null, may be empty) pragmas map
    289         * @since 3.1
    290         */
    291        Map<String, Object> getPragmas();
    292
    293        /**
    294         * Gets the list of variables accessed by this template.
    295         * <p>This method will visit all nodes of the sub-expressions and extract all variables whether they
    296         * are written in 'dot' or 'bracketed' notation. (a.b is equivalent to a['b']).</p>
    297         *
    298         * @return the set of variables, each as a list of strings (ant-ish variables use more than 1 string)
    299         * or the empty set if no variables are used
    300         */
    301        Set<List<String>> getVariables();
    302
    303        /**
    304         * Prepares this template by expanding any contained deferred TemplateExpression.
    305         *
    306         * @param context the context to prepare against
    307         * @return the prepared version of the template
    308         */
    309        Template prepare(JexlContext context);
    310    }
    311
    312    /**
    313     * Clears the cache.
    314     */
    315    public abstract void clearCache();
    316
    317    /**
    318     * Creates a {@link Expression} from an expression string.
    319     * Uses and fills up the expression cache if any.
    320     *
    321     * <p>If the underlying JEXL engine is silent, errors will be logged through its logger as warnings.</p>
    322     *
    323     * @param info the {@link JexlInfo} source information
    324     * @param expression the {@link Template} string expression
    325     * @return the {@link Expression}, null if silent and an error occurred
    326     * @throws Exception if an error occurs and the {@link JexlEngine} is not silent
    327     */
    328    public abstract Expression createExpression(JexlInfo info, String expression);
    329
    330    /**
    331     * Creates a {@link Expression} from an expression string.
    332     * Uses and fills up the expression cache if any.
    333     *
    334     * <p>If the underlying JEXL engine is silent, errors will be logged through its logger as warnings.</p>
    335     *
    336     * @param expression the {@link Template} string expression
    337     * @return the {@link Expression}, null if silent and an error occurred
    338     * @throws Exception if an error occurs and the {@link JexlEngine} is not silent
    339     */
    340    public Expression createExpression(final String expression) {
    341        return createExpression(null, expression);
    342    }
    343
    344    /**
    345     * Creates a new template.
    346     *
    347     * @param info the source info
    348     * @param source the source
    349     * @return the template
    350     */
    351    public Template createTemplate(final JexlInfo info, final String source) {
    352        return createTemplate(info, "$$", new StringReader(source), (String[]) null);
    353    }
    354
    355    /**
    356     * Creates a new template.
    357     *
    358     * @param info the jexl info (file, line, column)
    359     * @param prefix the directive prefix
    360     * @param source the source
    361     * @param parms the parameter names
    362     * @return the template
    363     */
    364    public abstract Template createTemplate(JexlInfo info, String prefix, Reader source, String... parms);
    365
    366    /**
    367     * Creates a new template.
    368     *
    369     * @param info the source info
    370     * @param parms the parameter names
    371     * @param source the source
    372     * @return the template
    373     */
    374    public Template createTemplate(final JexlInfo info, final String source, final String... parms) {
    375        return createTemplate(info, "$$", new StringReader(source), parms);
    376    }
    377
    378    /**
    379     * Creates a new template.
    380     *
    381     * @param source the source
    382     * @return the template
    383     */
    384    public Template createTemplate(final String source) {
    385        return createTemplate(null, source);
    386    }
    387
    388    /**
    389     * Creates a new template.
    390     *
    391     * @param prefix the directive prefix
    392     * @param source the source
    393     * @param parms the parameter names
    394     * @return the template
    395     */
    396    public Template createTemplate(final String prefix, final Reader source, final String... parms) {
    397        return createTemplate(null, prefix, source, parms);
    398    }
    399
    400    /**
    401     * Creates a new template.
    402     *
    403     * @param source the source
    404     * @param parms the parameter names
    405     * @return the template
    406     */
    407    public Template createTemplate(final String source, final String... parms) {
    408        return createTemplate(null, source, parms);
    409    }
    410
    411    /**
    412     * Gets the {@link JexlEngine} underlying this template engine.
    413     *
    414     * @return the JexlEngine
    415     */
    416    public abstract JexlEngine getEngine();
    417}