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