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.Reader;
20  import java.io.Writer;
21  import java.util.Collections;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Objects;
25  import java.util.Set;
26  
27  import org.apache.commons.jexl3.JexlContext;
28  import org.apache.commons.jexl3.JexlException;
29  import org.apache.commons.jexl3.JexlInfo;
30  import org.apache.commons.jexl3.JexlOptions;
31  import org.apache.commons.jexl3.JxltEngine;
32  import org.apache.commons.jexl3.internal.TemplateEngine.Block;
33  import org.apache.commons.jexl3.internal.TemplateEngine.BlockType;
34  import org.apache.commons.jexl3.internal.TemplateEngine.TemplateExpression;
35  import org.apache.commons.jexl3.parser.ASTArguments;
36  import org.apache.commons.jexl3.parser.ASTFunctionNode;
37  import org.apache.commons.jexl3.parser.ASTIdentifier;
38  import org.apache.commons.jexl3.parser.ASTJexlScript;
39  import org.apache.commons.jexl3.parser.ASTNumberLiteral;
40  import org.apache.commons.jexl3.parser.JexlNode;
41  
42  /**
43   * A Template instance.
44   */
45  public final class TemplateScript implements JxltEngine.Template {
46  
47      /**
48       * Collects the call-site surrounding a call to jexl:print(i).
49       * <p>This allows parsing the blocks with the known symbols
50       * in the frame visible to the parser.</p>
51       *
52       * @param node the visited node
53       * @param callSites the map of printed expression number to node info
54       */
55      private static void collectPrintScope(final JexlNode node, final JexlNode.Info[] callSites) {
56          final int nc = node.jjtGetNumChildren();
57          if (node instanceof ASTFunctionNode && nc == 2) {
58              // is child[0] jexl:print()?
59              final ASTIdentifier nameNode = (ASTIdentifier) node.jjtGetChild(0);
60              if ("print".equals(nameNode.getName()) && "jexl".equals(nameNode.getNamespace())) {
61                  // is there one argument?
62                  final ASTArguments argNode = (ASTArguments) node.jjtGetChild(1);
63                  if (argNode.jjtGetNumChildren() == 1) {
64                      // seek the expression number
65                      final JexlNode arg0 = argNode.jjtGetChild(0);
66                      if (arg0 instanceof ASTNumberLiteral) {
67                          final int exprNumber = ((ASTNumberLiteral) arg0).getLiteral().intValue();
68                          callSites[exprNumber] = new JexlNode.Info(nameNode);
69                          return;
70                      }
71                  }
72              }
73          }
74          for (int c = 0; c < nc; ++c) {
75              collectPrintScope(node.jjtGetChild(c), callSites);
76          }
77      }
78  
79      /**
80       * Gets the scope from a node info.
81       *
82       * @param info the node info
83       * @param scope the outer scope
84       * @return the scope
85       */
86      private static Scope scopeOf(final JexlNode.Info info, final Scope scope) {
87          Scope found = null;
88          JexlNode walk = info.getNode();
89          while (walk != null) {
90              if (walk instanceof ASTJexlScript) {
91                  found = ((ASTJexlScript) walk).getScope();
92                  break;
93              }
94              walk = walk.jjtGetParent();
95          }
96          return found != null ? found : scope;
97      }
98  
99      /**
100      * Creates the expression array from the list of blocks.
101      *
102      * @param scope the outer scope
103      * @param blocks the list of blocks
104      * @return the array of expressions
105      */
106     private TemplateExpression[] calleeScripts(final Scope scope, final Block[] blocks, final JexlNode.Info[] callSites) {
107         final TemplateExpression[] expressions = new TemplateExpression[callSites.length];
108         // jexl:print(...) expression counter
109         int jpe = 0;
110         // create the expressions using the intended scopes
111         for (final Block block : blocks) {
112             if (block.getType() == BlockType.VERBATIM) {
113                 final JexlNode.Info ji = callSites[jpe];
114                 // no node info means this verbatim is surrounded by comments markers;
115                 // expr at this index is never called
116                 final TemplateExpression te = ji != null
117                     ? jxlt.parseExpression(ji, block.getBody(), scopeOf(ji, scope))
118                     : jxlt.new ConstantExpression(block.getBody(), null);
119                 expressions[jpe++] = te;
120             }
121         }
122         return expressions;
123     }
124 
125     /**
126      * Creates the script calling the list of blocks.
127      * <p>This is used to create a script from a list of blocks
128      * that were parsed from a template.</p>
129      *
130      * @param blocks the list of blocks
131      * @return the script source
132      */
133     private static String callerScript(final Block[] blocks) {
134         final StringBuilder strb = new StringBuilder();
135         int nuexpr = 0;
136         int line = 1;
137         for (final Block block : blocks) {
138             final int bl = block.getLine();
139             while (line < bl) {
140                 strb.append("//\n");
141                 line += 1;
142             }
143             if (block.getType() == BlockType.VERBATIM) {
144                 strb.append("jexl:print(");
145                 strb.append(nuexpr++);
146                 strb.append(");\n");
147                 line += 1;
148             } else {
149                 final String body = block.getBody();
150                 strb.append(body);
151                 // keep track of the line number
152                 for (int c = 0; c < body.length(); ++c) {
153                     if (body.charAt(c) == '\n') {
154                         line += 1;
155                     }
156                 }
157             }
158         }
159         return strb.toString();
160     }
161 
162     /** The prefix marker. */
163     private final String prefix;
164 
165     /** The array of source blocks. */
166     private final Block[] source;
167 
168     /** The resulting script. */
169     private final ASTJexlScript script;
170 
171     /** The TemplateEngine expressions called by the script. */
172     private final TemplateExpression[] exprs;
173 
174     /** The engine. */
175     private final TemplateEngine jxlt;
176 
177     /**
178      * Creates a new template from an character input.
179      *
180      * @param engine the template engine
181      * @param jexlInfo the source info
182      * @param directive the prefix for lines of code; cannot be "$", "${", "#" or "#{"
183                   since this would preclude being able to differentiate directives and jxlt expressions
184      * @param reader    the input reader
185      * @param parms     the parameter names
186      * @throws NullPointerException     if either the directive prefix or input is null
187      * @throws IllegalArgumentException if the directive prefix is invalid
188      */
189     public TemplateScript(final TemplateEngine engine,
190                           final JexlInfo jexlInfo,
191                           final String directive,
192                           final Reader reader,
193                           final String... parms) {
194         Objects.requireNonNull(directive, "directive");
195         final String engineImmediateCharString = Character.toString(engine.getImmediateChar());
196         final String engineDeferredCharString = Character.toString(engine.getDeferredChar());
197 
198         if (engineImmediateCharString.equals(directive)
199                 || engineDeferredCharString.equals(directive)
200                 || (engineImmediateCharString + "{").equals(directive)
201                 || (engineDeferredCharString + "{").equals(directive)) {
202             throw new IllegalArgumentException(directive + ": is not a valid directive pattern");
203         }
204         Objects.requireNonNull(reader, "reader");
205         this.jxlt = engine;
206         this.prefix = directive;
207         final Engine jexl = jxlt.getEngine();
208         // create the caller script
209         final Block[] blocks = jxlt.readTemplate(prefix, reader).toArray(new Block[0]);
210         int verbatims = 0;
211         for(final Block b : blocks) {
212             if (BlockType.VERBATIM == b.getType()) {
213                 verbatims += 1;
214             }
215         }
216         final String scriptSource = callerScript(blocks);
217         // allow lambda defining params
218         final JexlInfo info = jexlInfo == null ? jexl.createInfo() : jexlInfo;
219         final Scope scope = parms == null ? null : new Scope(null, parms);
220         final JexlInfo templateInfo = jexl.scriptFeatures.isIgnoreTemplatePrefix()
221             ? new TemplateInfo(info.at(1, 1), Collections.singleton(prefix))
222             : info.at(1, 1);
223         final ASTJexlScript callerScript = jexl.jxltParse(templateInfo, false, scriptSource, scope).script();
224         // seek the map of expression number to scope so we can parse Unified
225         // expression blocks with the appropriate symbols
226         final JexlNode.Info[] callSites = new JexlNode.Info[verbatims];
227         collectPrintScope(callerScript.script(), callSites);
228         // create the expressions from the blocks
229         this.exprs = calleeScripts(scope, blocks, callSites);
230         this.script = callerScript;
231         this.source = blocks;
232     }
233 
234     /**
235      * Private ctor used to expand deferred expressions during prepare.
236      *
237      * @param engine    the template engine
238      * @param thePrefix the directive prefix
239      * @param theSource the source
240      * @param theScript the script
241      * @param theExprs  the expressions
242      */
243     TemplateScript(final TemplateEngine engine,
244                    final String thePrefix,
245                    final Block[] theSource,
246                    final ASTJexlScript theScript,
247                    final TemplateExpression[] theExprs) {
248         jxlt = engine;
249         prefix = thePrefix;
250         source = theSource;
251         script = theScript;
252         exprs = theExprs;
253     }
254 
255     @Override
256     public String asString() {
257         final StringBuilder strb = new StringBuilder();
258         int e = 0;
259         for (final Block block : source) {
260             if (block.getType() == BlockType.DIRECTIVE) {
261                 strb.append(prefix);
262                 strb.append(block.getBody());
263             } else {
264                 exprs[e++].asString(strb);
265             }
266         }
267         return strb.toString();
268     }
269 
270     @Override
271     public void evaluate(final JexlContext context, final Writer writer) {
272         evaluate(context, writer, (Object[]) null);
273     }
274 
275     @Override
276     public void evaluate(final JexlContext context, final Writer writer, final Object... args) {
277         final Engine jexl = jxlt.getEngine();
278         final JexlOptions options = jexl.evalOptions(script, context);
279         final Frame frame = script.createFrame(args);
280         final TemplateInterpreter.Arguments targs = new TemplateInterpreter
281                 .Arguments(jexl)
282                 .context(context)
283                 .options(options)
284                 .frame(frame)
285                 .expressions(exprs)
286                 .writer(writer);
287         final Interpreter interpreter = jexl.createTemplateInterpreter(targs);
288         interpreter.interpret(script);
289     }
290 
291     /**
292      * @return exprs
293      */
294     TemplateExpression[] getExpressions() {
295         return exprs;
296     }
297 
298     @Override
299     public String[] getParameters() {
300         return script.getParameters();
301     }
302 
303     @Override
304     public Map<String, Object> getPragmas() {
305         return script.getPragmas();
306     }
307 
308     /**
309      * @return script
310      */
311     ASTJexlScript getScript() {
312         return script;
313     }
314 
315     @Override
316     public Set<List<String>> getVariables() {
317         final Engine.VarCollector collector = jxlt.getEngine().varCollector();
318         for (final TemplateExpression expr : exprs) {
319             expr.getVariables(collector);
320         }
321         return collector.collected();
322     }
323 
324     @Override
325     public TemplateScript prepare(final JexlContext context) {
326         return prepare(context, (Object[]) null);
327     }
328 
329     @Override
330     public TemplateScript prepare(final JexlContext context, final Object... args) {
331         final Engine jexl = jxlt.getEngine();
332         final JexlOptions options = jexl.evalOptions(script, context);
333         final Frame frame = script.createFrame(args);
334         final TemplateInterpreter.Arguments targs = new TemplateInterpreter
335                 .Arguments(jxlt.getEngine())
336                 .context(context)
337                 .options(options)
338                 .frame(frame);
339         final Interpreter interpreter = jexl.createTemplateInterpreter(targs);
340         final TemplateExpression[] prepared = new TemplateExpression[exprs.length];
341         for (int e = 0; e < exprs.length; ++e) {
342             try {
343                 prepared[e] = exprs[e].prepare(interpreter);
344             } catch (final JexlException xjexl) {
345                 final JexlException xuel = TemplateEngine.createException(xjexl.getInfo(), "prepare", exprs[e], xjexl);
346                 if (jexl.isSilent()) {
347                     if (jexl.logger.isWarnEnabled()) {
348                         jexl.logger.warn(xuel.getMessage(), xuel.getCause());
349                     }
350                     return null;
351                 }
352                 throw xuel;
353             }
354         }
355         return new TemplateScript(jxlt, prefix, source, script, prepared);
356     }
357 
358     @Override
359     public String toString() {
360         final StringBuilder strb = new StringBuilder();
361         for (final Block block : source) {
362             block.toString(strb, prefix);
363         }
364         return strb.toString();
365     }
366 }