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      * 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      * 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         if (!Character.isSpaceChar(builder.charAt(builder.length() - 1))) {
740             builder.append(' ');
741         }
742         builder.append('}');
743         return data;
744     }
745 
746     @Override
747     protected Object visit(final ASTBreak node, final Object data) {
748         return check(node, "break", data);
749     }
750 
751     @Override
752     protected Object visit(final ASTConstructorNode node, final Object data) {
753         final int num = node.jjtGetNumChildren();
754         builder.append("new");
755         if (num > 0) {
756             final JexlNode c0 = node.jjtGetChild(0);
757             boolean first = true;
758             if (c0 instanceof ASTQualifiedIdentifier) {
759                 builder.append(' ');
760                 accept(c0, data);
761                 builder.append('(');
762             } else {
763                 first = false;
764                 builder.append('(');
765                 accept(c0, data);
766             }
767             for (int i = 1; i < num; ++i) {
768                 if (!first) {
769                     builder.append(", ");
770                 }
771                 accept(node.jjtGetChild(i), data);
772             }
773         }
774         builder.append(")");
775         return data;
776     }
777 
778     @Override
779     protected Object visit(final ASTContinue node, final Object data) {
780         return check(node, "continue", data);
781     }
782 
783     @Override
784     protected Object visit(final ASTDecrementGetNode node, final Object data) {
785         return prefixChild(node, "--", data);
786     }
787 
788     @Override
789     protected Object visit(final ASTDefineVars node, final Object data) {
790         final int num = node.jjtGetNumChildren();
791         if (num > 0) {
792             // var, let, const
793             accept(node.jjtGetChild(0), data);
794             for (int i = 1; i < num; ++i) {
795                 builder.append(", ");
796                 final JexlNode child = node.jjtGetChild(i);
797                 if (child instanceof ASTAssignment) {
798                     final ASTAssignment assign = (ASTAssignment) child;
799                     final int nc = assign.jjtGetNumChildren();
800                     final ASTVar avar = (ASTVar) assign.jjtGetChild(0);
801                     builder.append(avar.getName());
802                     if (nc > 1) {
803                         builder.append(" = ");
804                         accept(assign.jjtGetChild(1), data);
805                     }
806                 } else if (child instanceof ASTVar) {
807                     final ASTVar avar = (ASTVar) child;
808                     builder.append(avar.getName());
809                 } else {
810                     // that's odd
811                     accept(child, data);
812                 }
813             }
814         }
815         return data;
816     }
817 
818     @Override
819     protected Object visit(final ASTDivNode node, final Object data) {
820         return infixChildren(node, " / ", false, data);
821     }
822 
823     @Override
824     protected Object visit(final ASTDoWhileStatement node, final Object data) {
825         builder.append("do ");
826         final int nc = node.jjtGetNumChildren();
827         if (nc > 1) {
828             acceptStatement(node.jjtGetChild(0), data);
829         } else {
830             builder.append(";");
831         }
832         builder.append(" while (");
833         accept(node.jjtGetChild(nc - 1), data);
834         builder.append(")");
835         return data;
836     }
837 
838     @Override
839     protected Object visit(final ASTEmptyFunction node, final Object data) {
840         builder.append("empty ");
841         accept(node.jjtGetChild(0), data);
842         return data;
843     }
844 
845     @Override
846     protected Object visit(final ASTEQNode node, final Object data) {
847         return infixChildren(node, " == ", false, data);
848     }
849 
850     @Override
851     protected Object visit(final ASTEQSNode node, final Object data) {
852         return infixChildren(node, " === ", false, data);
853     }
854 
855     @Override
856     protected Object visit(final ASTERNode node, final Object data) {
857         return infixChildren(node, " =~ ", false, data);
858     }
859 
860     @Override
861     protected Object visit(final ASTEWNode node, final Object data) {
862         return infixChildren(node, " =$ ", false, data);
863     }
864 
865     @Override
866     protected Object visit(final ASTExtendedLiteral node, final Object data) {
867         builder.append("...");
868         return data;
869     }
870 
871     @Override
872     protected Object visit(final ASTFalseNode node, final Object data) {
873         return check(node, "false", data);
874     }
875 
876     @Override
877     protected Object visit(final ASTForeachStatement node, final Object data) {
878         final int form = node.getLoopForm();
879         builder.append("for(");
880         final JexlNode body;
881         if (form == 0) {
882             // for( .. : ...)
883             accept(node.jjtGetChild(0), data);
884             builder.append(" : ");
885             accept(node.jjtGetChild(1), data);
886             builder.append(") ");
887             body = node.jjtGetNumChildren() > 2? node.jjtGetChild(2) : null;
888         } else {
889             // for( .. ; ... ; ..)
890             int nc = 0;
891             // first child is var declaration(s)
892             final JexlNode vars = (form & 1) != 0 ? node.jjtGetChild(nc++) : null;
893             final JexlNode predicate = (form & 2) != 0 ? node.jjtGetChild(nc++) : null;
894             // the loop step
895             final JexlNode step = (form & 4) != 0 ? node.jjtGetChild(nc++) : null;
896             // last child is body
897             body = (form & 8) != 0 ? node.jjtGetChild(nc) : null;
898             if (vars != null) {
899                 accept(vars, data);
900             }
901             builder.append("; ");
902             if (predicate != null) {
903                 accept(predicate, data);
904             }
905             builder.append("; ");
906             if (step != null) {
907                 accept(step, data);
908             }
909             builder.append(") ");
910         }
911         // the body
912         if (body != null) {
913             accept(body, data);
914         } else {
915             builder.append(';');
916         }
917         return data;
918     }
919 
920     @Override
921     protected Object visit(final ASTFunctionNode node, final Object data) {
922         final int num = node.jjtGetNumChildren();
923         if (num == 3) {
924             accept(node.jjtGetChild(0), data);
925             builder.append(":");
926             accept(node.jjtGetChild(1), data);
927             accept(node.jjtGetChild(2), data);
928         } else if (num == 2) {
929             accept(node.jjtGetChild(0), data);
930             accept(node.jjtGetChild(1), data);
931         }
932         return data;
933     }
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         accept(node.jjtGetChild(0), data);
1267         return data;
1268     }
1269 
1270     @Override
1271     protected Object visit(final ASTSetAddNode node, final Object data) {
1272         return infixChildren(node, " += ", false, data);
1273     }
1274 
1275     @Override
1276     protected Object visit(final ASTSetAndNode node, final Object data) {
1277         return infixChildren(node, " &= ", false, data);
1278     }
1279 
1280     @Override
1281     protected Object visit(final ASTSetDivNode node, final Object data) {
1282         return infixChildren(node, " /= ", false, data);
1283     }
1284 
1285     @Override
1286     protected Object visit(final ASTSetLiteral node, final Object data) {
1287         final int num = node.jjtGetNumChildren();
1288         builder.append("{ ");
1289         if (num > 0) {
1290             if (depth <= 0) {
1291                 builder.append("...");
1292             } else {
1293                 accept(node.jjtGetChild(0), data);
1294                 for (int i = 1; i < num; ++i) {
1295                     builder.append(",");
1296                     accept(node.jjtGetChild(i), data);
1297                 }
1298             }
1299         }
1300         builder.append(" }");
1301         return data;
1302     }
1303 
1304     @Override
1305     protected Object visit(final ASTSetModNode node, final Object data) {
1306         return infixChildren(node, " %= ", false, data);
1307     }
1308 
1309     @Override
1310     protected Object visit(final ASTSetMultNode node, final Object data) {
1311         return infixChildren(node, " *= ", false, data);
1312     }
1313 
1314     @Override
1315     protected Object visit(final ASTSetOrNode node, final Object data) {
1316         return infixChildren(node, " |= ", false, data);
1317     }
1318 
1319     @Override
1320     protected Object visit(final ASTSetShiftLeftNode node, final Object data) {
1321         return infixChildren(node, " <<= ", false, data);
1322     }
1323 
1324     @Override
1325     protected Object visit(final ASTSetShiftRightNode node, final Object data) {
1326         return infixChildren(node, " >>= ", false, data);
1327     }
1328 
1329     @Override
1330     protected Object visit(final ASTSetShiftRightUnsignedNode node, final Object data) {
1331         return infixChildren(node, " >>>= ", false, data);
1332     }
1333 
1334     @Override
1335     protected Object visit(final ASTSetSubNode node, final Object data) {
1336         return infixChildren(node, " -= ", false, data);
1337     }
1338 
1339     @Override
1340     protected Object visit(final ASTSetXorNode node, final Object data) {
1341         return infixChildren(node, " ^= ", false, data);
1342     }
1343 
1344     @Override
1345     protected Object visit(final ASTShiftLeftNode node, final Object data) {
1346         return infixChildren(node, " << ", false, data);
1347     }
1348 
1349     @Override
1350     protected Object visit(final ASTShiftRightNode node, final Object data) {
1351         return infixChildren(node, " >> ", false, data);
1352     }
1353 
1354     @Override
1355     protected Object visit(final ASTShiftRightUnsignedNode node, final Object data) {
1356         return infixChildren(node, " >>> ", false, data);
1357     }
1358 
1359     @Override
1360     protected Object visit(final ASTSizeFunction node, final Object data) {
1361         builder.append("size ");
1362         accept(node.jjtGetChild(0), data);
1363         return data;
1364     }
1365 
1366     @Override
1367     protected Object visit(final ASTStringLiteral node, final Object data) {
1368         final String img = StringParser.escapeString(node.getLiteral(), '\'');
1369         return check(node, img, data);
1370     }
1371 
1372     @Override
1373     protected Object visit(final ASTSubNode node, final Object data) {
1374         return additiveNode(node, " - ", data);
1375     }
1376 
1377     @Override
1378     protected Object visit(final ASTSWNode node, final Object data) {
1379         return infixChildren(node, " =^ ", false, data);
1380     }
1381 
1382     @Override
1383     protected Object visit(final ASTTernaryNode node, final Object data) {
1384         accept(node.jjtGetChild(0), data);
1385         if (node.jjtGetNumChildren() > 2) {
1386             builder.append("? ");
1387             accept(node.jjtGetChild(1), data);
1388             builder.append(" : ");
1389             accept(node.jjtGetChild(2), data);
1390         } else {
1391             builder.append("?: ");
1392             accept(node.jjtGetChild(1), data);
1393 
1394         }
1395         return data;
1396     }
1397 
1398     @Override
1399     protected Object visit(final ASTThrowStatement node, final Object data) {
1400         builder.append("throw ");
1401         accept(node.jjtGetChild(0), data);
1402         return data;
1403     }
1404 
1405     @Override
1406     protected Object visit(final ASTTrueNode node, final Object data) {
1407         check(node, "true", data);
1408         return data;
1409     }
1410 
1411     @Override
1412     protected Object visit(final ASTTryResources node, final Object data) {
1413         final int tryBody = node.jjtGetNumChildren() - 1;
1414         builder.append('(');
1415         accept(node.jjtGetChild(0), data);
1416         for(int c = 1; c < tryBody; ++c) {
1417             builder.append("; ");
1418             accept(node.jjtGetChild(c), data);
1419         }
1420         builder.append(") ");
1421         accept(node.jjtGetChild(tryBody), data);
1422         return data;
1423     }
1424 
1425     @Override
1426     protected Object visit(final ASTTryStatement node, final Object data) {
1427         builder.append("try");
1428         int nc = 0;
1429         // try-body (with or without resources)
1430         accept(node.jjtGetChild(nc++), data);
1431         // catch-body
1432         if (node.hasCatchClause()) {
1433             builder.append("catch(");
1434             accept(node.jjtGetChild(nc++), data);
1435             builder.append(") ");
1436             accept(node.jjtGetChild(nc++), data);
1437         }
1438         // finally-body
1439         if (node.hasFinallyClause()) {
1440             builder.append(indent > 0? lf : ' ');
1441             builder.append("finally ");
1442             accept(node.jjtGetChild(nc), data);
1443         }
1444         return data;
1445     }
1446 
1447     @Override
1448     protected Object visit(final ASTUnaryMinusNode node, final Object data) {
1449         return prefixChild(node, "-", data);
1450     }
1451 
1452     @Override
1453     protected Object visit(final ASTUnaryPlusNode node, final Object data) {
1454         return prefixChild(node, "+", data);
1455     }
1456 
1457     @Override
1458     protected Object visit(final ASTVar node, final Object data) {
1459         if (node.isConstant()) {
1460             builder.append("const ");
1461         } else  if (node.isLexical()) {
1462             builder.append("let ");
1463         } else {
1464             builder.append("var ");
1465         }
1466         check(node, node.getName(), data);
1467         return data;
1468     }
1469 
1470     @Override
1471     protected Object visit(final ASTWhileStatement node, final Object data) {
1472         builder.append("while (");
1473         accept(node.jjtGetChild(0), data);
1474         builder.append(") ");
1475         if (node.jjtGetNumChildren() > 1) {
1476             acceptStatement(node.jjtGetChild(1), data);
1477         } else {
1478             builder.append(';');
1479         }
1480         return data;
1481     }
1482 
1483     /**
1484      * A pseudo visitor for parameters.
1485      * @param p the parameter name
1486      * @param data the visitor argument
1487      * @return the parameter name to use
1488      */
1489     protected String visitParameter(final String p, final Object data) {
1490         return p;
1491     }
1492 }