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