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