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    *      http://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          /** The options. */
50          JexlOptions options;
51          /** The context. */
52          JexlContext jcontext;
53          /** The frame. */
54          Frame jframe;
55          /** The expressions. */
56          TemplateExpression[] expressions;
57          /** The writer. */
58          Writer out;
59  
60          /**
61           * Sole ctor.
62           * @param e the JEXL engine
63           */
64          Arguments(final Engine e) {
65              this.jexl = e;
66          }
67          /**
68           * Sets the context.
69           * @param j the context
70           * @return this instance
71           */
72          Arguments context(final JexlContext j) {
73              this.jcontext = j;
74              return this;
75          }
76          /**
77           * Sets the expressions.
78           * @param e the expressions
79           * @return this instance
80           */
81          Arguments expressions(final TemplateExpression[] e) {
82              this.expressions = e;
83              return this;
84          }
85          /**
86           * Sets the frame.
87           * @param f the frame
88           * @return this instance
89           */
90          Arguments frame(final Frame f) {
91              this.jframe = f;
92              return this;
93          }
94          /**
95           * Sets the options.
96           * @param o the options
97           * @return this instance
98           */
99          Arguments options(final JexlOptions o) {
100             this.options = o;
101             return this;
102         }
103         /**
104          * Sets the writer.
105          * @param o the writer
106          * @return this instance
107          */
108         Arguments writer(final Writer o) {
109             this.out = o;
110             return this;
111         }
112     }
113     /** The array of template expressions. */
114     final TemplateExpression[] exprs;
115 
116     /** The writer used to output. */
117     final Writer writer;
118 
119     /**
120      * Creates a template interpreter instance.
121      * @param args the template interpreter arguments
122      */
123     protected TemplateInterpreter(final Arguments args) {
124         super(args.jexl, args.options, args.jcontext, args.jframe);
125         exprs = args.expressions;
126         writer = args.out;
127         block = new LexicalFrame(frame, null);
128     }
129 
130     /**
131      * Prints to output.
132      * <p>
133      * This will dynamically try to find the best suitable method in the writer through uberspection.
134      * Subclassing Writer by adding 'print' methods should be the preferred way to specialize output.
135      * </p>
136      * @param info the source info
137      * @param arg  the argument to print out
138      */
139     private void doPrint(final JexlInfo info, final Object arg) {
140         try {
141             if (writer != null) {
142                 if (arg instanceof CharSequence) {
143                     writer.write(arg.toString());
144                 } else if (arg != null) {
145                     final Object[] value = {arg};
146                     final JexlUberspect uber = jexl.getUberspect();
147                     final JexlMethod method = uber.getMethod(writer, "print", value);
148                     if (method != null) {
149                         method.invoke(writer, value);
150                     } else {
151                         writer.write(arg.toString());
152                     }
153                 }
154             }
155         } catch (final java.io.IOException xio) {
156             throw TemplateEngine.createException(info, "call print", null, xio);
157         } catch (final java.lang.Exception xany) {
158             throw TemplateEngine.createException(info, "invoke print", null, xany);
159         }
160     }
161 
162     /**
163      * Includes a call to another template.
164      * <p>
165      * Includes another template using this template initial context and writer.</p>
166      * @param script the TemplateScript to evaluate
167      * @param args   the arguments
168      */
169     public void include(final JxltEngine.Template script, final Object... args) {
170         script.evaluate(context, writer, args);
171     }
172 
173     /**
174      * Prints a unified expression evaluation result.
175      * @param e the expression number
176      */
177     public void print(final int e) {
178         if (e < 0 || e >= exprs.length) {
179             return;
180         }
181         TemplateEngine.TemplateExpression expr = exprs[e];
182         if (expr.isDeferred()) {
183             expr = expr.prepare(context, frame, options);
184         }
185         if (expr instanceof TemplateEngine.CompositeExpression) {
186             printComposite((TemplateEngine.CompositeExpression) expr);
187         } else {
188             doPrint(expr.getInfo(), expr.evaluate(this));
189         }
190     }
191 
192     /**
193      * Prints a composite expression.
194      * @param composite the composite expression
195      */
196     private void printComposite(final TemplateEngine.CompositeExpression composite) {
197         final TemplateEngine.TemplateExpression[] cexprs = composite.exprs;
198         Object value;
199         for (final TemplateExpression cexpr : cexprs) {
200             value = cexpr.evaluate(this);
201             doPrint(cexpr.getInfo(), value);
202         }
203     }
204 
205     @Override
206     protected Object resolveNamespace(final String prefix, final JexlNode node) {
207         return "jexl".equals(prefix)? this : super.resolveNamespace(prefix, node);
208     }
209 
210     /**
211      * Interprets a function node.
212      * print() and include() must be decoded by this interpreter since delegating to the Uberspect
213      * may be sandboxing the interpreter itself making it unable to call the function.
214      * @param node the function node
215      * @param data the data
216      * @return the function evaluation result.
217      */
218     @Override
219     protected Object visit(final ASTFunctionNode node, final Object data) {
220         final int argc = node.jjtGetNumChildren();
221         if (argc == 2) {
222             final ASTIdentifier functionNode = (ASTIdentifier) node.jjtGetChild(0);
223             if ("jexl".equals(functionNode.getNamespace())) {
224                 final String functionName = functionNode.getName();
225                 final ASTArguments argNode = (ASTArguments) node.jjtGetChild(1);
226                 if ("print".equals(functionName)) {
227                     // evaluate the arguments
228                     final Object[] argv = visit(argNode, null);
229                     if (argv != null && argv.length > 0 && argv[0] instanceof Number) {
230                         print(((Number) argv[0]).intValue());
231                         return null;
232                     }
233                 }
234                 if ("include".equals(functionName)) {
235                     // evaluate the arguments
236                     Object[] argv = visit(argNode, null);
237                     if (argv != null && argv.length > 0 && argv[0] instanceof TemplateScript) {
238                         final TemplateScript script = (TemplateScript) argv[0];
239                         argv = argv.length > 1? Arrays.copyOfRange(argv, 1, argv.length) : null;
240                         include(script, argv);
241                         return null;
242                     }
243                 }
244                 // fail safe
245                 throw new JxltEngine.Exception(node.jexlInfo(), "no callable template function " + functionName, null);
246             }
247         }
248         return super.visit(node, data);
249     }
250 
251     @Override
252     protected Object visit(final ASTIdentifier node, final Object data) {
253         final String name = node.getName();
254         if ("$jexl".equals(name)) {
255             return writer;
256         }
257         return super.visit(node, data);
258     }
259 
260     @Override
261     protected Object visit(final ASTJexlScript script, final Object data) {
262         if (script instanceof ASTJexlLambda && !((ASTJexlLambda) script).isTopLevel()) {
263             return new Closure(this, (ASTJexlLambda) script) {
264                 @Override
265                 protected Interpreter createInterpreter(final JexlContext context, final Frame local, final JexlOptions options) {
266                     final TemplateInterpreter.Arguments targs = new TemplateInterpreter.Arguments(jexl)
267                         .context(context)
268                         .options(options)
269                         .frame(local)
270                         .expressions(exprs)
271                         .writer(writer);
272                     return jexl.createTemplateInterpreter(targs);
273                 }
274             };
275         }
276         // otherwise...
277         final int numChildren = script.jjtGetNumChildren();
278         Object result = null;
279         for (int i = 0; i < numChildren; i++) {
280             final JexlNode child = script.jjtGetChild(i);
281             result = child.jjtAccept(this, data);
282             cancelCheck(child);
283         }
284         return result;
285     }
286 
287 }