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