001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.jexl3; 019 020import org.apache.commons.jexl3.internal.Debugger; 021import org.apache.commons.jexl3.parser.JavaccError; 022import org.apache.commons.jexl3.parser.JexlNode; 023import org.apache.commons.jexl3.parser.ParseException; 024import org.apache.commons.jexl3.parser.TokenMgrException; 025 026import java.lang.reflect.InvocationTargetException; 027import java.lang.reflect.UndeclaredThrowableException; 028 029import java.util.ArrayList; 030import java.util.List; 031import java.util.Objects; 032 033import java.io.BufferedReader; 034import java.io.IOException; 035import java.io.StringReader; 036 037/** 038 * Wraps any error that might occur during interpretation of a script or expression. 039 * 040 * @since 2.0 041 */ 042public class JexlException extends RuntimeException { 043 private static final long serialVersionUID = 20210606123900L; 044 045 /** The point of origin for this exception. */ 046 private final transient JexlNode mark; 047 048 /** The debug info. */ 049 private final transient JexlInfo info; 050 051 /** Maximum number of characters around exception location. */ 052 private static final int MAX_EXCHARLOC = 42; 053 054 055 /** 056 * Creates a new JexlException. 057 * 058 * @param node the node causing the error 059 * @param msg the error message 060 */ 061 public JexlException(final JexlNode node, final String msg) { 062 this(node, msg, null); 063 } 064 065 /** 066 * Creates a new JexlException. 067 * 068 * @param node the node causing the error 069 * @param msg the error message 070 * @param cause the exception causing the error 071 */ 072 public JexlException(final JexlNode node, final String msg, final Throwable cause) { 073 this(node, msg != null ? msg : "", unwrap(cause), true); 074 } 075 076 /** 077 * Creates a new JexlException. 078 * 079 * @param node the node causing the error 080 * @param msg the error message 081 * @param cause the exception causing the error 082 * @param trace whether this exception has a stacktrace and can <em>not</em> be suppressed 083 */ 084 protected JexlException(final JexlNode node, final String msg, final Throwable cause, boolean trace) { 085 super(msg != null ? msg : "", unwrap(cause), !trace, trace); 086 if (node != null) { 087 mark = node; 088 info = node.jexlInfo(); 089 } else { 090 mark = null; 091 info = null; 092 } 093 } 094 095 /** 096 * Creates a new JexlException. 097 * 098 * @param jinfo the debugging information associated 099 * @param msg the error message 100 * @param cause the exception causing the error 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 * Gets the specific information for this exception. 110 * 111 * @return the information 112 */ 113 public JexlInfo getInfo() { 114 return detailedInfo(mark, info); 115 } 116 117 /** 118 * Creates a string builder pre-filled with common error information (if possible). 119 * 120 * @param node the node 121 * @return a string builder 122 */ 123 private 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 * Gets the most specific information attached to a node. 137 * 138 * @param node the node 139 * @param info the information 140 * @return the information or null 141 * @deprecated 3.2 142 */ 143 @Deprecated 144 public static JexlInfo getInfo(final JexlNode node, final JexlInfo info) { 145 return detailedInfo(node, info); 146 } 147 148 /** 149 * Gets the most specific information attached to a node. 150 * 151 * @param node the node 152 * @param info the information 153 * @return the information or null 154 */ 155 private 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 * Cleans a JexlException from any org.apache.commons.jexl3.internal stack trace element. 172 * 173 * @return this exception 174 */ 175 public JexlException clean() { 176 return clean(this); 177 } 178 179 /** 180 * Cleans a Throwable from any org.apache.commons.jexl3.internal stack trace element. 181 * 182 * @param <X> the throwable type 183 * @param xthrow the thowable 184 * @return the throwable 185 */ 186 private 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[stackJexl.size()])); 197 } 198 return xthrow; 199 } 200 201 /** 202 * Unwraps the cause of a throwable due to reflection. 203 * 204 * @param xthrow the throwable 205 * @return the cause 206 */ 207 private 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 * Merge the node info and the cause info to obtain best possible location. 218 * 219 * @param info the node 220 * @param cause the cause 221 * @return the info to use 222 */ 223 private 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 * Accesses detailed message. 235 * 236 * @return the message 237 */ 238 protected String detailedMessage() { 239 Class<? extends JexlException> clazz = getClass(); 240 String name = clazz == JexlException.class? "JEXL" : clazz.getSimpleName().toLowerCase(); 241 return name + " error : " + getDetail(); 242 } 243 244 /** 245 * @return this exception specific detail 246 * @since 3.2 247 */ 248 public final String getDetail() { 249 return super.getMessage(); 250 } 251 252 /** 253 * Formats an error message from the parser. 254 * 255 * @param prefix the prefix to the message 256 * @param expr the expression in error 257 * @return the formatted message 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 * Pleasing checkstyle. 281 * @return the info 282 */ 283 protected JexlInfo info() { 284 return info; 285 } 286 287 /** 288 * Thrown when tokenization fails. 289 * 290 * @since 3.0 291 */ 292 public static class Tokenization extends JexlException { 293 private static final long serialVersionUID = 20210606123901L; 294 /** 295 * Creates a new Tokenization exception instance. 296 * @param info the location info 297 * @param cause the javacc cause 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 * Thrown when parsing fails. 311 * 312 * @since 3.0 313 */ 314 public static class Parsing extends JexlException { 315 private static final long serialVersionUID = 20210606123902L; 316 /** 317 * Creates a new Parsing exception instance. 318 * 319 * @param info the location information 320 * @param cause the javacc cause 321 */ 322 public Parsing(final JexlInfo info, final ParseException cause) { 323 super(merge(info, cause), Objects.requireNonNull(cause).getAfter(), null); 324 } 325 326 /** 327 * Creates a new Parsing exception instance. 328 * 329 * @param info the location information 330 * @param msg the message 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 * Thrown when parsing fails due to an ambiguous statement. 344 * 345 * @since 3.0 346 */ 347 public static class Ambiguous extends Parsing { 348 private static final long serialVersionUID = 20210606123903L; 349 /** The mark at which ambiguity might stop and recover. */ 350 private final transient JexlInfo recover; 351 /** 352 * Creates a new Ambiguous statement exception instance. 353 * @param info the location information 354 * @param expr the source expression line 355 */ 356 public Ambiguous(final JexlInfo info, final String expr) { 357 this(info, null, expr); 358 } 359 360 /** 361 * Creates a new Ambiguous statement exception instance. 362 * @param begin the start location information 363 * @param end the end location information 364 * @param expr the source expression line 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 * Tries to remove this ambiguity in the source. 378 * @param src the source that triggered this exception 379 * @return the source with the ambiguous statement removed 380 * or null if no recovery was possible 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 * Removes a slice from a source. 392 * @param src the source 393 * @param froml the begin line 394 * @param fromc the begin column 395 * @param tol the to line 396 * @param toc the to column 397 * @return the source with the (begin) to (to) zone removed 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 } // else ignore line 416 cl += 1; 417 } 418 } catch (final IOException xignore) { 419 //damn the checked exceptions :-) 420 } 421 return buffer.toString(); 422 } 423 424 /** 425 * Thrown when reaching stack-overflow. 426 * 427 * @since 3.2 428 */ 429 public static class StackOverflow extends JexlException { 430 private static final long serialVersionUID = 20210606123904L; 431 /** 432 * Creates a new stack overflow exception instance. 433 * 434 * @param info the location information 435 * @param name the unknown method 436 * @param cause the exception causing the error 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 * Thrown when parsing fails due to an invalid assigment. 450 * 451 * @since 3.0 452 */ 453 public static class Assignment extends Parsing { 454 private static final long serialVersionUID = 20210606123905L; 455 /** 456 * Creates a new Assignment statement exception instance. 457 * 458 * @param info the location information 459 * @param expr the source expression line 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 * Thrown when parsing fails due to a disallowed feature. 473 * 474 * @since 3.2 475 */ 476 public static class Feature extends Parsing { 477 private static final long serialVersionUID = 20210606123906L; 478 /** The feature code. */ 479 private final int code; 480 /** 481 * Creates a new Ambiguous statement exception instance. 482 * @param info the location information 483 * @param feature the feature code 484 * @param expr the source expression line 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 /** Used 3 times. */ 498 private static final String VARQUOTE = "variable '"; 499 500 /** 501 * The various type of variable issues. 502 */ 503 public enum VariableIssue { 504 /** The variable is undefined. */ 505 UNDEFINED, 506 /** The variable is already declared. */ 507 REDEFINED, 508 /** The variable has a null value. */ 509 NULLVALUE; 510 511 /** 512 * Stringifies the variable issue. 513 * @param var the variable name 514 * @return the issue message 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 * Thrown when a variable is unknown. 528 * 529 * @since 3.0 530 */ 531 public static class Variable extends JexlException { 532 private static final long serialVersionUID = 20210606123907L; 533 /** 534 * Undefined variable flag. 535 */ 536 private final VariableIssue issue; 537 538 /** 539 * Creates a new Variable exception instance. 540 * 541 * @param node the offending ASTnode 542 * @param var the unknown variable 543 * @param vi the variable issue 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 * Creates a new Variable exception instance. 552 * 553 * @param node the offending ASTnode 554 * @param var the unknown variable 555 * @param undef whether the variable is undefined or evaluated as null 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 * Whether the variable causing an error is undefined or evaluated as null. 563 * 564 * @return true if undefined, false otherwise 565 */ 566 public boolean isUndefined() { 567 return issue == VariableIssue.UNDEFINED; 568 } 569 570 /** 571 * @return the variable name 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 * Generates a message for a variable error. 585 * 586 * @param node the node where the error occurred 587 * @param variable the variable 588 * @param undef whether the variable is null or undefined 589 * @return the error message 590 * @deprecated 3.2 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 * Generates a message for a variable error. 599 * 600 * @param node the node where the error occurred 601 * @param variable the variable 602 * @param issue the variable kind of issue 603 * @return the error message 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 * Thrown when a property is unknown. 613 * 614 * @since 3.0 615 */ 616 public static class Property extends JexlException { 617 private static final long serialVersionUID = 20210606123908L; 618 /** 619 * Undefined variable flag. 620 */ 621 private final boolean undefined; 622 623 /** 624 * Creates a new Property exception instance. 625 * 626 * @param node the offending ASTnode 627 * @param pty the unknown property 628 * @deprecated 3.2 629 */ 630 @Deprecated 631 public Property(final JexlNode node, final String pty) { 632 this(node, pty, true, null); 633 } 634 /** 635 * Creates a new Property exception instance. 636 * 637 * @param node the offending ASTnode 638 * @param pty the unknown property 639 * @param cause the exception causing the error 640 * @deprecated 3.2 641 */ 642 @Deprecated 643 public Property(final JexlNode node, final String pty, final Throwable cause) { 644 this(node, pty, true, cause); 645 } 646 647 /** 648 * Creates a new Property exception instance. 649 * 650 * @param node the offending ASTnode 651 * @param pty the unknown property 652 * @param undef whether the variable is null or undefined 653 * @param cause the exception causing the error 654 */ 655 public Property(final JexlNode node, final String pty, final boolean undef, final Throwable cause) { 656 super(node, pty, cause); 657 undefined = undef; 658 } 659 660 /** 661 * Whether the variable causing an error is undefined or evaluated as null. 662 * 663 * @return true if undefined, false otherwise 664 */ 665 public boolean isUndefined() { 666 return undefined; 667 } 668 669 /** 670 * @return the property name 671 */ 672 public String getProperty() { 673 return getDetail(); 674 } 675 676 @Override 677 protected String detailedMessage() { 678 return (undefined? "undefined" : "null value") + " property '" + getProperty() + "'"; 679 } 680 } 681 682 /** 683 * Generates a message for an unsolvable property error. 684 * 685 * @param node the node where the error occurred 686 * @param pty the property 687 * @param undef whether the property is null or undefined 688 * @return the error message 689 */ 690 public static String propertyError(final JexlNode node, final String pty, final boolean undef) { 691 final StringBuilder msg = errorAt(node); 692 if (undef) { 693 msg.append("unsolvable"); 694 } else { 695 msg.append("null value"); 696 } 697 msg.append(" property '"); 698 msg.append(pty); 699 msg.append('\''); 700 return msg.toString(); 701 } 702 703 /** 704 * Generates a message for an unsolvable property error. 705 * 706 * @param node the node where the error occurred 707 * @param var the variable 708 * @return the error message 709 * @deprecated 3.2 710 */ 711 @Deprecated 712 public static String propertyError(final JexlNode node, final String var) { 713 return propertyError(node, var, true); 714 } 715 716 /** 717 * Thrown when a method or ctor is unknown, ambiguous or inaccessible. 718 * 719 * @since 3.0 720 */ 721 public static class Method extends JexlException { 722 private static final long serialVersionUID = 20210606123909L; 723 /** 724 * Creates a new Method exception instance. 725 * 726 * @param node the offending ASTnode 727 * @param name the method name 728 * @deprecated as of 3.2, use call with method arguments 729 */ 730 @Deprecated 731 public Method(final JexlNode node, final String name) { 732 this(node, name, null); 733 } 734 735 /** 736 * Creates a new Method exception instance. 737 * 738 * @param info the location information 739 * @param name the unknown method 740 * @param cause the exception causing the error 741 * @deprecated as of 3.2, use call with method arguments 742 */ 743 @Deprecated 744 public Method(final JexlInfo info, final String name, final Throwable cause) { 745 this(info, name, null, cause); 746 } 747 748 /** 749 * Creates a new Method exception instance. 750 * 751 * @param node the offending ASTnode 752 * @param name the method name 753 * @param args the method arguments 754 * @since 3.2 755 */ 756 public Method(final JexlNode node, final String name, final Object[] args) { 757 super(node, methodSignature(name, args)); 758 } 759 760 /** 761 * Creates a new Method exception instance. 762 * 763 * @param info the location information 764 * @param name the method name 765 * @param args the method arguments 766 * @since 3.2 767 */ 768 public Method(final JexlInfo info, final String name, final Object[] args) { 769 this(info, name, args, null); 770 } 771 772 773 /** 774 * Creates a new Method exception instance. 775 * 776 * @param info the location information 777 * @param name the method name 778 * @param cause the exception causing the error 779 * @param args the method arguments 780 * @since 3.2 781 */ 782 public Method(final JexlInfo info, final String name, final Object[] args, final Throwable cause) { 783 super(info, methodSignature(name, args), cause); 784 } 785 786 /** 787 * @return the method name 788 */ 789 public String getMethod() { 790 final String signature = getMethodSignature(); 791 final int lparen = signature.indexOf('('); 792 return lparen > 0? signature.substring(0, lparen) : signature; 793 } 794 795 /** 796 * @return the method signature 797 * @since 3.2 798 */ 799 public String getMethodSignature() { 800 return getDetail(); 801 } 802 803 @Override 804 protected String detailedMessage() { 805 return "unsolvable function/method '" + getMethodSignature() + "'"; 806 } 807 } 808 809 /** 810 * Creates a signed-name for a given method name and arguments. 811 * @param name the method name 812 * @param args the method arguments 813 * @return a suitable signed name 814 */ 815 private static String methodSignature(final String name, final Object[] args) { 816 if (args != null && args.length > 0) { 817 final StringBuilder strb = new StringBuilder(name); 818 strb.append('('); 819 for (int a = 0; a < args.length; ++a) { 820 if (a > 0) { 821 strb.append(", "); 822 } 823 final Class<?> clazz = args[a] == null ? Object.class : args[a].getClass(); 824 strb.append(clazz.getSimpleName()); 825 } 826 strb.append(')'); 827 return strb.toString(); 828 } 829 return name; 830 } 831 832 /** 833 * Generates a message for a unsolvable method error. 834 * 835 * @param node the node where the error occurred 836 * @param method the method name 837 * @return the error message 838 * @deprecated 3.2 839 */ 840 @Deprecated 841 public static String methodError(final JexlNode node, final String method) { 842 return methodError(node, method, null); 843 } 844 845 /** 846 * Generates a message for a unsolvable method error. 847 * 848 * @param node the node where the error occurred 849 * @param method the method name 850 * @param args the method arguments 851 * @return the error message 852 */ 853 public static String methodError(final JexlNode node, final String method, final Object[] args) { 854 final StringBuilder msg = errorAt(node); 855 msg.append("unsolvable function/method '"); 856 msg.append(methodSignature(method, args)); 857 msg.append('\''); 858 return msg.toString(); 859 } 860 861 /** 862 * Thrown when an operator fails. 863 * 864 * @since 3.0 865 */ 866 public static class Operator extends JexlException { 867 private static final long serialVersionUID = 20210606124100L; 868 /** 869 * Creates a new Operator exception instance. 870 * 871 * @param node the location information 872 * @param symbol the operator name 873 * @param cause the exception causing the error 874 */ 875 public Operator(final JexlNode node, final String symbol, final Throwable cause) { 876 super(node, symbol, cause); 877 } 878 879 /** 880 * @return the method name 881 */ 882 public String getSymbol() { 883 return getDetail(); 884 } 885 886 @Override 887 protected String detailedMessage() { 888 return "error calling operator '" + getSymbol() + "'"; 889 } 890 } 891 892 /** 893 * Generates a message for an operator error. 894 * 895 * @param node the node where the error occurred 896 * @param symbol the operator name 897 * @return the error message 898 */ 899 public static String operatorError(final JexlNode node, final String symbol) { 900 final StringBuilder msg = errorAt(node); 901 msg.append("error calling operator '"); 902 msg.append(symbol); 903 msg.append('\''); 904 return msg.toString(); 905 } 906 907 /** 908 * Thrown when an annotation handler throws an exception. 909 * 910 * @since 3.1 911 */ 912 public static class Annotation extends JexlException { 913 private static final long serialVersionUID = 20210606124101L; 914 /** 915 * Creates a new Annotation exception instance. 916 * 917 * @param node the annotated statement node 918 * @param name the annotation name 919 * @param cause the exception causing the error 920 */ 921 public Annotation(final JexlNode node, final String name, final Throwable cause) { 922 super(node, name, cause); 923 } 924 925 /** 926 * @return the annotation name 927 */ 928 public String getAnnotation() { 929 return getDetail(); 930 } 931 932 @Override 933 protected String detailedMessage() { 934 return "error processing annotation '" + getAnnotation() + "'"; 935 } 936 } 937 938 /** 939 * Generates a message for an annotation error. 940 * 941 * @param node the node where the error occurred 942 * @param annotation the annotation name 943 * @return the error message 944 * @since 3.1 945 */ 946 public static String annotationError(final JexlNode node, final String annotation) { 947 final StringBuilder msg = errorAt(node); 948 msg.append("error processing annotation '"); 949 msg.append(annotation); 950 msg.append('\''); 951 return msg.toString(); 952 } 953 954 /** 955 * Thrown to return a value. 956 * 957 * @since 3.0 958 */ 959 public static class Return extends JexlException { 960 private static final long serialVersionUID = 20210606124102L; 961 962 /** The returned value. */ 963 private final transient Object result; 964 965 /** 966 * Creates a new instance of Return. 967 * 968 * @param node the return node 969 * @param msg the message 970 * @param value the returned value 971 */ 972 public Return(final JexlNode node, final String msg, final Object value) { 973 super(node, msg, null, false); 974 this.result = value; 975 } 976 977 /** 978 * @return the returned value 979 */ 980 public Object getValue() { 981 return result; 982 } 983 } 984 985 /** 986 * Thrown to cancel a script execution. 987 * 988 * @since 3.0 989 */ 990 public static class Cancel extends JexlException { 991 /** 992 * Creates a new instance of Cancel. 993 * 994 * @param node the node where the interruption was detected 995 */ 996 public Cancel(final JexlNode node) { 997 super(node, "execution cancelled", null); 998 } 999 } 1000 1001 /** 1002 * Thrown to break a loop. 1003 * 1004 * @since 3.0 1005 */ 1006 public static class Break extends JexlException { 1007 private static final long serialVersionUID = 20210606124103L; 1008 /** 1009 * Creates a new instance of Break. 1010 * 1011 * @param node the break 1012 */ 1013 public Break(final JexlNode node) { 1014 super(node, "break loop", null, false); 1015 } 1016 } 1017 1018 /** 1019 * Thrown to continue a loop. 1020 * 1021 * @since 3.0 1022 */ 1023 public static class Continue extends JexlException { 1024 private static final long serialVersionUID = 20210606124104L; 1025 /** 1026 * Creates a new instance of Continue. 1027 * 1028 * @param node the continue 1029 */ 1030 public Continue(final JexlNode node) { 1031 super(node, "continue loop", null, false); 1032 } 1033 } 1034 1035 /** 1036 * Thrown when method/ctor invocation fails. 1037 * <p>These wrap InvocationTargetException as runtime exception 1038 * allowing to go through without signature modifications. 1039 * @since 3.2 1040 */ 1041 public static class TryFailed extends JexlException { 1042 private static final long serialVersionUID = 20210606124105L; 1043 /** 1044 * Creates a new instance. 1045 * @param xany the original invocation target exception 1046 */ 1047 private TryFailed(final InvocationTargetException xany) { 1048 super((JexlInfo) null, "tryFailed", xany.getCause()); 1049 } 1050 } 1051 1052 /** 1053 * Wrap an invocation exception. 1054 * <p>Return the cause if it is already a JexlException. 1055 * @param xinvoke the invocation exception 1056 * @return a JexlException 1057 */ 1058 public static JexlException tryFailed(final InvocationTargetException xinvoke) { 1059 final Throwable cause = xinvoke.getCause(); 1060 return cause instanceof JexlException 1061 ? (JexlException) cause 1062 : new JexlException.TryFailed(xinvoke); // fail 1063 } 1064 1065 1066 /** 1067 * Detailed info message about this error. 1068 * Format is "debug![begin,end]: string \n msg" where: 1069 * 1070 * - debug is the debugging information if it exists (@link JexlEngine.setDebug) 1071 * - begin, end are character offsets in the string for the precise location of the error 1072 * - string is the string representation of the offending expression 1073 * - msg is the actual explanation message for this error 1074 * 1075 * @return this error as a string 1076 */ 1077 @Override 1078 public String getMessage() { 1079 final StringBuilder msg = new StringBuilder(); 1080 if (info != null) { 1081 msg.append(info.toString()); 1082 } else { 1083 msg.append("?:"); 1084 } 1085 msg.append(' '); 1086 msg.append(detailedMessage()); 1087 final Throwable cause = getCause(); 1088 if (cause instanceof JexlArithmetic.NullOperand) { 1089 msg.append(" caused by null operand"); 1090 } 1091 return msg.toString(); 1092 } 1093}