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