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    /** 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         * Retrieves 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, may be 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    /**
316     * Clears the cache.
317     */
318    public abstract void clearCache();
319
320    /**
321     * Creates a {@link Expression} from an expression string.
322     * Uses and fills up the expression cache if any.
323     *
324     * <p>If the underlying JEXL engine is silent, errors will be logged through its logger as warnings.</p>
325     *
326     * @param info the {@link JexlInfo} source information
327     * @param expression the {@link Template} string expression
328     * @return the {@link Expression}, null if silent and an error occurred
329     * @throws Exception if an error occurs and the {@link JexlEngine} is not silent
330     */
331    public abstract Expression createExpression(JexlInfo info, String expression);
332
333    /**
334     * Creates a {@link Expression} from an expression string.
335     * Uses and fills up the expression cache if any.
336     *
337     * <p>If the underlying JEXL engine is silent, errors will be logged through its logger as warnings.</p>
338     *
339     * @param expression the {@link Template} string expression
340     * @return the {@link Expression}, null if silent and an error occurred
341     * @throws Exception if an error occurs and the {@link JexlEngine} is not silent
342     */
343    public Expression createExpression(final String expression) {
344        return createExpression(null, expression);
345    }
346
347    /**
348     * Creates a new template.
349     *
350     * @param info the source info
351     * @param source the source
352     * @return the template
353     */
354    public Template createTemplate(final JexlInfo info, final String source) {
355        return createTemplate(info, "$$", new StringReader(source), (String[]) null);
356    }
357
358    /**
359     * Creates a new template.
360     *
361     * @param info the jexl info (file, line, column)
362     * @param prefix the directive prefix
363     * @param source the source
364     * @param parms the parameter names
365     * @return the template
366     */
367    public abstract Template createTemplate(JexlInfo info, String prefix, Reader source, String... parms);
368
369    /**
370     * Creates a new template.
371     *
372     * @param info the source info
373     * @param parms the parameter names
374     * @param source the source
375     * @return the template
376     */
377    public Template createTemplate(final JexlInfo info, final String source, final String... parms) {
378        return createTemplate(info, "$$", new StringReader(source), parms);
379    }
380
381    /**
382     * Creates a new template.
383     *
384     * @param source the source
385     * @return the template
386     */
387    public Template createTemplate(final String source) {
388        return createTemplate(null, source);
389    }
390
391    /**
392     * Creates a new template.
393     *
394     * @param prefix the directive prefix
395     * @param source the source
396     * @param parms the parameter names
397     * @return the template
398     */
399    public Template createTemplate(final String prefix, final Reader source, final String... parms) {
400        return createTemplate(null, prefix, source, parms);
401    }
402
403    /**
404     * Creates a new template.
405     *
406     * @param source the source
407     * @param parms the parameter names
408     * @return the template
409     */
410    public Template createTemplate(final String source, final String... parms) {
411        return createTemplate(null, source, parms);
412    }
413
414    /**
415     * Gets the {@link JexlEngine} underlying this template engine.
416     *
417     * @return the JexlEngine
418     */
419    public abstract JexlEngine getEngine();
420}