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}