    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.Constructor;
    020    import java.lang.reflect.Array;
    021    import java.lang.reflect.InvocationTargetException;
    022    import java.math.BigDecimal;
    023    import java.math.BigInteger;
    024    import java.util.Collection;
    025    import java.util.HashMap;
    026    import java.util.Iterator;
    027    import java.util.Map;
    028
    029    import org.apache.commons.jexl2.parser.SimpleNode;
    030    import org.apache.commons.logging.Log;
    031
    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.ASTFloatLiteral;
    053    import org.apache.commons.jexl2.parser.ASTForeachStatement;
    054    import org.apache.commons.jexl2.parser.ASTGENode;
    055    import org.apache.commons.jexl2.parser.ASTGTNode;
    056    import org.apache.commons.jexl2.parser.ASTIdentifier;
    057    import org.apache.commons.jexl2.parser.ASTIfStatement;
    058    import org.apache.commons.jexl2.parser.ASTIntegerLiteral;
    059    import org.apache.commons.jexl2.parser.ASTJexlScript;
    060    import org.apache.commons.jexl2.parser.ASTLENode;
    061    import org.apache.commons.jexl2.parser.ASTLTNode;
    062    import org.apache.commons.jexl2.parser.ASTMapEntry;
    063    import org.apache.commons.jexl2.parser.ASTMapLiteral;
    064    import org.apache.commons.jexl2.parser.ASTMethodNode;
    065    import org.apache.commons.jexl2.parser.ASTModNode;
    066    import org.apache.commons.jexl2.parser.ASTMulNode;
    067    import org.apache.commons.jexl2.parser.ASTNENode;
    068    import org.apache.commons.jexl2.parser.ASTNRNode;
    069    import org.apache.commons.jexl2.parser.ASTNotNode;
    070    import org.apache.commons.jexl2.parser.ASTNullLiteral;
    071    import org.apache.commons.jexl2.parser.ASTOrNode;
    072    import org.apache.commons.jexl2.parser.ASTReference;
    073    import org.apache.commons.jexl2.parser.ASTSizeFunction;
    074    import org.apache.commons.jexl2.parser.ASTSizeMethod;
    075    import org.apache.commons.jexl2.parser.ASTStringLiteral;
    076    import org.apache.commons.jexl2.parser.ASTTernaryNode;
    077    import org.apache.commons.jexl2.parser.ASTTrueNode;
    078    import org.apache.commons.jexl2.parser.ASTUnaryMinusNode;
    079    import org.apache.commons.jexl2.parser.ASTWhileStatement;
    080    import org.apache.commons.jexl2.parser.Node;
    081    import org.apache.commons.jexl2.parser.ParserVisitor;
    082
    083    import org.apache.commons.jexl2.introspection.Uberspect;
    084    import org.apache.commons.jexl2.introspection.JexlMethod;
    085    import org.apache.commons.jexl2.introspection.JexlPropertyGet;
    086    import org.apache.commons.jexl2.introspection.JexlPropertySet;
    087
    088    /**
    089     * An interpreter of JEXL syntax.
    090     *
    091     * @since 2.0
    092     */
    093    public class Interpreter implements ParserVisitor {
    094        /** The logger. */
    095        protected final Log logger;
    096        /** The uberspect. */
    097        protected final Uberspect uberspect;
    098        /** The arithmetic handler. */
    099        protected final JexlArithmetic arithmetic;
    100        /** The map of registered functions. */
    101        protected final Map<String, Object> functions;
    102        /** The map of registered functions. */
    103        protected Map<String, Object> functors;
    104        /** The context to store/retrieve variables. */
    105        protected final JexlContext context;
    106        /** Strict interpreter flag. */
    107        protected final boolean strict;
    108        /** Silent intepreter flag. */
    109        protected boolean silent;
    110        /** Cache executors. */
    111        protected final boolean  cache;
    112        /** Registers made of 2 pairs of {register-name, value}. */
    113        protected Object[] registers = null;
    114        /** Empty parameters for method matching. */
    115        protected static final Object[] EMPTY_PARAMS = new Object[0];
    116
    117        /**
    118         * Creates an interpreter.
    119         * @param jexl the engine creating this interpreter
    120         * @param aContext the context to evaluate expression
    121         */
    122        public Interpreter(JexlEngine jexl, JexlContext aContext) {
    123            this.logger = jexl.logger;
    124            this.uberspect = jexl.uberspect;
    125            this.arithmetic = jexl.arithmetic;
    126            this.functions = jexl.functions;
    127            this.strict = !this.arithmetic.isLenient();
    128            this.silent = jexl.silent;
    129            this.cache = jexl.cache != null;
    130            this.context = aContext;
    131            this.functors = null;
    132        }
    133
    134        /**
    135         * Sets whether this interpreter throws JexlException during evaluation.
    136         * @param flag true means no JexlException will be thrown but will be logged
    137         *        as info through the Jexl engine logger, false allows them to be thrown.
    138         */
    139        public void setSilent(boolean flag) {
    140            this.silent = flag;
    141        }
    142
    143        /**
    144         * Checks whether this interpreter throws JexlException during evaluation.
    145         * @return true if silent, false otherwise
    146         */
    147        public boolean isSilent() {
    148            return this.silent;
    149        }
    150
    151        /**
    152         * Interpret the given script/expression.
    153         * <p>
    154         * If the underlying JEXL engine is silent, errors will be logged through its logger as info.
    155         * </p>
    156         * @param node the script or expression to interpret.
    157         * @return the result of the interpretation.
    158         * @throws JexlException if any error occurs during interpretation.
    159         */
    160        public Object interpret(JexlNode node) {
    161            try {
    162                return node.jjtAccept(this, null);
    163            } catch (JexlException xjexl) {
    164                if (silent) {
    165                    logger.warn(xjexl.getMessage(), xjexl.getCause());
    166                    return null;
    167                }
    168                throw xjexl;
    169            }
    170        }
    171
    172        /**
    173         * Gets the uberspect.
    174         * @return an {@link Uberspect}
    175         */
    176        protected Uberspect getUberspect() {
    177            return uberspect;
    178        }
    179
    180        /**
    181         * Sets this interpreter registers for bean access/assign expressions.
    182         * @param theRegisters the array of registers
    183         */
    184        protected void setRegisters(Object[] theRegisters) {
    185            this.registers = theRegisters;
    186        }
    187
    188        /**
    189         * Finds the node causing a NPE for diadic operators.
    190         * @param xrt the RuntimeException
    191         * @param node the parent node
    192         * @param left the left argument
    193         * @param right the right argument
    194         * @return the left, right or parent node
    195         */
    196        protected JexlNode findNullOperand(RuntimeException xrt, JexlNode node, Object left, Object right) {
    197            if (xrt instanceof NullPointerException
    198                    && JexlException.NULL_OPERAND == xrt.getMessage()) {
    199                if (left == null) {
    200                    return node.jjtGetChild(0);
    201                }
    202                if (right == null) {
    203                    return node.jjtGetChild(1);
    204                }
    205            }
    206            return node;
    207        }
    208
    209        /**
    210         * Triggered when variable can not be resolved.
    211         * @param xjexl the JexlException ("undefined variable " + variable)
    212         * @return throws JexlException if strict, null otherwise
    213         */
    214        protected Object unknownVariable(JexlException xjexl) {
    215            if (strict) {
    216                throw xjexl;
    217            }
    218            if (!silent) {
    219                logger.warn(xjexl.getMessage());
    220            }
    221            return null;
    222        }
    223
    224        /**
    225         * Triggered when method, function or constructor invocation fails.
    226         * @param xjexl the JexlException wrapping the original error
    227         * @return throws JexlException if strict, null otherwise
    228         */
    229        protected Object invocationFailed(JexlException xjexl) {
    230            if (strict) {
    231                throw xjexl;
    232            }
    233            if (!silent) {
    234                logger.warn(xjexl.getMessage(), xjexl.getCause());
    235            }
    236            return null;
    237        }
    238
    239        /**
    240         * Resolves a namespace, eventually allocating an instance using context as constructor argument.
    241         * The lifetime of such instances span the current expression or script evaluation.
    242         *
    243         * @param prefix the prefix name (may be null for global namespace)
    244         * @param node the AST node
    245         * @return the namespace instance
    246         */
    247        protected Object resolveNamespace(String prefix, JexlNode node) {
    248            Object namespace;
    249            // check whether this namespace is a functor
    250            if (functors != null) {
    251                namespace = functors.get(prefix);
    252                if (namespace != null) {
    253                    return namespace;
    254                }
    255            }
    256            namespace = functions.get(prefix);
    257            if (namespace == null) {
    258                throw new JexlException(node, "no such function namespace " + prefix);
    259            }
    260            // allow namespace to be instantiated as functor with context
    261            if (namespace instanceof Class<?>) {
    262                Object[] args = new Object[]{context};
    263                Constructor<?> ctor = uberspect.getConstructor(namespace,args, node);
    264                if (ctor != null) {
    265                    try {
    266                        namespace = ctor.newInstance(args);
    267                        if (functors == null) {
    268                            functors = new HashMap<String, Object>();
    269                        }
    270                        functors.put(prefix, namespace);
    271                    } catch (Exception xinst) {
    272                        throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
    273                    }
    274                }
    275            }
    276            return namespace;
    277        }
    278
    279        /** {@inheritDoc} */
    280        public Object visit(ASTAdditiveNode node, Object data) {
    281            /**
    282             * The pattern for exception mgmt is to let the child*.jjtAccept
    283             * out of the try/catch loop so that if one fails, the ex will
    284             * traverse up to the interpreter.
    285             * In cases where this is not convenient/possible, JexlException must
    286             * be caught explicitly and rethrown.
    287             */
    288            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    289            for(int c = 2, size = node.jjtGetNumChildren(); c < size; c += 2) {
    290                Object right = node.jjtGetChild(c).jjtAccept(this, data);
    291                try {
    292                    JexlNode op = node.jjtGetChild(c - 1);
    293                    if (op instanceof ASTAdditiveOperator) {
    294                        String which = ((ASTAdditiveOperator) op).image;
    295                        if ("+".equals(which)) {
    296                            left = arithmetic.add(left, right);
    297                            continue;
    298                        }
    299                        if ("-".equals(which)) {
    300                            left = arithmetic.subtract(left, right);
    301                            continue;
    302                        }
    303                        throw new UnsupportedOperationException("unknown operator " + which);
    304                    }
    305                    throw new IllegalArgumentException("unknown operator " + op);
    306                } catch (RuntimeException xrt) {
    307                    JexlNode xnode = findNullOperand(xrt, node, left, right);
    308                    throw new JexlException(xnode, "+/- error", xrt);
    309                }
    310            }
    311            return left;
    312        }
    313
    314        /** {@inheritDoc} */
    315        public Object visit(ASTAdditiveOperator node, Object data) {
    316            throw new UnsupportedOperationException("Shoud not be called.");
    317        }
    318
    319        /** {@inheritDoc} */
    320        public Object visit(ASTAndNode node, Object data) {
    321            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    322            try {
    323                boolean leftValue = arithmetic.toBoolean(left);
    324                if (!leftValue) {
    325                    return Boolean.FALSE;
    326                }
    327            } catch (RuntimeException xrt) {
    328                throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
    329            }
    330            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    331            try {
    332                boolean rightValue = arithmetic.toBoolean(right);
    333                if (!rightValue) {
    334                    return Boolean.FALSE;
    335                }
    336            } catch (RuntimeException xrt) {
    337                throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt);
    338            }
    339            return Boolean.TRUE;
    340        }
    341
    342        /** {@inheritDoc} */
    343        public Object visit(ASTArrayAccess node, Object data) {
    344            // first objectNode is the identifier
    345            Object object = node.jjtGetChild(0).jjtAccept(this, data);
    346            // can have multiple nodes - either an expression, integer literal or
    347            // reference
    348            int numChildren = node.jjtGetNumChildren();
    349            for (int i = 1; i < numChildren; i++) {
    350                JexlNode nindex = node.jjtGetChild(i);
    351                Object index = nindex.jjtAccept(this, null);
    352                object = getAttribute(object, index, nindex);
    353            }
    354
    355            return object;
    356        }
    357
    358        /** {@inheritDoc} */
    359        public Object visit(ASTArrayLiteral node, Object data) {
    360            int childCount = node.jjtGetNumChildren();
    361            Object[] array = new Object[childCount];
    362            for (int i = 0; i < childCount; i++) {
    363                Object entry = node.jjtGetChild(i).jjtAccept(this, data);
    364                array[i] = entry;
    365            }
    366            return arithmetic.narrowArrayType(array);
    367        }
    368
    369        /** {@inheritDoc} */
    370        public Object visit(ASTAssignment node, Object data) {
    371            // left contains the reference to assign to
    372            JexlNode left = node.jjtGetChild(0);
    373            if (!(left instanceof ASTReference)) {
    374                throw new JexlException(left, "illegal assignment form");
    375            }
    376            // right is the value expression to assign
    377            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    378
    379            // determine initial object & property:
    380            JexlNode objectNode = null;
    381            Object object = null;
    382            JexlNode propertyNode = null;
    383            Object property = null;
    384            boolean isVariable = true;
    385            int v = 0;
    386            StringBuilder variableName = null;
    387            // 1: follow children till penultimate
    388            int last = left.jjtGetNumChildren() - 1;
    389            for (int c = 0; c < last; ++c) {
    390                objectNode = left.jjtGetChild(c);
    391                // evaluate the property within the object
    392                object = objectNode.jjtAccept(this, object);
    393                if (object != null) {
    394                    continue;
    395                }
    396                isVariable &= objectNode instanceof ASTIdentifier;
    397                // if we get null back as a result, check for an ant variable
    398                if (isVariable) {
    399                    if (v == 0) {
    400                        variableName = new StringBuilder(left.jjtGetChild(0).image);
    401                        v = 1;
    402                    }
    403                    for(; v <= c; ++v) {
    404                        variableName.append('.');
    405                        variableName.append(left.jjtGetChild(v).image);
    406                    }
    407                    object = context.get(variableName.toString());
    408                    // disallow mixing ant & bean with same root; avoid ambiguity
    409                    if (object != null) {
    410                        isVariable = false;
    411                    }
    412                } else {
    413                    throw new JexlException(objectNode, "illegal assignment form");
    414                }
    415            }
    416            // 2: last objectNode will perform assignement in all cases
    417            propertyNode = left.jjtGetChild(last);
    418            if (propertyNode instanceof ASTIdentifier) {
    419                property = ((ASTIdentifier) propertyNode).image;
    420                // deal with ant variable
    421                if (isVariable && object == null) {
    422                    if (variableName != null) {
    423                        if (last > 0) {
    424                            variableName.append('.');
    425                        }
    426                        variableName.append(property);
    427                        property = variableName.toString();
    428                    }
    429                    context.set(String.valueOf(property), right);
    430                    return right;
    431                }
    432            } else if (propertyNode instanceof ASTIntegerLiteral) {
    433                property = visit((ASTIntegerLiteral) propertyNode, null);
    434                // deal with ant variable
    435                if (isVariable && object == null) {
    436                    if (variableName != null) {
    437                        if (last > 0) {
    438                            variableName.append('.');
    439                        }
    440                        variableName.append(property);
    441                        property = variableName.toString();
    442                    }
    443                    context.set(String.valueOf(property), right);
    444                    return right;
    445                }
    446            } else if (propertyNode instanceof ASTArrayAccess) {
    447                // first objectNode is the identifier
    448                objectNode = propertyNode;
    449                ASTArrayAccess narray = (ASTArrayAccess) objectNode;
    450                Object nobject = narray.jjtGetChild(0).jjtAccept(this, object);
    451                if (nobject == null) {
    452                    throw new JexlException(objectNode, "array element is null");
    453                } else {
    454                    object = nobject;
    455                }
    456                // can have multiple nodes - either an expression, integer literal or
    457                // reference
    458                last = narray.jjtGetNumChildren() - 1;
    459                for (int i = 1; i < last; i++) {
    460                    objectNode = narray.jjtGetChild(i);
    461                    Object index = objectNode.jjtAccept(this, null);
    462                    object = getAttribute(object, index, objectNode);
    463                }
    464                property = narray.jjtGetChild(last).jjtAccept(this, null);
    465            } else {
    466                throw new JexlException(objectNode, "illegal assignment form");
    467            }
    468            if (property == null) {
    469                // no property, we fail
    470                throw new JexlException(propertyNode, "property is null");
    471            }
    472            if (object == null) {
    473                // no object, we fail
    474                throw new JexlException(objectNode, "bean is null");
    475            }
    476            // one before last, assign
    477            setAttribute(object, property, right, propertyNode);
    478            return right;
    479        }
    480
    481        /** {@inheritDoc} */
    482        public Object visit(ASTBitwiseAndNode node, Object data) {
    483            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    484            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    485            int n = 0;
    486            // coerce these two values longs and 'and'.
    487            try {
    488                long l = arithmetic.toLong(left);
    489                n = 1;
    490                long r = arithmetic.toLong(right);
    491                return Long.valueOf(l & r);
    492            } catch (RuntimeException xrt) {
    493                throw new JexlException(node.jjtGetChild(n), "long coercion error", xrt);
    494            }
    495        }
    496
    497        /** {@inheritDoc} */
    498        public Object visit(ASTBitwiseComplNode node, Object data) {
    499            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    500            try {
    501                long l = arithmetic.toLong(left);
    502                return Long.valueOf(~l);
    503            } catch (RuntimeException xrt) {
    504                throw new JexlException(node.jjtGetChild(0), "long coercion error", xrt);
    505            }
    506        }
    507
    508        /** {@inheritDoc} */
    509        public Object visit(ASTBitwiseOrNode node, Object data) {
    510            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    511            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    512            int n = 0;
    513            // coerce these two values longs and 'or'.
    514            try {
    515                long l = arithmetic.toLong(left);
    516                n = 1;
    517                long r = arithmetic.toLong(right);
    518                return Long.valueOf(l | r);
    519            } catch (RuntimeException xrt) {
    520                throw new JexlException(node.jjtGetChild(n), "long coercion error", xrt);
    521            }
    522        }
    523
    524        /** {@inheritDoc} */
    525        public Object visit(ASTBitwiseXorNode node, Object data) {
    526            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    527            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    528            int n = 0;
    529            // coerce these two values longs and 'xor'.
    530            try {
    531                long l = arithmetic.toLong(left);
    532                n = 1;
    533                long r = arithmetic.toLong(right);
    534                return Long.valueOf(l ^ r);
    535            } catch (RuntimeException xrt) {
    536                throw new JexlException(node.jjtGetChild(n), "long coercion error", xrt);
    537            }
    538        }
    539
    540        /** {@inheritDoc} */
    541        public Object visit(ASTBlock node, Object data) {
    542            int numChildren = node.jjtGetNumChildren();
    543            Object result = null;
    544            for (int i = 0; i < numChildren; i++) {
    545                result = node.jjtGetChild(i).jjtAccept(this, data);
    546            }
    547            return result;
    548        }
    549
    550        /** {@inheritDoc} */
    551        public Object visit(ASTDivNode node, Object data) {
    552            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    553            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    554            try {
    555                return arithmetic.divide(left, right);
    556            } catch (RuntimeException xrt) {
    557                if (!strict && xrt instanceof ArithmeticException) {
    558                    return new Double(0.0);
    559                }
    560                JexlNode xnode = findNullOperand(xrt, node, left, right);
    561                throw new JexlException(xnode, "divide error", xrt);
    562            }
    563        }
    564
    565        /** {@inheritDoc} */
    566        public Object visit(ASTEmptyFunction node, Object data) {
    567            Object o = node.jjtGetChild(0).jjtAccept(this, data);
    568            if (o == null) {
    569                return Boolean.TRUE;
    570            }
    571            if (o instanceof String && "".equals(o)) {
    572                return Boolean.TRUE;
    573            }
    574            if (o.getClass().isArray() && ((Object[]) o).length == 0) {
    575                return Boolean.TRUE;
    576            }
    577            if (o instanceof Collection<?> && ((Collection<?>) o).isEmpty()) {
    578                return Boolean.TRUE;
    579            }
    580            // Map isn't a collection
    581            if (o instanceof Map<?, ?> && ((Map<?, ?>) o).isEmpty()) {
    582                return Boolean.TRUE;
    583            }
    584            return Boolean.FALSE;
    585        }
    586
    587        /** {@inheritDoc} */
    588        public Object visit(ASTEQNode node, Object data) {
    589            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    590            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    591            try {
    592                return arithmetic.equals(left, right) ? Boolean.TRUE : Boolean.FALSE;
    593            } catch (RuntimeException xrt) {
    594                throw new JexlException(node, "== error", xrt);
    595            }
    596        }
    597
    598        /** {@inheritDoc} */
    599        public Object visit(ASTFalseNode node, Object data) {
    600            return Boolean.FALSE;
    601        }
    602
    603        /** {@inheritDoc} */
    604        public Object visit(ASTFloatLiteral node, Object data) {
    605            Float value = (Float) node.jjtGetValue();
    606            if (value == null) {
    607                value = Float.valueOf(node.image);
    608                node.jjtSetValue(value);
    609            }
    610            return value;
    611        }
    612
    613        /** {@inheritDoc} */
    614        public Object visit(ASTForeachStatement node, Object data) {
    615            Object result = null;
    616            /* first objectNode is the loop variable */
    617            ASTReference loopReference = (ASTReference) node.jjtGetChild(0);
    618            ASTIdentifier loopVariable = (ASTIdentifier) loopReference.jjtGetChild(0);
    619            /* second objectNode is the variable to iterate */
    620            Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data);
    621            // make sure there is a value to iterate on and a statement to execute
    622            if (iterableValue != null && node.jjtGetNumChildren() >= 3) {
    623                /* third objectNode is the statement to execute */
    624                JexlNode statement = node.jjtGetChild(2);
    625                // get an iterator for the collection/array etc via the
    626                // introspector.
    627                Iterator<?> itemsIterator = getUberspect().getIterator(iterableValue, node);
    628                if (itemsIterator != null) {
    629                    while (itemsIterator.hasNext()) {
    630                        // set loopVariable to value of iterator
    631                        Object value = itemsIterator.next();
    632                        context.set(loopVariable.image, value);
    633                        // execute statement
    634                        result = statement.jjtAccept(this, data);
    635                    }
    636                }
    637            }
    638            return result;
    639        }
    640
    641        /** {@inheritDoc} */
    642        public Object visit(ASTGENode node, Object data) {
    643            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    644            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    645            try {
    646                return arithmetic.greaterThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE;
    647            } catch (RuntimeException xrt) {
    648                throw new JexlException(node, ">= error", xrt);
    649            }
    650        }
    651
    652        /** {@inheritDoc} */
    653        public Object visit(ASTGTNode node, Object data) {
    654            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    655            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    656            try {
    657                return arithmetic.greaterThan(left, right) ? Boolean.TRUE : Boolean.FALSE;
    658            } catch (RuntimeException xrt) {
    659                throw new JexlException(node, "> error", xrt);
    660            }
    661        }
    662
    663        /** {@inheritDoc} */
    664        public Object visit(ASTERNode node, Object data) {
    665            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    666            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    667            try {
    668                return arithmetic.matches(left, right) ? Boolean.TRUE : Boolean.FALSE;
    669            } catch (RuntimeException xrt) {
    670                throw new JexlException(node, "=~ error", xrt);
    671            }
    672        }
    673
    674        /** {@inheritDoc} */
    675        public Object visit(ASTIdentifier node, Object data) {
    676            String name = node.image;
    677            if (data == null) {
    678                if (registers != null) {
    679                    if (registers[0].equals(name)) {
    680                        return registers[1];
    681                    }
    682                    if (registers[2].equals(name)) {
    683                        return registers[3];
    684                    }
    685                }
    686                Object value = context.get(name);
    687                if (value == null
    688                    && !(node.jjtGetParent() instanceof ASTReference)
    689                    && !context.has(name)) {
    690                    JexlException xjexl = new JexlException(node, "undefined variable " + name);
    691                    return unknownVariable(xjexl);
    692                }
    693                return value;
    694            } else {
    695                return getAttribute(data, name, node);
    696            }
    697        }
    698
    699        /** {@inheritDoc} */
    700        public Object visit(ASTIfStatement node, Object data) {
    701            int n = 0;
    702            try {
    703                Object result = null;
    704                /* first objectNode is the expression */
    705                Object expression = node.jjtGetChild(0).jjtAccept(this, data);
    706                if (arithmetic.toBoolean(expression)) {
    707                    // first objectNode is true statement
    708                    n = 1;
    709                    result = node.jjtGetChild(1).jjtAccept(this, data);
    710                } else {
    711                    // if there is a false, execute it. false statement is the second
    712                    // objectNode
    713                    if (node.jjtGetNumChildren() == 3) {
    714                        n = 2;
    715                        result = node.jjtGetChild(2).jjtAccept(this, data);
    716                    }
    717                }
    718                return result;
    719            } catch (JexlException error) {
    720                throw error;
    721            } catch (RuntimeException xrt) {
    722                throw new JexlException(node.jjtGetChild(n), "if error", xrt);
    723            }
    724        }
    725
    726        /** {@inheritDoc} */
    727        public Object visit(ASTIntegerLiteral node, Object data) {
    728            if (data != null) {
    729                Integer value = Integer.valueOf(node.image);
    730                return getAttribute(data, value, node);
    731            }
    732            Integer value = (Integer) node.jjtGetValue();
    733            if (value == null) {
    734                value = Integer.valueOf(node.image);
    735                node.jjtSetValue(value);
    736            }
    737            return value;
    738        }
    739
    740        /** {@inheritDoc} */
    741        public Object visit(ASTJexlScript node, Object data) {
    742            int numChildren = node.jjtGetNumChildren();
    743            Object result = null;
    744            for (int i = 0; i < numChildren; i++) {
    745                JexlNode child = node.jjtGetChild(i);
    746                result = child.jjtAccept(this, data);
    747            }
    748            return result;
    749        }
    750
    751        /** {@inheritDoc} */
    752        public Object visit(ASTLENode node, Object data) {
    753            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    754            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    755            try {
    756                return arithmetic.lessThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE;
    757            } catch (RuntimeException xrt) {
    758                throw new JexlException(node, "<= error", xrt);
    759            }
    760        }
    761
    762        /** {@inheritDoc} */
    763        public Object visit(ASTLTNode node, Object data) {
    764            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    765            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    766            try {
    767                return arithmetic.lessThan(left, right) ? Boolean.TRUE : Boolean.FALSE;
    768            } catch (RuntimeException xrt) {
    769                throw new JexlException(node, "< error", xrt);
    770            }
    771        }
    772
    773        /** {@inheritDoc} */
    774        public Object visit(ASTMapEntry node, Object data) {
    775            Object key = node.jjtGetChild(0).jjtAccept(this, data);
    776            Object value = node.jjtGetChild(1).jjtAccept(this, data);
    777            return new Object[]{key, value};
    778        }
    779
    780        /** {@inheritDoc} */
    781        public Object visit(ASTMapLiteral node, Object data) {
    782            int childCount = node.jjtGetNumChildren();
    783            Map<Object, Object> map = new HashMap<Object, Object>();
    784
    785            for (int i = 0; i < childCount; i++) {
    786                Object[] entry = (Object[]) (node.jjtGetChild(i)).jjtAccept(this, data);
    787                map.put(entry[0], entry[1]);
    788            }
    789
    790            return map;
    791        }
    792
    793        /** {@inheritDoc} */
    794        public Object visit(ASTMethodNode node, Object data) {
    795            // the object to invoke the method on should be in the data argument
    796            if (data == null) {
    797                // if the first child of the (ASTReference) parent,
    798                // it is considered as calling a 'top level' function
    799                if (node.jjtGetParent().jjtGetChild(0) == node) {
    800                    data = resolveNamespace(null, node);
    801                    if (data == null) {
    802                        throw new JexlException(node, "no default function namespace");
    803                    }
    804                } else {
    805                    throw new JexlException(node, "attempting to call method on null");
    806                }
    807            }
    808            // objectNode 0 is the identifier (method name), the others are parameters.
    809            String methodName = ((ASTIdentifier) node.jjtGetChild(0)).image;
    810
    811            // get our arguments
    812            int argc = node.jjtGetNumChildren() - 1;
    813            Object[] argv = new Object[argc];
    814            for (int i = 0; i < argc; i++) {
    815                argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, null);
    816            }
    817
    818            JexlException xjexl = null;
    819            try {
    820                // attempt to reuse last executor cached in volatile JexlNode.value
    821                if (cache) {
    822                    Object cached = node.jjtGetValue();
    823                    if (cached instanceof JexlMethod) {
    824                        JexlMethod me = (JexlMethod) cached;
    825                        Object eval = me.tryInvoke(methodName, data, argv);
    826                        if (!me.tryFailed(eval)) {
    827                            return eval;
    828                        }
    829                    }
    830                }
    831                JexlMethod vm = uberspect.getMethod(data, methodName, argv, node);
    832                // DG: If we can't find an exact match, narrow the parameters and try again!
    833                if (vm == null) {
    834                    if (arithmetic.narrowArguments(argv)) {
    835                        vm = uberspect.getMethod(data, methodName, argv, node);
    836                    }
    837                    if (vm == null) {
    838                        xjexl = new JexlException(node, "unknown or ambiguous method", null);
    839                    }
    840                }
    841                if (xjexl == null) {
    842                    Object eval = vm.invoke(data, argv); // vm cannot be null if xjexl is null
    843                    // cache executor in volatile JexlNode.value
    844                    if (cache && vm.isCacheable()) {
    845                        node.jjtSetValue(vm);
    846                    }
    847                    return eval;
    848                }
    849            } catch (InvocationTargetException e) {
    850                xjexl = new JexlException(node, "method invocation error", e.getCause());
    851            } catch (Exception e) {
    852                xjexl = new JexlException(node, "method error", e);
    853            }
    854            return invocationFailed(xjexl);
    855        }
    856
    857        /** {@inheritDoc} */
    858        public Object visit(ASTConstructorNode node, Object data) {
    859            // first child is class or class name
    860            Object cobject = node.jjtGetChild(0).jjtAccept(this, data);
    861            // get the ctor args
    862            int argc = node.jjtGetNumChildren() - 1;
    863            Object[] argv = new Object[argc];
    864            for (int i = 0; i < argc; i++) {
    865                argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, null);
    866            }
    867
    868            JexlException xjexl = null;
    869            try {
    870                Constructor<?> ctor = uberspect.getConstructor(cobject, argv, node);
    871                // DG: If we can't find an exact match, narrow the parameters and
    872                // try again!
    873                if (ctor == null) {
    874                    if (arithmetic.narrowArguments(argv)) {
    875                        ctor = uberspect.getConstructor(cobject, argv, node);
    876                    }
    877                    if (ctor == null) {
    878                        xjexl = new JexlException(node, "unknown constructor", null);
    879                    }
    880                }
    881                if (xjexl == null) {
    882                    return ctor.newInstance(argv);
    883                }
    884            } catch (InvocationTargetException e) {
    885                xjexl = new JexlException(node, "constructor invocation error", e.getCause());
    886            } catch (Exception e) {
    887                xjexl = new JexlException(node, "constructor error", e);
    888            }
    889            return invocationFailed(xjexl);
    890        }
    891
    892        /** {@inheritDoc} */
    893        public Object visit(ASTFunctionNode node, Object data) {
    894            // objectNode 0 is the prefix
    895            String prefix = ((ASTIdentifier) node.jjtGetChild(0)).image;
    896            Object namespace = resolveNamespace(prefix, node);
    897            // objectNode 1 is the identifier , the others are parameters.
    898            String function = ((ASTIdentifier) node.jjtGetChild(1)).image;
    899
    900            // get our args
    901            int argc = node.jjtGetNumChildren() - 2;
    902            Object[] argv = new Object[argc];
    903            for (int i = 0; i < argc; i++) {
    904                argv[i] = node.jjtGetChild(i + 2).jjtAccept(this, null);
    905            }
    906
    907            JexlException xjexl = null;
    908            try {
    909                // attempt to reuse last executor cached in volatile JexlNode.value
    910                if (cache) {
    911                    Object cached = node.jjtGetValue();
    912                    if (cached instanceof JexlMethod) {
    913                        JexlMethod me = (JexlMethod) cached;
    914                        Object eval = me.tryInvoke(function, namespace, argv);
    915                        if (!me.tryFailed(eval)) {
    916                            return eval;
    917                        }
    918                    }
    919                }
    920                JexlMethod vm = uberspect.getMethod(namespace, function, argv, node);
    921                // DG: If we can't find an exact match, narrow the parameters and
    922                // try again!
    923                if (vm == null) {
    924                    // replace all numbers with the smallest type that will fit
    925                    if (arithmetic.narrowArguments(argv)) {
    926                        vm = uberspect.getMethod(namespace, function, argv, node);
    927                    }
    928                    if (vm == null) {
    929                        xjexl = new JexlException(node, "unknown function", null);
    930                    }
    931                }
    932                if (xjexl == null) {
    933                    Object eval = vm.invoke(namespace, argv); // vm cannot be null if xjexl is null
    934                    // cache executor in volatile JexlNode.value
    935                    if (cache && vm.isCacheable()) {
    936                        node.jjtSetValue(vm);
    937                    }
    938                    return eval;
    939                }
    940            } catch (InvocationTargetException e) {
    941                xjexl = new JexlException(node, "function invocation error", e.getCause());
    942            } catch (Exception e) {
    943                xjexl = new JexlException(node, "function error", e);
    944            }
    945            return invocationFailed(xjexl);
    946        }
    947
    948        /** {@inheritDoc} */
    949        public Object visit(ASTModNode node, Object data) {
    950            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    951            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    952            try {
    953                return arithmetic.mod(left, right);
    954            } catch (RuntimeException xrt) {
    955                if (!strict && xrt instanceof ArithmeticException) {
    956                    return new Double(0.0);
    957                }
    958                JexlNode xnode = findNullOperand(xrt, node, left, right);
    959                throw new JexlException(xnode, "% error", xrt);
    960            }
    961        }
    962
    963        /** {@inheritDoc} */
    964        public Object visit(ASTMulNode node, Object data) {
    965            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    966            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    967            try {
    968                return arithmetic.multiply(left, right);
    969            } catch (RuntimeException xrt) {
    970                JexlNode xnode = findNullOperand(xrt, node, left, right);
    971                throw new JexlException(xnode, "* error", xrt);
    972            }
    973        }
    974
    975        /** {@inheritDoc} */
    976        public Object visit(ASTNENode node, Object data) {
    977            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    978            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    979            try {
    980                return arithmetic.equals(left, right) ? Boolean.FALSE : Boolean.TRUE;
    981            } catch (RuntimeException xrt) {
    982                JexlNode xnode = findNullOperand(xrt, node, left, right);
    983                throw new JexlException(xnode, "!= error", xrt);
    984            }
    985        }
    986
    987        /** {@inheritDoc} */
    988        public Object visit(ASTNRNode node, Object data) {
    989            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    990            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    991            try {
    992                return arithmetic.matches(left, right) ? Boolean.FALSE : Boolean.TRUE;
    993            } catch (RuntimeException xrt) {
    994                throw new JexlException(node, "!~ error", xrt);
    995            }
    996        }
    997
    998        /** {@inheritDoc} */
    999        public Object visit(ASTNotNode node, Object data) {
    1000            Object val = node.jjtGetChild(0).jjtAccept(this, data);
    1001            return arithmetic.toBoolean(val) ? Boolean.FALSE : Boolean.TRUE;
    1002        }
    1003
    1004        /** {@inheritDoc} */
    1005        public Object visit(ASTNullLiteral node, Object data) {
    1006            return null;
    1007        }
    1008
    1009        /** {@inheritDoc} */
    1010        public Object visit(ASTOrNode node, Object data) {
    1011            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    1012            try {
    1013                boolean leftValue = arithmetic.toBoolean(left);
    1014                if (leftValue) {
    1015                    return Boolean.TRUE;
    1016                }
    1017            } catch (RuntimeException xrt) {
    1018                throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
    1019            }
    1020            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    1021            try {
    1022                boolean rightValue = arithmetic.toBoolean(right);
    1023                if (rightValue) {
    1024                    return Boolean.TRUE;
    1025                }
    1026            } catch (RuntimeException xrt) {
    1027                throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt);
    1028            }
    1029            return Boolean.FALSE;
    1030        }
    1031
    1032        /** {@inheritDoc} */
    1033        public Object visit(ASTReference node, Object data) {
    1034            // could be array access, identifier or map literal
    1035            // followed by zero or more ("." and array access, method, size,
    1036            // identifier or integer literal)
    1037
    1038            int numChildren = node.jjtGetNumChildren();
    1039
    1040            // pass first piece of data in and loop through children
    1041            Object result = null;
    1042            StringBuilder variableName = null;
    1043            boolean isVariable = true;
    1044            int v = 0;
    1045            for (int c = 0; c < numChildren; c++) {
    1046                JexlNode theNode = node.jjtGetChild(c);
    1047                isVariable &= (theNode instanceof ASTIdentifier);
    1048                result = theNode.jjtAccept(this, result);
    1049                // if we get null back a result, check for an ant variable
    1050                if (result == null && isVariable) {
    1051                    if (v == 0) {
    1052                        variableName = new StringBuilder(node.jjtGetChild(0).image);
    1053                        v = 1;
    1054                    }
    1055                    for(; v <= c; ++v) {
    1056                        variableName.append('.');
    1057                        variableName.append(node.jjtGetChild(v).image);
    1058                    }
    1059                    result = context.get(variableName.toString());
    1060                }
    1061            }
    1062            if (result == null) {
    1063                if (isVariable
    1064                    && !(node.jjtGetParent() instanceof ASTTernaryNode)
    1065                    && !context.has(variableName.toString())) {
    1066                    JexlException xjexl = new JexlException(node, "undefined variable " + variableName.toString());
    1067                    return unknownVariable(xjexl);
    1068                }
    1069            }
    1070            return result;
    1071        }
    1072
    1073        /** {@inheritDoc} */
    1074        public Object visit(ASTSizeFunction node, Object data) {
    1075            Object val = node.jjtGetChild(0).jjtAccept(this, data);
    1076
    1077            if (val == null) {
    1078                throw new JexlException(node, "size() : argument is null", null);
    1079            }
    1080
    1081            return Integer.valueOf(sizeOf(node, val));
    1082        }
    1083
    1084        /** {@inheritDoc} */
    1085        public Object visit(ASTSizeMethod node, Object data) {
    1086            return Integer.valueOf(sizeOf(node, data));
    1087        }
    1088
    1089        /** {@inheritDoc} */
    1090        public Object visit(ASTStringLiteral node, Object data) {
    1091            return node.image;
    1092        }
    1093
    1094        /** {@inheritDoc} */
    1095        public Object visit(ASTTernaryNode node, Object data) {
    1096            Object condition = node.jjtGetChild(0).jjtAccept(this, data);
    1097            if (node.jjtGetNumChildren() == 3) {
    1098                if (condition != null && arithmetic.toBoolean(condition)) {
    1099                    return node.jjtGetChild(1).jjtAccept(this, data);
    1100                } else {
    1101                    return node.jjtGetChild(2).jjtAccept(this, data);
    1102                }
    1103            }
    1104            if (condition != null && !Boolean.FALSE.equals(condition)) {
    1105                return condition;
    1106            } else {
    1107                return node.jjtGetChild(1).jjtAccept(this, data);
    1108            }
    1109        }
    1110
    1111        /** {@inheritDoc} */
    1112        public Object visit(ASTTrueNode node, Object data) {
    1113            return Boolean.TRUE;
    1114        }
    1115
    1116        /** {@inheritDoc} */
    1117        public Object visit(ASTUnaryMinusNode node, Object data) {
    1118            JexlNode valNode = node.jjtGetChild(0);
    1119            Object val = valNode.jjtAccept(this, data);
    1120            if (val instanceof Byte) {
    1121                byte valueAsByte = ((Byte) val).byteValue();
    1122                return Byte.valueOf((byte) -valueAsByte);
    1123            } else if (val instanceof Short) {
    1124                short valueAsShort = ((Short) val).shortValue();
    1125                return Short.valueOf((short) -valueAsShort);
    1126            } else if (val instanceof Integer) {
    1127                int valueAsInt = ((Integer) val).intValue();
    1128                return Integer.valueOf(-valueAsInt);
    1129            } else if (val instanceof Long) {
    1130                long valueAsLong = ((Long) val).longValue();
    1131                return Long.valueOf(-valueAsLong);
    1132            } else if (val instanceof Float) {
    1133                float valueAsFloat = ((Float) val).floatValue();
    1134                return new Float(-valueAsFloat);
    1135            } else if (val instanceof Double) {
    1136                double valueAsDouble = ((Double) val).doubleValue();
    1137                return new Double(-valueAsDouble);
    1138            } else if (val instanceof BigDecimal) {
    1139                BigDecimal valueAsBigD = (BigDecimal) val;
    1140                return valueAsBigD.negate();
    1141            } else if (val instanceof BigInteger) {
    1142                BigInteger valueAsBigI = (BigInteger) val;
    1143                return valueAsBigI.negate();
    1144            }
    1145            throw new JexlException(valNode, "not a number");
    1146        }
    1147
    1148        /** {@inheritDoc} */
    1149        public Object visit(ASTWhileStatement node, Object data) {
    1150            Object result = null;
    1151            /* first objectNode is the expression */
    1152            Node expressionNode = node.jjtGetChild(0);
    1153            while (arithmetic.toBoolean(expressionNode.jjtAccept(this, data))) {
    1154                // execute statement
    1155                result = node.jjtGetChild(1).jjtAccept(this, data);
    1156            }
    1157
    1158            return result;
    1159        }
    1160
    1161        /**
    1162         * Calculate the <code>size</code> of various types: Collection, Array,
    1163         * Map, String, and anything that has a int size() method.
    1164         * @param node the node that gave the value to size
    1165         * @param val the object to get the size of.
    1166         * @return the size of val
    1167         */
    1168        private int sizeOf(JexlNode node, Object val) {
    1169            if (val instanceof Collection<?>) {
    1170                return ((Collection<?>) val).size();
    1171            } else if (val.getClass().isArray()) {
    1172                return Array.getLength(val);
    1173            } else if (val instanceof Map<?, ?>) {
    1174                return ((Map<?, ?>) val).size();
    1175            } else if (val instanceof String) {
    1176                return ((String) val).length();
    1177            } else {
    1178                // check if there is a size method on the object that returns an
    1179                // integer and if so, just use it
    1180                Object[] params = new Object[0];
    1181                JexlMethod vm = uberspect.getMethod(val, "size", EMPTY_PARAMS, node);
    1182                if (vm != null && vm.getReturnType() == Integer.TYPE) {
    1183                    Integer result;
    1184                    try {
    1185                        result = (Integer) vm.invoke(val, params);
    1186                    } catch (Exception e) {
    1187                        throw new JexlException(node, "size() : error executing", e);
    1188                    }
    1189                    return result.intValue();
    1190                }
    1191                throw new JexlException(node, "size() : unsupported type : " + val.getClass(), null);
    1192            }
    1193        }
    1194
    1195        /**
    1196         * Gets an attribute of an object.
    1197         *
    1198         * @param object to retrieve value from
    1199         * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
    1200         *            key for a map
    1201         * @return the attribute value
    1202         */
    1203        public Object getAttribute(Object object, Object attribute) {
    1204            return getAttribute(object, attribute, null);
    1205        }
    1206
    1207        /**
    1208         * Gets an attribute of an object.
    1209         *
    1210         * @param object to retrieve value from
    1211         * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
    1212         *            key for a map
    1213         * @param node the node that evaluated as the object
    1214         * @return the attribute value
    1215         */
    1216        protected Object getAttribute(Object object, Object attribute, JexlNode node) {
    1217            if (object == null) {
    1218                throw new JexlException(node, "object is null");
    1219            }
    1220            // attempt to reuse last executor cached in volatile JexlNode.value
    1221            if (node != null && cache) {
    1222                Object cached = node.jjtGetValue();
    1223                if (cached instanceof JexlPropertyGet) {
    1224                    JexlPropertyGet vg = (JexlPropertyGet) cached;
    1225                    Object value = vg.tryInvoke(object, attribute);
    1226                    if (!vg.tryFailed(value)) {
    1227                        return value;
    1228                    }
    1229                }
    1230            }
    1231            JexlPropertyGet vg = uberspect.getPropertyGet(object, attribute, node);
    1232            if (vg != null) {
    1233                try {
    1234                    Object value = vg.invoke(object);
    1235                    // cache executor in volatile JexlNode.value
    1236                    if (node != null && cache && vg.isCacheable()) {
    1237                        node.jjtSetValue(vg);
    1238                    }
    1239                    return value;
    1240                } catch (Exception xany) {
    1241                    if (node == null) {
    1242                        throw new RuntimeException(xany);
    1243                    } else {
    1244                        JexlException xjexl = new JexlException(node, "get object property error", xany);
    1245                        if (strict) {
    1246                            throw xjexl;
    1247                        }
    1248                        if (!silent) {
    1249                            logger.warn(xjexl.getMessage());
    1250                        }
    1251                    }
    1252                }
    1253            }
    1254
    1255            return null;
    1256        }
    1257
    1258        /**
    1259         * Sets an attribute of an object.
    1260         *
    1261         * @param object to set the value to
    1262         * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
    1263         *            key for a map
    1264         * @param value the value to assign to the object's attribute
    1265         */
    1266        public void setAttribute(Object object, Object attribute, Object value) {
    1267            setAttribute(object, attribute, value, null);
    1268        }
    1269
    1270        /**
    1271         * Sets an attribute of an object.
    1272         *
    1273         * @param object to set the value to
    1274         * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
    1275         *            key for a map
    1276         * @param value the value to assign to the object's attribute
    1277         * @param node the node that evaluated as the object
    1278         */
    1279        protected void setAttribute(Object object, Object attribute, Object value, JexlNode node) {
    1280            // attempt to reuse last executor cached in volatile JexlNode.value
    1281            if (node != null && cache) {
    1282                Object cached = node.jjtGetValue();
    1283                if (cached instanceof JexlPropertySet) {
    1284                    JexlPropertySet setter = (JexlPropertySet) cached;
    1285                    Object eval = setter.tryInvoke(object, attribute, value);
    1286                    if (!setter.tryFailed(eval)) {
    1287                        return;
    1288                    }
    1289                }
    1290            }
    1291            JexlException xjexl = null;
    1292            JexlPropertySet vs = uberspect.getPropertySet(object, attribute, value, node);
    1293            if (vs != null) {
    1294                try {
    1295                    // cache executor in volatile JexlNode.value
    1296                    vs.invoke(object, value);
    1297                    if (node != null && cache && vs.isCacheable()) {
    1298                        node.jjtSetValue(vs);
    1299                    }
    1300                    return;
    1301                } catch (RuntimeException xrt) {
    1302                    if (node == null) {
    1303                        throw xrt;
    1304                    }
    1305                    xjexl = new JexlException(node, "set object property error", xrt);
    1306                } catch (Exception xany) {
    1307                    if (node == null) {
    1308                        throw new RuntimeException(xany);
    1309                    }
    1310                    xjexl = new JexlException(node, "set object property error", xany);
    1311                }
    1312            }
    1313            if (xjexl == null) {
    1314                String error = "unable to set object property"
    1315                               + ", class: " + object.getClass().getName()
    1316                               + ", property: " + attribute;
    1317                if (node == null) {
    1318                    throw new UnsupportedOperationException(error);
    1319                }
    1320                xjexl = new JexlException(node, error, null);
    1321            }
    1322            if (strict) {
    1323                throw xjexl;
    1324            }
    1325            if (!silent) {
    1326                logger.warn(xjexl.getMessage());
    1327            }
    1328        }
    1329
    1330        /**
    1331         * Unused, satisfy ParserVisitor interface.
    1332         * @param node a node
    1333         * @param data the data
    1334         * @return does not return
    1335         */
    1336        public Object visit(SimpleNode node, Object data) {
    1337            throw new UnsupportedOperationException("Not supported yet.");
    1338        }
    1339
    1340        /**
    1341         * Unused, should throw in Parser.
    1342         * @param node a node
    1343         * @param data the data
    1344         * @return does not return
    1345         */
    1346        public Object visit(ASTAmbiguous node, Object data) {
    1347            throw new UnsupportedOperationException("unexpected type of node");
    1348        }
    1349    }