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.jexl2;
18  
19  import java.util.regex.Pattern;
20  import org.apache.commons.jexl2.parser.ASTAdditiveNode;
21  import org.apache.commons.jexl2.parser.ASTAdditiveOperator;
22  import org.apache.commons.jexl2.parser.ASTAmbiguous;
23  import org.apache.commons.jexl2.parser.ASTAndNode;
24  import org.apache.commons.jexl2.parser.ASTArrayAccess;
25  import org.apache.commons.jexl2.parser.ASTArrayLiteral;
26  import org.apache.commons.jexl2.parser.ASTAssignment;
27  import org.apache.commons.jexl2.parser.ASTBitwiseAndNode;
28  import org.apache.commons.jexl2.parser.ASTBitwiseComplNode;
29  import org.apache.commons.jexl2.parser.ASTBitwiseOrNode;
30  import org.apache.commons.jexl2.parser.ASTBitwiseXorNode;
31  import org.apache.commons.jexl2.parser.ASTBlock;
32  import org.apache.commons.jexl2.parser.ASTConstructorNode;
33  import org.apache.commons.jexl2.parser.ASTDivNode;
34  import org.apache.commons.jexl2.parser.ASTEQNode;
35  import org.apache.commons.jexl2.parser.ASTERNode;
36  import org.apache.commons.jexl2.parser.ASTEmptyFunction;
37  import org.apache.commons.jexl2.parser.ASTFalseNode;
38  import org.apache.commons.jexl2.parser.ASTForeachStatement;
39  import org.apache.commons.jexl2.parser.ASTFunctionNode;
40  import org.apache.commons.jexl2.parser.ASTGENode;
41  import org.apache.commons.jexl2.parser.ASTGTNode;
42  import org.apache.commons.jexl2.parser.ASTIdentifier;
43  import org.apache.commons.jexl2.parser.ASTIfStatement;
44  import org.apache.commons.jexl2.parser.ASTJexlScript;
45  import org.apache.commons.jexl2.parser.ASTLENode;
46  import org.apache.commons.jexl2.parser.ASTLTNode;
47  import org.apache.commons.jexl2.parser.ASTMapEntry;
48  import org.apache.commons.jexl2.parser.ASTMapLiteral;
49  import org.apache.commons.jexl2.parser.ASTMethodNode;
50  import org.apache.commons.jexl2.parser.ASTModNode;
51  import org.apache.commons.jexl2.parser.ASTMulNode;
52  import org.apache.commons.jexl2.parser.ASTNENode;
53  import org.apache.commons.jexl2.parser.ASTNRNode;
54  import org.apache.commons.jexl2.parser.ASTNotNode;
55  import org.apache.commons.jexl2.parser.ASTNullLiteral;
56  import org.apache.commons.jexl2.parser.ASTNumberLiteral;
57  import org.apache.commons.jexl2.parser.ASTOrNode;
58  import org.apache.commons.jexl2.parser.ASTReference;
59  import org.apache.commons.jexl2.parser.ASTReferenceExpression;
60  import org.apache.commons.jexl2.parser.ASTReturnStatement;
61  import org.apache.commons.jexl2.parser.ASTSizeFunction;
62  import org.apache.commons.jexl2.parser.ASTSizeMethod;
63  import org.apache.commons.jexl2.parser.ASTStringLiteral;
64  import org.apache.commons.jexl2.parser.ASTTernaryNode;
65  import org.apache.commons.jexl2.parser.ASTTrueNode;
66  import org.apache.commons.jexl2.parser.ASTUnaryMinusNode;
67  import org.apache.commons.jexl2.parser.ASTVar;
68  import org.apache.commons.jexl2.parser.ASTWhileStatement;
69  import org.apache.commons.jexl2.parser.JexlNode;
70  
71  import org.apache.commons.jexl2.parser.ParserVisitor;
72  import org.apache.commons.jexl2.parser.SimpleNode;
73  
74  /**
75   * Helps pinpoint the cause of problems in expressions that fail during evaluation.
76   * <p>
77   * It rebuilds an expression string from the tree and the start/end offsets of the cause
78   * in that string.
79   * </p>
80   * This implies that exceptions during evaluation do allways carry the node that's causing
81   * the error.
82   * @since 2.0
83   */
84  final class Debugger implements ParserVisitor {
85      /** The builder to compose messages. */
86      private final StringBuilder builder;
87      /** The cause of the issue to debug. */
88      private JexlNode cause;
89      /** The starting character location offset of the cause in the builder. */
90      private int start;
91      /** The ending character location offset of the cause in the builder. */
92      private int end;
93  
94      /**
95       * Creates a Debugger.
96       */
97      Debugger() {
98          builder = new StringBuilder();
99          cause = null;
100         start = 0;
101         end = 0;
102     }
103 
104     /**
105      * Seeks the location of an error cause (a node) in an expression.
106      * @param node the node to debug
107      * @return true if the cause was located, false otherwise
108      */
109     public boolean debug(JexlNode node) {
110         start = 0;
111         end = 0;
112         if (node != null) {
113             builder.setLength(0);
114             this.cause = node;
115             // make arg cause become the root cause
116             JexlNode root = node;
117             while (root.jjtGetParent() != null) {
118                 root = root.jjtGetParent();
119             }
120             root.jjtAccept(this, null);
121         }
122         return end > 0;
123     }
124 
125     /**
126      * @return The rebuilt expression
127      */
128     public String data() {
129         return builder.toString();
130     }
131     
132     
133     /**
134      * Rebuilds an expression from a Jexl node.
135      * @param node the node to rebuilt from
136      * @return the rebuilt expression
137      * @since 2.1
138      */
139     public String data(JexlNode node) {
140         start = 0;
141         end = 0;
142         if (node != null) {
143             builder.setLength(0);
144             this.cause = node;
145             node.jjtAccept(this, null);
146         }
147         return builder.toString();
148     }
149 
150     /**
151      * @return The starting offset location of the cause in the expression
152      */
153     public int start() {
154         return start;
155     }
156 
157     /**
158      * @return The end offset location of the cause in the expression
159      */
160     public int end() {
161         return end;
162     }
163 
164     /**
165      * Checks if a child node is the cause to debug &amp; adds its representation
166      * to the rebuilt expression.
167      * @param node the child node
168      * @param data visitor pattern argument
169      * @return visitor pattern value
170      */
171     private Object accept(JexlNode node, Object data) {
172         if (node == cause) {
173             start = builder.length();
174         }
175         Object value = node.jjtAccept(this, data);
176         if (node == cause) {
177             end = builder.length();
178         }
179         return value;
180     }
181 
182     /**
183      * Adds a statement node to the rebuilt expression.
184      * @param child the child node
185      * @param data visitor pattern argument
186      * @return visitor pattern value
187      */
188     private Object acceptStatement(JexlNode child, Object data) {
189         Object value = accept(child, data);
190         // blocks, if, for & while dont need a ';' at end
191         if (child instanceof ASTBlock
192                 || child instanceof ASTIfStatement
193                 || child instanceof ASTForeachStatement
194                 || child instanceof ASTWhileStatement) {
195             return value;
196         }
197         builder.append(";");
198         return value;
199     }
200 
201     /**
202      * Checks if a terminal node is the the cause to debug &amp; adds its
203      * representation to the rebuilt expression.
204      * @param node the child node
205      * @param image the child node token image (may be null)
206      * @param data visitor pattern argument
207      * @return visitor pattern value
208      */
209     private Object check(JexlNode node, String image, Object data) {
210         if (node == cause) {
211             start = builder.length();
212         }
213         if (image != null) {
214             builder.append(image);
215         } else {
216             builder.append(node.toString());
217         }
218         if (node == cause) {
219             end = builder.length();
220         }
221         return data;
222     }
223 
224     /**
225      * Checks if the children of a node using infix notation is the cause to debug,
226      * adds their representation to the rebuilt expression.
227      * @param node the child node
228      * @param infix the child node token
229      * @param paren whether the child should be parenthesized
230      * @param data visitor pattern argument
231      * @return visitor pattern value
232      */
233     private Object infixChildren(JexlNode node, String infix, boolean paren, Object data) {
234         int num = node.jjtGetNumChildren(); //child.jjtGetNumChildren() > 1;
235         if (paren) {
236             builder.append("(");
237         }
238         for (int i = 0; i < num; ++i) {
239             if (i > 0) {
240                 builder.append(infix);
241             }
242             accept(node.jjtGetChild(i), data);
243         }
244         if (paren) {
245             builder.append(")");
246         }
247         return data;
248     }
249 
250     /**
251      * Checks if the child of a node using prefix notation is the cause to debug,
252      * adds their representation to the rebuilt expression.
253      * @param node the node
254      * @param prefix the node token
255      * @param data visitor pattern argument
256      * @return visitor pattern value
257      */
258     private Object prefixChild(JexlNode node, String prefix, Object data) {
259         boolean paren = node.jjtGetChild(0).jjtGetNumChildren() > 1;
260         builder.append(prefix);
261         if (paren) {
262             builder.append("(");
263         }
264         accept(node.jjtGetChild(0), data);
265         if (paren) {
266             builder.append(")");
267         }
268         return data;
269     }
270 
271     /** {@inheritDoc} */
272     public Object visit(ASTAdditiveNode node, Object data) {
273         // need parenthesis if not in operator precedence order
274         boolean paren = node.jjtGetParent() instanceof ASTMulNode
275                 || node.jjtGetParent() instanceof ASTDivNode
276                 || node.jjtGetParent() instanceof ASTModNode;
277         int num = node.jjtGetNumChildren(); //child.jjtGetNumChildren() > 1;
278         if (paren) {
279             builder.append("(");
280         }
281         accept(node.jjtGetChild(0), data);
282         for (int i = 1; i < num; ++i) {
283             accept(node.jjtGetChild(i), data);
284         }
285         if (paren) {
286             builder.append(")");
287         }
288         return data;
289     }
290 
291     /** {@inheritDoc} */
292     public Object visit(ASTAdditiveOperator node, Object data) {
293         builder.append(' ');
294         builder.append(node.image);
295         builder.append(' ');
296         return data;
297     }
298 
299     /** {@inheritDoc} */
300     public Object visit(ASTAndNode node, Object data) {
301         return infixChildren(node, " && ", false, data);
302     }
303 
304     /** {@inheritDoc} */
305     public Object visit(ASTArrayAccess node, Object data) {
306         accept(node.jjtGetChild(0), data);
307         int num = node.jjtGetNumChildren();
308         for (int i = 1; i < num; ++i) {
309             builder.append("[");
310             accept(node.jjtGetChild(i), data);
311             builder.append("]");
312         }
313         return data;
314     }
315 
316     /** {@inheritDoc} */
317     public Object visit(ASTArrayLiteral node, Object data) {
318         int num = node.jjtGetNumChildren();
319         builder.append("[ ");
320         if (num > 0) {
321             accept(node.jjtGetChild(0), data);
322             for (int i = 1; i < num; ++i) {
323                 builder.append(", ");
324                 accept(node.jjtGetChild(i), data);
325             }
326         }
327         builder.append(" ]");
328         return data;
329     }
330 
331     /** {@inheritDoc} */
332     public Object visit(ASTAssignment node, Object data) {
333         return infixChildren(node, " = ", false, data);
334     }
335 
336     /** {@inheritDoc} */
337     public Object visit(ASTBitwiseAndNode node, Object data) {
338         return infixChildren(node, " & ", false, data);
339     }
340 
341     /** {@inheritDoc} */
342     public Object visit(ASTBitwiseComplNode node, Object data) {
343         return prefixChild(node, "~", data);
344     }
345 
346     /** {@inheritDoc} */
347     public Object visit(ASTBitwiseOrNode node, Object data) {
348         boolean paren = node.jjtGetParent() instanceof ASTBitwiseAndNode;
349         return infixChildren(node, " | ", paren, data);
350     }
351 
352     /** {@inheritDoc} */
353     public Object visit(ASTBitwiseXorNode node, Object data) {
354         boolean paren = node.jjtGetParent() instanceof ASTBitwiseAndNode;
355         return infixChildren(node, " ^ ", paren, data);
356     }
357 
358     /** {@inheritDoc} */
359     public Object visit(ASTBlock node, Object data) {
360         builder.append("{ ");
361         int num = node.jjtGetNumChildren();
362         for (int i = 0; i < num; ++i) {
363             JexlNode child = node.jjtGetChild(i);
364             acceptStatement(child, data);
365         }
366         builder.append(" }");
367         return data;
368     }
369 
370     /** {@inheritDoc} */
371     public Object visit(ASTDivNode node, Object data) {
372         return infixChildren(node, " / ", false, data);
373     }
374 
375     /** {@inheritDoc} */
376     public Object visit(ASTEmptyFunction node, Object data) {
377         builder.append("empty(");
378         accept(node.jjtGetChild(0), data);
379         builder.append(")");
380         return data;
381     }
382 
383     /** {@inheritDoc} */
384     public Object visit(ASTEQNode node, Object data) {
385         return infixChildren(node, " == ", false, data);
386     }
387 
388     /** {@inheritDoc} */
389     public Object visit(ASTERNode node, Object data) {
390         return infixChildren(node, " =~ ", false, data);
391     }
392 
393     /** {@inheritDoc} */
394     public Object visit(ASTFalseNode node, Object data) {
395         return check(node, "false", data);
396     }
397 
398     /** {@inheritDoc} */
399     public Object visit(ASTForeachStatement node, Object data) {
400         builder.append("for(");
401         accept(node.jjtGetChild(0), data);
402         builder.append(" : ");
403         accept(node.jjtGetChild(1), data);
404         builder.append(") ");
405         if (node.jjtGetNumChildren() > 2) {
406             acceptStatement(node.jjtGetChild(2), data);
407         } else {
408             builder.append(';');
409         }
410         return data;
411     }
412 
413     /** {@inheritDoc} */
414     public Object visit(ASTGENode node, Object data) {
415         return infixChildren(node, " >= ", false, data);
416     }
417 
418     /** {@inheritDoc} */
419     public Object visit(ASTGTNode node, Object data) {
420         return infixChildren(node, " > ", false, data);
421     }
422 
423     /** Checks identifiers that contain space, quote, double-quotes or backspace. */
424     private static final Pattern QUOTED_IDENTIFIER = Pattern.compile("['\"\\s\\\\]");
425     
426     /** {@inheritDoc} */
427     public Object visit(ASTIdentifier node, Object data) {
428         String image = node.image;
429         if (QUOTED_IDENTIFIER.matcher(image).find()) {
430             // quote it
431             image = "'" + node.image.replace("'", "\\'") + "'";
432         }
433         return check(node, image, data);
434     }
435 
436     /** {@inheritDoc} */
437     public Object visit(ASTIfStatement node, Object data) {
438         builder.append("if (");
439         accept(node.jjtGetChild(0), data);
440         builder.append(") ");
441         if (node.jjtGetNumChildren() > 1) {
442             acceptStatement(node.jjtGetChild(1), data);
443             if (node.jjtGetNumChildren() > 2) {
444                 builder.append(" else ");
445                 acceptStatement(node.jjtGetChild(2), data);
446             } else {
447                 builder.append(';');
448             }
449         } else {
450             builder.append(';');
451         }
452         return data;
453     }
454 
455     /** {@inheritDoc} */
456     public Object visit(ASTNumberLiteral node, Object data) {
457         return check(node, node.image, data);
458     }
459 
460     /** {@inheritDoc} */
461     public Object visit(ASTJexlScript node, Object data) {
462         int num = node.jjtGetNumChildren();
463         for (int i = 0; i < num; ++i) {
464             JexlNode child = node.jjtGetChild(i);
465             acceptStatement(child, data);
466         }
467         return data;
468     }
469 
470     /** {@inheritDoc} */
471     public Object visit(ASTLENode node, Object data) {
472         return infixChildren(node, " <= ", false, data);
473     }
474 
475     /** {@inheritDoc} */
476     public Object visit(ASTLTNode node, Object data) {
477         return infixChildren(node, " < ", false, data);
478     }
479 
480     /** {@inheritDoc} */
481     public Object visit(ASTMapEntry node, Object data) {
482         accept(node.jjtGetChild(0), data);
483         builder.append(" : ");
484         accept(node.jjtGetChild(1), data);
485         return data;
486     }
487 
488     /** {@inheritDoc} */
489     public Object visit(ASTMapLiteral node, Object data) {
490         int num = node.jjtGetNumChildren();
491         builder.append("{ ");
492         if (num > 0) {
493             accept(node.jjtGetChild(0), data);
494             for (int i = 1; i < num; ++i) {
495                 builder.append(", ");
496                 accept(node.jjtGetChild(i), data);
497             }
498         } else {
499             builder.append(':');
500         }
501         builder.append(" }");
502         return data;
503     }
504 
505     /** {@inheritDoc} */
506     public Object visit(ASTConstructorNode node, Object data) {
507         int num = node.jjtGetNumChildren();
508         builder.append("new ");
509         builder.append("(");
510         accept(node.jjtGetChild(0), data);
511         for (int i = 1; i < num; ++i) {
512             builder.append(", ");
513             accept(node.jjtGetChild(i), data);
514         }
515         builder.append(")");
516         return data;
517     }
518 
519     /** {@inheritDoc} */
520     public Object visit(ASTFunctionNode node, Object data) {
521         int num = node.jjtGetNumChildren();
522         accept(node.jjtGetChild(0), data);
523         builder.append(":");
524         accept(node.jjtGetChild(1), data);
525         builder.append("(");
526         for (int i = 2; i < num; ++i) {
527             if (i > 2) {
528                 builder.append(", ");
529             }
530             accept(node.jjtGetChild(i), data);
531         }
532         builder.append(")");
533         return data;
534     }
535 
536     /** {@inheritDoc} */
537     public Object visit(ASTMethodNode node, Object data) {
538         int num = node.jjtGetNumChildren();
539         accept(node.jjtGetChild(0), data);
540         builder.append("(");
541         for (int i = 1; i < num; ++i) {
542             if (i > 1) {
543                 builder.append(", ");
544             }
545             accept(node.jjtGetChild(i), data);
546         }
547         builder.append(")");
548         return data;
549     }
550 
551     /** {@inheritDoc} */
552     public Object visit(ASTModNode node, Object data) {
553         return infixChildren(node, " % ", false, data);
554     }
555 
556     /** {@inheritDoc} */
557     public Object visit(ASTMulNode node, Object data) {
558         return infixChildren(node, " * ", false, data);
559     }
560 
561     /** {@inheritDoc} */
562     public Object visit(ASTNENode node, Object data) {
563         return infixChildren(node, " != ", false, data);
564     }
565 
566     /** {@inheritDoc} */
567     public Object visit(ASTNRNode node, Object data) {
568         return infixChildren(node, " !~ ", false, data);
569     }
570 
571     /** {@inheritDoc} */
572     public Object visit(ASTNotNode node, Object data) {
573         builder.append("!");
574         accept(node.jjtGetChild(0), data);
575         return data;
576     }
577 
578     /** {@inheritDoc} */
579     public Object visit(ASTNullLiteral node, Object data) {
580         check(node, "null", data);
581         return data;
582     }
583 
584     /** {@inheritDoc} */
585     public Object visit(ASTOrNode node, Object data) {
586         // need parenthesis if not in operator precedence order
587         boolean paren = node.jjtGetParent() instanceof ASTAndNode;
588         return infixChildren(node, " || ", paren, data);
589     }
590 
591     /** {@inheritDoc} */
592     public Object visit(ASTReference node, Object data) {
593         int num = node.jjtGetNumChildren();
594         accept(node.jjtGetChild(0), data);
595         for (int i = 1; i < num; ++i) {
596             builder.append(".");
597             accept(node.jjtGetChild(i), data);
598         }
599         return data;
600     }
601 
602     /** {@inheritDoc} */
603     public Object visit(ASTReferenceExpression node, Object data) {
604         JexlNode first = node.jjtGetChild(0);
605         builder.append('(');
606         accept(first, data);
607         builder.append(')');
608         int num = node.jjtGetNumChildren();
609         for (int i = 1; i < num; ++i) {
610             builder.append("[");
611             accept(node.jjtGetChild(i), data);
612             builder.append("]");
613         }
614         return data;
615     }
616 
617     /** {@inheritDoc} */
618     public Object visit(ASTReturnStatement node, Object data) {
619         builder.append("return ");
620         accept(node.jjtGetChild(0), data);
621         return data;
622     }
623 
624     /** {@inheritDoc} */
625     public Object visit(ASTSizeFunction node, Object data) {
626         builder.append("size(");
627         accept(node.jjtGetChild(0), data);
628         builder.append(")");
629         return data;
630     }
631 
632     /** {@inheritDoc} */
633     public Object visit(ASTSizeMethod node, Object data) {
634         check(node, "size()", data);
635         return data;
636     }
637 
638     /** {@inheritDoc} */
639     public Object visit(ASTStringLiteral node, Object data) {
640         String img = node.image.replace("'", "\\'");
641         return check(node, "'" + img + "'", data);
642     }
643 
644     /** {@inheritDoc} */
645     public Object visit(ASTTernaryNode node, Object data) {
646         accept(node.jjtGetChild(0), data);
647         if (node.jjtGetNumChildren() > 2) {
648             builder.append("? ");
649             accept(node.jjtGetChild(1), data);
650             builder.append(" : ");
651             accept(node.jjtGetChild(2), data);
652         } else {
653             builder.append("?:");
654             accept(node.jjtGetChild(1), data);
655 
656         }
657         return data;
658     }
659 
660     /** {@inheritDoc} */
661     public Object visit(ASTTrueNode node, Object data) {
662         check(node, "true", data);
663         return data;
664     }
665 
666     /** {@inheritDoc} */
667     public Object visit(ASTUnaryMinusNode node, Object data) {
668         return prefixChild(node, "-", data);
669     }
670 
671     /** {@inheritDoc} */
672     public Object visit(ASTVar node, Object data) {
673         builder.append("var ");
674         check(node, node.image, data);
675         return data;
676     }
677 
678     /** {@inheritDoc} */
679     public Object visit(ASTWhileStatement node, Object data) {
680         builder.append("while (");
681         accept(node.jjtGetChild(0), data);
682         builder.append(") ");
683         if (node.jjtGetNumChildren() > 1) {
684             acceptStatement(node.jjtGetChild(1), data);
685         } else {
686             builder.append(';');
687         }
688         return data;
689     }
690 
691     /** {@inheritDoc} */
692     public Object visit(SimpleNode node, Object data) {
693         throw new UnsupportedOperationException("unexpected type of node");
694     }
695 
696     /** {@inheritDoc} */
697     public Object visit(ASTAmbiguous node, Object data) {
698         throw new UnsupportedOperationException("unexpected type of node");
699     }
700 }