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.util.Collections;
20  import java.util.Map;
21  import java.util.Set;
22  import java.util.regex.Pattern;
23  
24  import org.apache.commons.jexl3.JexlExpression;
25  import org.apache.commons.jexl3.JexlFeatures;
26  import org.apache.commons.jexl3.JexlInfo;
27  import org.apache.commons.jexl3.JexlScript;
28  import org.apache.commons.jexl3.parser.ASTAddNode;
29  import org.apache.commons.jexl3.parser.ASTAndNode;
30  import org.apache.commons.jexl3.parser.ASTAnnotatedStatement;
31  import org.apache.commons.jexl3.parser.ASTAnnotation;
32  import org.apache.commons.jexl3.parser.ASTArguments;
33  import org.apache.commons.jexl3.parser.ASTArrayAccess;
34  import org.apache.commons.jexl3.parser.ASTArrayLiteral;
35  import org.apache.commons.jexl3.parser.ASTAssignment;
36  import org.apache.commons.jexl3.parser.ASTBitwiseAndNode;
37  import org.apache.commons.jexl3.parser.ASTBitwiseComplNode;
38  import org.apache.commons.jexl3.parser.ASTBitwiseOrNode;
39  import org.apache.commons.jexl3.parser.ASTBitwiseXorNode;
40  import org.apache.commons.jexl3.parser.ASTBlock;
41  import org.apache.commons.jexl3.parser.ASTBreak;
42  import org.apache.commons.jexl3.parser.ASTConstructorNode;
43  import org.apache.commons.jexl3.parser.ASTContinue;
44  import org.apache.commons.jexl3.parser.ASTDecrementGetNode;
45  import org.apache.commons.jexl3.parser.ASTDefineVars;
46  import org.apache.commons.jexl3.parser.ASTDivNode;
47  import org.apache.commons.jexl3.parser.ASTDoWhileStatement;
48  import org.apache.commons.jexl3.parser.ASTEQNode;
49  import org.apache.commons.jexl3.parser.ASTEQSNode;
50  import org.apache.commons.jexl3.parser.ASTERNode;
51  import org.apache.commons.jexl3.parser.ASTEWNode;
52  import org.apache.commons.jexl3.parser.ASTEmptyFunction;
53  import org.apache.commons.jexl3.parser.ASTExtendedLiteral;
54  import org.apache.commons.jexl3.parser.ASTFalseNode;
55  import org.apache.commons.jexl3.parser.ASTForeachStatement;
56  import org.apache.commons.jexl3.parser.ASTFunctionNode;
57  import org.apache.commons.jexl3.parser.ASTGENode;
58  import org.apache.commons.jexl3.parser.ASTGTNode;
59  import org.apache.commons.jexl3.parser.ASTGetDecrementNode;
60  import org.apache.commons.jexl3.parser.ASTGetIncrementNode;
61  import org.apache.commons.jexl3.parser.ASTIdentifier;
62  import org.apache.commons.jexl3.parser.ASTIdentifierAccess;
63  import org.apache.commons.jexl3.parser.ASTIfStatement;
64  import org.apache.commons.jexl3.parser.ASTIncrementGetNode;
65  import org.apache.commons.jexl3.parser.ASTInstanceOf;
66  import org.apache.commons.jexl3.parser.ASTJexlLambda;
67  import org.apache.commons.jexl3.parser.ASTJexlScript;
68  import org.apache.commons.jexl3.parser.ASTJxltLiteral;
69  import org.apache.commons.jexl3.parser.ASTLENode;
70  import org.apache.commons.jexl3.parser.ASTLTNode;
71  import org.apache.commons.jexl3.parser.ASTMapEntry;
72  import org.apache.commons.jexl3.parser.ASTMapLiteral;
73  import org.apache.commons.jexl3.parser.ASTMethodNode;
74  import org.apache.commons.jexl3.parser.ASTModNode;
75  import org.apache.commons.jexl3.parser.ASTMulNode;
76  import org.apache.commons.jexl3.parser.ASTNENode;
77  import org.apache.commons.jexl3.parser.ASTNESNode;
78  import org.apache.commons.jexl3.parser.ASTNEWNode;
79  import org.apache.commons.jexl3.parser.ASTNRNode;
80  import org.apache.commons.jexl3.parser.ASTNSWNode;
81  import org.apache.commons.jexl3.parser.ASTNotInstanceOf;
82  import org.apache.commons.jexl3.parser.ASTNotNode;
83  import org.apache.commons.jexl3.parser.ASTNullLiteral;
84  import org.apache.commons.jexl3.parser.ASTNullpNode;
85  import org.apache.commons.jexl3.parser.ASTNumberLiteral;
86  import org.apache.commons.jexl3.parser.ASTOrNode;
87  import org.apache.commons.jexl3.parser.ASTQualifiedIdentifier;
88  import org.apache.commons.jexl3.parser.ASTRangeNode;
89  import org.apache.commons.jexl3.parser.ASTReference;
90  import org.apache.commons.jexl3.parser.ASTReferenceExpression;
91  import org.apache.commons.jexl3.parser.ASTRegexLiteral;
92  import org.apache.commons.jexl3.parser.ASTReturnStatement;
93  import org.apache.commons.jexl3.parser.ASTSWNode;
94  import org.apache.commons.jexl3.parser.ASTSetAddNode;
95  import org.apache.commons.jexl3.parser.ASTSetAndNode;
96  import org.apache.commons.jexl3.parser.ASTSetDivNode;
97  import org.apache.commons.jexl3.parser.ASTSetLiteral;
98  import org.apache.commons.jexl3.parser.ASTSetModNode;
99  import org.apache.commons.jexl3.parser.ASTSetMultNode;
100 import org.apache.commons.jexl3.parser.ASTSetOrNode;
101 import org.apache.commons.jexl3.parser.ASTSetShiftLeftNode;
102 import org.apache.commons.jexl3.parser.ASTSetShiftRightNode;
103 import org.apache.commons.jexl3.parser.ASTSetShiftRightUnsignedNode;
104 import org.apache.commons.jexl3.parser.ASTSetSubNode;
105 import org.apache.commons.jexl3.parser.ASTSetXorNode;
106 import org.apache.commons.jexl3.parser.ASTShiftLeftNode;
107 import org.apache.commons.jexl3.parser.ASTShiftRightNode;
108 import org.apache.commons.jexl3.parser.ASTShiftRightUnsignedNode;
109 import org.apache.commons.jexl3.parser.ASTSizeFunction;
110 import org.apache.commons.jexl3.parser.ASTStringLiteral;
111 import org.apache.commons.jexl3.parser.ASTSubNode;
112 import org.apache.commons.jexl3.parser.ASTTernaryNode;
113 import org.apache.commons.jexl3.parser.ASTThrowStatement;
114 import org.apache.commons.jexl3.parser.ASTTrueNode;
115 import org.apache.commons.jexl3.parser.ASTTryResources;
116 import org.apache.commons.jexl3.parser.ASTTryStatement;
117 import org.apache.commons.jexl3.parser.ASTUnaryMinusNode;
118 import org.apache.commons.jexl3.parser.ASTUnaryPlusNode;
119 import org.apache.commons.jexl3.parser.ASTVar;
120 import org.apache.commons.jexl3.parser.ASTWhileStatement;
121 import org.apache.commons.jexl3.parser.JexlNode;
122 import org.apache.commons.jexl3.parser.ParserVisitor;
123 import org.apache.commons.jexl3.parser.StringParser;
124 
125 /**
126  * Helps pinpoint the cause of problems in expressions that fail during evaluation.
127  * <p>
128  * It rebuilds an expression string from the tree and the start/end offsets of the cause in that string.
129  * This implies that exceptions during evaluation do always carry the node that's causing the error.
130  * </p>
131  * @since 2.0
132  */
133 public class Debugger extends ParserVisitor implements JexlInfo.Detail {
134     /** Checks identifiers that contain spaces or punctuation
135      * (but underscore, at-sign, sharp-sign and dollar).
136      */
137     protected static final Pattern QUOTED_IDENTIFIER =
138             Pattern.compile("\\s|\\p{Punct}&&[^@#$_]");
139     private static  boolean isLambdaExpr(final ASTJexlLambda lambda) {
140         return lambda.jjtGetNumChildren() == 1 && !isStatement(lambda.jjtGetChild(0));
141     }
142     /**
143      * Tests whether a node is a statement (vs an expression).
144      * @param child the node
145      * @return true if node is a statement
146      */
147     private static boolean isStatement(final JexlNode child) {
148         return child instanceof ASTJexlScript
149                 || child instanceof ASTBlock
150                 || child instanceof ASTIfStatement
151                 || child instanceof ASTForeachStatement
152                 || child instanceof ASTTryStatement
153                 || child instanceof ASTWhileStatement
154                 || child instanceof ASTDoWhileStatement
155                 || child instanceof ASTAnnotation
156                 || child instanceof ASTThrowStatement;
157     }
158     /**
159      * Tests whether a script or expression ends with a semicolumn.
160      * @param cs the string
161      * @return true if a semicolumn is the last non-whitespace character
162      */
163     private static boolean semicolTerminated(final CharSequence cs) {
164         for(int i = cs.length() - 1; i >= 0; --i) {
165             final char c = cs.charAt(i);
166             if (c == ';') {
167                 return true;
168             }
169             if (!Character.isWhitespace(c)) {
170                 break;
171             }
172         }
173         return false;
174     }
175     /**
176      * Stringifies the pragmas.
177      * @param builder where to stringify
178      * @param pragmas the pragmas, may be null
179      */
180     private static void writePragmas(final StringBuilder builder, final Map<String, Object> pragmas) {
181         if (pragmas != null) {
182             for (final Map.Entry<String, Object> pragma : pragmas.entrySet()) {
183                 final String key = pragma.getKey();
184                 final Object value = pragma.getValue();
185                 final Set<Object> values = value instanceof Set<?>
186                     ? (Set<Object>) value
187                     : Collections.singleton(value);
188                 for (final Object pragmaValue : values) {
189                     builder.append("#pragma ");
190                     builder.append(key);
191                     builder.append(' ');
192                     builder.append(pragmaValue.toString());
193                     builder.append('\n');
194                 }
195             }
196         }
197 
198     }
199     /** The builder to compose messages. */
200     protected final StringBuilder builder = new StringBuilder();
201     /** The cause of the issue to debug. */
202     protected JexlNode cause;
203     /** The starting character location offset of the cause in the builder. */
204     protected int start;
205     /** The ending character location offset of the cause in the builder. */
206     protected int end;
207     /** The indentation level. */
208     protected int indentLevel;
209 
210     /** Perform indentation?. */
211     protected int indent = 2;
212 
213     /** Accept() relative depth. */
214     protected int depth = Integer.MAX_VALUE;
215 
216     /** Arrow symbol. */
217     protected String arrow = "->";
218 
219     /** EOL. */
220     protected String lf = "\n";
221 
222     /** Pragmas out. */
223     protected boolean outputPragmas;
224 
225     /**
226      * Creates a Debugger.
227      */
228     public Debugger() {
229         // nothing to initialize
230     }
231 
232     /**
233      * Checks if a child node is the cause to debug &amp; adds its representation to the rebuilt expression.
234      * @param node the child node
235      * @param data visitor pattern argument
236      * @return visitor pattern value
237      */
238     protected Object accept(final JexlNode node, final Object data) {
239         if (depth <= 0 && builder.length() > 0) {
240             builder.append("...");
241             return data;
242         }
243         if (node == cause) {
244             start = builder.length();
245         }
246         depth -= 1;
247         final Object value = node.jjtAccept(this, data);
248         depth += 1;
249         if (node == cause) {
250             end = builder.length();
251         }
252         return value;
253     }
254 
255     /**
256      * Adds a statement node to the rebuilt expression.
257      * @param child the child node
258      * @param data  visitor pattern argument
259      * @return visitor pattern value
260      */
261     protected Object acceptStatement(final JexlNode child, final Object data) {
262         final JexlNode parent = child.jjtGetParent();
263         if (indent > 0 && (parent instanceof ASTBlock || parent instanceof ASTJexlScript)) {
264             for (int i = 0; i < indentLevel; ++i) {
265                 for(int s = 0; s < indent; ++s) {
266                     builder.append(' ');
267                 }
268             }
269         }
270         depth -= 1;
271         final Object value = accept(child, data);
272         depth += 1;
273         // blocks, if, for & while don't need a ';' at end
274         if (!isStatement(child) && !semicolTerminated(builder)) {
275             builder.append(';');
276             if (indent > 0) {
277                 builder.append(lf);
278             } else {
279                 builder.append(' ');
280             }
281         }
282         return value;
283     }
284 
285     /**
286      * Rebuilds an additive expression.
287      * @param node the node
288      * @param op   the operator
289      * @param data visitor pattern argument
290      * @return visitor pattern value
291      */
292     protected Object additiveNode(final JexlNode node, final String op, final Object data) {
293         // need parenthesis if not in operator precedence order
294         final boolean paren = node.jjtGetParent() instanceof ASTMulNode
295                 || node.jjtGetParent() instanceof ASTDivNode
296                 || node.jjtGetParent() instanceof ASTModNode;
297         final int num = node.jjtGetNumChildren();
298         if (paren) {
299             builder.append('(');
300         }
301         accept(node.jjtGetChild(0), data);
302         for (int i = 1; i < num; ++i) {
303             builder.append(op);
304             accept(node.jjtGetChild(i), data);
305         }
306         if (paren) {
307             builder.append(')');
308         }
309         return data;
310     }
311 
312     /**
313      * Checks if a terminal node is the cause to debug &amp; adds its representation to the rebuilt expression.
314      * @param node  the child node
315      * @param image the child node token image (optionally null)
316      * @param data  visitor pattern argument
317      * @return visitor pattern value
318      */
319     protected Object check(final JexlNode node, final String image, final Object data) {
320         if (node == cause) {
321             start = builder.length();
322         }
323         if (image != null) {
324             builder.append(image);
325         } else {
326             builder.append(node.toString());
327         }
328         if (node == cause) {
329             end = builder.length();
330         }
331         return data;
332     }
333 
334     /**
335      * Rebuilds an expression from a JEXL node.
336      * @param node the node to rebuilt from
337      * @return the rebuilt expression
338      * @since 3.0
339      */
340     public String data(final JexlNode node) {
341         start = 0;
342         end = 0;
343         indentLevel = 0;
344         setArrowSymbol(node);
345         if (node != null) {
346             builder.setLength(0);
347             cause = node;
348             accept(node, null);
349         }
350         return builder.toString();
351     }
352 
353     /**
354      * Position the debugger on the root of an expression.
355      * @param jscript the expression
356      * @return true if the expression was a {@link Script} instance, false otherwise
357      */
358     public boolean debug(final JexlExpression jscript) {
359         if (jscript instanceof Script) {
360             final Script script = (Script) jscript;
361             return debug(script.script);
362         }
363         return false;
364     }
365 
366     /**
367      * Seeks the location of an error cause (a node) in an expression.
368      * @param node the node to debug
369      * @return true if the cause was located, false otherwise
370      */
371     public boolean debug(final JexlNode node) {
372         return debug(node, true);
373     }
374 
375     /**
376      * Seeks the location of an error cause (a node) in an expression.
377      * @param node the node to debug
378      * @param r whether we should actively find the root node of the debugged node
379      * @return true if the cause was located, false otherwise
380      */
381     public boolean debug(final JexlNode node, final boolean r) {
382         start = 0;
383         end = 0;
384         indentLevel = 0;
385         setArrowSymbol(node);
386         if (node != null) {
387             builder.setLength(0);
388             cause = node;
389             // make arg cause become the root cause
390             JexlNode walk = node;
391             if (r) {
392                 while (walk.jjtGetParent() != null) {
393                     walk = walk.jjtGetParent();
394                 }
395             }
396             accept(walk, null);
397         }
398         return end > 0;
399     }
400 
401     /**
402      * Position the debugger on the root of a script.
403      * @param jscript the script
404      * @return true if the script was a {@link Script} instance, false otherwise
405      */
406     public boolean debug(final JexlScript jscript) {
407         if (jscript instanceof Script) {
408             final Script script = (Script) jscript;
409             return debug(script.script);
410         }
411         return false;
412     }
413 
414     /**
415      * Sets this debugger relative maximum depth.
416      * @param rdepth the maximum relative depth from the debugged node
417      * @return this debugger instance
418      */
419     public Debugger depth(final int rdepth) {
420         this.depth = rdepth;
421         return this;
422     }
423 
424     /**
425      * @return The end offset location of the cause in the expression
426      */
427     @Override
428     public int end() {
429         return end;
430     }
431 
432     /**
433      * Tries (hard) to find the features used to parse a node.
434      * @param node the node
435      * @return the features or null
436      */
437     protected JexlFeatures getFeatures(final JexlNode node) {
438         JexlNode walk = node;
439         while(walk != null) {
440             if (walk instanceof ASTJexlScript) {
441                 final ASTJexlScript script = (ASTJexlScript) walk;
442                 return script.getFeatures();
443             }
444             walk = walk.jjtGetParent();
445         }
446         return null;
447     }
448 
449     /**
450      * Sets the indentation level.
451      * @param level the number of spaces for indentation, none if less or equal to zero
452      * @return this debugger instance
453      */
454     public Debugger indentation(final int level) {
455         indent = Math.max(level, 0);
456         indentLevel = 0;
457         return this;
458     }
459 
460     /**
461      * Checks if the children of a node using infix notation is the cause to debug, adds their representation to the
462      * rebuilt expression.
463      * @param node  the child node
464      * @param infix the child node token
465      * @param paren whether the child should be parenthesized
466      * @param data  visitor pattern argument
467      * @return visitor pattern value
468      */
469     protected Object infixChildren(final JexlNode node, final String infix, final boolean paren, final Object data) {
470         final int num = node.jjtGetNumChildren();
471         if (paren) {
472             builder.append('(');
473         }
474         for (int i = 0; i < num; ++i) {
475             if (i > 0) {
476                 builder.append(infix);
477             }
478             accept(node.jjtGetChild(i), data);
479         }
480         if (paren) {
481             builder.append(')');
482         }
483         return data;
484     }
485 
486     /**
487      * Sets this debugger line-feed string.
488      * @param lf the string used to delineate lines (usually "\" or "")
489      * @return this debugger instance
490      */
491     public Debugger lineFeed(final String lf) {
492         this.lf = lf;
493         return this;
494     }
495 
496     /**
497      * Checks whether an identifier should be quoted or not.
498      * @param str the identifier
499      * @return true if needing quotes, false otherwise
500      */
501     protected boolean needQuotes(final String str) {
502         return QUOTED_IDENTIFIER.matcher(str).find()
503                 || "size".equals(str)
504                 || "empty".equals(str);
505     }
506 
507     /**
508      * Lets the debugger write out pragmas if any.
509      * @param flag turn on or off
510      * @return this debugger instance
511      */
512     public Debugger outputPragmas(final boolean flag) {
513         this.outputPragmas = flag;
514         return this;
515     }
516 
517     /**
518      * Postfix operators.
519      * @param node a postfix operator
520      * @param prefix the postfix
521      * @param data visitor pattern argument
522      * @return visitor pattern value
523      */
524     protected Object postfixChild(final JexlNode node, final String prefix, final Object data) {
525         final boolean paren = node.jjtGetChild(0).jjtGetNumChildren() > 1;
526         if (paren) {
527             builder.append('(');
528         }
529         accept(node.jjtGetChild(0), data);
530         if (paren) {
531             builder.append(')');
532         }
533         builder.append(prefix);
534         return data;
535     }
536 
537     /**
538      * Checks if the child of a node using prefix notation is the cause to debug, adds their representation to the
539      * rebuilt expression.
540      * @param node   the node
541      * @param prefix the node token
542      * @param data   visitor pattern argument
543      * @return visitor pattern value
544      */
545     protected Object prefixChild(final JexlNode node, final String prefix, final Object data) {
546         final boolean paren = node.jjtGetChild(0).jjtGetNumChildren() > 1;
547         builder.append(prefix);
548         if (paren) {
549             builder.append('(');
550         }
551         accept(node.jjtGetChild(0), data);
552         if (paren) {
553             builder.append(')');
554         }
555         return data;
556     }
557 
558     /**
559      * Resets this debugger state.
560      */
561     public void reset() {
562         builder.setLength(0);
563         cause = null;
564         start = 0;
565         end = 0;
566         indentLevel = 0;
567         indent = 2;
568         depth = Integer.MAX_VALUE;
569     }
570 
571     /**
572      * Sets the arrow style (fat or thin) depending on features.
573      * @param node the node to start seeking features from.
574      */
575     protected void setArrowSymbol(final JexlNode node) {
576         final JexlFeatures features = getFeatures(node);
577         if (features != null && features.supportsFatArrow() && !features.supportsThinArrow()) {
578             arrow = "=>";
579         } else {
580             arrow = "->";
581         }
582     }
583 
584     /**
585      * Sets the indentation level.
586      * @param level the number of spaces for indentation, none if less or equal to zero
587      */
588     public void setIndentation(final int level) {
589         indentation(level);
590     }
591 
592     /**
593      * @return The starting offset location of the cause in the expression
594      */
595     @Override
596     public int start() {
597         return start;
598     }
599 
600     /**
601      * @return The rebuilt expression
602      */
603     @Override
604     public String toString() {
605         return builder.toString();
606     }
607 
608     @Override
609     protected Object visit(final ASTAddNode node, final Object data) {
610         return additiveNode(node, " + ", data);
611     }
612 
613     @Override
614     protected Object visit(final ASTAndNode node, final Object data) {
615         return infixChildren(node, " && ", false, data);
616     }
617 
618     @Override
619     protected Object visit(final ASTAnnotatedStatement node, final Object data) {
620         final int num = node.jjtGetNumChildren();
621         for (int i = 0; i < num; ++i) {
622             if (i > 0) {
623                 builder.append(' ');
624             }
625             final JexlNode child = node.jjtGetChild(i);
626             acceptStatement(child, data);
627         }
628         return data;
629     }
630 
631     @Override
632     protected Object visit(final ASTAnnotation node, final Object data) {
633         final int num = node.jjtGetNumChildren();
634         builder.append('@');
635         builder.append(node.getName());
636         if (num > 0) {
637             accept(node.jjtGetChild(0), data); // zut
638         }
639         return null;
640     }
641 
642     @Override
643     protected Object visit(final ASTArguments node, final Object data) {
644         final int num = node.jjtGetNumChildren();
645         builder.append("(");
646         if (num > 0) {
647             accept(node.jjtGetChild(0), data);
648             for (int i = 1; i < num; ++i) {
649                 builder.append(", ");
650                 accept(node.jjtGetChild(i), data);
651             }
652         }
653         builder.append(")");
654         return data;
655     }
656 
657     @Override
658     protected Object visit(final ASTArrayAccess node, final Object data) {
659         final int num = node.jjtGetNumChildren();
660         for (int i = 0; i < num; ++i) {
661             if (node.isSafeChild(i)) {
662                 builder.append('?');
663             }
664             builder.append('[');
665             accept(node.jjtGetChild(i), data);
666             builder.append(']');
667         }
668         return data;
669     }
670 
671     @Override
672     protected Object visit(final ASTArrayLiteral node, final Object data) {
673         final int num = node.jjtGetNumChildren();
674         builder.append("[ ");
675         if (num > 0) {
676             if (depth <= 0) {
677                 builder.append("...");
678             } else {
679                 accept(node.jjtGetChild(0), data);
680                 for (int i = 1; i < num; ++i) {
681                     builder.append(", ");
682                     accept(node.jjtGetChild(i), data);
683                 }
684             }
685         }
686         builder.append(" ]");
687         return data;
688     }
689 
690     @Override
691     protected Object visit(final ASTAssignment node, final Object data) {
692         return infixChildren(node, " = ", false, data);
693     }
694 
695     @Override
696     protected Object visit(final ASTBitwiseAndNode node, final Object data) {
697         return infixChildren(node, " & ", false, data);
698     }
699 
700     @Override
701     protected Object visit(final ASTBitwiseComplNode node, final Object data) {
702         return prefixChild(node, "~", data);
703     }
704 
705     @Override
706     protected Object visit(final ASTBitwiseOrNode node, final Object data) {
707         final boolean paren = node.jjtGetParent() instanceof ASTBitwiseAndNode;
708         return infixChildren(node, " | ", paren, data);
709     }
710 
711     @Override
712     protected Object visit(final ASTBitwiseXorNode node, final Object data) {
713         final boolean paren = node.jjtGetParent() instanceof ASTBitwiseAndNode;
714         return infixChildren(node, " ^ ", paren, data);
715     }
716 
717     @Override
718     protected Object visit(final ASTBlock node, final Object data) {
719         builder.append('{');
720         if (indent > 0) {
721             indentLevel += 1;
722             builder.append(lf);
723         } else {
724             builder.append(' ');
725         }
726         final int num = node.jjtGetNumChildren();
727         for (int i = 0; i < num; ++i) {
728             final JexlNode child = node.jjtGetChild(i);
729             acceptStatement(child, data);
730         }
731         if (indent > 0) {
732             indentLevel -= 1;
733             for (int i = 0; i < indentLevel; ++i) {
734                 for(int s = 0; s < indent; ++s) {
735                     builder.append(' ');
736                 }
737             }
738         }
739         char lastChar = builder.charAt(builder.length() - 1);
740         if (!Character.isSpaceChar(lastChar) && lastChar != '\n') {
741             builder.append(' ');
742         }
743         builder.append('}');
744         return data;
745     }
746 
747     @Override
748     protected Object visit(final ASTBreak node, final Object data) {
749         return check(node, "break", data);
750     }
751 
752     @Override
753     protected Object visit(final ASTConstructorNode node, final Object data) {
754         final int num = node.jjtGetNumChildren();
755         builder.append("new");
756         if (num > 0) {
757             final JexlNode c0 = node.jjtGetChild(0);
758             boolean first = true;
759             if (c0 instanceof ASTQualifiedIdentifier) {
760                 builder.append(' ');
761                 accept(c0, data);
762                 builder.append('(');
763             } else {
764                 first = false;
765                 builder.append('(');
766                 accept(c0, data);
767             }
768             for (int i = 1; i < num; ++i) {
769                 if (!first) {
770                     builder.append(", ");
771                 }
772                 accept(node.jjtGetChild(i), data);
773             }
774         }
775         builder.append(")");
776         return data;
777     }
778 
779     @Override
780     protected Object visit(final ASTContinue node, final Object data) {
781         return check(node, "continue", data);
782     }
783 
784     @Override
785     protected Object visit(final ASTDecrementGetNode node, final Object data) {
786         return prefixChild(node, "--", data);
787     }
788 
789     @Override
790     protected Object visit(final ASTDefineVars node, final Object data) {
791         final int num = node.jjtGetNumChildren();
792         if (num > 0) {
793             // var, let, const
794             accept(node.jjtGetChild(0), data);
795             for (int i = 1; i < num; ++i) {
796                 builder.append(", ");
797                 final JexlNode child = node.jjtGetChild(i);
798                 if (child instanceof ASTAssignment) {
799                     final ASTAssignment assign = (ASTAssignment) child;
800                     final int nc = assign.jjtGetNumChildren();
801                     final ASTVar avar = (ASTVar) assign.jjtGetChild(0);
802                     builder.append(avar.getName());
803                     if (nc > 1) {
804                         builder.append(" = ");
805                         accept(assign.jjtGetChild(1), data);
806                     }
807                 } else if (child instanceof ASTVar) {
808                     final ASTVar avar = (ASTVar) child;
809                     builder.append(avar.getName());
810                 } else {
811                     // that's odd
812                     accept(child, data);
813                 }
814             }
815         }
816         return data;
817     }
818 
819     @Override
820     protected Object visit(final ASTDivNode node, final Object data) {
821         return infixChildren(node, " / ", false, data);
822     }
823 
824     @Override
825     protected Object visit(final ASTDoWhileStatement node, final Object data) {
826         builder.append("do ");
827         final int nc = node.jjtGetNumChildren();
828         if (nc > 1) {
829             acceptStatement(node.jjtGetChild(0), data);
830         } else {
831             builder.append(";");
832         }
833         builder.append(" while (");
834         accept(node.jjtGetChild(nc - 1), data);
835         builder.append(")");
836         return data;
837     }
838 
839     @Override
840     protected Object visit(final ASTEmptyFunction node, final Object data) {
841         builder.append("empty ");
842         accept(node.jjtGetChild(0), data);
843         return data;
844     }
845 
846     @Override
847     protected Object visit(final ASTEQNode node, final Object data) {
848         return infixChildren(node, " == ", false, data);
849     }
850 
851     @Override
852     protected Object visit(final ASTEQSNode node, final Object data) {
853         return infixChildren(node, " === ", false, data);
854     }
855 
856     @Override
857     protected Object visit(final ASTERNode node, final Object data) {
858         return infixChildren(node, " =~ ", false, data);
859     }
860 
861     @Override
862     protected Object visit(final ASTEWNode node, final Object data) {
863         return infixChildren(node, " =$ ", false, data);
864     }
865 
866     @Override
867     protected Object visit(final ASTExtendedLiteral node, final Object data) {
868         builder.append("...");
869         return data;
870     }
871 
872     @Override
873     protected Object visit(final ASTFalseNode node, final Object data) {
874         return check(node, "false", data);
875     }
876 
877     @Override
878     protected Object visit(final ASTForeachStatement node, final Object data) {
879         final int form = node.getLoopForm();
880         builder.append("for (");
881         final JexlNode body;
882         if (form == 0) {
883             // for( .. : ...)
884             accept(node.jjtGetChild(0), data);
885             builder.append(" : ");
886             accept(node.jjtGetChild(1), data);
887             builder.append(") ");
888             body = node.jjtGetNumChildren() > 2? node.jjtGetChild(2) : null;
889         } else {
890             // for( .. ; ... ; ..)
891             int nc = 0;
892             // first child is var declaration(s)
893             final JexlNode vars = (form & 1) != 0 ? node.jjtGetChild(nc++) : null;
894             final JexlNode predicate = (form & 2) != 0 ? node.jjtGetChild(nc++) : null;
895             // the loop step
896             final JexlNode step = (form & 4) != 0 ? node.jjtGetChild(nc++) : null;
897             // last child is body
898             body = (form & 8) != 0 ? node.jjtGetChild(nc) : null;
899             if (vars != null) {
900                 accept(vars, data);
901             }
902             builder.append("; ");
903             if (predicate != null) {
904                 accept(predicate, data);
905             }
906             builder.append("; ");
907             if (step != null) {
908                 accept(step, data);
909             }
910             builder.append(") ");
911         }
912         // the body
913         if (body != null) {
914             accept(body, data);
915         } else {
916             builder.append(';');
917         }
918         return data;
919     }
920 
921     @Override
922     protected Object visit(final ASTFunctionNode node, final Object data) {
923         final int num = node.jjtGetNumChildren();
924         if (num == 3) {
925             accept(node.jjtGetChild(0), data);
926             builder.append(":");
927             accept(node.jjtGetChild(1), data);
928             accept(node.jjtGetChild(2), data);
929         } else if (num == 2) {
930             accept(node.jjtGetChild(0), data);
931             accept(node.jjtGetChild(1), data);
932         }
933         return data;
934     }
935 
936     @Override
937     protected Object visit(final ASTGENode node, final Object data) {
938         return infixChildren(node, " >= ", false, data);
939     }
940 
941     @Override
942     protected Object visit(final ASTGetDecrementNode node, final Object data) {
943         return postfixChild(node, "--", data);
944     }
945 
946     @Override
947     protected Object visit(final ASTGetIncrementNode node, final Object data) {
948         return postfixChild(node, "++", data);
949     }
950 
951     @Override
952     protected Object visit(final ASTGTNode node, final Object data) {
953         return infixChildren(node, " > ", false, data);
954     }
955 
956     @Override
957     protected Object visit(final ASTIdentifier node, final Object data) {
958         final String ns = node.getNamespace();
959         final String image = StringParser.escapeIdentifier(node.getName());
960         if (ns == null) {
961             return check(node, image, data);
962         }
963         final String nsid = StringParser.escapeIdentifier(ns) + ":" + image;
964         return check(node, nsid, data);
965     }
966 
967     @Override
968     protected Object visit(final ASTIdentifierAccess node, final Object data) {
969         builder.append(node.isSafe() ? "?." : ".");
970         final String image = node.getName();
971         if (node.isExpression()) {
972             builder.append('`');
973             builder.append(image.replace("`", "\\`"));
974             builder.append('`');
975         } else if (needQuotes(image)) {
976             // quote it
977             builder.append('\'');
978             builder.append(image.replace("'", "\\'"));
979             builder.append('\'');
980         } else {
981             builder.append(image);
982         }
983         return data;
984     }
985 
986     @Override
987     protected Object visit(final ASTIfStatement node, final Object data) {
988         final int numChildren = node.jjtGetNumChildren();
989         // if (...) ...
990         builder.append("if (");
991         accept(node.jjtGetChild(0), data);
992         builder.append(") ");
993         acceptStatement(node.jjtGetChild(1), data);
994         //.. else if (...) ...
995         for(int c = 2; c <  numChildren - 1; c += 2) {
996             builder.append(" else if (");
997             accept(node.jjtGetChild(c), data);
998             builder.append(") ");
999             acceptStatement(node.jjtGetChild(c + 1), data);
1000         }
1001         // else... (if odd)
1002         if ((numChildren & 1) == 1) {
1003             builder.append(" else ");
1004             acceptStatement(node.jjtGetChild(numChildren - 1), data);
1005         }
1006         return data;
1007     }
1008 
1009     @Override
1010     protected Object visit(final ASTIncrementGetNode node, final Object data) {
1011         return prefixChild(node, "++", data);
1012     }
1013 
1014     @Override
1015     protected Object visit(final ASTInstanceOf node, final Object data) {
1016         return infixChildren(node, " instanceof ", false, data);
1017     }
1018 
1019     @Override
1020     protected Object visit(final ASTJexlScript node, final Object arg) {
1021         if (outputPragmas) {
1022             writePragmas(builder, node.getPragmas());
1023         }
1024         Object data = arg;
1025         boolean named = false;
1026         // if lambda, produce parameters
1027         if (node instanceof ASTJexlLambda) {
1028             final ASTJexlLambda lambda = (ASTJexlLambda) node;
1029             final JexlNode parent = node.jjtGetParent();
1030             // use lambda syntax if not assigned
1031             final boolean expr = isLambdaExpr(lambda);
1032             named = node.jjtGetChild(0) instanceof ASTVar;
1033             final boolean assigned = parent instanceof ASTAssignment || named;
1034             if (assigned && !expr) {
1035                 builder.append("function");
1036                 if (named) {
1037                     final ASTVar avar = (ASTVar) node.jjtGetChild(0);
1038                     builder.append(' ');
1039                     builder.append(avar.getName());
1040                 }
1041             }
1042             builder.append('(');
1043             final String[] params = lambda.getParameters();
1044             if (params != null) {
1045                 final Scope scope = lambda.getScope();
1046                 final LexicalScope lexicalScope = lambda.getLexicalScope();
1047                 for (int p = 0; p < params.length; ++p) {
1048                     if (p > 0) {
1049                         builder.append(", ");
1050                     }
1051                     final String param = params[p];
1052                     final int symbol = scope.getSymbol(param);
1053                     if (lexicalScope.isConstant(symbol)) {
1054                         builder.append("const ");
1055                     } else if (scope.isLexical(symbol)) {
1056                         builder.append("let ");
1057                     }
1058                     builder.append(visitParameter(param, data));
1059                 }
1060             }
1061             builder.append(')');
1062             if (assigned && !expr) {
1063                 // block follows
1064                 builder.append(' ');
1065             } else {
1066                 builder.append(arrow);
1067                 // add a space if lambda expr otherwise block follows
1068                 if (expr) {
1069                     builder.append(' ');
1070                 }
1071             }
1072         }
1073         // no parameters or done with them
1074         final int num = node.jjtGetNumChildren();
1075         if (num == 1 && !(node instanceof ASTJexlLambda)) {
1076             data = accept(node.jjtGetChild(0), data);
1077         } else {
1078             for (int i = named? 1 : 0; i < num; ++i) {
1079                 final JexlNode child = node.jjtGetChild(i);
1080                 acceptStatement(child, data);
1081             }
1082         }
1083         return data;
1084     }
1085 
1086     @Override
1087     protected Object visit(final ASTJxltLiteral node, final Object data) {
1088         final String img = StringParser.escapeString(node.getLiteral(), '`');
1089         return check(node, img, data);
1090     }
1091 
1092     @Override
1093     protected Object visit(final ASTLENode node, final Object data) {
1094         return infixChildren(node, " <= ", false, data);
1095     }
1096 
1097     @Override
1098     protected Object visit(final ASTLTNode node, final Object data) {
1099         return infixChildren(node, " < ", false, data);
1100     }
1101 
1102     @Override
1103     protected Object visit(final ASTMapEntry node, final Object data) {
1104         accept(node.jjtGetChild(0), data);
1105         builder.append(" : ");
1106         accept(node.jjtGetChild(1), data);
1107         return data;
1108     }
1109 
1110     @Override
1111     protected Object visit(final ASTMapLiteral node, final Object data) {
1112         final int num = node.jjtGetNumChildren();
1113         builder.append("{ ");
1114         if (num > 0) {
1115             if (depth <= 0) {
1116                 builder.append("...");
1117             } else {
1118                 accept(node.jjtGetChild(0), data);
1119                 for (int i = 1; i < num; ++i) {
1120                     builder.append(",");
1121                     accept(node.jjtGetChild(i), data);
1122                 }
1123             }
1124         } else {
1125             builder.append(':');
1126         }
1127         builder.append(" }");
1128         return data;
1129     }
1130 
1131     @Override
1132     protected Object visit(final ASTMethodNode node, final Object data) {
1133         final int num = node.jjtGetNumChildren();
1134         if (num == 2) {
1135             accept(node.jjtGetChild(0), data);
1136             if (depth <= 0) {
1137                 builder.append("(...)");
1138             } else {
1139                 accept(node.jjtGetChild(1), data);
1140             }
1141         }
1142         return data;
1143     }
1144 
1145     @Override
1146     protected Object visit(final ASTModNode node, final Object data) {
1147         return infixChildren(node, " % ", false, data);
1148     }
1149 
1150     @Override
1151     protected Object visit(final ASTMulNode node, final Object data) {
1152         return infixChildren(node, " * ", false, data);
1153     }
1154 
1155     @Override
1156     protected Object visit(final ASTNENode node, final Object data) {
1157         return infixChildren(node, " != ", false, data);
1158     }
1159 
1160     @Override
1161     protected Object visit(final ASTNESNode node, final Object data) {
1162         return infixChildren(node, " !== ", false, data);
1163     }
1164 
1165     @Override
1166     protected Object visit(final ASTNEWNode node, final Object data) {
1167         return infixChildren(node, " !$ ", false, data);
1168     }
1169 
1170     @Override
1171     protected Object visit(final ASTNotInstanceOf node, final Object data) {
1172         return infixChildren(node, " !instanceof ", false, data);
1173     }
1174 
1175     @Override
1176     protected Object visit(final ASTNotNode node, final Object data) {
1177         builder.append("!");
1178         accept(node.jjtGetChild(0), data);
1179         return data;
1180     }
1181 
1182     @Override
1183     protected Object visit(final ASTNRNode node, final Object data) {
1184         return infixChildren(node, " !~ ", false, data);
1185     }
1186 
1187     @Override
1188     protected Object visit(final ASTNSWNode node, final Object data) {
1189         return infixChildren(node, " !^ ", false, data);
1190     }
1191 
1192     @Override
1193     protected Object visit(final ASTNullLiteral node, final Object data) {
1194         check(node, "null", data);
1195         return data;
1196     }
1197 
1198     @Override
1199     protected Object visit(final ASTNullpNode node, final Object data) {
1200         accept(node.jjtGetChild(0), data);
1201         builder.append("??");
1202         accept(node.jjtGetChild(1), data);
1203         return data;
1204     }
1205 
1206     @Override
1207     protected Object visit(final ASTNumberLiteral node, final Object data) {
1208         return check(node, node.toString(), data);
1209     }
1210 
1211     @Override
1212     protected Object visit(final ASTOrNode node, final Object data) {
1213         // need parenthesis if not in operator precedence order
1214         final boolean paren = node.jjtGetParent() instanceof ASTAndNode;
1215         return infixChildren(node, " || ", paren, data);
1216     }
1217 
1218     @Override
1219     protected Object visit(final ASTQualifiedIdentifier node, final Object data) {
1220         final String img = node.getName();
1221         return check(node, img, data);
1222     }
1223 
1224     @Override
1225     protected Object visit(final ASTRangeNode node, final Object data) {
1226         if (depth <= 0) {
1227             builder.append("( .. )");
1228             return data;
1229         }
1230         return infixChildren(node, " .. ", false, data);
1231     }
1232 
1233     @Override
1234     protected Object visit(final ASTReference node, final Object data) {
1235         final int num = node.jjtGetNumChildren();
1236         for (int i = 0; i < num; ++i) {
1237             accept(node.jjtGetChild(i), data);
1238         }
1239         return data;
1240     }
1241 
1242     @Override
1243     protected Object visit(final ASTReferenceExpression node, final Object data) {
1244         final JexlNode first = node.jjtGetChild(0);
1245         builder.append('(');
1246         accept(first, data);
1247         builder.append(')');
1248         final int num = node.jjtGetNumChildren();
1249         for (int i = 1; i < num; ++i) {
1250             builder.append("[");
1251             accept(node.jjtGetChild(i), data);
1252             builder.append("]");
1253         }
1254         return data;
1255     }
1256 
1257     @Override
1258     protected Object visit(final ASTRegexLiteral node, final Object data) {
1259         final String img = StringParser.escapeString(node.toString(), '/');
1260         return check(node, "~" + img, data);
1261     }
1262 
1263     @Override
1264     protected Object visit(final ASTReturnStatement node, final Object data) {
1265         builder.append("return");
1266         if (node.jjtGetNumChildren() > 0) {
1267             builder.append(' ');
1268             accept(node.jjtGetChild(0), data);
1269         }
1270         return data;
1271     }
1272 
1273     @Override
1274     protected Object visit(final ASTSetAddNode node, final Object data) {
1275         return infixChildren(node, " += ", false, data);
1276     }
1277 
1278     @Override
1279     protected Object visit(final ASTSetAndNode node, final Object data) {
1280         return infixChildren(node, " &= ", false, data);
1281     }
1282 
1283     @Override
1284     protected Object visit(final ASTSetDivNode node, final Object data) {
1285         return infixChildren(node, " /= ", false, data);
1286     }
1287 
1288     @Override
1289     protected Object visit(final ASTSetLiteral node, final Object data) {
1290         final int num = node.jjtGetNumChildren();
1291         builder.append("{ ");
1292         if (num > 0) {
1293             if (depth <= 0) {
1294                 builder.append("...");
1295             } else {
1296                 accept(node.jjtGetChild(0), data);
1297                 for (int i = 1; i < num; ++i) {
1298                     builder.append(",");
1299                     accept(node.jjtGetChild(i), data);
1300                 }
1301             }
1302         }
1303         builder.append(" }");
1304         return data;
1305     }
1306 
1307     @Override
1308     protected Object visit(final ASTSetModNode node, final Object data) {
1309         return infixChildren(node, " %= ", false, data);
1310     }
1311 
1312     @Override
1313     protected Object visit(final ASTSetMultNode node, final Object data) {
1314         return infixChildren(node, " *= ", false, data);
1315     }
1316 
1317     @Override
1318     protected Object visit(final ASTSetOrNode node, final Object data) {
1319         return infixChildren(node, " |= ", false, data);
1320     }
1321 
1322     @Override
1323     protected Object visit(final ASTSetShiftLeftNode node, final Object data) {
1324         return infixChildren(node, " <<= ", false, data);
1325     }
1326 
1327     @Override
1328     protected Object visit(final ASTSetShiftRightNode node, final Object data) {
1329         return infixChildren(node, " >>= ", false, data);
1330     }
1331 
1332     @Override
1333     protected Object visit(final ASTSetShiftRightUnsignedNode node, final Object data) {
1334         return infixChildren(node, " >>>= ", false, data);
1335     }
1336 
1337     @Override
1338     protected Object visit(final ASTSetSubNode node, final Object data) {
1339         return infixChildren(node, " -= ", false, data);
1340     }
1341 
1342     @Override
1343     protected Object visit(final ASTSetXorNode node, final Object data) {
1344         return infixChildren(node, " ^= ", false, data);
1345     }
1346 
1347     @Override
1348     protected Object visit(final ASTShiftLeftNode node, final Object data) {
1349         return infixChildren(node, " << ", false, data);
1350     }
1351 
1352     @Override
1353     protected Object visit(final ASTShiftRightNode node, final Object data) {
1354         return infixChildren(node, " >> ", false, data);
1355     }
1356 
1357     @Override
1358     protected Object visit(final ASTShiftRightUnsignedNode node, final Object data) {
1359         return infixChildren(node, " >>> ", false, data);
1360     }
1361 
1362     @Override
1363     protected Object visit(final ASTSizeFunction node, final Object data) {
1364         builder.append("size ");
1365         accept(node.jjtGetChild(0), data);
1366         return data;
1367     }
1368 
1369     @Override
1370     protected Object visit(final ASTStringLiteral node, final Object data) {
1371         final String img = StringParser.escapeString(node.getLiteral(), '\'');
1372         return check(node, img, data);
1373     }
1374 
1375     @Override
1376     protected Object visit(final ASTSubNode node, final Object data) {
1377         return additiveNode(node, " - ", data);
1378     }
1379 
1380     @Override
1381     protected Object visit(final ASTSWNode node, final Object data) {
1382         return infixChildren(node, " =^ ", false, data);
1383     }
1384 
1385     @Override
1386     protected Object visit(final ASTTernaryNode node, final Object data) {
1387         accept(node.jjtGetChild(0), data);
1388         if (node.jjtGetNumChildren() > 2) {
1389             builder.append("? ");
1390             accept(node.jjtGetChild(1), data);
1391             builder.append(" : ");
1392             accept(node.jjtGetChild(2), data);
1393         } else {
1394             builder.append("?: ");
1395             accept(node.jjtGetChild(1), data);
1396 
1397         }
1398         return data;
1399     }
1400 
1401     @Override
1402     protected Object visit(final ASTThrowStatement node, final Object data) {
1403         builder.append("throw ");
1404         accept(node.jjtGetChild(0), data);
1405         return data;
1406     }
1407 
1408     @Override
1409     protected Object visit(final ASTTrueNode node, final Object data) {
1410         check(node, "true", data);
1411         return data;
1412     }
1413 
1414     @Override
1415     protected Object visit(final ASTTryResources node, final Object data) {
1416         final int tryBody = node.jjtGetNumChildren() - 1;
1417         builder.append(" (");
1418         accept(node.jjtGetChild(0), data);
1419         for(int c = 1; c < tryBody; ++c) {
1420             builder.append("; ");
1421             accept(node.jjtGetChild(c), data);
1422         }
1423         builder.append(") ");
1424         accept(node.jjtGetChild(tryBody), data);
1425         return data;
1426     }
1427 
1428     @Override
1429     protected Object visit(final ASTTryStatement node, final Object data) {
1430         builder.append("try");
1431         int nc = 0;
1432         // try-body (with or without resources)
1433         accept(node.jjtGetChild(nc++), data);
1434         // catch-body
1435         if (node.hasCatchClause()) {
1436             builder.append("catch (");
1437             accept(node.jjtGetChild(nc++), data);
1438             builder.append(") ");
1439             accept(node.jjtGetChild(nc++), data);
1440         }
1441         // finally-body
1442         if (node.hasFinallyClause()) {
1443             builder.append(indent > 0? lf : ' ');
1444             builder.append("finally ");
1445             accept(node.jjtGetChild(nc), data);
1446         }
1447         return data;
1448     }
1449 
1450     @Override
1451     protected Object visit(final ASTUnaryMinusNode node, final Object data) {
1452         return prefixChild(node, "-", data);
1453     }
1454 
1455     @Override
1456     protected Object visit(final ASTUnaryPlusNode node, final Object data) {
1457         return prefixChild(node, "+", data);
1458     }
1459 
1460     @Override
1461     protected Object visit(final ASTVar node, final Object data) {
1462         if (node.isConstant()) {
1463             builder.append("const ");
1464         } else  if (node.isLexical()) {
1465             builder.append("let ");
1466         } else {
1467             builder.append("var ");
1468         }
1469         check(node, node.getName(), data);
1470         return data;
1471     }
1472 
1473     @Override
1474     protected Object visit(final ASTWhileStatement node, final Object data) {
1475         builder.append("while (");
1476         accept(node.jjtGetChild(0), data);
1477         builder.append(") ");
1478         if (node.jjtGetNumChildren() > 1) {
1479             acceptStatement(node.jjtGetChild(1), data);
1480         } else {
1481             builder.append(';');
1482         }
1483         return data;
1484     }
1485 
1486     /**
1487      * A pseudo visitor for parameters.
1488      * @param p the parameter name
1489      * @param data the visitor argument
1490      * @return the parameter name to use
1491      */
1492     protected String visitParameter(final String p, final Object data) {
1493         return p;
1494     }
1495 }