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