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