1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.jexl3;
19
20 import java.io.BufferedReader;
21 import java.io.IOException;
22 import java.io.StringReader;
23 import java.lang.reflect.InvocationTargetException;
24 import java.lang.reflect.UndeclaredThrowableException;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.Objects;
28
29 import org.apache.commons.jexl3.internal.Debugger;
30 import org.apache.commons.jexl3.parser.JavaccError;
31 import org.apache.commons.jexl3.parser.JexlNode;
32 import org.apache.commons.jexl3.parser.ParseException;
33 import org.apache.commons.jexl3.parser.TokenMgrException;
34
35 /**
36 * Wraps any error that might occur during interpretation of a script or expression.
37 *
38 * @since 2.0
39 */
40 public class JexlException extends RuntimeException {
41
42 private static final StackTraceElement[] EMPTY_STACK_TRACE_ELEMENT_ARRAY = {};
43
44 /**
45 * Thrown when parsing fails due to an ambiguous statement.
46 *
47 * @since 3.0
48 */
49 public static class Ambiguous extends Parsing {
50 private static final long serialVersionUID = 20210606123903L;
51
52 /** The mark at which ambiguity might stop and recover. */
53 private final transient JexlInfo recover;
54
55 /**
56 * Creates a new Ambiguous statement exception instance.
57 *
58 * @param begin the start location information
59 * @param end the end location information
60 * @param expr the source expression line
61 */
62 public Ambiguous(final JexlInfo begin, final JexlInfo end, final String expr) {
63 super(begin, expr);
64 recover = end;
65 }
66
67 /**
68 * Creates a new Ambiguous statement exception instance.
69 *
70 * @param info the location information
71 * @param expr the source expression line
72 */
73 public Ambiguous(final JexlInfo info, final String expr) {
74 this(info, null, expr);
75 }
76
77 @Override
78 protected String detailedMessage() {
79 return parserError("ambiguous statement", getDetail());
80 }
81
82 /**
83 * Tries to remove this ambiguity in the source.
84 *
85 * @param src the source that triggered this exception
86 * @return the source with the ambiguous statement removed
87 * or null if no recovery was possible
88 */
89 public String tryCleanSource(final String src) {
90 final JexlInfo ji = info();
91 return ji == null || recover == null
92 ? src
93 : sliceSource(src, ji.getLine(), ji.getColumn(), recover.getLine(), recover.getColumn());
94 }
95 }
96
97 /**
98 * Thrown when an annotation handler throws an exception.
99 *
100 * @since 3.1
101 */
102 public static class Annotation extends JexlException {
103 private static final long serialVersionUID = 20210606124101L;
104
105 /**
106 * Creates a new Annotation exception instance.
107 *
108 * @param node the annotated statement node
109 * @param name the annotation name
110 * @param cause the exception causing the error
111 */
112 public Annotation(final JexlNode node, final String name, final Throwable cause) {
113 super(node, name, cause);
114 }
115
116 @Override
117 protected String detailedMessage() {
118 return "error processing annotation '" + getAnnotation() + "'";
119 }
120
121 /**
122 * Gets the annotation name
123 *
124 * @return the annotation name
125 */
126 public String getAnnotation() {
127 return getDetail();
128 }
129 }
130
131 /**
132 * Thrown when parsing fails due to an invalid assignment.
133 *
134 * @since 3.0
135 */
136 public static class Assignment extends Parsing {
137 private static final long serialVersionUID = 20210606123905L;
138
139 /**
140 * Creates a new Assignment statement exception instance.
141 *
142 * @param info the location information
143 * @param expr the source expression line
144 */
145 public Assignment(final JexlInfo info, final String expr) {
146 super(info, expr);
147 }
148
149 @Override
150 protected String detailedMessage() {
151 return parserError("assignment", getDetail());
152 }
153 }
154
155 /**
156 * Thrown to break a loop.
157 *
158 * @since 3.0
159 */
160 public static class Break extends JexlException {
161 private static final long serialVersionUID = 20210606124103L;
162
163 /**
164 * Creates a new instance of Break.
165 *
166 * @param node the break
167 */
168 public Break(final JexlNode node) {
169 super(node, "break loop", null, false);
170 }
171 }
172
173 /**
174 * Thrown to cancel a script execution.
175 *
176 * @since 3.0
177 */
178 public static class Cancel extends JexlException {
179 private static final long serialVersionUID = 7735706658499597964L;
180
181 /**
182 * Creates a new instance of Cancel.
183 *
184 * @param node the node where the interruption was detected
185 */
186 public Cancel(final JexlNode node) {
187 super(node, "execution cancelled", null);
188 }
189 }
190
191 /**
192 * Thrown to continue a loop.
193 *
194 * @since 3.0
195 */
196 public static class Continue extends JexlException {
197 private static final long serialVersionUID = 20210606124104L;
198
199 /**
200 * Creates a new instance of Continue.
201 *
202 * @param node the continue-node
203 */
204 public Continue(final JexlNode node) {
205 super(node, "continue loop", null, false);
206 }
207 }
208
209 /**
210 * Thrown when parsing fails due to a disallowed feature.
211 *
212 * @since 3.2
213 */
214 public static class Feature extends Parsing {
215 private static final long serialVersionUID = 20210606123906L;
216
217 /** The feature code. */
218 private final int code;
219
220 /**
221 * Creates a new Ambiguous statement exception instance.
222 *
223 * @param info the location information
224 * @param feature the feature code
225 * @param expr the source expression line
226 */
227 public Feature(final JexlInfo info, final int feature, final String expr) {
228 super(info, expr);
229 this.code = feature;
230 }
231
232 @Override
233 protected String detailedMessage() {
234 return parserError(JexlFeatures.stringify(code), getDetail());
235 }
236 }
237
238 /**
239 * Thrown when a method or ctor is unknown, ambiguous or inaccessible.
240 *
241 * @since 3.0
242 */
243 public static class Method extends JexlException {
244 private static final long serialVersionUID = 20210606123909L;
245
246 /**
247 * Creates a new Method exception instance.
248 *
249 * @param info the location information
250 * @param name the method name
251 * @param args the method arguments
252 * @since 3.2
253 */
254 public Method(final JexlInfo info, final String name, final Object[] args) {
255 this(info, name, args, null);
256 }
257
258 /**
259 * Creates a new Method exception instance.
260 *
261 * @param info the location information
262 * @param name the method name
263 * @param cause the exception causing the error
264 * @param args the method arguments
265 * @since 3.2
266 */
267 public Method(final JexlInfo info, final String name, final Object[] args, final Throwable cause) {
268 super(info, methodSignature(name, args), cause);
269 }
270
271 /**
272 * Creates a new Method exception instance.
273 *
274 * @param info the location information
275 * @param name the unknown method
276 * @param cause the exception causing the error
277 * @deprecated as of 3.2, use call with method arguments
278 */
279 @Deprecated
280 public Method(final JexlInfo info, final String name, final Throwable cause) {
281 this(info, name, null, cause);
282 }
283
284 /**
285 * Creates a new Method exception instance.
286 *
287 * @param node the offending ASTnode
288 * @param name the method name
289 * @deprecated as of 3.2, use call with method arguments
290 */
291 @Deprecated
292 public Method(final JexlNode node, final String name) {
293 this(node, name, null);
294 }
295
296 /**
297 * Creates a new Method exception instance.
298 *
299 * @param node the offending ASTnode
300 * @param name the method name
301 * @param args the method arguments
302 * @since 3.2
303 */
304 public Method(final JexlNode node, final String name, final Object[] args) {
305 super(node, methodSignature(name, args));
306 }
307
308 @Override
309 protected String detailedMessage() {
310 return "unsolvable function/method '" + getMethodSignature() + "'";
311 }
312
313 /**
314 * Gets the method name
315 *
316 * @return the method name
317 */
318 public String getMethod() {
319 final String signature = getMethodSignature();
320 final int lparen = signature.indexOf('(');
321 return lparen > 0? signature.substring(0, lparen) : signature;
322 }
323
324 /**
325 * Gets the method signature
326 *
327 * @return the method signature
328 * @since 3.2
329 */
330 public String getMethodSignature() {
331 return getDetail();
332 }
333 }
334
335 /**
336 * Thrown when an operator fails.
337 *
338 * @since 3.0
339 */
340 public static class Operator extends JexlException {
341 private static final long serialVersionUID = 20210606124100L;
342
343 /**
344 * Creates a new Operator exception instance.
345 *
346 * @param node the location information
347 * @param symbol the operator name
348 * @param cause the exception causing the error
349 */
350 public Operator(final JexlNode node, final String symbol, final Throwable cause) {
351 super(node, symbol, cause);
352 }
353
354 @Override
355 protected String detailedMessage() {
356 return "error calling operator '" + getSymbol() + "'";
357 }
358
359 /**
360 * Gets the method name
361 *
362 * @return the method name
363 */
364 public String getSymbol() {
365 return getDetail();
366 }
367 }
368
369 /**
370 * Thrown when parsing fails.
371 *
372 * @since 3.0
373 */
374 public static class Parsing extends JexlException {
375 private static final long serialVersionUID = 20210606123902L;
376
377 /**
378 * Creates a new Parsing exception instance.
379 *
380 * @param info the location information
381 * @param cause the javacc cause
382 */
383 public Parsing(final JexlInfo info, final ParseException cause) {
384 super(merge(info, cause), Objects.requireNonNull(cause).getAfter(), null);
385 }
386
387 /**
388 * Creates a new Parsing exception instance.
389 *
390 * @param info the location information
391 * @param msg the message
392 */
393 public Parsing(final JexlInfo info, final String msg) {
394 super(info, msg, null);
395 }
396
397 @Override
398 protected String detailedMessage() {
399 return parserError("parsing", getDetail());
400 }
401 }
402
403 /**
404 * Thrown when a property is unknown.
405 *
406 * @since 3.0
407 */
408 public static class Property extends JexlException {
409 private static final long serialVersionUID = 20210606123908L;
410
411 /**
412 * Undefined variable flag.
413 */
414 private final boolean undefined;
415
416 /**
417 * Creates a new Property exception instance.
418 *
419 * @param node the offending ASTnode
420 * @param pty the unknown property
421 * @deprecated 3.2
422 */
423 @Deprecated
424 public Property(final JexlNode node, final String pty) {
425 this(node, pty, true, null);
426 }
427
428 /**
429 * Creates a new Property exception instance.
430 *
431 * @param node the offending ASTnode
432 * @param pty the unknown property
433 * @param undef whether the variable is null or undefined
434 * @param cause the exception causing the error
435 */
436 public Property(final JexlNode node, final String pty, final boolean undef, final Throwable cause) {
437 super(node, pty, cause);
438 undefined = undef;
439 }
440
441 /**
442 * Creates a new Property exception instance.
443 *
444 * @param node the offending ASTnode
445 * @param pty the unknown property
446 * @param cause the exception causing the error
447 * @deprecated 3.2
448 */
449 @Deprecated
450 public Property(final JexlNode node, final String pty, final Throwable cause) {
451 this(node, pty, true, cause);
452 }
453
454 @Override
455 protected String detailedMessage() {
456 return (undefined? "undefined" : "null value") + " property '" + getProperty() + "'";
457 }
458
459 /**
460 * Gets the property name
461 *
462 * @return the property name
463 */
464 public String getProperty() {
465 return getDetail();
466 }
467
468 /**
469 * Tests whether the variable causing an error is undefined or evaluated as null.
470 *
471 * @return true if undefined, false otherwise
472 */
473 public boolean isUndefined() {
474 return undefined;
475 }
476 }
477
478 /**
479 * Thrown to return a value.
480 *
481 * @since 3.0
482 */
483 public static class Return extends JexlException {
484 private static final long serialVersionUID = 20210606124102L;
485
486 /** The returned value. */
487 private final transient Object result;
488
489 /**
490 * Creates a new instance of Return.
491 *
492 * @param node the return node
493 * @param msg the message
494 * @param value the returned value
495 */
496 public Return(final JexlNode node, final String msg, final Object value) {
497 super(node, msg, null, false);
498 this.result = value;
499 }
500
501 /**
502 * Gets the returned value
503 *
504 * @return the returned value
505 */
506 public Object getValue() {
507 return result;
508 }
509 }
510
511 /**
512 * Thrown when reaching stack-overflow.
513 *
514 * @since 3.2
515 */
516 public static class StackOverflow extends JexlException {
517 private static final long serialVersionUID = 20210606123904L;
518
519 /**
520 * Creates a new stack overflow exception instance.
521 *
522 * @param info the location information
523 * @param name the unknown method
524 * @param cause the exception causing the error
525 */
526 public StackOverflow(final JexlInfo info, final String name, final Throwable cause) {
527 super(info, name, cause);
528 }
529
530 @Override
531 protected String detailedMessage() {
532 return "stack overflow " + getDetail();
533 }
534 }
535
536 /**
537 * Thrown to throw a value.
538 *
539 * @since 3.3.1
540 */
541 public static class Throw extends JexlException {
542 private static final long serialVersionUID = 20210606124102L;
543
544 /** The thrown value. */
545 private final transient Object result;
546
547 /**
548 * Creates a new instance of Throw.
549 *
550 * @param node the throw node
551 * @param value the thrown value
552 */
553 public Throw(final JexlNode node, final Object value) {
554 super(node, null, null, false);
555 this.result = value;
556 }
557
558 /**
559 * Gets the thrown value
560 *
561 * @return the thrown value
562 */
563 public Object getValue() {
564 return result;
565 }
566 }
567
568 /**
569 * Thrown when tokenization fails.
570 *
571 * @since 3.0
572 */
573 public static class Tokenization extends JexlException {
574 private static final long serialVersionUID = 20210606123901L;
575
576 /**
577 * Creates a new Tokenization exception instance.
578 *
579 * @param info the location info
580 * @param cause the javacc cause
581 */
582 public Tokenization(final JexlInfo info, final TokenMgrException cause) {
583 super(merge(info, cause), Objects.requireNonNull(cause).getAfter(), null);
584 }
585
586 @Override
587 protected String detailedMessage() {
588 return parserError("tokenization", getDetail());
589 }
590 }
591
592 /**
593 * Thrown when method/ctor invocation fails.
594 * <p>These wrap InvocationTargetException as runtime exception
595 * allowing to go through without signature modifications.
596 *
597 * @since 3.2
598 */
599 public static class TryFailed extends JexlException {
600 private static final long serialVersionUID = 20210606124105L;
601
602 /**
603 * Creates a new instance.
604 *
605 * @param xany the original invocation target exception
606 */
607 TryFailed(final InvocationTargetException xany) {
608 super((JexlInfo) null, "tryFailed", xany.getCause());
609 }
610 }
611
612 /**
613 * Thrown when a variable is unknown.
614 *
615 * @since 3.0
616 */
617 public static class Variable extends JexlException {
618 private static final long serialVersionUID = 20210606123907L;
619
620 /**
621 * Undefined variable flag.
622 */
623 private final VariableIssue issue;
624
625 /**
626 * Creates a new Variable exception instance.
627 *
628 * @param node the offending ASTnode
629 * @param var the unknown variable
630 * @param undef whether the variable is undefined or evaluated as null
631 */
632 public Variable(final JexlNode node, final String var, final boolean undef) {
633 this(node, var, undef ? VariableIssue.UNDEFINED : VariableIssue.NULLVALUE);
634 }
635
636 /**
637 * Creates a new Variable exception instance.
638 *
639 * @param node the offending ASTnode
640 * @param var the unknown variable
641 * @param vi the variable issue
642 */
643 public Variable(final JexlNode node, final String var, final VariableIssue vi) {
644 super(node, var, null);
645 issue = vi;
646 }
647
648 @Override
649 protected String detailedMessage() {
650 return issue.message(getVariable());
651 }
652
653 /**
654 * Gets the variable name
655 *
656 * @return the variable name
657 */
658 public String getVariable() {
659 return getDetail();
660 }
661
662 /**
663 * Tests whether the variable causing an error is undefined or evaluated as null.
664 *
665 * @return true if undefined, false otherwise
666 */
667 public boolean isUndefined() {
668 return issue == VariableIssue.UNDEFINED;
669 }
670 }
671
672 /**
673 * The various type of variable issues.
674 */
675 public enum VariableIssue {
676
677 /** The variable is undefined. */
678 UNDEFINED,
679
680 /** The variable is already declared. */
681 REDEFINED,
682
683 /** The variable has a null value. */
684 NULLVALUE,
685
686 /** THe variable is const and an attempt is made to assign it*/
687 CONST;
688
689 /**
690 * Stringifies the variable issue.
691 *
692 * @param var the variable name
693 * @return the issue message
694 */
695 public String message(final String var) {
696 switch(this) {
697 case NULLVALUE : return VARQUOTE + var + "' is null";
698 case REDEFINED : return VARQUOTE + var + "' is already defined";
699 case CONST : return VARQUOTE + var + "' is const";
700 case UNDEFINED :
701 default: return VARQUOTE + var + "' is undefined";
702 }
703 }
704 }
705
706 private static final long serialVersionUID = 20210606123900L;
707
708 /** Maximum number of characters around exception location. */
709 private static final int MAX_EXCHARLOC = 128;
710
711 /** Used 3 times. */
712 private static final String VARQUOTE = "variable '";
713
714 /**
715 * Generates a message for an annotation error.
716 *
717 * @param node the node where the error occurred
718 * @param annotation the annotation name
719 * @return the error message
720 * @since 3.1
721 */
722 public static String annotationError(final JexlNode node, final String annotation) {
723 final StringBuilder msg = errorAt(node);
724 msg.append("error processing annotation '");
725 msg.append(annotation);
726 msg.append('\'');
727 return msg.toString();
728 }
729
730 /**
731 * Cleans a Throwable from any org.apache.commons.jexl3.internal stack trace element.
732 *
733 * @param <X> the throwable type
734 * @param xthrow the thowable
735 * @return the throwable
736 */
737 static <X extends Throwable> X clean(final X xthrow) {
738 if (xthrow != null) {
739 final List<StackTraceElement> stackJexl = new ArrayList<>();
740 for (final StackTraceElement se : xthrow.getStackTrace()) {
741 final String className = se.getClassName();
742 if (!className.startsWith("org.apache.commons.jexl3.internal")
743 && !className.startsWith("org.apache.commons.jexl3.parser")) {
744 stackJexl.add(se);
745 }
746 }
747 xthrow.setStackTrace(stackJexl.toArray(EMPTY_STACK_TRACE_ELEMENT_ARRAY));
748 }
749 return xthrow;
750 }
751
752 /**
753 * Gets the most specific information attached to a node.
754 *
755 * @param node the node
756 * @param info the information
757 * @return the information or null
758 */
759 static JexlInfo detailedInfo(final JexlNode node, final JexlInfo info) {
760 if (info != null && node != null) {
761 final Debugger dbg = new Debugger();
762 if (dbg.debug(node)) {
763 return new JexlInfo(info) {
764 @Override
765 public JexlInfo.Detail getDetail() {
766 return dbg;
767 }
768 };
769 }
770 }
771 return info;
772 }
773
774 /**
775 * Creates a string builder pre-filled with common error information (if possible).
776 *
777 * @param node the node
778 * @return a string builder
779 */
780 static StringBuilder errorAt(final JexlNode node) {
781 final JexlInfo info = node != null ? detailedInfo(node, node.jexlInfo()) : null;
782 final StringBuilder msg = new StringBuilder();
783 if (info != null) {
784 msg.append(info.toString());
785 } else {
786 msg.append("?:");
787 }
788 msg.append(' ');
789 return msg;
790 }
791
792 /**
793 * Gets the most specific information attached to a node.
794 *
795 * @param node the node
796 * @param info the information
797 * @return the information or null
798 * @deprecated 3.2
799 */
800 @Deprecated
801 public static JexlInfo getInfo(final JexlNode node, final JexlInfo info) {
802 return detailedInfo(node, info);
803 }
804
805 /**
806 * Merge the node info and the cause info to obtain the best possible location.
807 *
808 * @param info the node
809 * @param cause the cause
810 * @return the info to use
811 */
812 static JexlInfo merge(final JexlInfo info, final JavaccError cause) {
813 if (cause == null || cause.getLine() < 0) {
814 return info;
815 }
816 if (info == null) {
817 return new JexlInfo("", cause.getLine(), cause.getColumn());
818 }
819 return new JexlInfo(info.getName(), cause.getLine(), cause.getColumn());
820 }
821
822 /**
823 * Generates a message for a unsolvable method error.
824 *
825 * @param node the node where the error occurred
826 * @param method the method name
827 * @return the error message
828 * @deprecated 3.2
829 */
830 @Deprecated
831 public static String methodError(final JexlNode node, final String method) {
832 return methodError(node, method, null);
833 }
834
835 /**
836 * Generates a message for a unsolvable method error.
837 *
838 * @param node the node where the error occurred
839 * @param method the method name
840 * @param args the method arguments
841 * @return the error message
842 */
843 public static String methodError(final JexlNode node, final String method, final Object[] args) {
844 final StringBuilder msg = errorAt(node);
845 msg.append("unsolvable function/method '");
846 msg.append(methodSignature(method, args));
847 msg.append('\'');
848 return msg.toString();
849 }
850
851 /**
852 * Creates a signed-name for a given method name and arguments.
853 *
854 * @param name the method name
855 * @param args the method arguments
856 * @return a suitable signed name
857 */
858 static String methodSignature(final String name, final Object[] args) {
859 if (args != null && args.length > 0) {
860 final StringBuilder strb = new StringBuilder(name);
861 strb.append('(');
862 for (int a = 0; a < args.length; ++a) {
863 if (a > 0) {
864 strb.append(", ");
865 }
866 final Class<?> clazz = args[a] == null ? Object.class : args[a].getClass();
867 strb.append(clazz.getSimpleName());
868 }
869 strb.append(')');
870 return strb.toString();
871 }
872 return name;
873 }
874
875 /**
876 * Generates a message for an operator error.
877 *
878 * @param node the node where the error occurred
879 * @param symbol the operator name
880 * @return the error message
881 */
882 public static String operatorError(final JexlNode node, final String symbol) {
883 final StringBuilder msg = errorAt(node);
884 msg.append("error calling operator '");
885 msg.append(symbol);
886 msg.append('\'');
887 return msg.toString();
888 }
889
890 /**
891 * Generates a message for an unsolvable property error.
892 *
893 * @param node the node where the error occurred
894 * @param var the variable
895 * @return the error message
896 * @deprecated 3.2
897 */
898 @Deprecated
899 public static String propertyError(final JexlNode node, final String var) {
900 return propertyError(node, var, true);
901 }
902
903 /**
904 * Generates a message for an unsolvable property error.
905 *
906 * @param node the node where the error occurred
907 * @param pty the property
908 * @param undef whether the property is null or undefined
909 * @return the error message
910 */
911 public static String propertyError(final JexlNode node, final String pty, final boolean undef) {
912 final StringBuilder msg = errorAt(node);
913 if (undef) {
914 msg.append("unsolvable");
915 } else {
916 msg.append("null value");
917 }
918 msg.append(" property '");
919 msg.append(pty);
920 msg.append('\'');
921 return msg.toString();
922 }
923
924 /**
925 * Removes a slice from a source.
926 *
927 * @param src the source
928 * @param froml the beginning line
929 * @param fromc the beginning column
930 * @param tol the ending line
931 * @param toc the ending column
932 * @return the source with the (begin) to (to) zone removed
933 */
934 public static String sliceSource(final String src, final int froml, final int fromc, final int tol, final int toc) {
935 final BufferedReader reader = new BufferedReader(new StringReader(src));
936 final StringBuilder buffer = new StringBuilder();
937 String line;
938 int cl = 1;
939 try {
940 while ((line = reader.readLine()) != null) {
941 if (cl < froml || cl > tol) {
942 buffer.append(line).append('\n');
943 } else {
944 if (cl == froml) {
945 buffer.append(line, 0, fromc - 1);
946 }
947 if (cl == tol) {
948 buffer.append(line.substring(toc + 1));
949 }
950 } // else ignore line
951 cl += 1;
952 }
953 } catch (final IOException xignore) {
954 //damn the checked exceptions :-)
955 }
956 return buffer.toString();
957 }
958
959 /**
960 * Wrap an invocation exception.
961 * <p>Return the cause if it is already a JexlException.
962 *
963 * @param xinvoke the invocation exception
964 * @return a JexlException
965 */
966 public static JexlException tryFailed(final InvocationTargetException xinvoke) {
967 final Throwable cause = xinvoke.getCause();
968 return cause instanceof JexlException
969 ? (JexlException) cause
970 : new JexlException.TryFailed(xinvoke); // fail
971 }
972
973 /**
974 * Unwraps the cause of a throwable due to reflection.
975 *
976 * @param xthrow the throwable
977 * @return the cause
978 */
979 static Throwable unwrap(final Throwable xthrow) {
980 if (xthrow instanceof TryFailed
981 || xthrow instanceof InvocationTargetException
982 || xthrow instanceof UndeclaredThrowableException) {
983 return xthrow.getCause();
984 }
985 return xthrow;
986 }
987
988 /**
989 * Generates a message for a variable error.
990 *
991 * @param node the node where the error occurred
992 * @param variable the variable
993 * @param undef whether the variable is null or undefined
994 * @return the error message
995 * @deprecated 3.2
996 */
997 @Deprecated
998 public static String variableError(final JexlNode node, final String variable, final boolean undef) {
999 return variableError(node, variable, undef? VariableIssue.UNDEFINED : VariableIssue.NULLVALUE);
1000 }
1001
1002 /**
1003 * Generates a message for a variable error.
1004 *
1005 * @param node the node where the error occurred
1006 * @param variable the variable
1007 * @param issue the variable kind of issue
1008 * @return the error message
1009 */
1010 public static String variableError(final JexlNode node, final String variable, final VariableIssue issue) {
1011 final StringBuilder msg = errorAt(node);
1012 msg.append(issue.message(variable));
1013 return msg.toString();
1014 }
1015
1016 /** The point of origin for this exception. */
1017 private final transient JexlNode mark;
1018
1019 /** The debug info. */
1020 private final transient JexlInfo info;
1021
1022 /**
1023 * Creates a new JexlException.
1024 *
1025 * @param jinfo the debugging information associated
1026 * @param msg the error message
1027 * @param cause the exception causing the error
1028 */
1029 public JexlException(final JexlInfo jinfo, final String msg, final Throwable cause) {
1030 super(msg != null ? msg : "", unwrap(cause));
1031 mark = null;
1032 info = jinfo;
1033 }
1034
1035 /**
1036 * Creates a new JexlException.
1037 *
1038 * @param node the node causing the error
1039 * @param msg the error message
1040 */
1041 public JexlException(final JexlNode node, final String msg) {
1042 this(node, msg, null);
1043 }
1044
1045 /**
1046 * Creates a new JexlException.
1047 *
1048 * @param node the node causing the error
1049 * @param msg the error message
1050 * @param cause the exception causing the error
1051 */
1052 public JexlException(final JexlNode node, final String msg, final Throwable cause) {
1053 this(node, msg != null ? msg : "", unwrap(cause), true);
1054 }
1055
1056 /**
1057 * Creates a new JexlException.
1058 *
1059 * @param node the node causing the error
1060 * @param msg the error message
1061 * @param cause the exception causing the error
1062 * @param trace whether this exception has a stack trace and can <em>not</em> be suppressed
1063 */
1064 protected JexlException(final JexlNode node, final String msg, final Throwable cause, final boolean trace) {
1065 super(msg != null ? msg : "", unwrap(cause), !trace, trace);
1066 if (node != null) {
1067 mark = node;
1068 info = node.jexlInfo();
1069 } else {
1070 mark = null;
1071 info = null;
1072 }
1073 }
1074
1075 /**
1076 * Cleans a JexlException from any org.apache.commons.jexl3.internal stack trace element.
1077 *
1078 * @return this exception
1079 */
1080 public JexlException clean() {
1081 return clean(this);
1082 }
1083
1084 /**
1085 * Accesses detailed message.
1086 *
1087 * @return the message
1088 */
1089 protected String detailedMessage() {
1090 final Class<? extends JexlException> clazz = getClass();
1091 final String name = clazz == JexlException.class? "JEXL" : clazz.getSimpleName().toLowerCase();
1092 return name + " error : " + getDetail();
1093 }
1094
1095 /**
1096 * Gets the exception specific detail
1097 *
1098 * @return this exception specific detail
1099 * @since 3.2
1100 */
1101 public final String getDetail() {
1102 return super.getMessage();
1103 }
1104
1105 /**
1106 * Gets the specific information for this exception.
1107 *
1108 * @return the information
1109 */
1110 public JexlInfo getInfo() {
1111 return detailedInfo(mark, info);
1112 }
1113
1114 /**
1115 * Detailed info message about this error.
1116 * <p>
1117 * Format is "debug![begin,end]: string \n msg" where:
1118 * </p>
1119 * <ul>
1120 * <li>debug is the debugging information if it exists {@link JexlBuilder#debug(boolean)}</li>
1121 * <li>begin, end are character offsets in the string for the precise location of the error</li>
1122 * <li>string is the string representation of the offending expression</li>
1123 * <li>msg is the actual explanation message for this error</li>
1124 * </ul>
1125 *
1126 * @see JexlEngine#isDebug()
1127 * @return this error as a string
1128 */
1129 @Override
1130 public String getMessage() {
1131 final StringBuilder msg = new StringBuilder();
1132 if (info != null) {
1133 msg.append(info.toString());
1134 } else {
1135 msg.append("?:");
1136 }
1137 msg.append(' ');
1138 msg.append(detailedMessage());
1139 final Throwable cause = getCause();
1140 if (cause instanceof JexlArithmetic.NullOperand) {
1141 msg.append(" caused by null operand");
1142 }
1143 return msg.toString();
1144 }
1145
1146 /**
1147 * Pleasing checkstyle.
1148 *
1149 * @return the info
1150 */
1151 protected JexlInfo info() {
1152 return info;
1153 }
1154
1155 /**
1156 * Formats an error message from the parser.
1157 *
1158 * @param prefix the prefix to the message
1159 * @param expr the expression in error
1160 * @return the formatted message
1161 */
1162 protected String parserError(final String prefix, final String expr) {
1163 final int length = expr.length();
1164 if (length < MAX_EXCHARLOC) {
1165 return prefix + " error in '" + expr + "'";
1166 }
1167 final int me = MAX_EXCHARLOC / 2;
1168 int begin = info.getColumn() - me;
1169 if (begin < 0 || length < me) {
1170 begin = 0;
1171 } else if (begin > length) {
1172 begin = me;
1173 }
1174 int end = begin + MAX_EXCHARLOC;
1175 if (end > length) {
1176 end = length;
1177 }
1178 return prefix + " error near '... "
1179 + expr.substring(begin, end) + " ...'";
1180 }
1181 }