    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    package org.apache.commons.jexl2;
    018
    019    import java.lang.reflect.Array;
    020    import java.lang.reflect.InvocationTargetException;
    021    import java.util.Collection;
    022    import java.util.HashMap;
    023    import java.util.Iterator;
    024    import java.util.Map;
    025    import java.util.Set;
    026
    027    import org.apache.commons.jexl2.parser.SimpleNode;
    028    import org.apache.commons.logging.Log;
    029
    030    import org.apache.commons.jexl2.parser.ASTFloatLiteral;
    031    import org.apache.commons.jexl2.parser.ASTIntegerLiteral;
    032    import org.apache.commons.jexl2.parser.JexlNode;
    033    import org.apache.commons.jexl2.parser.ASTAdditiveNode;
    034    import org.apache.commons.jexl2.parser.ASTAdditiveOperator;
    035    import org.apache.commons.jexl2.parser.ASTAndNode;
    036    import org.apache.commons.jexl2.parser.ASTAmbiguous;
    037    import org.apache.commons.jexl2.parser.ASTArrayAccess;
    038    import org.apache.commons.jexl2.parser.ASTArrayLiteral;
    039    import org.apache.commons.jexl2.parser.ASTAssignment;
    040    import org.apache.commons.jexl2.parser.ASTBitwiseAndNode;
    041    import org.apache.commons.jexl2.parser.ASTBitwiseComplNode;
    042    import org.apache.commons.jexl2.parser.ASTBitwiseOrNode;
    043    import org.apache.commons.jexl2.parser.ASTBitwiseXorNode;
    044    import org.apache.commons.jexl2.parser.ASTBlock;
    045    import org.apache.commons.jexl2.parser.ASTConstructorNode;
    046    import org.apache.commons.jexl2.parser.ASTDivNode;
    047    import org.apache.commons.jexl2.parser.ASTEQNode;
    048    import org.apache.commons.jexl2.parser.ASTERNode;
    049    import org.apache.commons.jexl2.parser.ASTEmptyFunction;
    050    import org.apache.commons.jexl2.parser.ASTFalseNode;
    051    import org.apache.commons.jexl2.parser.ASTFunctionNode;
    052    import org.apache.commons.jexl2.parser.ASTForeachStatement;
    053    import org.apache.commons.jexl2.parser.ASTGENode;
    054    import org.apache.commons.jexl2.parser.ASTGTNode;
    055    import org.apache.commons.jexl2.parser.ASTIdentifier;
    056    import org.apache.commons.jexl2.parser.ASTIfStatement;
    057    import org.apache.commons.jexl2.parser.ASTJexlScript;
    058    import org.apache.commons.jexl2.parser.ASTLENode;
    059    import org.apache.commons.jexl2.parser.ASTLTNode;
    060    import org.apache.commons.jexl2.parser.ASTMapEntry;
    061    import org.apache.commons.jexl2.parser.ASTMapLiteral;
    062    import org.apache.commons.jexl2.parser.ASTMethodNode;
    063    import org.apache.commons.jexl2.parser.ASTModNode;
    064    import org.apache.commons.jexl2.parser.ASTMulNode;
    065    import org.apache.commons.jexl2.parser.ASTNENode;
    066    import org.apache.commons.jexl2.parser.ASTNRNode;
    067    import org.apache.commons.jexl2.parser.ASTNotNode;
    068    import org.apache.commons.jexl2.parser.ASTNullLiteral;
    069    import org.apache.commons.jexl2.parser.ASTNumberLiteral;
    070    import org.apache.commons.jexl2.parser.ASTOrNode;
    071    import org.apache.commons.jexl2.parser.ASTReference;
    072    import org.apache.commons.jexl2.parser.ASTReferenceExpression;
    073    import org.apache.commons.jexl2.parser.ASTReturnStatement;
    074    import org.apache.commons.jexl2.parser.ASTSizeFunction;
    075    import org.apache.commons.jexl2.parser.ASTSizeMethod;
    076    import org.apache.commons.jexl2.parser.ASTStringLiteral;
    077    import org.apache.commons.jexl2.parser.ASTTernaryNode;
    078    import org.apache.commons.jexl2.parser.ASTTrueNode;
    079    import org.apache.commons.jexl2.parser.ASTUnaryMinusNode;
    080    import org.apache.commons.jexl2.parser.ASTWhileStatement;
    081    import org.apache.commons.jexl2.parser.Node;
    082    import org.apache.commons.jexl2.parser.ParserVisitor;
    083
    084    import org.apache.commons.jexl2.introspection.Uberspect;
    085    import org.apache.commons.jexl2.introspection.JexlMethod;
    086    import org.apache.commons.jexl2.introspection.JexlPropertyGet;
    087    import org.apache.commons.jexl2.introspection.JexlPropertySet;
    088    import org.apache.commons.jexl2.parser.ASTVar;
    089
    090    /**
    091     * An interpreter of JEXL syntax.
    092     *
    093     * @since 2.0
    094     */
    095    public class Interpreter implements ParserVisitor {
    096        /** The logger. */
    097        protected final Log logger;
    098        /** The uberspect. */
    099        protected final Uberspect uberspect;
    100        /** The arithmetic handler. */
    101        protected final JexlArithmetic arithmetic;
    102        /** The map of registered functions. */
    103        protected final Map<String, Object> functions;
    104        /** The map of registered functions. */
    105        protected Map<String, Object> functors;
    106        /** The context to store/retrieve variables. */
    107        protected final JexlContext context;
    108        /** Strict interpreter flag. Do not modify; will be made final/private in a later version. */
    109        protected boolean strict;
    110        /** Silent intepreter flag.  Do not modify; will be made final/private in a later version. */
    111        protected boolean silent;
    112        /** Cache executors. */
    113        protected final boolean cache;
    114        /** Registers or arguments. */
    115        protected Object[] registers = null;
    116        /**
    117         * Parameter names if any.
    118         * Intended for use in debugging; not currently used externally.
    119         * @since 2.1
    120         */
    121        @SuppressWarnings("unused")
    122        private String[] parameters = null;
    123
    124        /**
    125         * Cancellation support.
    126         * @see #isCancelled()
    127         * @since 2.1
    128         */
    129        private volatile boolean cancelled = false;
    130
    131        /** Empty parameters for method matching. */
    132        protected static final Object[] EMPTY_PARAMS = new Object[0];
    133
    134        /**
    135         * Creates an interpreter.
    136         * @param jexl the engine creating this interpreter
    137         * @param aContext the context to evaluate expression
    138         * @deprecated
    139         */
    140        @Deprecated
    141        public Interpreter(JexlEngine jexl, JexlContext aContext) {
    142            this(jexl, aContext, !jexl.isLenient(), jexl.isSilent());
    143        }
    144
    145        /**
    146         * Creates an interpreter.
    147         * @param jexl the engine creating this interpreter
    148         * @param aContext the context to evaluate expression
    149         * @param strictFlag whether this interpreter runs in strict mode
    150         * @param silentFlag whether this interpreter runs in silent mode
    151         * @since 2.1
    152         */
    153        public Interpreter(JexlEngine jexl, JexlContext aContext, boolean strictFlag, boolean silentFlag) {
    154            this.logger = jexl.logger;
    155            this.uberspect = jexl.uberspect;
    156            this.arithmetic = jexl.arithmetic;
    157            this.functions = jexl.functions;
    158            this.strict = strictFlag;
    159            this.silent = silentFlag;
    160            this.cache = jexl.cache != null;
    161            this.context = aContext != null? aContext : JexlEngine.EMPTY_CONTEXT;
    162            this.functors = null;
    163        }
    164
    165        /**
    166         * Copy constructor.
    167         * @param base the base to copy
    168         * @since 2.1
    169         */
    170        protected Interpreter(Interpreter base) {
    171            this.logger = base.logger;
    172            this.uberspect = base.uberspect;
    173            this.arithmetic = base.arithmetic;
    174            this.functions = base.functions;
    175            this.strict = base.strict;
    176            this.silent = base.silent;
    177            this.cache = base.cache;
    178            this.context = base.context;
    179            this.functors = base.functors;
    180        }
    181
    182        /**
    183         * Sets whether this interpreter considers unknown variables, methods and constructors as errors.
    184         * @param flag true for strict, false for lenient
    185         * @deprecated Do not use; will be removed in a later version
    186         * @since 2.1
    187         */
    188        // TODO why add a method and then deprecate it?
    189        @Deprecated
    190        public void setStrict(boolean flag) {
    191            this.strict = flag;
    192        }
    193
    194        /**
    195         * Sets whether this interpreter throws JexlException when encountering errors.
    196         * @param flag true for silent, false for verbose
    197         * @deprecated Do not use; will be removed in a later version
    198         */
    199        @Deprecated
    200        public void setSilent(boolean flag) {
    201            this.silent = flag;
    202        }
    203
    204        /**
    205         * Checks whether this interpreter considers unknown variables, methods and constructors as errors.
    206         * @return true if strict, false otherwise
    207         * @since 2.1
    208         */
    209        public boolean isStrict() {
    210            return this.strict;
    211        }
    212
    213        /**
    214         * Checks whether this interpreter throws JexlException when encountering errors.
    215         * @return true if silent, false otherwise
    216         */
    217        public boolean isSilent() {
    218            return this.silent;
    219        }
    220
    221        /**
    222         * Interpret the given script/expression.
    223         * <p>
    224         * If the underlying JEXL engine is silent, errors will be logged through its logger as info.
    225         * </p>
    226         * @param node the script or expression to interpret.
    227         * @return the result of the interpretation.
    228         * @throws JexlException if any error occurs during interpretation.
    229         */
    230        public Object interpret(JexlNode node) {
    231            try {
    232                return node.jjtAccept(this, null);
    233            } catch (JexlException.Return xreturn) {
    234                Object value = xreturn.getValue();
    235                return value;
    236            } catch (JexlException xjexl) {
    237                if (silent) {
    238                    logger.warn(xjexl.getMessage(), xjexl.getCause());
    239                    return null;
    240                }
    241                throw xjexl;
    242            } finally {
    243                functors = null;
    244                parameters = null;
    245                registers = null;
    246            }
    247        }
    248
    249        /**
    250         * Gets the context.
    251         * @return the {@link JexlContext} used for evaluation.
    252         * @since 2.1
    253         */
    254        protected JexlContext getContext() {
    255            return context;
    256        }
    257
    258        /**
    259         * Gets the uberspect.
    260         * @return an {@link Uberspect}
    261         */
    262        protected Uberspect getUberspect() {
    263            return uberspect;
    264        }
    265
    266        /**
    267         * Sets this interpreter registers for bean access/assign expressions.
    268         * <p>Use setFrame(...) instead.</p>
    269         * @param theRegisters the array of registers
    270         */
    271        @Deprecated
    272        protected void setRegisters(Object... theRegisters) {
    273            if (theRegisters != null) {
    274                String[] regStrs = new String[theRegisters.length];
    275                for (int r = 0; r < regStrs.length; ++r) {
    276                    regStrs[r] = "#" + r;
    277                }
    278                this.parameters = regStrs;
    279            }
    280            this.registers = theRegisters;
    281        }
    282
    283        /**
    284         * Sets this interpreter parameters and arguments.
    285         * @param frame the calling frame
    286         * @since 2.1
    287         */
    288        protected void setFrame(JexlEngine.Frame frame) {
    289            if (frame != null) {
    290                this.parameters = frame.getParameters();
    291                this.registers = frame.getRegisters();
    292            } else {
    293                this.parameters = null;
    294                this.registers = null;
    295            }
    296        }
    297
    298        /**
    299         * Finds the node causing a NPE for diadic operators.
    300         * @param xrt the RuntimeException
    301         * @param node the parent node
    302         * @param left the left argument
    303         * @param right the right argument
    304         * @return the left, right or parent node
    305         */
    306        protected JexlNode findNullOperand(RuntimeException xrt, JexlNode node, Object left, Object right) {
    307            if (xrt instanceof ArithmeticException
    308                    && JexlException.NULL_OPERAND == xrt.getMessage()) {
    309                if (left == null) {
    310                    return node.jjtGetChild(0);
    311                }
    312                if (right == null) {
    313                    return node.jjtGetChild(1);
    314                }
    315            }
    316            return node;
    317        }
    318
    319        /**
    320         * Triggered when variable can not be resolved.
    321         * @param xjexl the JexlException ("undefined variable " + variable)
    322         * @return throws JexlException if strict, null otherwise
    323         */
    324        protected Object unknownVariable(JexlException xjexl) {
    325            if (strict) {
    326                throw xjexl;
    327            }
    328            if (!silent) {
    329                logger.warn(xjexl.getMessage());
    330            }
    331            return null;
    332        }
    333
    334        /**
    335         * Triggered when method, function or constructor invocation fails.
    336         * @param xjexl the JexlException wrapping the original error
    337         * @return throws JexlException if strict, null otherwise
    338         */
    339        protected Object invocationFailed(JexlException xjexl) {
    340            if (strict || xjexl instanceof JexlException.Return) {
    341                throw xjexl;
    342            }
    343            if (!silent) {
    344                logger.warn(xjexl.getMessage(), xjexl.getCause());
    345            }
    346            return null;
    347        }
    348
    349        /**
    350         * Checks whether this interpreter execution was cancelled due to thread interruption.
    351         * @return true if cancelled, false otherwise
    352         * @since 2.1
    353         */
    354        protected boolean isCancelled() {
    355            if (cancelled | Thread.interrupted()) {
    356                cancelled = true;
    357            }
    358            return cancelled;
    359        }
    360
    361        /**
    362         * Resolves a namespace, eventually allocating an instance using context as constructor argument.
    363         * The lifetime of such instances span the current expression or script evaluation.
    364         *
    365         * @param prefix the prefix name (may be null for global namespace)
    366         * @param node the AST node
    367         * @return the namespace instance
    368         */
    369        protected Object resolveNamespace(String prefix, JexlNode node) {
    370            Object namespace = null;
    371            // check whether this namespace is a functor
    372            if (functors != null) {
    373                namespace = functors.get(prefix);
    374                if (namespace != null) {
    375                    return namespace;
    376                }
    377            }
    378            // check if namespace if a resolver
    379            if (context instanceof NamespaceResolver) {
    380                namespace = ((NamespaceResolver) context).resolveNamespace(prefix);
    381            }
    382            if (namespace == null) {
    383                namespace = functions.get(prefix);
    384                if (prefix != null && namespace == null) {
    385                    throw new JexlException(node, "no such function namespace " + prefix);
    386                }
    387            }
    388            // allow namespace to be instantiated as functor with context if possible, not an error otherwise
    389            if (namespace instanceof Class<?>) {
    390                Object[] args = new Object[]{context};
    391                JexlMethod ctor = uberspect.getConstructorMethod(namespace, args, node);
    392                if (ctor != null) {
    393                    try {
    394                        namespace = ctor.invoke(namespace, args);
    395                        if (functors == null) {
    396                            functors = new HashMap<String, Object>();
    397                        }
    398                        functors.put(prefix, namespace);
    399                    } catch (Exception xinst) {
    400                        throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
    401                    }
    402                }
    403            }
    404            return namespace;
    405        }
    406
    407        /** {@inheritDoc} */
    408        public Object visit(ASTAdditiveNode node, Object data) {
    409            /**
    410             * The pattern for exception mgmt is to let the child*.jjtAccept
    411             * out of the try/catch loop so that if one fails, the ex will
    412             * traverse up to the interpreter.
    413             * In cases where this is not convenient/possible, JexlException must
    414             * be caught explicitly and rethrown.
    415             */
    416            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    417            for (int c = 2, size = node.jjtGetNumChildren(); c < size; c += 2) {
    418                Object right = node.jjtGetChild(c).jjtAccept(this, data);
    419                try {
    420                    JexlNode op = node.jjtGetChild(c - 1);
    421                    if (op instanceof ASTAdditiveOperator) {
    422                        String which = op.image;
    423                        if ("+".equals(which)) {
    424                            left = arithmetic.add(left, right);
    425                            continue;
    426                        }
    427                        if ("-".equals(which)) {
    428                            left = arithmetic.subtract(left, right);
    429                            continue;
    430                        }
    431                        throw new UnsupportedOperationException("unknown operator " + which);
    432                    }
    433                    throw new IllegalArgumentException("unknown operator " + op);
    434                } catch (ArithmeticException xrt) {
    435                    JexlNode xnode = findNullOperand(xrt, node, left, right);
    436                    throw new JexlException(xnode, "+/- error", xrt);
    437                }
    438            }
    439            return left;
    440        }
    441
    442        /** {@inheritDoc} */
    443        public Object visit(ASTAdditiveOperator node, Object data) {
    444            throw new UnsupportedOperationException("Shoud not be called.");
    445        }
    446
    447        /** {@inheritDoc} */
    448        public Object visit(ASTAndNode node, Object data) {
    449            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    450            try {
    451                boolean leftValue = arithmetic.toBoolean(left);
    452                if (!leftValue) {
    453                    return Boolean.FALSE;
    454                }
    455            } catch (RuntimeException xrt) {
    456                throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
    457            }
    458            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    459            try {
    460                boolean rightValue = arithmetic.toBoolean(right);
    461                if (!rightValue) {
    462                    return Boolean.FALSE;
    463                }
    464            } catch (ArithmeticException xrt) {
    465                throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt);
    466            }
    467            return Boolean.TRUE;
    468        }
    469
    470        /** {@inheritDoc} */
    471        public Object visit(ASTArrayAccess node, Object data) {
    472            // first objectNode is the identifier
    473            Object object = node.jjtGetChild(0).jjtAccept(this, data);
    474            // can have multiple nodes - either an expression, integer literal or reference
    475            int numChildren = node.jjtGetNumChildren();
    476            for (int i = 1; i < numChildren; i++) {
    477                JexlNode nindex = node.jjtGetChild(i);
    478                if (nindex instanceof JexlNode.Literal<?>) {
    479                    object = nindex.jjtAccept(this, object);
    480                } else {
    481                    Object index = nindex.jjtAccept(this, null);
    482                    object = getAttribute(object, index, nindex);
    483                }
    484            }
    485
    486            return object;
    487        }
    488
    489        /** {@inheritDoc} */
    490        public Object visit(ASTArrayLiteral node, Object data) {
    491            Object literal = node.getLiteral();
    492            if (literal == null) {
    493                int childCount = node.jjtGetNumChildren();
    494                Object[] array = new Object[childCount];
    495                for (int i = 0; i < childCount; i++) {
    496                    Object entry = node.jjtGetChild(i).jjtAccept(this, data);
    497                    array[i] = entry;
    498                }
    499                literal = arithmetic.narrowArrayType(array);
    500                node.setLiteral(literal);
    501            }
    502            return literal;
    503        }
    504
    505        /** {@inheritDoc} */
    506        public Object visit(ASTAssignment node, Object data) {
    507            // left contains the reference to assign to
    508            int register = -1;
    509            JexlNode left = node.jjtGetChild(0);
    510            if (left instanceof ASTIdentifier) {
    511                ASTIdentifier var = (ASTIdentifier) left;
    512                register = var.getRegister();
    513                if (register < 0) {
    514                    throw new JexlException(left, "unknown variable " + left.image);
    515                }
    516            } else if (!(left instanceof ASTReference)) {
    517                throw new JexlException(left, "illegal assignment form 0");
    518            }
    519            // right is the value expression to assign
    520            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    521
    522            // determine initial object & property:
    523            JexlNode objectNode = null;
    524            Object object = register >= 0 ? registers[register] : null;
    525            JexlNode propertyNode = null;
    526            Object property = null;
    527            boolean isVariable = true;
    528            int v = 0;
    529            StringBuilder variableName = null;
    530            // 1: follow children till penultimate, resolve dot/array
    531            int last = left.jjtGetNumChildren() - 1;
    532            // check we are not assigning a register itself
    533            boolean isRegister = last < 0 && register >= 0;
    534            // start at 1 if register
    535            for (int c = register >= 0 ? 1 : 0; c < last; ++c) {
    536                objectNode = left.jjtGetChild(c);
    537                // evaluate the property within the object
    538                object = objectNode.jjtAccept(this, object);
    539                if (object != null) {
    540                    continue;
    541                }
    542                isVariable &= objectNode instanceof ASTIdentifier
    543                        || (objectNode instanceof ASTNumberLiteral && ((ASTNumberLiteral) objectNode).isInteger());
    544                // if we get null back as a result, check for an ant variable
    545                if (isVariable) {
    546                    if (v == 0) {
    547                        variableName = new StringBuilder(left.jjtGetChild(0).image);
    548                        v = 1;
    549                    }
    550                    for (; v <= c; ++v) {
    551                        variableName.append('.');
    552                        variableName.append(left.jjtGetChild(v).image);
    553                    }
    554                    object = context.get(variableName.toString());
    555                    // disallow mixing ant & bean with same root; avoid ambiguity
    556                    if (object != null) {
    557                        isVariable = false;
    558                    }
    559                } else {
    560                    throw new JexlException(objectNode, "illegal assignment form");
    561                }
    562            }
    563            // 2: last objectNode will perform assignement in all cases
    564            propertyNode = isRegister ? null : left.jjtGetChild(last);
    565            boolean antVar = false;
    566            if (propertyNode instanceof ASTIdentifier) {
    567                ASTIdentifier identifier = (ASTIdentifier) propertyNode;
    568                register = identifier.getRegister();
    569                if (register >= 0) {
    570                    isRegister = true;
    571                } else {
    572                    property = identifier.image;
    573                    antVar = true;
    574                }
    575            } else if (propertyNode instanceof ASTNumberLiteral && ((ASTNumberLiteral) propertyNode).isInteger()) {
    576                property = ((ASTNumberLiteral) propertyNode).getLiteral();
    577                antVar = true;
    578            } else if (propertyNode instanceof ASTArrayAccess) {
    579                // first objectNode is the identifier
    580                objectNode = propertyNode;
    581                ASTArrayAccess narray = (ASTArrayAccess) objectNode;
    582                Object nobject = narray.jjtGetChild(0).jjtAccept(this, object);
    583                if (nobject == null) {
    584                    throw new JexlException(objectNode, "array element is null");
    585                } else {
    586                    object = nobject;
    587                }
    588                // can have multiple nodes - either an expression, integer literal or
    589                // reference
    590                last = narray.jjtGetNumChildren() - 1;
    591                for (int i = 1; i < last; i++) {
    592                    objectNode = narray.jjtGetChild(i);
    593                    if (objectNode instanceof JexlNode.Literal<?>) {
    594                        object = objectNode.jjtAccept(this, object);
    595                    } else {
    596                        Object index = objectNode.jjtAccept(this, null);
    597                        object = getAttribute(object, index, objectNode);
    598                    }
    599                }
    600                property = narray.jjtGetChild(last).jjtAccept(this, null);
    601            } else if (!isRegister) {
    602                throw new JexlException(objectNode, "illegal assignment form");
    603            }
    604            // deal with ant variable; set context
    605            if (isRegister) {
    606                registers[register] = right;
    607                return right;
    608            } else if (antVar) {
    609                if (isVariable && object == null) {
    610                    if (variableName != null) {
    611                        if (last > 0) {
    612                            variableName.append('.');
    613                        }
    614                        variableName.append(property);
    615                        property = variableName.toString();
    616                    }
    617                    try {
    618                        context.set(String.valueOf(property), right);
    619                    } catch (UnsupportedOperationException xsupport) {
    620                        throw new JexlException(node, "context is readonly", xsupport);
    621                    }
    622                    return right;
    623                }
    624            }
    625            if (property == null) {
    626                // no property, we fail
    627                throw new JexlException(propertyNode, "property is null");
    628            }
    629            if (object == null) {
    630                // no object, we fail
    631                throw new JexlException(objectNode, "bean is null");
    632            }
    633            // one before last, assign
    634            setAttribute(object, property, right, propertyNode);
    635            return right;
    636        }
    637
    638        /** {@inheritDoc} */
    639        public Object visit(ASTBitwiseAndNode node, Object data) {
    640            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    641            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    642            try {
    643                return arithmetic.bitwiseAnd(left, right);
    644            } catch (ArithmeticException xrt) {
    645                throw new JexlException(node, "& error", xrt);
    646            }
    647        }
    648
    649        /** {@inheritDoc} */
    650        public Object visit(ASTBitwiseComplNode node, Object data) {
    651            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    652            try {
    653                return arithmetic.bitwiseComplement(left);
    654            } catch (ArithmeticException xrt) {
    655                throw new JexlException(node, "~ error", xrt);
    656            }
    657        }
    658
    659        /** {@inheritDoc} */
    660        public Object visit(ASTBitwiseOrNode node, Object data) {
    661            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    662            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    663            try {
    664                return arithmetic.bitwiseOr(left, right);
    665            } catch (ArithmeticException xrt) {
    666                throw new JexlException(node, "| error", xrt);
    667            }
    668        }
    669
    670        /** {@inheritDoc} */
    671        public Object visit(ASTBitwiseXorNode node, Object data) {
    672            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    673            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    674            try {
    675                return arithmetic.bitwiseXor(left, right);
    676            } catch (ArithmeticException xrt) {
    677                throw new JexlException(node, "^ error", xrt);
    678            }
    679        }
    680
    681        /** {@inheritDoc} */
    682        public Object visit(ASTBlock node, Object data) {
    683            int numChildren = node.jjtGetNumChildren();
    684            Object result = null;
    685            for (int i = 0; i < numChildren; i++) {
    686                result = node.jjtGetChild(i).jjtAccept(this, data);
    687            }
    688            return result;
    689        }
    690
    691        /** {@inheritDoc} */
    692        public Object visit(ASTDivNode node, Object data) {
    693            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    694            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    695            try {
    696                return arithmetic.divide(left, right);
    697            } catch (ArithmeticException xrt) {
    698                if (!strict) {
    699                    return new Double(0.0);
    700                }
    701                JexlNode xnode = findNullOperand(xrt, node, left, right);
    702                throw new JexlException(xnode, "divide error", xrt);
    703            }
    704        }
    705
    706        /** {@inheritDoc} */
    707        public Object visit(ASTEmptyFunction node, Object data) {
    708            Object o = node.jjtGetChild(0).jjtAccept(this, data);
    709            if (o == null) {
    710                return Boolean.TRUE;
    711            }
    712            if (o instanceof String && "".equals(o)) {
    713                return Boolean.TRUE;
    714            }
    715            if (o.getClass().isArray() && ((Object[]) o).length == 0) {
    716                return Boolean.TRUE;
    717            }
    718            if (o instanceof Collection<?>) {
    719                return ((Collection<?>) o).isEmpty() ? Boolean.TRUE : Boolean.FALSE;
    720            }
    721            // Map isn't a collection
    722            if (o instanceof Map<?, ?>) {
    723                return ((Map<?, ?>) o).isEmpty() ? Boolean.TRUE : Boolean.FALSE;
    724            }
    725            return Boolean.FALSE;
    726        }
    727
    728        /** {@inheritDoc} */
    729        public Object visit(ASTEQNode node, Object data) {
    730            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    731            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    732            try {
    733                return arithmetic.equals(left, right) ? Boolean.TRUE : Boolean.FALSE;
    734            } catch (ArithmeticException xrt) {
    735                throw new JexlException(node, "== error", xrt);
    736            }
    737        }
    738
    739        /** {@inheritDoc} */
    740        public Object visit(ASTFalseNode node, Object data) {
    741            return Boolean.FALSE;
    742        }
    743
    744        /** {@inheritDoc} */
    745        public Object visit(ASTForeachStatement node, Object data) {
    746            Object result = null;
    747            /* first objectNode is the loop variable */
    748            ASTReference loopReference = (ASTReference) node.jjtGetChild(0);
    749            ASTIdentifier loopVariable = (ASTIdentifier) loopReference.jjtGetChild(0);
    750            int register = loopVariable.getRegister();
    751            /* second objectNode is the variable to iterate */
    752            Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data);
    753            // make sure there is a value to iterate on and a statement to execute
    754            if (iterableValue != null && node.jjtGetNumChildren() >= 3) {
    755                /* third objectNode is the statement to execute */
    756                JexlNode statement = node.jjtGetChild(2);
    757                // get an iterator for the collection/array etc via the
    758                // introspector.
    759                Iterator<?> itemsIterator = uberspect.getIterator(iterableValue, node);
    760                if (itemsIterator != null) {
    761                    while (itemsIterator.hasNext()) {
    762                        if (isCancelled()) {
    763                            throw new JexlException.Cancel(node);
    764                        }
    765                        // set loopVariable to value of iterator
    766                        Object value = itemsIterator.next();
    767                        if (register < 0) {
    768                            context.set(loopVariable.image, value);
    769                        } else {
    770                            registers[register] = value;
    771                        }
    772                        // execute statement
    773                        result = statement.jjtAccept(this, data);
    774                    }
    775                }
    776            }
    777            return result;
    778        }
    779
    780        /** {@inheritDoc} */
    781        public Object visit(ASTGENode node, Object data) {
    782            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    783            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    784            try {
    785                return arithmetic.greaterThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE;
    786            } catch (ArithmeticException xrt) {
    787                throw new JexlException(node, ">= error", xrt);
    788            }
    789        }
    790
    791        /** {@inheritDoc} */
    792        public Object visit(ASTGTNode node, Object data) {
    793            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    794            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    795            try {
    796                return arithmetic.greaterThan(left, right) ? Boolean.TRUE : Boolean.FALSE;
    797            } catch (ArithmeticException xrt) {
    798                throw new JexlException(node, "> error", xrt);
    799            }
    800        }
    801
    802        /** {@inheritDoc} */
    803        public Object visit(ASTERNode node, Object data) {
    804            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    805            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    806            try {
    807                // use arithmetic / pattern matching ?
    808                if (right instanceof java.util.regex.Pattern || right instanceof String) {
    809                    return arithmetic.matches(left, right) ? Boolean.TRUE : Boolean.FALSE;
    810                }
    811                // left in right ? <=> right.contains(left) ?
    812                // try contains on collection
    813                if (right instanceof Set<?>) {
    814                    return ((Set<?>) right).contains(left) ? Boolean.TRUE : Boolean.FALSE;
    815                }
    816                // try contains on map key
    817                if (right instanceof Map<?, ?>) {
    818                    return ((Map<?, ?>) right).containsKey(left) ? Boolean.TRUE : Boolean.FALSE;
    819                }
    820                // try contains on collection
    821                if (right instanceof Collection<?>) {
    822                    return ((Collection<?>) right).contains(left) ? Boolean.TRUE : Boolean.FALSE;
    823                }
    824                // try a contains method (duck type set)
    825                try {
    826                    Object[] argv = {left};
    827                    JexlMethod vm = uberspect.getMethod(right, "contains", argv, node);
    828                    if (vm != null) {
    829                        return arithmetic.toBoolean(vm.invoke(right, argv)) ? Boolean.TRUE : Boolean.FALSE;
    830                    } else if (arithmetic.narrowArguments(argv)) {
    831                        vm = uberspect.getMethod(right, "contains", argv, node);
    832                        if (vm != null) {
    833                            return arithmetic.toBoolean(vm.invoke(right, argv)) ? Boolean.TRUE : Boolean.FALSE;
    834                        }
    835                    }
    836                } catch (InvocationTargetException e) {
    837                    throw new JexlException(node, "=~ invocation error", e.getCause());
    838                } catch (Exception e) {
    839                    throw new JexlException(node, "=~ error", e);
    840                }
    841                // try iterative comparison
    842                Iterator<?> it = uberspect.getIterator(right, node);
    843                if (it != null) {
    844                    while (it.hasNext()) {
    845                        Object next = it.next();
    846                        if (next == left || (next != null && next.equals(left))) {
    847                            return Boolean.TRUE;
    848                        }
    849                    }
    850                    return Boolean.FALSE;
    851                }
    852                // defaults to equal
    853                return arithmetic.equals(left, right) ? Boolean.TRUE : Boolean.FALSE;
    854            } catch (ArithmeticException xrt) {
    855                throw new JexlException(node, "=~ error", xrt);
    856            }
    857        }
    858
    859        /** {@inheritDoc} */
    860        public Object visit(ASTIdentifier node, Object data) {
    861            if (isCancelled()) {
    862                throw new JexlException.Cancel(node);
    863            }
    864            String name = node.image;
    865            if (data == null) {
    866                int register = node.getRegister();
    867                if (register >= 0) {
    868                    return registers[register];
    869                }
    870                Object value = context.get(name);
    871                if (value == null
    872                        && !(node.jjtGetParent() instanceof ASTReference)
    873                        && !context.has(name)
    874                        && !isTernaryProtected(node)) {
    875                    JexlException xjexl = new JexlException.Variable(node, name);
    876                    return unknownVariable(xjexl);
    877                }
    878                return value;
    879            } else {
    880                return getAttribute(data, name, node);
    881            }
    882        }
    883
    884        /**
    885         * @deprecated Do not use
    886         */
    887        @Deprecated
    888        public Object visit(ASTFloatLiteral node, Object data) {
    889            throw new UnsupportedOperationException("Method should not be called; only present for API compatibiltiy");
    890        }
    891
    892        /**
    893         * @deprecated Do not use
    894         */
    895        @Deprecated
    896        public Object visit(ASTIntegerLiteral node, Object data) {
    897            throw new UnsupportedOperationException("Method should not be called; only present for API compatibiltiy");
    898        }
    899
    900        /** {@inheritDoc} */
    901        public Object visit(ASTVar node, Object data) {
    902            return visit((ASTIdentifier) node, data);
    903        }
    904
    905        /** {@inheritDoc} */
    906        public Object visit(ASTIfStatement node, Object data) {
    907            int n = 0;
    908            try {
    909                Object result = null;
    910                /* first objectNode is the condition */
    911                Object expression = node.jjtGetChild(0).jjtAccept(this, data);
    912                if (arithmetic.toBoolean(expression)) {
    913                    // first objectNode is true statement
    914                    n = 1;
    915                    result = node.jjtGetChild(1).jjtAccept(this, data);
    916                } else {
    917                    // if there is a false, execute it. false statement is the second
    918                    // objectNode
    919                    if (node.jjtGetNumChildren() == 3) {
    920                        n = 2;
    921                        result = node.jjtGetChild(2).jjtAccept(this, data);
    922                    }
    923                }
    924                return result;
    925            } catch (JexlException error) {
    926                throw error;
    927            } catch (ArithmeticException xrt) {
    928                throw new JexlException(node.jjtGetChild(n), "if error", xrt);
    929            }
    930        }
    931
    932        /** {@inheritDoc} */
    933        public Object visit(ASTNumberLiteral node, Object data) {
    934            if (data != null && node.isInteger()) {
    935                return getAttribute(data, node.getLiteral(), node);
    936            }
    937            return node.getLiteral();
    938        }
    939
    940        /** {@inheritDoc} */
    941        public Object visit(ASTJexlScript node, Object data) {
    942            int numChildren = node.jjtGetNumChildren();
    943            Object result = null;
    944            for (int i = 0; i < numChildren; i++) {
    945                JexlNode child = node.jjtGetChild(i);
    946                result = child.jjtAccept(this, data);
    947            }
    948            return result;
    949        }
    950
    951        /** {@inheritDoc} */
    952        public Object visit(ASTLENode node, Object data) {
    953            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    954            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    955            try {
    956                return arithmetic.lessThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE;
    957            } catch (ArithmeticException xrt) {
    958                throw new JexlException(node, "<= error", xrt);
    959            }
    960        }
    961
    962        /** {@inheritDoc} */
    963        public Object visit(ASTLTNode node, Object data) {
    964            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    965            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    966            try {
    967                return arithmetic.lessThan(left, right) ? Boolean.TRUE : Boolean.FALSE;
    968            } catch (ArithmeticException xrt) {
    969                throw new JexlException(node, "< error", xrt);
    970            }
    971        }
    972
    973        /** {@inheritDoc} */
    974        public Object visit(ASTMapEntry node, Object data) {
    975            Object key = node.jjtGetChild(0).jjtAccept(this, data);
    976            Object value = node.jjtGetChild(1).jjtAccept(this, data);
    977            return new Object[]{key, value};
    978        }
    979
    980        /** {@inheritDoc} */
    981        public Object visit(ASTMapLiteral node, Object data) {
    982            int childCount = node.jjtGetNumChildren();
    983            Map<Object, Object> map = new HashMap<Object, Object>();
    984
    985            for (int i = 0; i < childCount; i++) {
    986                Object[] entry = (Object[]) (node.jjtGetChild(i)).jjtAccept(this, data);
    987                map.put(entry[0], entry[1]);
    988            }
    989
    990            return map;
    991        }
    992
    993        /**
    994         * Calls a method (or function).
    995         * <p>
    996         * Method resolution is a follows:
    997         * 1 - attempt to find a method in the bean passed as parameter;
    998         * 2 - if this fails, narrow the arguments and try again
    999         * 3 - if this still fails, seeks a Script or JexlMethod as a property of that bean.
    1000         * </p>
    1001         * @param node the method node
    1002         * @param bean the bean this method should be invoked upon
    1003         * @param methodNode the node carrying the method name
    1004         * @param argb the first argument index, child of the method node
    1005         * @return the result of the method invocation
    1006         */
    1007        private Object call(JexlNode node, Object bean, ASTIdentifier methodNode, int argb) {
    1008            if (isCancelled()) {
    1009                throw new JexlException.Cancel(node);
    1010            }
    1011            String methodName = methodNode.image;
    1012            // evaluate the arguments
    1013            int argc = node.jjtGetNumChildren() - argb;
    1014            Object[] argv = new Object[argc];
    1015            for (int i = 0; i < argc; i++) {
    1016                argv[i] = node.jjtGetChild(i + argb).jjtAccept(this, null);
    1017            }
    1018
    1019            JexlException xjexl = null;
    1020            try {
    1021                // attempt to reuse last executor cached in volatile JexlNode.value
    1022                if (cache) {
    1023                    Object cached = node.jjtGetValue();
    1024                    if (cached instanceof JexlMethod) {
    1025                        JexlMethod me = (JexlMethod) cached;
    1026                        Object eval = me.tryInvoke(methodName, bean, argv);
    1027                        if (!me.tryFailed(eval)) {
    1028                            return eval;
    1029                        }
    1030                    }
    1031                }
    1032                boolean cacheable = cache;
    1033                JexlMethod vm = uberspect.getMethod(bean, methodName, argv, node);
    1034                // DG: If we can't find an exact match, narrow the parameters and try again
    1035                if (vm == null) {
    1036                    if (arithmetic.narrowArguments(argv)) {
    1037                        vm = uberspect.getMethod(bean, methodName, argv, node);
    1038                    }
    1039                    if (vm == null) {
    1040                        Object functor = null;
    1041                        // could not find a method, try as a var
    1042                        if (bean == context) {
    1043                            int register = methodNode.getRegister();
    1044                            if (register >= 0) {
    1045                                functor = registers[register];
    1046                            } else {
    1047                                functor = context.get(methodName);
    1048                            }
    1049                        } else {
    1050                            JexlPropertyGet gfunctor = uberspect.getPropertyGet(bean, methodName, node);
    1051                            if (gfunctor != null) {
    1052                                functor = gfunctor.tryInvoke(bean, methodName);
    1053                            }
    1054                        }
    1055                        // script of jexl method will do
    1056                        if (functor instanceof Script) {
    1057                            return ((Script) functor).execute(context, argv.length > 0 ? argv : null);
    1058                        } else if (functor instanceof JexlMethod) {
    1059                            vm = (JexlMethod) functor;
    1060                            cacheable = false;
    1061                        } else {
    1062                            xjexl = new JexlException.Method(node, methodName);
    1063                        }
    1064                    }
    1065                }
    1066                if (xjexl == null) {
    1067                    // vm cannot be null if xjexl is null
    1068                    Object eval = vm.invoke(bean, argv);
    1069                    // cache executor in volatile JexlNode.value
    1070                    if (cacheable && vm.isCacheable()) {
    1071                        node.jjtSetValue(vm);
    1072                    }
    1073                    return eval;
    1074                }
    1075            } catch (InvocationTargetException e) {
    1076                xjexl = new JexlException(node, "method invocation error", e.getCause());
    1077            } catch (Exception e) {
    1078                xjexl = new JexlException(node, "method error", e);
    1079            }
    1080            return invocationFailed(xjexl);
    1081        }
    1082
    1083        /** {@inheritDoc} */
    1084        public Object visit(ASTMethodNode node, Object data) {
    1085            // the object to invoke the method on should be in the data argument
    1086            if (data == null) {
    1087                // if the method node is the first child of the (ASTReference) parent,
    1088                // it is considered as calling a 'top level' function
    1089                if (node.jjtGetParent().jjtGetChild(0) == node) {
    1090                    data = resolveNamespace(null, node);
    1091                    if (data == null) {
    1092                        data = context;
    1093                    }
    1094                } else {
    1095                    throw new JexlException(node, "attempting to call method on null");
    1096                }
    1097            }
    1098            // objectNode 0 is the identifier (method name), the others are parameters.
    1099            ASTIdentifier methodNode = (ASTIdentifier) node.jjtGetChild(0);
    1100            return call(node, data, methodNode, 1);
    1101        }
    1102
    1103        /** {@inheritDoc} */
    1104        public Object visit(ASTFunctionNode node, Object data) {
    1105            // objectNode 0 is the prefix
    1106            String prefix = node.jjtGetChild(0).image;
    1107            Object namespace = resolveNamespace(prefix, node);
    1108            // objectNode 1 is the identifier , the others are parameters.
    1109            ASTIdentifier functionNode = (ASTIdentifier) node.jjtGetChild(1);
    1110            return call(node, namespace, functionNode, 2);
    1111        }
    1112
    1113        /** {@inheritDoc} */
    1114        public Object visit(ASTConstructorNode node, Object data) {
    1115            if (isCancelled()) {
    1116                throw new JexlException.Cancel(node);
    1117            }
    1118            // first child is class or class name
    1119            Object cobject = node.jjtGetChild(0).jjtAccept(this, data);
    1120            // get the ctor args
    1121            int argc = node.jjtGetNumChildren() - 1;
    1122            Object[] argv = new Object[argc];
    1123            for (int i = 0; i < argc; i++) {
    1124                argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, null);
    1125            }
    1126
    1127            JexlException xjexl = null;
    1128            try {
    1129                // attempt to reuse last constructor cached in volatile JexlNode.value
    1130                if (cache) {
    1131                    Object cached = node.jjtGetValue();
    1132                    if (cached instanceof JexlMethod) {
    1133                        JexlMethod mctor = (JexlMethod) cached;
    1134                        Object eval = mctor.tryInvoke(null, cobject, argv);
    1135                        if (!mctor.tryFailed(eval)) {
    1136                            return eval;
    1137                        }
    1138                    }
    1139                }
    1140                JexlMethod ctor = uberspect.getConstructorMethod(cobject, argv, node);
    1141                // DG: If we can't find an exact match, narrow the parameters and try again
    1142                if (ctor == null) {
    1143                    if (arithmetic.narrowArguments(argv)) {
    1144                        ctor = uberspect.getConstructorMethod(cobject, argv, node);
    1145                    }
    1146                    if (ctor == null) {
    1147                        xjexl = new JexlException.Method(node, cobject.toString());
    1148                    }
    1149                }
    1150                if (xjexl == null) {
    1151                    Object instance = ctor.invoke(cobject, argv);
    1152                    // cache executor in volatile JexlNode.value
    1153                    if (cache && ctor.isCacheable()) {
    1154                        node.jjtSetValue(ctor);
    1155                    }
    1156                    return instance;
    1157                }
    1158            } catch (InvocationTargetException e) {
    1159                xjexl = new JexlException(node, "constructor invocation error", e.getCause());
    1160            } catch (Exception e) {
    1161                xjexl = new JexlException(node, "constructor error", e);
    1162            }
    1163            return invocationFailed(xjexl);
    1164        }
    1165
    1166        /** {@inheritDoc} */
    1167        public Object visit(ASTModNode node, Object data) {
    1168            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    1169            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    1170            try {
    1171                return arithmetic.mod(left, right);
    1172            } catch (ArithmeticException xrt) {
    1173                if (!strict) {
    1174                    return new Double(0.0);
    1175                }
    1176                JexlNode xnode = findNullOperand(xrt, node, left, right);
    1177                throw new JexlException(xnode, "% error", xrt);
    1178            }
    1179        }
    1180
    1181        /** {@inheritDoc} */
    1182        public Object visit(ASTMulNode node, Object data) {
    1183            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    1184            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    1185            try {
    1186                return arithmetic.multiply(left, right);
    1187            } catch (ArithmeticException xrt) {
    1188                JexlNode xnode = findNullOperand(xrt, node, left, right);
    1189                throw new JexlException(xnode, "* error", xrt);
    1190            }
    1191        }
    1192
    1193        /** {@inheritDoc} */
    1194        public Object visit(ASTNENode node, Object data) {
    1195            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    1196            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    1197            try {
    1198                return arithmetic.equals(left, right) ? Boolean.FALSE : Boolean.TRUE;
    1199            } catch (ArithmeticException xrt) {
    1200                JexlNode xnode = findNullOperand(xrt, node, left, right);
    1201                throw new JexlException(xnode, "!= error", xrt);
    1202            }
    1203        }
    1204
    1205        /** {@inheritDoc} */
    1206        public Object visit(ASTNRNode node, Object data) {
    1207            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    1208            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    1209            try {
    1210                if (right instanceof java.util.regex.Pattern || right instanceof String) {
    1211                    // use arithmetic / pattern matching
    1212                    return arithmetic.matches(left, right) ? Boolean.FALSE : Boolean.TRUE;
    1213                }
    1214                // try contains on collection
    1215                if (right instanceof Set<?>) {
    1216                    return ((Set<?>) right).contains(left) ? Boolean.FALSE : Boolean.TRUE;
    1217                }
    1218                // try contains on map key
    1219                if (right instanceof Map<?, ?>) {
    1220                    return ((Map<?, ?>) right).containsKey(left) ? Boolean.FALSE : Boolean.TRUE;
    1221                }
    1222                // try contains on collection
    1223                if (right instanceof Collection<?>) {
    1224                    return ((Collection<?>) right).contains(left) ? Boolean.FALSE : Boolean.TRUE;
    1225                }
    1226                // try a contains method (duck type set)
    1227                try {
    1228                    Object[] argv = {left};
    1229                    JexlMethod vm = uberspect.getMethod(right, "contains", argv, node);
    1230                    if (vm != null) {
    1231                        return arithmetic.toBoolean(vm.invoke(right, argv)) ? Boolean.FALSE : Boolean.TRUE;
    1232                    } else if (arithmetic.narrowArguments(argv)) {
    1233                        vm = uberspect.getMethod(right, "contains", argv, node);
    1234                        if (vm != null) {
    1235                            return arithmetic.toBoolean(vm.invoke(right, argv)) ? Boolean.FALSE : Boolean.TRUE;
    1236                        }
    1237                    }
    1238                } catch (InvocationTargetException e) {
    1239                    throw new JexlException(node, "!~ invocation error", e.getCause());
    1240                } catch (Exception e) {
    1241                    throw new JexlException(node, "!~ error", e);
    1242                }
    1243                // try iterative comparison
    1244                Iterator<?> it = uberspect.getIterator(right, node.jjtGetChild(1));
    1245                if (it != null) {
    1246                    while (it.hasNext()) {
    1247                        Object next = it.next();
    1248                        if (next == left || (next != null && next.equals(left))) {
    1249                            return Boolean.FALSE;
    1250                        }
    1251                    }
    1252                    return Boolean.TRUE;
    1253                }
    1254                // defaults to not equal
    1255                return arithmetic.equals(left, right) ? Boolean.FALSE : Boolean.TRUE;
    1256            } catch (ArithmeticException xrt) {
    1257                throw new JexlException(node, "!~ error", xrt);
    1258            }
    1259        }
    1260
    1261        /** {@inheritDoc} */
    1262        public Object visit(ASTNotNode node, Object data) {
    1263            Object val = node.jjtGetChild(0).jjtAccept(this, data);
    1264            return arithmetic.toBoolean(val) ? Boolean.FALSE : Boolean.TRUE;
    1265        }
    1266
    1267        /** {@inheritDoc} */
    1268        public Object visit(ASTNullLiteral node, Object data) {
    1269            return null;
    1270        }
    1271
    1272        /** {@inheritDoc} */
    1273        public Object visit(ASTOrNode node, Object data) {
    1274            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    1275            try {
    1276                boolean leftValue = arithmetic.toBoolean(left);
    1277                if (leftValue) {
    1278                    return Boolean.TRUE;
    1279                }
    1280            } catch (ArithmeticException xrt) {
    1281                throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
    1282            }
    1283            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    1284            try {
    1285                boolean rightValue = arithmetic.toBoolean(right);
    1286                if (rightValue) {
    1287                    return Boolean.TRUE;
    1288                }
    1289            } catch (ArithmeticException xrt) {
    1290                throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt);
    1291            }
    1292            return Boolean.FALSE;
    1293        }
    1294
    1295        /** {@inheritDoc} */
    1296        public Object visit(ASTReference node, Object data) {
    1297            // could be array access, identifier or map literal
    1298            // followed by zero or more ("." and array access, method, size,
    1299            // identifier or integer literal)
    1300            int numChildren = node.jjtGetNumChildren();
    1301            // pass first piece of data in and loop through children
    1302            Object result = null;
    1303            StringBuilder variableName = null;
    1304            String propertyName = null;
    1305            boolean isVariable = true;
    1306            int v = 0;
    1307            for (int c = 0; c < numChildren; c++) {
    1308                if (isCancelled()) {
    1309                    throw new JexlException.Cancel(node);
    1310                }
    1311                JexlNode theNode = node.jjtGetChild(c);
    1312                // integer literals may be part of an antish var name only if no bean was found so far
    1313                if (result == null && theNode instanceof ASTNumberLiteral && ((ASTNumberLiteral) theNode).isInteger()) {
    1314                    isVariable &= v > 0;
    1315                } else {
    1316                    isVariable &= (theNode instanceof ASTIdentifier);
    1317                    result = theNode.jjtAccept(this, result);
    1318                }
    1319                // if we get null back a result, check for an ant variable
    1320                if (result == null && isVariable) {
    1321                    if (v == 0) {
    1322                        variableName = new StringBuilder(node.jjtGetChild(0).image);
    1323                        v = 1;
    1324                    }
    1325                    for (; v <= c; ++v) {
    1326                        variableName.append('.');
    1327                        variableName.append(node.jjtGetChild(v).image);
    1328                    }
    1329                    result = context.get(variableName.toString());
    1330                } else {
    1331                    propertyName = theNode.image;
    1332                }
    1333            }
    1334            if (result == null) {
    1335                if (isVariable && !isTernaryProtected(node)
    1336                        // variable unknow in context and not (from) a register
    1337                        && !(context.has(variableName.toString())
    1338                        || (numChildren == 1
    1339                        && node.jjtGetChild(0) instanceof ASTIdentifier
    1340                        && ((ASTIdentifier) node.jjtGetChild(0)).getRegister() >= 0))) {
    1341                    JexlException xjexl = propertyName != null
    1342                                          ? new JexlException.Property(node, propertyName)
    1343                                          : new JexlException.Variable(node, variableName.toString());
    1344                    return unknownVariable(xjexl);
    1345                }
    1346            }
    1347            return result;
    1348        }
    1349
    1350        /**
    1351         * {@inheritDoc}
    1352         * @since 2.1
    1353         */
    1354        public Object visit(ASTReferenceExpression node, Object data) {
    1355            ASTArrayAccess upper = node;
    1356            return visit(upper, data);
    1357        }
    1358
    1359        /**
    1360         * {@inheritDoc}
    1361         * @since 2.1
    1362         */
    1363        public Object visit(ASTReturnStatement node, Object data) {
    1364            Object val = node.jjtGetChild(0).jjtAccept(this, data);
    1365            throw new JexlException.Return(node, null, val);
    1366        }
    1367
    1368        /**
    1369         * Check if a null evaluated expression is protected by a ternary expression.
    1370         * The rationale is that the ternary / elvis expressions are meant for the user to explictly take
    1371         * control over the error generation; ie, ternaries can return null even if the engine in strict mode
    1372         * would normally throw an exception.
    1373         * @param node the expression node
    1374         * @return true if nullable variable, false otherwise
    1375         */
    1376        private boolean isTernaryProtected(JexlNode node) {
    1377            for (JexlNode walk = node.jjtGetParent(); walk != null; walk = walk.jjtGetParent()) {
    1378                if (walk instanceof ASTTernaryNode) {
    1379                    return true;
    1380                } else if (!(walk instanceof ASTReference || walk instanceof ASTArrayAccess)) {
    1381                    break;
    1382                }
    1383            }
    1384            return false;
    1385        }
    1386
    1387        /** {@inheritDoc} */
    1388        public Object visit(ASTSizeFunction node, Object data) {
    1389            Object val = node.jjtGetChild(0).jjtAccept(this, data);
    1390            if (val == null) {
    1391                throw new JexlException(node, "size() : argument is null", null);
    1392            }
    1393            return Integer.valueOf(sizeOf(node, val));
    1394        }
    1395
    1396        /** {@inheritDoc} */
    1397        public Object visit(ASTSizeMethod node, Object data) {
    1398            return Integer.valueOf(sizeOf(node, data));
    1399        }
    1400
    1401        /** {@inheritDoc} */
    1402        public Object visit(ASTStringLiteral node, Object data) {
    1403            if (data != null) {
    1404                return getAttribute(data, node.getLiteral(), node);
    1405            }
    1406            return node.image;
    1407        }
    1408
    1409        /** {@inheritDoc} */
    1410        public Object visit(ASTTernaryNode node, Object data) {
    1411            Object condition = node.jjtGetChild(0).jjtAccept(this, data);
    1412            if (node.jjtGetNumChildren() == 3) {
    1413                if (condition != null && arithmetic.toBoolean(condition)) {
    1414                    return node.jjtGetChild(1).jjtAccept(this, data);
    1415                } else {
    1416                    return node.jjtGetChild(2).jjtAccept(this, data);
    1417                }
    1418            }
    1419            if (condition != null && arithmetic.toBoolean(condition)) {
    1420                return condition;
    1421            } else {
    1422                return node.jjtGetChild(1).jjtAccept(this, data);
    1423            }
    1424        }
    1425
    1426        /** {@inheritDoc} */
    1427        public Object visit(ASTTrueNode node, Object data) {
    1428            return Boolean.TRUE;
    1429        }
    1430
    1431        /** {@inheritDoc} */
    1432        public Object visit(ASTUnaryMinusNode node, Object data) {
    1433            JexlNode valNode = node.jjtGetChild(0);
    1434            Object val = valNode.jjtAccept(this, data);
    1435            try {
    1436                Object number = arithmetic.negate(val);
    1437                // attempt to recoerce to literal class
    1438                if (valNode instanceof ASTNumberLiteral && number instanceof Number) {
    1439                    number = arithmetic.narrowNumber((Number) number, ((ASTNumberLiteral) valNode).getLiteralClass());
    1440                }
    1441                return number;
    1442            } catch (ArithmeticException xrt) {
    1443                throw new JexlException(valNode, "arithmetic error", xrt);
    1444            }
    1445        }
    1446
    1447        /** {@inheritDoc} */
    1448        public Object visit(ASTWhileStatement node, Object data) {
    1449            Object result = null;
    1450            /* first objectNode is the expression */
    1451            Node expressionNode = node.jjtGetChild(0);
    1452            while (arithmetic.toBoolean(expressionNode.jjtAccept(this, data))) {
    1453                if (isCancelled()) {
    1454                    throw new JexlException.Cancel(node);
    1455                }
    1456                // execute statement
    1457                if (node.jjtGetNumChildren() > 1) {
    1458                    result = node.jjtGetChild(1).jjtAccept(this, data);
    1459                }
    1460            }
    1461            return result;
    1462        }
    1463
    1464        /**
    1465         * Calculate the <code>size</code> of various types: Collection, Array,
    1466         * Map, String, and anything that has a int size() method.
    1467         * @param node the node that gave the value to size
    1468         * @param val the object to get the size of.
    1469         * @return the size of val
    1470         */
    1471        private int sizeOf(JexlNode node, Object val) {
    1472            if (val instanceof Collection<?>) {
    1473                return ((Collection<?>) val).size();
    1474            } else if (val.getClass().isArray()) {
    1475                return Array.getLength(val);
    1476            } else if (val instanceof Map<?, ?>) {
    1477                return ((Map<?, ?>) val).size();
    1478            } else if (val instanceof String) {
    1479                return ((String) val).length();
    1480            } else {
    1481                // check if there is a size method on the object that returns an
    1482                // integer and if so, just use it
    1483                Object[] params = new Object[0];
    1484                JexlMethod vm = uberspect.getMethod(val, "size", EMPTY_PARAMS, node);
    1485                if (vm != null && vm.getReturnType() == Integer.TYPE) {
    1486                    Integer result;
    1487                    try {
    1488                        result = (Integer) vm.invoke(val, params);
    1489                    } catch (Exception e) {
    1490                        throw new JexlException(node, "size() : error executing", e);
    1491                    }
    1492                    return result.intValue();
    1493                }
    1494                throw new JexlException(node, "size() : unsupported type : " + val.getClass(), null);
    1495            }
    1496        }
    1497
    1498        /**
    1499         * Gets an attribute of an object.
    1500         *
    1501         * @param object to retrieve value from
    1502         * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
    1503         *            key for a map
    1504         * @return the attribute value
    1505         */
    1506        public Object getAttribute(Object object, Object attribute) {
    1507            return getAttribute(object, attribute, null);
    1508        }
    1509
    1510        /**
    1511         * Gets an attribute of an object.
    1512         *
    1513         * @param object to retrieve value from
    1514         * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
    1515         *            key for a map
    1516         * @param node the node that evaluated as the object
    1517         * @return the attribute value
    1518         */
    1519        protected Object getAttribute(Object object, Object attribute, JexlNode node) {
    1520            if (object == null) {
    1521                throw new JexlException(node, "object is null");
    1522            }
    1523            if (isCancelled()) {
    1524                throw new JexlException.Cancel(node);
    1525            }
    1526            // attempt to reuse last executor cached in volatile JexlNode.value
    1527            if (node != null && cache) {
    1528                Object cached = node.jjtGetValue();
    1529                if (cached instanceof JexlPropertyGet) {
    1530                    JexlPropertyGet vg = (JexlPropertyGet) cached;
    1531                    Object value = vg.tryInvoke(object, attribute);
    1532                    if (!vg.tryFailed(value)) {
    1533                        return value;
    1534                    }
    1535                }
    1536            }
    1537            JexlPropertyGet vg = uberspect.getPropertyGet(object, attribute, node);
    1538            if (vg != null) {
    1539                try {
    1540                    Object value = vg.invoke(object);
    1541                    // cache executor in volatile JexlNode.value
    1542                    if (node != null && cache && vg.isCacheable()) {
    1543                        node.jjtSetValue(vg);
    1544                    }
    1545                    return value;
    1546                } catch (Exception xany) {
    1547                    if (node == null) {
    1548                        throw new RuntimeException(xany);
    1549                    } else {
    1550                        JexlException xjexl = new JexlException.Property(node, attribute.toString());
    1551                        if (strict) {
    1552                            throw xjexl;
    1553                        }
    1554                        if (!silent) {
    1555                            logger.warn(xjexl.getMessage());
    1556                        }
    1557                    }
    1558                }
    1559            }
    1560            return null;
    1561        }
    1562
    1563        /**
    1564         * Sets an attribute of an object.
    1565         *
    1566         * @param object to set the value to
    1567         * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
    1568         *            key for a map
    1569         * @param value the value to assign to the object's attribute
    1570         */
    1571        public void setAttribute(Object object, Object attribute, Object value) {
    1572            setAttribute(object, attribute, value, null);
    1573        }
    1574
    1575        /**
    1576         * Sets an attribute of an object.
    1577         *
    1578         * @param object to set the value to
    1579         * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
    1580         *            key for a map
    1581         * @param value the value to assign to the object's attribute
    1582         * @param node the node that evaluated as the object
    1583         */
    1584        protected void setAttribute(Object object, Object attribute, Object value, JexlNode node) {
    1585            if (isCancelled()) {
    1586                throw new JexlException.Cancel(node);
    1587            }
    1588            // attempt to reuse last executor cached in volatile JexlNode.value
    1589            if (node != null && cache) {
    1590                Object cached = node.jjtGetValue();
    1591                if (cached instanceof JexlPropertySet) {
    1592                    JexlPropertySet setter = (JexlPropertySet) cached;
    1593                    Object eval = setter.tryInvoke(object, attribute, value);
    1594                    if (!setter.tryFailed(eval)) {
    1595                        return;
    1596                    }
    1597                }
    1598            }
    1599            JexlException xjexl = null;
    1600            JexlPropertySet vs = uberspect.getPropertySet(object, attribute, value, node);
    1601            // if we can't find an exact match, narrow the value argument and try again
    1602            if (vs == null) {
    1603                // replace all numbers with the smallest type that will fit
    1604                Object[] narrow = {value};
    1605                if (arithmetic.narrowArguments(narrow)) {
    1606                    vs = uberspect.getPropertySet(object, attribute, narrow[0], node);
    1607                }
    1608            }
    1609            if (vs != null) {
    1610                try {
    1611                    // cache executor in volatile JexlNode.value
    1612                    vs.invoke(object, value);
    1613                    if (node != null && cache && vs.isCacheable()) {
    1614                        node.jjtSetValue(vs);
    1615                    }
    1616                    return;
    1617                } catch (RuntimeException xrt) {
    1618                    if (node == null) {
    1619                        throw xrt;
    1620                    }
    1621                    xjexl = new JexlException(node, "set object property error", xrt);
    1622                } catch (Exception xany) {
    1623                    if (node == null) {
    1624                        throw new RuntimeException(xany);
    1625                    }
    1626                    xjexl = new JexlException(node, "set object property error", xany);
    1627                }
    1628            }
    1629            if (xjexl == null) {
    1630                if (node == null) {
    1631                    String error = "unable to set object property"
    1632                            + ", class: " + object.getClass().getName()
    1633                            + ", property: " + attribute
    1634                            + ", argument: " + value.getClass().getSimpleName();
    1635                    throw new UnsupportedOperationException(error);
    1636                }
    1637                xjexl = new JexlException.Property(node, attribute.toString());
    1638            }
    1639            if (strict) {
    1640                throw xjexl;
    1641            }
    1642            if (!silent) {
    1643                logger.warn(xjexl.getMessage());
    1644            }
    1645        }
    1646
    1647        /**
    1648         * Unused, satisfy ParserVisitor interface.
    1649         * @param node a node
    1650         * @param data the data
    1651         * @return does not return
    1652         */
    1653        public Object visit(SimpleNode node, Object data) {
    1654            throw new UnsupportedOperationException("Not supported yet.");
    1655        }
    1656
    1657        /**
    1658         * Unused, should throw in Parser.
    1659         * @param node a node
    1660         * @param data the data
    1661         * @return does not return
    1662         */
    1663        public Object visit(ASTAmbiguous node, Object data) {
    1664            throw new UnsupportedOperationException("unexpected type of node");
    1665        }
    1666    }