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