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