View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3.internal;
18  
19  import java.io.Writer;
20  import java.util.Arrays;
21  
22  import org.apache.commons.jexl3.JexlContext;
23  import org.apache.commons.jexl3.JexlInfo;
24  import org.apache.commons.jexl3.JexlOptions;
25  import org.apache.commons.jexl3.JxltEngine;
26  import org.apache.commons.jexl3.internal.TemplateEngine.TemplateExpression;
27  import org.apache.commons.jexl3.introspection.JexlMethod;
28  import org.apache.commons.jexl3.introspection.JexlUberspect;
29  import org.apache.commons.jexl3.parser.ASTArguments;
30  import org.apache.commons.jexl3.parser.ASTFunctionNode;
31  import org.apache.commons.jexl3.parser.ASTIdentifier;
32  import org.apache.commons.jexl3.parser.ASTJexlLambda;
33  import org.apache.commons.jexl3.parser.ASTJexlScript;
34  import org.apache.commons.jexl3.parser.JexlNode;
35  
36  /**
37   * The type of interpreter to use during evaluation of templates.
38   * <p>This context exposes its writer as '$jexl' to the scripts.</p>
39   * <p>public for introspection purpose.</p>
40   */
41  public class TemplateInterpreter extends Interpreter {
42      /**
43       * Helper ctor.
44       * <p>Stores the different properties required to create a Template interpreter.
45       */
46      public static class Arguments {
47          /** The engine. */
48          Engine jexl;
49  
50          /** The options. */
51          JexlOptions options;
52  
53          /** The context. */
54          JexlContext jcontext;
55  
56          /** The frame. */
57          Frame jframe;
58  
59          /** The expressions. */
60          TemplateExpression[] expressions;
61  
62          /** The writer. */
63          Writer out;
64  
65          /**
66           * Sole ctor.
67           *
68           * @param e the JEXL engine
69           */
70          Arguments(final Engine e) {
71              this.jexl = e;
72          }
73  
74          /**
75           * Sets the context.
76           *
77           * @param j the context
78           * @return {@code this} instance
79           */
80          Arguments context(final JexlContext j) {
81              this.jcontext = j;
82              return this;
83          }
84  
85          /**
86           * Sets the expressions.
87           *
88           * @param e the expressions
89           * @return {@code this} instance
90           */
91          Arguments expressions(final TemplateExpression[] e) {
92              this.expressions = e;
93              return this;
94          }
95  
96          /**
97           * Sets the frame.
98           *
99           * @param f the frame
100          * @return {@code this} instance
101          */
102         Arguments frame(final Frame f) {
103             this.jframe = f;
104             return this;
105         }
106 
107         /**
108          * Sets the options.
109          *
110          * @param o the options
111          * @return {@code this} instance
112          */
113         Arguments options(final JexlOptions o) {
114             this.options = o;
115             return this;
116         }
117 
118         /**
119          * Sets the writer.
120          *
121          * @param o the writer
122          * @return {@code this} instance
123          */
124         Arguments writer(final Writer o) {
125             this.out = o;
126             return this;
127         }
128     }
129 
130     /** The array of template expressions. */
131     final TemplateExpression[] exprs;
132 
133     /** The writer used to output. */
134     final Writer writer;
135 
136     /**
137      * Creates a template interpreter instance.
138      *
139      * @param args the template interpreter arguments
140      */
141     protected TemplateInterpreter(final Arguments args) {
142         super(args.jexl, args.options, args.jcontext, args.jframe);
143         exprs = args.expressions;
144         writer = args.out;
145         block = new LexicalFrame(frame, null);
146     }
147 
148     /**
149      * Prints to output.
150      * <p>
151      * This will dynamically try to find the best suitable method in the writer through uberspection.
152      * Subclassing Writer by adding 'print' methods should be the preferred way to specialize output.
153      * </p>
154      *
155      * @param info the source info
156      * @param arg  the argument to print out
157      */
158     private void doPrint(final JexlInfo info, final Object arg) {
159         try {
160             if (writer != null) {
161                 if (arg instanceof CharSequence) {
162                     writer.write(arg.toString());
163                 } else if (arg != null) {
164                     final Object[] value = {arg};
165                     final JexlUberspect uber = jexl.getUberspect();
166                     final JexlMethod method = uber.getMethod(writer, "print", value);
167                     if (method != null) {
168                         method.invoke(writer, value);
169                     } else {
170                         writer.write(arg.toString());
171                     }
172                 }
173             }
174         } catch (final java.io.IOException xio) {
175             throw TemplateEngine.createException(info, "call print", null, xio);
176         } catch (final java.lang.Exception xany) {
177             throw TemplateEngine.createException(info, "invoke print", null, xany);
178         }
179     }
180 
181     /**
182      * Includes a call to another template.
183      * <p>
184      * Includes another template using this template initial context and writer.</p>
185      *
186      * @param script the TemplateScript to evaluate
187      * @param args   the arguments
188      */
189     public void include(final JxltEngine.Template script, final Object... args) {
190         script.evaluate(context, writer, args);
191     }
192 
193     /**
194      * Prints a unified expression evaluation result.
195      *
196      * @param e the expression number
197      */
198     public void print(final int e) {
199         if (e < 0 || e >= exprs.length) {
200             return;
201         }
202         TemplateEngine.TemplateExpression expr = exprs[e];
203         if (expr.isDeferred()) {
204             expr = expr.prepare(context, frame, options);
205         }
206         if (expr instanceof TemplateEngine.CompositeExpression) {
207             printComposite((TemplateEngine.CompositeExpression) expr);
208         } else {
209             doPrint(expr.getInfo(), expr.evaluate(this));
210         }
211     }
212 
213     /**
214      * Prints a composite expression.
215      *
216      * @param composite the composite expression
217      */
218     private void printComposite(final TemplateEngine.CompositeExpression composite) {
219         final TemplateEngine.TemplateExpression[] composites = composite.exprs;
220         Object value;
221         for (final TemplateExpression expr : composites) {
222             value = expr.evaluate(this);
223             doPrint(expr.getInfo(), value);
224         }
225     }
226 
227     @Override
228     protected Object resolveNamespace(final String prefix, final JexlNode node) {
229         return "jexl".equals(prefix)? this : super.resolveNamespace(prefix, node);
230     }
231 
232     /**
233      * Interprets a function node.
234      * print() and include() must be decoded by this interpreter since delegating to the Uberspect
235      * may be sandboxing the interpreter itself making it unable to call the function.
236      *
237      * @param node the function node
238      * @param data the data
239      * @return the function evaluation result.
240      */
241     @Override
242     protected Object visit(final ASTFunctionNode node, final Object data) {
243         final int argc = node.jjtGetNumChildren();
244         if (argc == 2) {
245             final ASTIdentifier functionNode = (ASTIdentifier) node.jjtGetChild(0);
246             if ("jexl".equals(functionNode.getNamespace())) {
247                 final String functionName = functionNode.getName();
248                 final ASTArguments argNode = (ASTArguments) node.jjtGetChild(1);
249                 if ("print".equals(functionName)) {
250                     // evaluate the arguments
251                     final Object[] argv = visit(argNode, null);
252                     if (argv != null && argv.length > 0 && argv[0] instanceof Number) {
253                         print(((Number) argv[0]).intValue());
254                         return null;
255                     }
256                 }
257                 if ("include".equals(functionName)) {
258                     // evaluate the arguments
259                     Object[] argv = visit(argNode, null);
260                     if (argv != null && argv.length > 0 && argv[0] instanceof TemplateScript) {
261                         final TemplateScript script = (TemplateScript) argv[0];
262                         argv = argv.length > 1? Arrays.copyOfRange(argv, 1, argv.length) : null;
263                         include(script, argv);
264                         return null;
265                     }
266                 }
267                 // fail safe
268                 throw new JxltEngine.Exception(node.jexlInfo(), "no callable template function " + functionName, null);
269             }
270         }
271         return super.visit(node, data);
272     }
273 
274     @Override
275     protected Object visit(final ASTIdentifier node, final Object data) {
276         final String name = node.getName();
277         if ("$jexl".equals(name)) {
278             return writer;
279         }
280         return super.visit(node, data);
281     }
282 
283     @Override
284     protected Object visit(final ASTJexlScript script, final Object data) {
285         if (script instanceof ASTJexlLambda && !((ASTJexlLambda) script).isTopLevel()) {
286             return new Closure(this, (ASTJexlLambda) script) {
287                 @Override
288                 protected Interpreter createInterpreter(final JexlContext context, final Frame local, final JexlOptions options) {
289                     final TemplateInterpreter.Arguments targs = new TemplateInterpreter.Arguments(jexl)
290                         .context(context)
291                         .options(options)
292                         .frame(local)
293                         .expressions(exprs)
294                         .writer(writer);
295                     return jexl.createTemplateInterpreter(targs);
296                 }
297             };
298         }
299         // otherwise...
300         final Object[] stack = saveStack();
301         try {
302             return runScript(script, data);
303         } finally {
304             restoreStack(stack);
305         }
306     }
307 
308     private Object[] saveStack() {
309         return frame != null && frame.stack != null? frame.stack.clone() : null;
310     }
311 
312     private void restoreStack(Object[] stack) {
313         if (stack != null) {
314             System.arraycopy(stack, 0, frame.stack, 0, stack.length);
315         }
316     }
317 
318 }