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