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 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
37
38
39
40 public class JexlException extends RuntimeException {
41
42
43
44
45
46 public static class Ambiguous extends Parsing {
47 private static final long serialVersionUID = 20210606123903L;
48
49 private final transient JexlInfo recover;
50
51
52
53
54
55
56 public Ambiguous(final JexlInfo begin, final JexlInfo end, final String expr) {
57 super(begin, expr);
58 recover = end;
59 }
60
61
62
63
64
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
77
78
79
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
91
92
93
94 public static class Annotation extends JexlException {
95 private static final long serialVersionUID = 20210606124101L;
96
97
98
99
100
101
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
114
115
116 public String getAnnotation() {
117 return getDetail();
118 }
119 }
120
121
122
123
124
125
126 public static class Assignment extends Parsing {
127 private static final long serialVersionUID = 20210606123905L;
128
129
130
131
132
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
146
147
148
149 public static class Break extends JexlException {
150 private static final long serialVersionUID = 20210606124103L;
151
152
153
154
155
156 public Break(final JexlNode node) {
157 super(node, "break loop", null, false);
158 }
159 }
160
161
162
163
164
165
166 public static class Cancel extends JexlException {
167 private static final long serialVersionUID = 7735706658499597964L;
168
169
170
171
172
173 public Cancel(final JexlNode node) {
174 super(node, "execution cancelled", null);
175 }
176 }
177
178
179
180
181
182
183 public static class Continue extends JexlException {
184 private static final long serialVersionUID = 20210606124104L;
185
186
187
188
189
190 public Continue(final JexlNode node) {
191 super(node, "continue loop", null, false);
192 }
193 }
194
195
196
197
198
199
200 public static class Feature extends Parsing {
201 private static final long serialVersionUID = 20210606123906L;
202
203 private final int code;
204
205
206
207
208
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
223
224
225
226 public static class Method extends JexlException {
227 private static final long serialVersionUID = 20210606123909L;
228
229
230
231
232
233
234
235
236 public Method(final JexlInfo info, final String name, final Object[] args) {
237 this(info, name, args, null);
238 }
239
240
241
242
243
244
245
246
247
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
255
256
257
258
259
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
268
269
270
271
272
273 @Deprecated
274 public Method(final JexlNode node, final String name) {
275 this(node, name, null);
276 }
277
278
279
280
281
282
283
284
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
297
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
307
308
309
310 public String getMethodSignature() {
311 return getDetail();
312 }
313 }
314
315
316
317
318
319
320 public static class Operator extends JexlException {
321 private static final long serialVersionUID = 20210606124100L;
322
323
324
325
326
327
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
340
341
342 public String getSymbol() {
343 return getDetail();
344 }
345 }
346
347
348
349
350
351
352 public static class Parsing extends JexlException {
353 private static final long serialVersionUID = 20210606123902L;
354
355
356
357
358
359
360 public Parsing(final JexlInfo info, final ParseException cause) {
361 super(merge(info, cause), Objects.requireNonNull(cause).getAfter(), null);
362 }
363
364
365
366
367
368
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
382
383
384
385 public static class Property extends JexlException {
386 private static final long serialVersionUID = 20210606123908L;
387
388
389
390 private final boolean undefined;
391
392
393
394
395
396
397
398
399 @Deprecated
400 public Property(final JexlNode node, final String pty) {
401 this(node, pty, true, null);
402 }
403
404
405
406
407
408
409
410
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
419
420
421
422
423
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
437
438
439 public String getProperty() {
440 return getDetail();
441 }
442
443
444
445
446
447
448 public boolean isUndefined() {
449 return undefined;
450 }
451 }
452
453
454
455
456
457
458 public static class Return extends JexlException {
459 private static final long serialVersionUID = 20210606124102L;
460
461
462 private final transient Object result;
463
464
465
466
467
468
469
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
478
479
480 public Object getValue() {
481 return result;
482 }
483 }
484
485
486
487
488
489
490 public static class StackOverflow extends JexlException {
491 private static final long serialVersionUID = 20210606123904L;
492
493
494
495
496
497
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
511
512
513
514 public static class Throw extends JexlException {
515 private static final long serialVersionUID = 20210606124102L;
516
517
518 private final transient Object result;
519
520
521
522
523
524
525
526 public Throw(final JexlNode node, final Object value) {
527 super(node, null, null, false);
528 this.result = value;
529 }
530
531
532
533
534
535 public Object getValue() {
536 return result;
537 }
538 }
539
540
541
542
543
544
545 public static class Tokenization extends JexlException {
546 private static final long serialVersionUID = 20210606123901L;
547
548
549
550
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
564
565
566
567
568 public static class TryFailed extends JexlException {
569 private static final long serialVersionUID = 20210606124105L;
570
571
572
573
574 TryFailed(final InvocationTargetException xany) {
575 super((JexlInfo) null, "tryFailed", xany.getCause());
576 }
577 }
578
579
580
581
582
583
584 public static class Variable extends JexlException {
585 private static final long serialVersionUID = 20210606123907L;
586
587
588
589 private final VariableIssue issue;
590
591
592
593
594
595
596
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
604
605
606
607
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
621
622
623 public String getVariable() {
624 return getDetail();
625 }
626
627
628
629
630
631
632 public boolean isUndefined() {
633 return issue == VariableIssue.UNDEFINED;
634 }
635 }
636
637
638
639
640 public enum VariableIssue {
641
642 UNDEFINED,
643
644 REDEFINED,
645
646 NULLVALUE,
647
648 CONST;
649
650
651
652
653
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
669 private static final int MAX_EXCHARLOC = 128;
670
671
672 private static final String VARQUOTE = "variable '";
673
674
675
676
677
678
679
680
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
692
693
694
695
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
714
715
716
717
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
736
737
738
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
754
755
756
757
758
759
760 @Deprecated
761 public static JexlInfo getInfo(final JexlNode node, final JexlInfo info) {
762 return detailedInfo(node, info);
763 }
764
765
766
767
768
769
770
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
784
785
786
787
788
789
790 @Deprecated
791 public static String methodError(final JexlNode node, final String method) {
792 return methodError(node, method, null);
793 }
794
795
796
797
798
799
800
801
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
813
814
815
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
836
837
838
839
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
851
852
853
854
855
856
857 @Deprecated
858 public static String propertyError(final JexlNode node, final String var) {
859 return propertyError(node, var, true);
860 }
861
862
863
864
865
866
867
868
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
885
886
887
888
889
890
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 }
909 cl += 1;
910 }
911 } catch (final IOException xignore) {
912
913 }
914 return buffer.toString();
915 }
916
917
918
919
920
921
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);
928 }
929
930
931
932
933
934
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
947
948
949
950
951
952
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
961
962
963
964
965
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
974 private final transient JexlNode mark;
975
976
977 private final transient JexlInfo info;
978
979
980
981
982
983
984
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
994
995
996
997
998 public JexlException(final JexlNode node, final String msg) {
999 this(node, msg, null);
1000 }
1001
1002
1003
1004
1005
1006
1007
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
1015
1016
1017
1018
1019
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
1034
1035
1036
1037 public JexlException clean() {
1038 return clean(this);
1039 }
1040
1041
1042
1043
1044
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
1054
1055
1056
1057 public final String getDetail() {
1058 return super.getMessage();
1059 }
1060
1061
1062
1063
1064
1065
1066 public JexlInfo getInfo() {
1067 return detailedInfo(mark, info);
1068 }
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
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
1099
1100
1101 protected JexlInfo info() {
1102 return info;
1103 }
1104
1105
1106
1107
1108
1109
1110
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 }