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.JxltEngine;
20  import org.apache.commons.jexl3.internal.TemplateEngine.CompositeExpression;
21  import org.apache.commons.jexl3.internal.TemplateEngine.ConstantExpression;
22  import org.apache.commons.jexl3.internal.TemplateEngine.DeferredExpression;
23  import org.apache.commons.jexl3.internal.TemplateEngine.ImmediateExpression;
24  import org.apache.commons.jexl3.internal.TemplateEngine.NestedExpression;
25  import org.apache.commons.jexl3.internal.TemplateEngine.TemplateExpression;
26  
27  import org.apache.commons.jexl3.parser.ASTBlock;
28  import org.apache.commons.jexl3.parser.ASTFunctionNode;
29  import org.apache.commons.jexl3.parser.ASTIdentifier;
30  import org.apache.commons.jexl3.parser.ASTJexlScript;
31  import org.apache.commons.jexl3.parser.ASTNumberLiteral;
32  import org.apache.commons.jexl3.parser.JexlNode;
33  
34  /**
35   * A visitor for templates.
36   * <p>A friend (ala C++) of template engine.
37   */
38  public class TemplateDebugger extends Debugger {
39      /** The outer script. */
40      private ASTJexlScript script;
41      /** The expressions called by the script through jexl:print. */
42      private TemplateExpression[] exprs;
43  
44      /**
45       * Default ctor.
46       */
47      public TemplateDebugger() {
48          // nothing to initialize
49      }
50  
51      @Override
52      public void reset() {
53          super.reset();
54          // so we can use it more than one time
55          exprs = null;
56          script = null;
57      }
58  
59      /**
60       * Position the debugger on the root of a template expression.
61       * @param je the expression
62       * @return true if the expression was a {@link TemplateExpression} instance, false otherwise
63       */
64      public boolean debug(final JxltEngine.Expression je) {
65          if (je instanceof TemplateExpression) {
66              final TemplateEngine.TemplateExpression te = (TemplateEngine.TemplateExpression) je;
67              return visit(te, this) != null;
68          }
69          return false;
70      }
71  
72      /**
73       * Position the debugger on the root of a template script.
74       * @param jt the template
75       * @return true if the template was a {@link TemplateScript} instance, false otherwise
76       */
77      public boolean debug(final JxltEngine.Template jt) {
78          if (!(jt instanceof TemplateScript)) {
79              return false;
80          }
81          final TemplateScript ts = (TemplateScript) jt;
82          // ensure expr is not null for templates
83          this.exprs = ts.getExpressions() == null? new TemplateExpression[0] : ts.getExpressions();
84          this.script = ts.getScript();
85          start = 0;
86          end = 0;
87          indentLevel = 0;
88          builder.setLength(0);
89          cause = script;
90          final int num = script.jjtGetNumChildren();
91          for (int i = 0; i < num; ++i) {
92              final JexlNode child = script.jjtGetChild(i);
93              acceptStatement(child, null);
94          }
95          // the last line
96          if (builder.length() > 0 && builder.charAt(builder.length() - 1) != '\n') {
97              builder.append('\n');
98          }
99          end = builder.length();
100         return end > 0;
101     }
102 
103 
104     @Override
105     protected Object visit(final ASTBlock node, final Object data) {
106         // if not really a template, must use super impl
107         if (exprs == null) {
108             return super.visit(node, data);
109         }
110         // open the block
111         builder.append('{');
112         if (indent > 0) {
113             indentLevel += 1;
114             builder.append('\n');
115         } else {
116             builder.append(' ');
117         }
118         final int num = node.jjtGetNumChildren();
119         for (int i = 0; i < num; ++i) {
120             final JexlNode child = node.jjtGetChild(i);
121             acceptStatement(child, data);
122         }
123         // before we close this block node, $$ might be needed
124         newJexlLine();
125         if (indent > 0) {
126             indentLevel -= 1;
127             for (int i = 0; i < indentLevel; ++i) {
128                 for(int s = 0; s < indent; ++s) {
129                     builder.append(' ');
130                 }
131             }
132         }
133         builder.append('}');
134         // closed the block
135         return data;
136     }
137 
138     @Override
139     protected Object acceptStatement(final JexlNode child, final Object data) {
140         // if not really a template, must use super impl
141         if (exprs == null) {
142             return super.acceptStatement(child, data);
143         }
144         final TemplateExpression te = getPrintStatement(child);
145         if (te != null) {
146             // if statement is a jexl:print(...), may need to prepend '\n'
147             newJxltLine();
148             return visit(te, data);
149         }
150         // if statement is not a jexl:print(...), need to prepend '$$'
151         newJexlLine();
152         return super.acceptStatement(child, data);
153     }
154 
155     /**
156      * In a template, any statement that is not 'jexl:print(n)' must be prefixed by "$$".
157      * @param child the node to check
158      * @return the expression number or -1 if the node is not a jexl:print
159      */
160     private TemplateExpression getPrintStatement(final JexlNode child) {
161         if (exprs != null && child instanceof ASTFunctionNode) {
162             final ASTFunctionNode node = (ASTFunctionNode) child;
163             final ASTIdentifier ns = (ASTIdentifier) node.jjtGetChild(0);
164             final JexlNode args = node.jjtGetChild(1);
165             if ("jexl".equals(ns.getNamespace())
166                 && "print".equals(ns.getName())
167                 && args.jjtGetNumChildren() == 1
168                 && args.jjtGetChild(0) instanceof ASTNumberLiteral) {
169                 final ASTNumberLiteral exprn = (ASTNumberLiteral) args.jjtGetChild(0);
170                 final int n = exprn.getLiteral().intValue();
171                 if (n >= 0 && n < exprs.length) {
172                     return exprs[n];
173                 }
174             }
175         }
176         return null;
177     }
178 
179     /**
180      * Insert $$ and \n when needed.
181      */
182     private void newJexlLine() {
183         final int length = builder.length();
184         if (length == 0) {
185             builder.append("$$ ");
186         } else {
187             for (int i = length - 1; i >= 0; --i) {
188                 final char c = builder.charAt(i);
189                 switch (c) {
190                     case '\n':
191                         builder.append("$$ ");
192                         return;
193                     case '}':
194                         builder.append("\n$$ ");
195                         return;
196                     case ' ':
197                     case ';':
198                         return;
199                     default: // continue
200                 }
201             }
202         }
203     }
204 
205     /**
206      * Insert \n when needed.
207      */
208     private void newJxltLine() {
209         final int length = builder.length();
210         for (int i = length - 1; i >= 0; --i) {
211             final char c = builder.charAt(i);
212             switch (c) {
213                 case '\n':
214                 case ';':
215                     return;
216                 case '}':
217                     builder.append('\n');
218                     return;
219                 default: // continue
220             }
221         }
222     }
223 
224     /**
225      * Visit a template expression.
226      * @param expr the constant expression
227      * @param data the visitor argument
228      * @return the visitor argument
229      */
230     private Object visit(final TemplateExpression expr, final Object data) {
231         Object r;
232         switch (expr.getType()) {
233             case CONSTANT:
234                 r = visit((ConstantExpression) expr, data);
235                 break;
236             case IMMEDIATE:
237                 r = visit((ImmediateExpression) expr, data);
238                 break;
239             case DEFERRED:
240                 r = visit((DeferredExpression) expr, data);
241                 break;
242             case NESTED:
243                 r = visit((NestedExpression) expr, data);
244                 break;
245             case COMPOSITE:
246                 r = visit((CompositeExpression) expr, data);
247                 break;
248             default:
249                 r = null;
250         }
251         return r;
252     }
253 
254     /**
255      * Visit a constant expression.
256      * @param expr the constant expression
257      * @param data the visitor argument
258      * @return the visitor argument
259      */
260     private Object visit(final ConstantExpression expr, final Object data) {
261         expr.asString(builder);
262         return data;
263     }
264 
265     /**
266      * Visit an immediate expression.
267      * @param expr the immediate expression
268      * @param data the visitor argument
269      * @return the visitor argument
270      */
271     private Object visit(final ImmediateExpression expr, final Object data) {
272         builder.append(expr.isImmediate() ? '$' : '#');
273         builder.append('{');
274         super.accept(expr.node, data);
275         builder.append('}');
276         return data;
277     }
278 
279     /**
280      * Visit a deferred expression.
281      * @param expr the deferred expression
282      * @param data the visitor argument
283      * @return the visitor argument
284      */
285     private Object visit(final DeferredExpression expr, final Object data) {
286         builder.append(expr.isImmediate() ? '$' : '#');
287         builder.append('{');
288         super.accept(expr.node, data);
289         builder.append('}');
290         return data;
291     }
292 
293     /**
294      * Visit a nested expression.
295      * @param expr the nested expression
296      * @param data the visitor argument
297      * @return the visitor argument
298      */
299     private Object visit(final NestedExpression expr, final Object data) {
300         super.accept(expr.node, data);
301         return data;
302     }
303     /**
304      * Visit a composite expression.
305      * @param expr the composite expression
306      * @param data the visitor argument
307      * @return the visitor argument
308      */
309     private Object visit(final CompositeExpression expr, final Object data) {
310         for (final TemplateExpression ce : expr.exprs) {
311             visit(ce, data);
312         }
313         return data;
314     }
315 
316 }