    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     * Creates a {@link Expression} from an expression string.
    208     * Uses and fills up the expression cache if any.
    209     *
    210     * <p>If the underlying JEXL engine is silent, errors will be logged through its logger as warnings.</p>
    211     *
    212     * @param expression the {@link Template} string expression
    213     * @return the {@link Expression}, null if silent and an error occurred
    214     * @throws Exception if an error occurs and the {@link JexlEngine} is not silent
    215     */
    216    public Expression createExpression(final String expression) {
    217        return createExpression(null, expression);
    218    }
    219
    220    /**
    221     * Creates a {@link Expression} from an expression string.
    222     * Uses and fills up the expression cache if any.
    223     *
    224     * <p>If the underlying JEXL engine is silent, errors will be logged through its logger as warnings.</p>
    225     *
    226     * @param info the {@link JexlInfo} source information
    227     * @param expression the {@link Template} string expression
    228     * @return the {@link Expression}, null if silent and an error occurred
    229     * @throws Exception if an error occurs and the {@link JexlEngine} is not silent
    230     */
    231    public abstract Expression createExpression(JexlInfo info, String expression);
    232
    233    /**
    234     * A template is a JEXL script that evaluates by writing its content through a Writer.
    235     * <p>
    236     * The source text is parsed considering each line beginning with '$$' (as default pattern) as JEXL script code
    237     * and all others as Unified JEXL expressions; those expressions will be invoked from the script during
    238     * evaluation and their output gathered through a writer.
    239     * It is thus possible to use looping or conditional construct "around" expressions generating output.
    240     * </p>
    241     * For instance:
    242     * <blockquote><pre>
    243     * $$ for(var x : [1, 3, 5, 42, 169]) {
    244     * $$   if (x == 42) {
    245     * Life, the universe, and everything
    246     * $$   } else if (x &gt; 42) {
    247     * The value $(x} is over forty-two
    248     * $$   } else {
    249     * The value ${x} is under forty-two
    250     * $$   }
    251     * $$ }
    252     * </pre></blockquote>
    253     *
    254     * <p>Will evaluate as:</p>
    255     *
    256     * <blockquote><pre>
    257     * The value 1 is under forty-two
    258     * The value 3 is under forty-two
    259     * The value 5 is under forty-two
    260     * Life, the universe, and everything
    261     * The value 169 is over forty-two
    262     * </pre></blockquote>
    263     *
    264     * <p>During evaluation, the template context exposes its writer as '$jexl' which is safe to use in this case.
    265     * This allows writing directly through the writer without adding new-lines as in:</p>
    266     *
    267     * <blockquote><pre>
    268     * $$ for(var cell : cells) { $jexl.print(cell); $jexl.print(';') }
    269     * </pre></blockquote>
    270     *
    271     * <p>A template is expanded as one JEXL script and a list of template expressions; each template expression is
    272     * being replaced in the script by a call to jexl:print(expr) (the expr is in fact the expr number in the template).
    273     * This integration uses a specialized JexlContext (TemplateContext) that serves as a namespace (for jexl:)
    274     * and stores the template expression array and the writer (java.io.Writer) that the 'jexl:print(...)'
    275     * delegates the output generation to.</p>
    276     *
    277     * @since 3.0
    278     */
    279    public interface Template {
    280
    281        /**
    282         * Recreate the template source from its inner components.
    283         *
    284         * @return the template source rewritten
    285         */
    286        String asString();
    287
    288        /**
    289         * Evaluates this template.
    290         *
    291         * @param context the context to use during evaluation
    292         * @param writer the writer to use for output
    293         */
    294        void evaluate(JexlContext context, Writer writer);
    295
    296        /**
    297         * Evaluates this template.
    298         *
    299         * @param context the context to use during evaluation
    300         * @param writer the writer to use for output
    301         * @param args the arguments
    302         */
    303        void evaluate(JexlContext context, Writer writer, Object... args);
    304
    305        /**
    306         * Prepares this template by expanding any contained deferred TemplateExpression.
    307         *
    308         * @param context the context to prepare against
    309         * @return the prepared version of the template
    310         */
    311        Template prepare(JexlContext context);
    312
    313        /**
    314         * Gets the list of variables accessed by this template.
    315         * <p>This method will visit all nodes of the sub-expressions and extract all variables whether they
    316         * are written in 'dot' or 'bracketed' notation. (a.b is equivalent to a['b']).</p>
    317         *
    318         * @return the set of variables, each as a list of strings (ant-ish variables use more than 1 string)
    319         * or the empty set if no variables are used
    320         */
    321        Set<List<String>> getVariables();
    322
    323        /**
    324         * Gets the list of parameters expected by this template.
    325         *
    326         * @return the parameter names array
    327         */
    328        String[] getParameters();
    329
    330        /**
    331         * Gets this script pragmas.
    332         *
    333         * @return the (non null, may be empty) pragmas map
    334         * @since 3.1
    335         */
    336        Map<String, Object> getPragmas();
    337    }
    338
    339    /**
    340     * Creates a new template.
    341     *
    342     * @param info the jexl info (file, line, column)
    343     * @param prefix the directive prefix
    344     * @param source the source
    345     * @param parms the parameter names
    346     * @return the template
    347     */
    348    public abstract Template createTemplate(JexlInfo info, String prefix, Reader source, String... parms);
    349
    350    /**
    351     * Creates a new template.
    352     *
    353     * @param info the source info
    354     * @param parms the parameter names
    355     * @param source the source
    356     * @return the template
    357     */
    358    public Template createTemplate(final JexlInfo info, final String source, final String... parms) {
    359        return createTemplate(info, "$$", new StringReader(source), parms);
    360    }
    361
    362    /**
    363     * Creates a new template.
    364     *
    365     * @param info the source info
    366     * @param source the source
    367     * @return the template
    368     */
    369    public Template createTemplate(final JexlInfo info, final String source) {
    370        return createTemplate(info, "$$", new StringReader(source), (String[]) null);
    371    }
    372
    373    /**
    374     * Creates a new template.
    375     *
    376     * @param prefix the directive prefix
    377     * @param source the source
    378     * @param parms the parameter names
    379     * @return the template
    380     */
    381    public Template createTemplate(final String prefix, final Reader source, final String... parms) {
    382        return createTemplate(null, prefix, source, parms);
    383    }
    384
    385    /**
    386     * Creates a new template.
    387     *
    388     * @param source the source
    389     * @param parms the parameter names
    390     * @return the template
    391     */
    392    public Template createTemplate(final String source, final String... parms) {
    393        return createTemplate(null, source, parms);
    394    }
    395
    396    /**
    397     * Creates a new template.
    398     *
    399     * @param source the source
    400     * @return the template
    401     */
    402    public Template createTemplate(final String source) {
    403        return createTemplate(null, source);
    404    }
    405
    406    /**
    407     * Gets the {@link JexlEngine} underlying this template engine.
    408     *
    409     * @return the JexlEngine
    410     */
    411    public abstract JexlEngine getEngine();
    412
    413    /**
    414     * Clears the cache.
    415     */
    416    public abstract void clearCache();
    417}