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