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