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