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 * The various type 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 any org.apache.commons.jexl3.internal stack trace element. 732 * 733 * @param <X> the throwable type 734 * @param xthrow the thowable 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 (!className.startsWith("org.apache.commons.jexl3.internal") 743 && !className.startsWith("org.apache.commons.jexl3.parser")) { 744 stackJexl.add(se); 745 } 746 } 747 xthrow.setStackTrace(stackJexl.toArray(EMPTY_STACK_TRACE_ELEMENT_ARRAY)); 748 } 749 return xthrow; 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 */ 759 static JexlInfo detailedInfo(final JexlNode node, final JexlInfo info) { 760 if (info != null && node != null) { 761 final Debugger dbg = new Debugger(); 762 if (dbg.debug(node)) { 763 return new JexlInfo(info) { 764 @Override 765 public JexlInfo.Detail getDetail() { 766 return dbg; 767 } 768 }; 769 } 770 } 771 return info; 772 } 773 774 /** 775 * Creates a string builder pre-filled with common error information (if possible). 776 * 777 * @param node the node 778 * @return a string builder 779 */ 780 static StringBuilder errorAt(final JexlNode node) { 781 final JexlInfo info = node != null ? detailedInfo(node, node.jexlInfo()) : null; 782 final StringBuilder msg = new StringBuilder(); 783 if (info != null) { 784 msg.append(info.toString()); 785 } else { 786 msg.append("?:"); 787 } 788 msg.append(' '); 789 return msg; 790 } 791 792 /** 793 * Gets the most specific information attached to a node. 794 * 795 * @param node the node 796 * @param info the information 797 * @return the information or null 798 * @deprecated 3.2 799 */ 800 @Deprecated 801 public static JexlInfo getInfo(final JexlNode node, final JexlInfo info) { 802 return detailedInfo(node, info); 803 } 804 805 /** 806 * Merge the node info and the cause info to obtain the best possible location. 807 * 808 * @param info the node 809 * @param cause the cause 810 * @return the info to use 811 */ 812 static JexlInfo merge(final JexlInfo info, final JavaccError cause) { 813 if (cause == null || cause.getLine() < 0) { 814 return info; 815 } 816 if (info == null) { 817 return new JexlInfo("", cause.getLine(), cause.getColumn()); 818 } 819 return new JexlInfo(info.getName(), cause.getLine(), cause.getColumn()); 820 } 821 822 /** 823 * Generates a message for a unsolvable method error. 824 * 825 * @param node the node where the error occurred 826 * @param method the method name 827 * @return the error message 828 * @deprecated 3.2 829 */ 830 @Deprecated 831 public static String methodError(final JexlNode node, final String method) { 832 return methodError(node, method, null); 833 } 834 835 /** 836 * Generates a message for a unsolvable method error. 837 * 838 * @param node the node where the error occurred 839 * @param method the method name 840 * @param args the method arguments 841 * @return the error message 842 */ 843 public static String methodError(final JexlNode node, final String method, final Object[] args) { 844 final StringBuilder msg = errorAt(node); 845 msg.append("unsolvable function/method '"); 846 msg.append(methodSignature(method, args)); 847 msg.append('\''); 848 return msg.toString(); 849 } 850 851 /** 852 * Creates a signed-name for a given method name and arguments. 853 * 854 * @param name the method name 855 * @param args the method arguments 856 * @return a suitable signed name 857 */ 858 static String methodSignature(final String name, final Object[] args) { 859 if (args != null && args.length > 0) { 860 final StringBuilder strb = new StringBuilder(name); 861 strb.append('('); 862 for (int a = 0; a < args.length; ++a) { 863 if (a > 0) { 864 strb.append(", "); 865 } 866 final Class<?> clazz = args[a] == null ? Object.class : args[a].getClass(); 867 strb.append(clazz.getSimpleName()); 868 } 869 strb.append(')'); 870 return strb.toString(); 871 } 872 return name; 873 } 874 875 /** 876 * Generates a message for an operator error. 877 * 878 * @param node the node where the error occurred 879 * @param symbol the operator name 880 * @return the error message 881 */ 882 public static String operatorError(final JexlNode node, final String symbol) { 883 final StringBuilder msg = errorAt(node); 884 msg.append("error calling operator '"); 885 msg.append(symbol); 886 msg.append('\''); 887 return msg.toString(); 888 } 889 890 /** 891 * Generates a message for an unsolvable property error. 892 * 893 * @param node the node where the error occurred 894 * @param var the variable 895 * @return the error message 896 * @deprecated 3.2 897 */ 898 @Deprecated 899 public static String propertyError(final JexlNode node, final String var) { 900 return propertyError(node, var, true); 901 } 902 903 /** 904 * Generates a message for an unsolvable property error. 905 * 906 * @param node the node where the error occurred 907 * @param pty the property 908 * @param undef whether the property is null or undefined 909 * @return the error message 910 */ 911 public static String propertyError(final JexlNode node, final String pty, final boolean undef) { 912 final StringBuilder msg = errorAt(node); 913 if (undef) { 914 msg.append("unsolvable"); 915 } else { 916 msg.append("null value"); 917 } 918 msg.append(" property '"); 919 msg.append(pty); 920 msg.append('\''); 921 return msg.toString(); 922 } 923 924 /** 925 * Removes a slice from a source. 926 * 927 * @param src the source 928 * @param froml the beginning line 929 * @param fromc the beginning column 930 * @param tol the ending line 931 * @param toc the ending column 932 * @return the source with the (begin) to (to) zone removed 933 */ 934 public static String sliceSource(final String src, final int froml, final int fromc, final int tol, final int toc) { 935 final BufferedReader reader = new BufferedReader(new StringReader(src)); 936 final StringBuilder buffer = new StringBuilder(); 937 String line; 938 int cl = 1; 939 try { 940 while ((line = reader.readLine()) != null) { 941 if (cl < froml || cl > tol) { 942 buffer.append(line).append('\n'); 943 } else { 944 if (cl == froml) { 945 buffer.append(line, 0, fromc - 1); 946 } 947 if (cl == tol) { 948 buffer.append(line.substring(toc + 1)); 949 } 950 } // else ignore line 951 cl += 1; 952 } 953 } catch (final IOException xignore) { 954 //damn the checked exceptions :-) 955 } 956 return buffer.toString(); 957 } 958 959 /** 960 * Wrap an invocation exception. 961 * <p>Return the cause if it is already a JexlException. 962 * 963 * @param xinvoke the invocation exception 964 * @return a JexlException 965 */ 966 public static JexlException tryFailed(final InvocationTargetException xinvoke) { 967 final Throwable cause = xinvoke.getCause(); 968 return cause instanceof JexlException 969 ? (JexlException) cause 970 : new JexlException.TryFailed(xinvoke); // fail 971 } 972 973 /** 974 * Unwraps the cause of a throwable due to reflection. 975 * 976 * @param xthrow the throwable 977 * @return the cause 978 */ 979 static Throwable unwrap(final Throwable xthrow) { 980 if (xthrow instanceof TryFailed 981 || xthrow instanceof InvocationTargetException 982 || xthrow instanceof UndeclaredThrowableException) { 983 return xthrow.getCause(); 984 } 985 return xthrow; 986 } 987 988 /** 989 * Generates a message for a variable error. 990 * 991 * @param node the node where the error occurred 992 * @param variable the variable 993 * @param undef whether the variable is null or undefined 994 * @return the error message 995 * @deprecated 3.2 996 */ 997 @Deprecated 998 public static String variableError(final JexlNode node, final String variable, final boolean undef) { 999 return variableError(node, variable, undef? VariableIssue.UNDEFINED : VariableIssue.NULLVALUE); 1000 } 1001 1002 /** 1003 * Generates a message for a variable error. 1004 * 1005 * @param node the node where the error occurred 1006 * @param variable the variable 1007 * @param issue the variable kind of issue 1008 * @return the error message 1009 */ 1010 public static String variableError(final JexlNode node, final String variable, final VariableIssue issue) { 1011 final StringBuilder msg = errorAt(node); 1012 msg.append(issue.message(variable)); 1013 return msg.toString(); 1014 } 1015 1016 /** The point of origin for this exception. */ 1017 private final transient JexlNode mark; 1018 1019 /** The debug info. */ 1020 private final transient JexlInfo info; 1021 1022 /** 1023 * Creates a new JexlException. 1024 * 1025 * @param jinfo the debugging information associated 1026 * @param msg the error message 1027 * @param cause the exception causing the error 1028 */ 1029 public JexlException(final JexlInfo jinfo, final String msg, final Throwable cause) { 1030 super(msg != null ? msg : "", unwrap(cause)); 1031 mark = null; 1032 info = jinfo; 1033 } 1034 1035 /** 1036 * Creates a new JexlException. 1037 * 1038 * @param node the node causing the error 1039 * @param msg the error message 1040 */ 1041 public JexlException(final JexlNode node, final String msg) { 1042 this(node, msg, null); 1043 } 1044 1045 /** 1046 * Creates a new JexlException. 1047 * 1048 * @param node the node causing the error 1049 * @param msg the error message 1050 * @param cause the exception causing the error 1051 */ 1052 public JexlException(final JexlNode node, final String msg, final Throwable cause) { 1053 this(node, msg != null ? msg : "", unwrap(cause), true); 1054 } 1055 1056 /** 1057 * Creates a new JexlException. 1058 * 1059 * @param node the node causing the error 1060 * @param msg the error message 1061 * @param cause the exception causing the error 1062 * @param trace whether this exception has a stack trace and can <em>not</em> be suppressed 1063 */ 1064 protected JexlException(final JexlNode node, final String msg, final Throwable cause, final boolean trace) { 1065 super(msg != null ? msg : "", unwrap(cause), !trace, trace); 1066 if (node != null) { 1067 mark = node; 1068 info = node.jexlInfo(); 1069 } else { 1070 mark = null; 1071 info = null; 1072 } 1073 } 1074 1075 /** 1076 * Cleans a JexlException from any org.apache.commons.jexl3.internal stack trace element. 1077 * 1078 * @return this exception 1079 */ 1080 public JexlException clean() { 1081 return clean(this); 1082 } 1083 1084 /** 1085 * Accesses detailed message. 1086 * 1087 * @return the message 1088 */ 1089 protected String detailedMessage() { 1090 final Class<? extends JexlException> clazz = getClass(); 1091 final String name = clazz == JexlException.class? "JEXL" : clazz.getSimpleName().toLowerCase(); 1092 return name + " error : " + getDetail(); 1093 } 1094 1095 /** 1096 * Gets the exception specific detail 1097 * 1098 * @return this exception specific detail 1099 * @since 3.2 1100 */ 1101 public final String getDetail() { 1102 return super.getMessage(); 1103 } 1104 1105 /** 1106 * Gets the specific information for this exception. 1107 * 1108 * @return the information 1109 */ 1110 public JexlInfo getInfo() { 1111 return detailedInfo(mark, info); 1112 } 1113 1114 /** 1115 * Detailed info message about this error. 1116 * <p> 1117 * Format is "debug![begin,end]: string \n msg" where: 1118 * </p> 1119 * <ul> 1120 * <li>debug is the debugging information if it exists {@link JexlBuilder#debug(boolean)}</li> 1121 * <li>begin, end are character offsets in the string for the precise location of the error</li> 1122 * <li>string is the string representation of the offending expression</li> 1123 * <li>msg is the actual explanation message for this error</li> 1124 * </ul> 1125 * 1126 * @see JexlEngine#isDebug() 1127 * @return this error as a string 1128 */ 1129 @Override 1130 public String getMessage() { 1131 final StringBuilder msg = new StringBuilder(); 1132 if (info != null) { 1133 msg.append(info.toString()); 1134 } else { 1135 msg.append("?:"); 1136 } 1137 msg.append(' '); 1138 msg.append(detailedMessage()); 1139 final Throwable cause = getCause(); 1140 if (cause instanceof JexlArithmetic.NullOperand) { 1141 msg.append(" caused by null operand"); 1142 } 1143 return msg.toString(); 1144 } 1145 1146 /** 1147 * Pleasing checkstyle. 1148 * 1149 * @return the info 1150 */ 1151 protected JexlInfo info() { 1152 return info; 1153 } 1154 1155 /** 1156 * Formats an error message from the parser. 1157 * 1158 * @param prefix the prefix to the message 1159 * @param expr the expression in error 1160 * @return the formatted message 1161 */ 1162 protected String parserError(final String prefix, final String expr) { 1163 final int length = expr.length(); 1164 if (length < MAX_EXCHARLOC) { 1165 return prefix + " error in '" + expr + "'"; 1166 } 1167 final int me = MAX_EXCHARLOC / 2; 1168 int begin = info.getColumn() - me; 1169 if (begin < 0 || length < me) { 1170 begin = 0; 1171 } else if (begin > length) { 1172 begin = me; 1173 } 1174 int end = begin + MAX_EXCHARLOC; 1175 if (end > length) { 1176 end = length; 1177 } 1178 return prefix + " error near '... " 1179 + expr.substring(begin, end) + " ...'"; 1180 } 1181}