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