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