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, final 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 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 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 static <X extends Throwable> X clean(final X xthrow) { 187 if (xthrow != null) { 188 final List<StackTraceElement> stackJexl = new ArrayList<>(); 189 for (final StackTraceElement se : xthrow.getStackTrace()) { 190 final String className = se.getClassName(); 191 if (!className.startsWith("org.apache.commons.jexl3.internal") 192 && !className.startsWith("org.apache.commons.jexl3.parser")) { 193 stackJexl.add(se); 194 } 195 } 196 xthrow.setStackTrace(stackJexl.toArray(new StackTraceElement[0])); 197 } 198 return xthrow; 199 } 200 201 /** 202 * Unwraps the cause of a throwable due to reflection. 203 * 204 * @param xthrow the throwable 205 * @return the cause 206 */ 207 static Throwable unwrap(final Throwable xthrow) { 208 if (xthrow instanceof TryFailed 209 || xthrow instanceof InvocationTargetException 210 || xthrow instanceof UndeclaredThrowableException) { 211 return xthrow.getCause(); 212 } 213 return xthrow; 214 } 215 216 /** 217 * Merge the node info and the cause info to obtain the best possible location. 218 * 219 * @param info the node 220 * @param cause the cause 221 * @return the info to use 222 */ 223 static JexlInfo merge(final JexlInfo info, final JavaccError cause) { 224 if (cause == null || cause.getLine() < 0) { 225 return info; 226 } 227 if (info == null) { 228 return new JexlInfo("", cause.getLine(), cause.getColumn()); 229 } 230 return new JexlInfo(info.getName(), cause.getLine(), cause.getColumn()); 231 } 232 233 /** 234 * Accesses detailed message. 235 * 236 * @return the message 237 */ 238 protected String detailedMessage() { 239 final Class<? extends JexlException> clazz = getClass(); 240 final String name = clazz == JexlException.class? "JEXL" : clazz.getSimpleName().toLowerCase(); 241 return name + " error : " + getDetail(); 242 } 243 244 /** 245 * @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 beginning line 394 * @param fromc the beginning column 395 * @param tol the ending line 396 * @param toc the ending 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 assignment. 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 /** 636 * Creates a new Property exception instance. 637 * 638 * @param node the offending ASTnode 639 * @param pty the unknown property 640 * @param cause the exception causing the error 641 * @deprecated 3.2 642 */ 643 @Deprecated 644 public Property(final JexlNode node, final String pty, final Throwable cause) { 645 this(node, pty, true, cause); 646 } 647 648 /** 649 * Creates a new Property exception instance. 650 * 651 * @param node the offending ASTnode 652 * @param pty the unknown property 653 * @param undef whether the variable is null or undefined 654 * @param cause the exception causing the error 655 */ 656 public Property(final JexlNode node, final String pty, final boolean undef, final Throwable cause) { 657 super(node, pty, cause); 658 undefined = undef; 659 } 660 661 /** 662 * Whether the variable causing an error is undefined or evaluated as null. 663 * 664 * @return true if undefined, false otherwise 665 */ 666 public boolean isUndefined() { 667 return undefined; 668 } 669 670 /** 671 * @return the property name 672 */ 673 public String getProperty() { 674 return getDetail(); 675 } 676 677 @Override 678 protected String detailedMessage() { 679 return (undefined? "undefined" : "null value") + " property '" + getProperty() + "'"; 680 } 681 } 682 683 /** 684 * Generates a message for an unsolvable property error. 685 * 686 * @param node the node where the error occurred 687 * @param pty the property 688 * @param undef whether the property is null or undefined 689 * @return the error message 690 */ 691 public static String propertyError(final JexlNode node, final String pty, final boolean undef) { 692 final StringBuilder msg = errorAt(node); 693 if (undef) { 694 msg.append("unsolvable"); 695 } else { 696 msg.append("null value"); 697 } 698 msg.append(" property '"); 699 msg.append(pty); 700 msg.append('\''); 701 return msg.toString(); 702 } 703 704 /** 705 * Generates a message for an unsolvable property error. 706 * 707 * @param node the node where the error occurred 708 * @param var the variable 709 * @return the error message 710 * @deprecated 3.2 711 */ 712 @Deprecated 713 public static String propertyError(final JexlNode node, final String var) { 714 return propertyError(node, var, true); 715 } 716 717 /** 718 * Thrown when a method or ctor is unknown, ambiguous or inaccessible. 719 * 720 * @since 3.0 721 */ 722 public static class Method extends JexlException { 723 private static final long serialVersionUID = 20210606123909L; 724 /** 725 * Creates a new Method exception instance. 726 * 727 * @param node the offending ASTnode 728 * @param name the method name 729 * @deprecated as of 3.2, use call with method arguments 730 */ 731 @Deprecated 732 public Method(final JexlNode node, final String name) { 733 this(node, name, null); 734 } 735 736 /** 737 * Creates a new Method exception instance. 738 * 739 * @param info the location information 740 * @param name the unknown method 741 * @param cause the exception causing the error 742 * @deprecated as of 3.2, use call with method arguments 743 */ 744 @Deprecated 745 public Method(final JexlInfo info, final String name, final Throwable cause) { 746 this(info, name, null, cause); 747 } 748 749 /** 750 * Creates a new Method exception instance. 751 * 752 * @param node the offending ASTnode 753 * @param name the method name 754 * @param args the method arguments 755 * @since 3.2 756 */ 757 public Method(final JexlNode node, final String name, final Object[] args) { 758 super(node, methodSignature(name, args)); 759 } 760 761 /** 762 * Creates a new Method exception instance. 763 * 764 * @param info the location information 765 * @param name the method name 766 * @param args the method arguments 767 * @since 3.2 768 */ 769 public Method(final JexlInfo info, final String name, final Object[] args) { 770 this(info, name, args, null); 771 } 772 773 774 /** 775 * Creates a new Method exception instance. 776 * 777 * @param info the location information 778 * @param name the method name 779 * @param cause the exception causing the error 780 * @param args the method arguments 781 * @since 3.2 782 */ 783 public Method(final JexlInfo info, final String name, final Object[] args, final Throwable cause) { 784 super(info, methodSignature(name, args), cause); 785 } 786 787 /** 788 * @return the method name 789 */ 790 public String getMethod() { 791 final String signature = getMethodSignature(); 792 final int lparen = signature.indexOf('('); 793 return lparen > 0? signature.substring(0, lparen) : signature; 794 } 795 796 /** 797 * @return the method signature 798 * @since 3.2 799 */ 800 public String getMethodSignature() { 801 return getDetail(); 802 } 803 804 @Override 805 protected String detailedMessage() { 806 return "unsolvable function/method '" + getMethodSignature() + "'"; 807 } 808 } 809 810 /** 811 * Creates a signed-name for a given method name and arguments. 812 * @param name the method name 813 * @param args the method arguments 814 * @return a suitable signed name 815 */ 816 static String methodSignature(final String name, final Object[] args) { 817 if (args != null && args.length > 0) { 818 final StringBuilder strb = new StringBuilder(name); 819 strb.append('('); 820 for (int a = 0; a < args.length; ++a) { 821 if (a > 0) { 822 strb.append(", "); 823 } 824 final Class<?> clazz = args[a] == null ? Object.class : args[a].getClass(); 825 strb.append(clazz.getSimpleName()); 826 } 827 strb.append(')'); 828 return strb.toString(); 829 } 830 return name; 831 } 832 833 /** 834 * Generates a message for a unsolvable method error. 835 * 836 * @param node the node where the error occurred 837 * @param method the method name 838 * @return the error message 839 * @deprecated 3.2 840 */ 841 @Deprecated 842 public static String methodError(final JexlNode node, final String method) { 843 return methodError(node, method, null); 844 } 845 846 /** 847 * Generates a message for a unsolvable method error. 848 * 849 * @param node the node where the error occurred 850 * @param method the method name 851 * @param args the method arguments 852 * @return the error message 853 */ 854 public static String methodError(final JexlNode node, final String method, final Object[] args) { 855 final StringBuilder msg = errorAt(node); 856 msg.append("unsolvable function/method '"); 857 msg.append(methodSignature(method, args)); 858 msg.append('\''); 859 return msg.toString(); 860 } 861 862 /** 863 * Thrown when an operator fails. 864 * 865 * @since 3.0 866 */ 867 public static class Operator extends JexlException { 868 private static final long serialVersionUID = 20210606124100L; 869 /** 870 * Creates a new Operator exception instance. 871 * 872 * @param node the location information 873 * @param symbol the operator name 874 * @param cause the exception causing the error 875 */ 876 public Operator(final JexlNode node, final String symbol, final Throwable cause) { 877 super(node, symbol, cause); 878 } 879 880 /** 881 * @return the method name 882 */ 883 public String getSymbol() { 884 return getDetail(); 885 } 886 887 @Override 888 protected String detailedMessage() { 889 return "error calling operator '" + getSymbol() + "'"; 890 } 891 } 892 893 /** 894 * Generates a message for an operator error. 895 * 896 * @param node the node where the error occurred 897 * @param symbol the operator name 898 * @return the error message 899 */ 900 public static String operatorError(final JexlNode node, final String symbol) { 901 final StringBuilder msg = errorAt(node); 902 msg.append("error calling operator '"); 903 msg.append(symbol); 904 msg.append('\''); 905 return msg.toString(); 906 } 907 908 /** 909 * Thrown when an annotation handler throws an exception. 910 * 911 * @since 3.1 912 */ 913 public static class Annotation extends JexlException { 914 private static final long serialVersionUID = 20210606124101L; 915 /** 916 * Creates a new Annotation exception instance. 917 * 918 * @param node the annotated statement node 919 * @param name the annotation name 920 * @param cause the exception causing the error 921 */ 922 public Annotation(final JexlNode node, final String name, final Throwable cause) { 923 super(node, name, cause); 924 } 925 926 /** 927 * @return the annotation name 928 */ 929 public String getAnnotation() { 930 return getDetail(); 931 } 932 933 @Override 934 protected String detailedMessage() { 935 return "error processing annotation '" + getAnnotation() + "'"; 936 } 937 } 938 939 /** 940 * Generates a message for an annotation error. 941 * 942 * @param node the node where the error occurred 943 * @param annotation the annotation name 944 * @return the error message 945 * @since 3.1 946 */ 947 public static String annotationError(final JexlNode node, final String annotation) { 948 final StringBuilder msg = errorAt(node); 949 msg.append("error processing annotation '"); 950 msg.append(annotation); 951 msg.append('\''); 952 return msg.toString(); 953 } 954 955 /** 956 * Thrown to return a value. 957 * 958 * @since 3.0 959 */ 960 public static class Return extends JexlException { 961 private static final long serialVersionUID = 20210606124102L; 962 963 /** The returned value. */ 964 private final transient Object result; 965 966 /** 967 * Creates a new instance of Return. 968 * 969 * @param node the return node 970 * @param msg the message 971 * @param value the returned value 972 */ 973 public Return(final JexlNode node, final String msg, final Object value) { 974 super(node, msg, null, false); 975 this.result = value; 976 } 977 978 /** 979 * @return the returned value 980 */ 981 public Object getValue() { 982 return result; 983 } 984 } 985 986 /** 987 * Thrown to cancel a script execution. 988 * 989 * @since 3.0 990 */ 991 public static class Cancel extends JexlException { 992 private static final long serialVersionUID = 7735706658499597964L; 993 /** 994 * Creates a new instance of Cancel. 995 * 996 * @param node the node where the interruption was detected 997 */ 998 public Cancel(final JexlNode node) { 999 super(node, "execution cancelled", null); 1000 } 1001 } 1002 1003 /** 1004 * Thrown to break a loop. 1005 * 1006 * @since 3.0 1007 */ 1008 public static class Break extends JexlException { 1009 private static final long serialVersionUID = 20210606124103L; 1010 /** 1011 * Creates a new instance of Break. 1012 * 1013 * @param node the break 1014 */ 1015 public Break(final JexlNode node) { 1016 super(node, "break loop", null, false); 1017 } 1018 } 1019 1020 /** 1021 * Thrown to continue a loop. 1022 * 1023 * @since 3.0 1024 */ 1025 public static class Continue extends JexlException { 1026 private static final long serialVersionUID = 20210606124104L; 1027 /** 1028 * Creates a new instance of Continue. 1029 * 1030 * @param node the continue-node 1031 */ 1032 public Continue(final JexlNode node) { 1033 super(node, "continue loop", null, false); 1034 } 1035 } 1036 1037 /** 1038 * Thrown when method/ctor invocation fails. 1039 * <p>These wrap InvocationTargetException as runtime exception 1040 * allowing to go through without signature modifications. 1041 * @since 3.2 1042 */ 1043 public static class TryFailed extends JexlException { 1044 private static final long serialVersionUID = 20210606124105L; 1045 /** 1046 * Creates a new instance. 1047 * @param xany the original invocation target exception 1048 */ 1049 private TryFailed(final InvocationTargetException xany) { 1050 super((JexlInfo) null, "tryFailed", xany.getCause()); 1051 } 1052 } 1053 1054 /** 1055 * Wrap an invocation exception. 1056 * <p>Return the cause if it is already a JexlException. 1057 * @param xinvoke the invocation exception 1058 * @return a JexlException 1059 */ 1060 public static JexlException tryFailed(final InvocationTargetException xinvoke) { 1061 final Throwable cause = xinvoke.getCause(); 1062 return cause instanceof JexlException 1063 ? (JexlException) cause 1064 : new JexlException.TryFailed(xinvoke); // fail 1065 } 1066 1067 1068 /** 1069 * Detailed info message about this error. 1070 * Format is "debug![begin,end]: string \n msg" where: 1071 * - debug is the debugging information if it exists (@link JexlEngine.setDebug) 1072 * - begin, end are character offsets in the string for the precise location of the error 1073 * - string is the string representation of the offending expression 1074 * - msg is the actual explanation message for this error 1075 * 1076 * @return this error as a string 1077 */ 1078 @Override 1079 public String getMessage() { 1080 final StringBuilder msg = new StringBuilder(); 1081 if (info != null) { 1082 msg.append(info.toString()); 1083 } else { 1084 msg.append("?:"); 1085 } 1086 msg.append(' '); 1087 msg.append(detailedMessage()); 1088 final Throwable cause = getCause(); 1089 if (cause instanceof JexlArithmetic.NullOperand) { 1090 msg.append(" caused by null operand"); 1091 } 1092 return msg.toString(); 1093 } 1094}