    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                if (nindex instanceof JexlNode.Literal<?>) {
    352                    object = nindex.jjtAccept(this, object);
    353                } else {
    354                    Object index = nindex.jjtAccept(this, null);
    355                    object = getAttribute(object, index, nindex);
    356                }
    357            }
    358
    359            return object;
    360        }
    361
    362        /** {@inheritDoc} */
    363        public Object visit(ASTArrayLiteral node, Object data) {
    364            Object literal = node.getLiteral();
    365            if (literal == null) {
    366                int childCount = node.jjtGetNumChildren();
    367                Object[] array = new Object[childCount];
    368                for (int i = 0; i < childCount; i++) {
    369                    Object entry = node.jjtGetChild(i).jjtAccept(this, data);
    370                    array[i] = entry;
    371                }
    372                literal = arithmetic.narrowArrayType(array);
    373                node.setLiteral(literal);
    374            }
    375            return literal;
    376        }
    377
    378        /** {@inheritDoc} */
    379        public Object visit(ASTAssignment node, Object data) {
    380            // left contains the reference to assign to
    381            JexlNode left = node.jjtGetChild(0);
    382            if (!(left instanceof ASTReference)) {
    383                throw new JexlException(left, "illegal assignment form");
    384            }
    385            // right is the value expression to assign
    386            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    387
    388            // determine initial object & property:
    389            JexlNode objectNode = null;
    390            Object object = null;
    391            JexlNode propertyNode = null;
    392            Object property = null;
    393            boolean isVariable = true;
    394            int v = 0;
    395            StringBuilder variableName = null;
    396            // 1: follow children till penultimate
    397            int last = left.jjtGetNumChildren() - 1;
    398            for (int c = 0; c < last; ++c) {
    399                objectNode = left.jjtGetChild(c);
    400                // evaluate the property within the object
    401                object = objectNode.jjtAccept(this, object);
    402                if (object != null) {
    403                    continue;
    404                }
    405                isVariable &= objectNode instanceof ASTIdentifier;
    406                // if we get null back as a result, check for an ant variable
    407                if (isVariable) {
    408                    if (v == 0) {
    409                        variableName = new StringBuilder(left.jjtGetChild(0).image);
    410                        v = 1;
    411                    }
    412                    for(; v <= c; ++v) {
    413                        variableName.append('.');
    414                        variableName.append(left.jjtGetChild(v).image);
    415                    }
    416                    object = context.get(variableName.toString());
    417                    // disallow mixing ant & bean with same root; avoid ambiguity
    418                    if (object != null) {
    419                        isVariable = false;
    420                    }
    421                } else {
    422                    throw new JexlException(objectNode, "illegal assignment form");
    423                }
    424            }
    425            // 2: last objectNode will perform assignement in all cases
    426            propertyNode = left.jjtGetChild(last);
    427            boolean antVar = false;
    428            if (propertyNode instanceof ASTIdentifier) {
    429                property = ((ASTIdentifier) propertyNode).image;
    430                antVar = true;
    431            } else if (propertyNode instanceof ASTIntegerLiteral) {
    432                property = ((ASTIntegerLiteral) propertyNode).getLiteral();
    433                antVar = true;
    434            } else if (propertyNode instanceof ASTArrayAccess) {
    435                // first objectNode is the identifier
    436                objectNode = propertyNode;
    437                ASTArrayAccess narray = (ASTArrayAccess) objectNode;
    438                Object nobject = narray.jjtGetChild(0).jjtAccept(this, object);
    439                if (nobject == null) {
    440                    throw new JexlException(objectNode, "array element is null");
    441                } else {
    442                    object = nobject;
    443                }
    444                // can have multiple nodes - either an expression, integer literal or
    445                // reference
    446                last = narray.jjtGetNumChildren() - 1;
    447                for (int i = 1; i < last; i++) {
    448                    objectNode = narray.jjtGetChild(i);
    449                    if (objectNode instanceof JexlNode.Literal<?>) {
    450                        object = objectNode.jjtAccept(this, object);
    451                    } else {
    452                        Object index = objectNode.jjtAccept(this, null);
    453                        object = getAttribute(object, index, objectNode);
    454                    }
    455                }
    456                property = narray.jjtGetChild(last).jjtAccept(this, null);
    457            } else {
    458                throw new JexlException(objectNode, "illegal assignment form");
    459            }
    460            // deal with ant variable; set context
    461            if (antVar) {
    462                if (isVariable && object == null) {
    463                    if (variableName != null) {
    464                        if (last > 0) {
    465                            variableName.append('.');
    466                        }
    467                        variableName.append(property);
    468                        property = variableName.toString();
    469                    }
    470                    context.set(String.valueOf(property), right);
    471                    return right;
    472                }
    473            }
    474            if (property == null) {
    475                // no property, we fail
    476                throw new JexlException(propertyNode, "property is null");
    477            }
    478            if (object == null) {
    479                // no object, we fail
    480                throw new JexlException(objectNode, "bean is null");
    481            }
    482            // one before last, assign
    483            setAttribute(object, property, right, propertyNode);
    484            return right;
    485        }
    486
    487        /** {@inheritDoc} */
    488        public Object visit(ASTBitwiseAndNode node, Object data) {
    489            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    490            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    491            int n = 0;
    492            // coerce these two values longs and 'and'.
    493            try {
    494                long l = arithmetic.toLong(left);
    495                n = 1;
    496                long r = arithmetic.toLong(right);
    497                return Long.valueOf(l & r);
    498            } catch (RuntimeException xrt) {
    499                throw new JexlException(node.jjtGetChild(n), "long coercion error", xrt);
    500            }
    501        }
    502
    503        /** {@inheritDoc} */
    504        public Object visit(ASTBitwiseComplNode node, Object data) {
    505            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    506            try {
    507                long l = arithmetic.toLong(left);
    508                return Long.valueOf(~l);
    509            } catch (RuntimeException xrt) {
    510                throw new JexlException(node.jjtGetChild(0), "long coercion error", xrt);
    511            }
    512        }
    513
    514        /** {@inheritDoc} */
    515        public Object visit(ASTBitwiseOrNode node, Object data) {
    516            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    517            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    518            int n = 0;
    519            // coerce these two values longs and 'or'.
    520            try {
    521                long l = arithmetic.toLong(left);
    522                n = 1;
    523                long r = arithmetic.toLong(right);
    524                return Long.valueOf(l | r);
    525            } catch (RuntimeException xrt) {
    526                throw new JexlException(node.jjtGetChild(n), "long coercion error", xrt);
    527            }
    528        }
    529
    530        /** {@inheritDoc} */
    531        public Object visit(ASTBitwiseXorNode node, Object data) {
    532            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    533            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    534            int n = 0;
    535            // coerce these two values longs and 'xor'.
    536            try {
    537                long l = arithmetic.toLong(left);
    538                n = 1;
    539                long r = arithmetic.toLong(right);
    540                return Long.valueOf(l ^ r);
    541            } catch (RuntimeException xrt) {
    542                throw new JexlException(node.jjtGetChild(n), "long coercion error", xrt);
    543            }
    544        }
    545
    546        /** {@inheritDoc} */
    547        public Object visit(ASTBlock node, Object data) {
    548            int numChildren = node.jjtGetNumChildren();
    549            Object result = null;
    550            for (int i = 0; i < numChildren; i++) {
    551                result = node.jjtGetChild(i).jjtAccept(this, data);
    552            }
    553            return result;
    554        }
    555
    556        /** {@inheritDoc} */
    557        public Object visit(ASTDivNode node, Object data) {
    558            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    559            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    560            try {
    561                return arithmetic.divide(left, right);
    562            } catch (RuntimeException xrt) {
    563                if (!strict && xrt instanceof ArithmeticException) {
    564                    return new Double(0.0);
    565                }
    566                JexlNode xnode = findNullOperand(xrt, node, left, right);
    567                throw new JexlException(xnode, "divide error", xrt);
    568            }
    569        }
    570
    571        /** {@inheritDoc} */
    572        public Object visit(ASTEmptyFunction node, Object data) {
    573            Object o = node.jjtGetChild(0).jjtAccept(this, data);
    574            if (o == null) {
    575                return Boolean.TRUE;
    576            }
    577            if (o instanceof String && "".equals(o)) {
    578                return Boolean.TRUE;
    579            }
    580            if (o.getClass().isArray() && ((Object[]) o).length == 0) {
    581                return Boolean.TRUE;
    582            }
    583            if (o instanceof Collection<?>) {
    584                return ((Collection<?>) o).isEmpty()? Boolean.TRUE : Boolean.FALSE;
    585            }
    586            // Map isn't a collection
    587            if (o instanceof Map<?, ?>) {
    588                return ((Map<?,?>) o).isEmpty()? Boolean.TRUE : Boolean.FALSE;
    589            }
    590            return Boolean.FALSE;
    591        }
    592
    593        /** {@inheritDoc} */
    594        public Object visit(ASTEQNode node, Object data) {
    595            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    596            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    597            try {
    598                return arithmetic.equals(left, right) ? Boolean.TRUE : Boolean.FALSE;
    599            } catch (RuntimeException xrt) {
    600                throw new JexlException(node, "== error", xrt);
    601            }
    602        }
    603
    604        /** {@inheritDoc} */
    605        public Object visit(ASTFalseNode node, Object data) {
    606            return Boolean.FALSE;
    607        }
    608
    609        /** {@inheritDoc} */
    610        public Object visit(ASTFloatLiteral node, Object data) {
    611            if (data != null) {
    612                return getAttribute(data, node.getLiteral(), node);
    613            }
    614            return node.getLiteral();
    615        }
    616
    617        /** {@inheritDoc} */
    618        public Object visit(ASTForeachStatement node, Object data) {
    619            Object result = null;
    620            /* first objectNode is the loop variable */
    621            ASTReference loopReference = (ASTReference) node.jjtGetChild(0);
    622            ASTIdentifier loopVariable = (ASTIdentifier) loopReference.jjtGetChild(0);
    623            /* second objectNode is the variable to iterate */
    624            Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data);
    625            // make sure there is a value to iterate on and a statement to execute
    626            if (iterableValue != null && node.jjtGetNumChildren() >= 3) {
    627                /* third objectNode is the statement to execute */
    628                JexlNode statement = node.jjtGetChild(2);
    629                // get an iterator for the collection/array etc via the
    630                // introspector.
    631                Iterator<?> itemsIterator = getUberspect().getIterator(iterableValue, node);
    632                if (itemsIterator != null) {
    633                    while (itemsIterator.hasNext()) {
    634                        // set loopVariable to value of iterator
    635                        Object value = itemsIterator.next();
    636                        context.set(loopVariable.image, value);
    637                        // execute statement
    638                        result = statement.jjtAccept(this, data);
    639                    }
    640                }
    641            }
    642            return result;
    643        }
    644
    645        /** {@inheritDoc} */
    646        public Object visit(ASTGENode node, Object data) {
    647            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    648            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    649            try {
    650                return arithmetic.greaterThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE;
    651            } catch (RuntimeException xrt) {
    652                throw new JexlException(node, ">= error", xrt);
    653            }
    654        }
    655
    656        /** {@inheritDoc} */
    657        public Object visit(ASTGTNode node, Object data) {
    658            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    659            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    660            try {
    661                return arithmetic.greaterThan(left, right) ? Boolean.TRUE : Boolean.FALSE;
    662            } catch (RuntimeException xrt) {
    663                throw new JexlException(node, "> error", xrt);
    664            }
    665        }
    666
    667        /** {@inheritDoc} */
    668        public Object visit(ASTERNode node, Object data) {
    669            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    670            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    671            try {
    672                return arithmetic.matches(left, right) ? Boolean.TRUE : Boolean.FALSE;
    673            } catch (RuntimeException xrt) {
    674                throw new JexlException(node, "=~ error", xrt);
    675            }
    676        }
    677
    678        /** {@inheritDoc} */
    679        public Object visit(ASTIdentifier node, Object data) {
    680            String name = node.image;
    681            if (data == null) {
    682                if (registers != null) {
    683                    return registers[name.charAt(1) - '0'];
    684                }
    685                Object value = context.get(name);
    686                if (value == null
    687                    && !(node.jjtGetParent() instanceof ASTReference)
    688                    && !context.has(name)) {
    689                    JexlException xjexl = new JexlException(node, "undefined variable " + name);
    690                    return unknownVariable(xjexl);
    691                }
    692                return value;
    693            } else {
    694                return getAttribute(data, name, node);
    695            }
    696        }
    697
    698        /** {@inheritDoc} */
    699        public Object visit(ASTIfStatement node, Object data) {
    700            int n = 0;
    701            try {
    702                Object result = null;
    703                /* first objectNode is the expression */
    704                Object expression = node.jjtGetChild(0).jjtAccept(this, data);
    705                if (arithmetic.toBoolean(expression)) {
    706                    // first objectNode is true statement
    707                    n = 1;
    708                    result = node.jjtGetChild(1).jjtAccept(this, data);
    709                } else {
    710                    // if there is a false, execute it. false statement is the second
    711                    // objectNode
    712                    if (node.jjtGetNumChildren() == 3) {
    713                        n = 2;
    714                        result = node.jjtGetChild(2).jjtAccept(this, data);
    715                    }
    716                }
    717                return result;
    718            } catch (JexlException error) {
    719                throw error;
    720            } catch (RuntimeException xrt) {
    721                throw new JexlException(node.jjtGetChild(n), "if error", xrt);
    722            }
    723        }
    724
    725        /** {@inheritDoc} */
    726        public Object visit(ASTIntegerLiteral node, Object data) {
    727            if (data != null) {
    728                return getAttribute(data, node.getLiteral(), node);
    729            }
    730            return node.getLiteral();
    731        }
    732
    733        /** {@inheritDoc} */
    734        public Object visit(ASTJexlScript node, Object data) {
    735            int numChildren = node.jjtGetNumChildren();
    736            Object result = null;
    737            for (int i = 0; i < numChildren; i++) {
    738                JexlNode child = node.jjtGetChild(i);
    739                result = child.jjtAccept(this, data);
    740            }
    741            return result;
    742        }
    743
    744        /** {@inheritDoc} */
    745        public Object visit(ASTLENode node, Object data) {
    746            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    747            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    748            try {
    749                return arithmetic.lessThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE;
    750            } catch (RuntimeException xrt) {
    751                throw new JexlException(node, "<= error", xrt);
    752            }
    753        }
    754
    755        /** {@inheritDoc} */
    756        public Object visit(ASTLTNode node, Object data) {
    757            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    758            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    759            try {
    760                return arithmetic.lessThan(left, right) ? Boolean.TRUE : Boolean.FALSE;
    761            } catch (RuntimeException xrt) {
    762                throw new JexlException(node, "< error", xrt);
    763            }
    764        }
    765
    766        /** {@inheritDoc} */
    767        public Object visit(ASTMapEntry node, Object data) {
    768            Object key = node.jjtGetChild(0).jjtAccept(this, data);
    769            Object value = node.jjtGetChild(1).jjtAccept(this, data);
    770            return new Object[]{key, value};
    771        }
    772
    773        /** {@inheritDoc} */
    774        public Object visit(ASTMapLiteral node, Object data) {
    775            int childCount = node.jjtGetNumChildren();
    776            Map<Object, Object> map = new HashMap<Object, Object>();
    777
    778            for (int i = 0; i < childCount; i++) {
    779                Object[] entry = (Object[]) (node.jjtGetChild(i)).jjtAccept(this, data);
    780                map.put(entry[0], entry[1]);
    781            }
    782
    783            return map;
    784        }
    785
    786        /** {@inheritDoc} */
    787        public Object visit(ASTMethodNode node, Object data) {
    788            // the object to invoke the method on should be in the data argument
    789            if (data == null) {
    790                // if the first child of the (ASTReference) parent,
    791                // it is considered as calling a 'top level' function
    792                if (node.jjtGetParent().jjtGetChild(0) == node) {
    793                    data = resolveNamespace(null, node);
    794                    if (data == null) {
    795                        throw new JexlException(node, "no default function namespace");
    796                    }
    797                } else {
    798                    throw new JexlException(node, "attempting to call method on null");
    799                }
    800            }
    801            // objectNode 0 is the identifier (method name), the others are parameters.
    802            String methodName = ((ASTIdentifier) node.jjtGetChild(0)).image;
    803
    804            // get our arguments
    805            int argc = node.jjtGetNumChildren() - 1;
    806            Object[] argv = new Object[argc];
    807            for (int i = 0; i < argc; i++) {
    808                argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, null);
    809            }
    810
    811            JexlException xjexl = null;
    812            try {
    813                // attempt to reuse last executor cached in volatile JexlNode.value
    814                if (cache) {
    815                    Object cached = node.jjtGetValue();
    816                    if (cached instanceof JexlMethod) {
    817                        JexlMethod me = (JexlMethod) cached;
    818                        Object eval = me.tryInvoke(methodName, data, argv);
    819                        if (!me.tryFailed(eval)) {
    820                            return eval;
    821                        }
    822                    }
    823                }
    824                JexlMethod vm = uberspect.getMethod(data, methodName, argv, node);
    825                // DG: If we can't find an exact match, narrow the parameters and try again!
    826                if (vm == null) {
    827                    if (arithmetic.narrowArguments(argv)) {
    828                        vm = uberspect.getMethod(data, methodName, argv, node);
    829                    }
    830                    if (vm == null) {
    831                        xjexl = new JexlException(node, "unknown or ambiguous method", null);
    832                    }
    833                }
    834                if (xjexl == null) {
    835                    Object eval = vm.invoke(data, argv); // vm cannot be null if xjexl is null
    836                    // cache executor in volatile JexlNode.value
    837                    if (cache && vm.isCacheable()) {
    838                        node.jjtSetValue(vm);
    839                    }
    840                    return eval;
    841                }
    842            } catch (InvocationTargetException e) {
    843                xjexl = new JexlException(node, "method invocation error", e.getCause());
    844            } catch (Exception e) {
    845                xjexl = new JexlException(node, "method error", e);
    846            }
    847            return invocationFailed(xjexl);
    848        }
    849
    850        /** {@inheritDoc} */
    851        public Object visit(ASTConstructorNode node, Object data) {
    852            // first child is class or class name
    853            Object cobject = node.jjtGetChild(0).jjtAccept(this, data);
    854            // get the ctor args
    855            int argc = node.jjtGetNumChildren() - 1;
    856            Object[] argv = new Object[argc];
    857            for (int i = 0; i < argc; i++) {
    858                argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, null);
    859            }
    860
    861            JexlException xjexl = null;
    862            try {
    863                Constructor<?> ctor = uberspect.getConstructor(cobject, argv, node);
    864                // DG: If we can't find an exact match, narrow the parameters and
    865                // try again!
    866                if (ctor == null) {
    867                    if (arithmetic.narrowArguments(argv)) {
    868                        ctor = uberspect.getConstructor(cobject, argv, node);
    869                    }
    870                    if (ctor == null) {
    871                        xjexl = new JexlException(node, "unknown constructor", null);
    872                    }
    873                }
    874                if (xjexl == null) {
    875                    return ctor.newInstance(argv);
    876                }
    877            } catch (InvocationTargetException e) {
    878                xjexl = new JexlException(node, "constructor invocation error", e.getCause());
    879            } catch (Exception e) {
    880                xjexl = new JexlException(node, "constructor error", e);
    881            }
    882            return invocationFailed(xjexl);
    883        }
    884
    885        /** {@inheritDoc} */
    886        public Object visit(ASTFunctionNode node, Object data) {
    887            // objectNode 0 is the prefix
    888            String prefix = ((ASTIdentifier) node.jjtGetChild(0)).image;
    889            Object namespace = resolveNamespace(prefix, node);
    890            // objectNode 1 is the identifier , the others are parameters.
    891            String function = ((ASTIdentifier) node.jjtGetChild(1)).image;
    892
    893            // get our args
    894            int argc = node.jjtGetNumChildren() - 2;
    895            Object[] argv = new Object[argc];
    896            for (int i = 0; i < argc; i++) {
    897                argv[i] = node.jjtGetChild(i + 2).jjtAccept(this, null);
    898            }
    899
    900            JexlException xjexl = null;
    901            try {
    902                // attempt to reuse last executor cached in volatile JexlNode.value
    903                if (cache) {
    904                    Object cached = node.jjtGetValue();
    905                    if (cached instanceof JexlMethod) {
    906                        JexlMethod me = (JexlMethod) cached;
    907                        Object eval = me.tryInvoke(function, namespace, argv);
    908                        if (!me.tryFailed(eval)) {
    909                            return eval;
    910                        }
    911                    }
    912                }
    913                JexlMethod vm = uberspect.getMethod(namespace, function, argv, node);
    914                // DG: If we can't find an exact match, narrow the parameters and
    915                // try again!
    916                if (vm == null) {
    917                    // replace all numbers with the smallest type that will fit
    918                    if (arithmetic.narrowArguments(argv)) {
    919                        vm = uberspect.getMethod(namespace, function, argv, node);
    920                    }
    921                    if (vm == null) {
    922                        xjexl = new JexlException(node, "unknown function", null);
    923                    }
    924                }
    925                if (xjexl == null) {
    926                    Object eval = vm.invoke(namespace, argv); // vm cannot be null if xjexl is null
    927                    // cache executor in volatile JexlNode.value
    928                    if (cache && vm.isCacheable()) {
    929                        node.jjtSetValue(vm);
    930                    }
    931                    return eval;
    932                }
    933            } catch (InvocationTargetException e) {
    934                xjexl = new JexlException(node, "function invocation error", e.getCause());
    935            } catch (Exception e) {
    936                xjexl = new JexlException(node, "function error", e);
    937            }
    938            return invocationFailed(xjexl);
    939        }
    940
    941        /** {@inheritDoc} */
    942        public Object visit(ASTModNode node, Object data) {
    943            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    944            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    945            try {
    946                return arithmetic.mod(left, right);
    947            } catch (RuntimeException xrt) {
    948                if (!strict && xrt instanceof ArithmeticException) {
    949                    return new Double(0.0);
    950                }
    951                JexlNode xnode = findNullOperand(xrt, node, left, right);
    952                throw new JexlException(xnode, "% error", xrt);
    953            }
    954        }
    955
    956        /** {@inheritDoc} */
    957        public Object visit(ASTMulNode node, Object data) {
    958            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    959            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    960            try {
    961                return arithmetic.multiply(left, right);
    962            } catch (RuntimeException xrt) {
    963                JexlNode xnode = findNullOperand(xrt, node, left, right);
    964                throw new JexlException(xnode, "* error", xrt);
    965            }
    966        }
    967
    968        /** {@inheritDoc} */
    969        public Object visit(ASTNENode node, Object data) {
    970            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    971            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    972            try {
    973                return arithmetic.equals(left, right) ? Boolean.FALSE : Boolean.TRUE;
    974            } catch (RuntimeException xrt) {
    975                JexlNode xnode = findNullOperand(xrt, node, left, right);
    976                throw new JexlException(xnode, "!= error", xrt);
    977            }
    978        }
    979
    980        /** {@inheritDoc} */
    981        public Object visit(ASTNRNode node, Object data) {
    982            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    983            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    984            try {
    985                return arithmetic.matches(left, right) ? Boolean.FALSE : Boolean.TRUE;
    986            } catch (RuntimeException xrt) {
    987                throw new JexlException(node, "!~ error", xrt);
    988            }
    989        }
    990
    991        /** {@inheritDoc} */
    992        public Object visit(ASTNotNode node, Object data) {
    993            Object val = node.jjtGetChild(0).jjtAccept(this, data);
    994            return arithmetic.toBoolean(val) ? Boolean.FALSE : Boolean.TRUE;
    995        }
    996
    997        /** {@inheritDoc} */
    998        public Object visit(ASTNullLiteral node, Object data) {
    999            return null;
    1000        }
    1001
    1002        /** {@inheritDoc} */
    1003        public Object visit(ASTOrNode node, Object data) {
    1004            Object left = node.jjtGetChild(0).jjtAccept(this, data);
    1005            try {
    1006                boolean leftValue = arithmetic.toBoolean(left);
    1007                if (leftValue) {
    1008                    return Boolean.TRUE;
    1009                }
    1010            } catch (RuntimeException xrt) {
    1011                throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
    1012            }
    1013            Object right = node.jjtGetChild(1).jjtAccept(this, data);
    1014            try {
    1015                boolean rightValue = arithmetic.toBoolean(right);
    1016                if (rightValue) {
    1017                    return Boolean.TRUE;
    1018                }
    1019            } catch (RuntimeException xrt) {
    1020                throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt);
    1021            }
    1022            return Boolean.FALSE;
    1023        }
    1024
    1025        /** {@inheritDoc} */
    1026        public Object visit(ASTReference node, Object data) {
    1027            // could be array access, identifier or map literal
    1028            // followed by zero or more ("." and array access, method, size,
    1029            // identifier or integer literal)
    1030
    1031            int numChildren = node.jjtGetNumChildren();
    1032
    1033            // pass first piece of data in and loop through children
    1034            Object result = null;
    1035            StringBuilder variableName = null;
    1036            boolean isVariable = true;
    1037            int v = 0;
    1038            for (int c = 0; c < numChildren; c++) {
    1039                JexlNode theNode = node.jjtGetChild(c);
    1040                // integer literals may be part of an antish var name only if no bean was found so far
    1041                if (result == null && theNode instanceof ASTIntegerLiteral) {
    1042                    isVariable &= v > 0;
    1043                } else {
    1044                    isVariable &= (theNode instanceof ASTIdentifier);
    1045                    result = theNode.jjtAccept(this, result);
    1046                }
    1047                // if we get null back a result, check for an ant variable
    1048                if (result == null && isVariable) {
    1049                    if (v == 0) {
    1050                        variableName = new StringBuilder(node.jjtGetChild(0).image);
    1051                        v = 1;
    1052                    }
    1053                    for (; v <= c; ++v) {
    1054                        variableName.append('.');
    1055                        variableName.append(node.jjtGetChild(v).image);
    1056                    }
    1057                    result = context.get(variableName.toString());
    1058                }
    1059            }
    1060            if (result == null) {
    1061                if (isVariable
    1062                        && !(node.jjtGetParent() instanceof ASTTernaryNode)
    1063                        && !context.has(variableName.toString())) {
    1064                    JexlException xjexl = new JexlException(node, "undefined variable " + variableName.toString());
    1065                    return unknownVariable(xjexl);
    1066                }
    1067            }
    1068            return result;
    1069        }
    1070
    1071        /** {@inheritDoc} */
    1072        public Object visit(ASTSizeFunction node, Object data) {
    1073            Object val = node.jjtGetChild(0).jjtAccept(this, data);
    1074
    1075            if (val == null) {
    1076                throw new JexlException(node, "size() : argument is null", null);
    1077            }
    1078
    1079            return Integer.valueOf(sizeOf(node, val));
    1080        }
    1081
    1082        /** {@inheritDoc} */
    1083        public Object visit(ASTSizeMethod node, Object data) {
    1084            return Integer.valueOf(sizeOf(node, data));
    1085        }
    1086
    1087        /** {@inheritDoc} */
    1088        public Object visit(ASTStringLiteral node, Object data) {
    1089            if (data != null) {
    1090                return getAttribute(data, node.getLiteral(), node);
    1091            }
    1092            return node.image;
    1093        }
    1094
    1095        /** {@inheritDoc} */
    1096        public Object visit(ASTTernaryNode node, Object data) {
    1097            Object condition = node.jjtGetChild(0).jjtAccept(this, data);
    1098            if (node.jjtGetNumChildren() == 3) {
    1099                if (condition != null && arithmetic.toBoolean(condition)) {
    1100                    return node.jjtGetChild(1).jjtAccept(this, data);
    1101                } else {
    1102                    return node.jjtGetChild(2).jjtAccept(this, data);
    1103                }
    1104            }
    1105            if (condition != null && !Boolean.FALSE.equals(condition)) {
    1106                return condition;
    1107            } else {
    1108                return node.jjtGetChild(1).jjtAccept(this, data);
    1109            }
    1110        }
    1111
    1112        /** {@inheritDoc} */
    1113        public Object visit(ASTTrueNode node, Object data) {
    1114            return Boolean.TRUE;
    1115        }
    1116
    1117        /** {@inheritDoc} */
    1118        public Object visit(ASTUnaryMinusNode node, Object data) {
    1119            JexlNode valNode = node.jjtGetChild(0);
    1120            Object val = valNode.jjtAccept(this, data);
    1121            if (val instanceof Byte) {
    1122                byte valueAsByte = ((Byte) val).byteValue();
    1123                return Byte.valueOf((byte) -valueAsByte);
    1124            } else if (val instanceof Short) {
    1125                short valueAsShort = ((Short) val).shortValue();
    1126                return Short.valueOf((short) -valueAsShort);
    1127            } else if (val instanceof Integer) {
    1128                int valueAsInt = ((Integer) val).intValue();
    1129                return Integer.valueOf(-valueAsInt);
    1130            } else if (val instanceof Long) {
    1131                long valueAsLong = ((Long) val).longValue();
    1132                return Long.valueOf(-valueAsLong);
    1133            } else if (val instanceof Float) {
    1134                float valueAsFloat = ((Float) val).floatValue();
    1135                return new Float(-valueAsFloat);
    1136            } else if (val instanceof Double) {
    1137                double valueAsDouble = ((Double) val).doubleValue();
    1138                return new Double(-valueAsDouble);
    1139            } else if (val instanceof BigDecimal) {
    1140                BigDecimal valueAsBigD = (BigDecimal) val;
    1141                return valueAsBigD.negate();
    1142            } else if (val instanceof BigInteger) {
    1143                BigInteger valueAsBigI = (BigInteger) val;
    1144                return valueAsBigI.negate();
    1145            }
    1146            throw new JexlException(valNode, "not a number");
    1147        }
    1148
    1149        /** {@inheritDoc} */
    1150        public Object visit(ASTWhileStatement node, Object data) {
    1151            Object result = null;
    1152            /* first objectNode is the expression */
    1153            Node expressionNode = node.jjtGetChild(0);
    1154            while (arithmetic.toBoolean(expressionNode.jjtAccept(this, data))) {
    1155                // execute statement
    1156                result = node.jjtGetChild(1).jjtAccept(this, data);
    1157            }
    1158
    1159            return result;
    1160        }
    1161
    1162        /**
    1163         * Calculate the <code>size</code> of various types: Collection, Array,
    1164         * Map, String, and anything that has a int size() method.
    1165         * @param node the node that gave the value to size
    1166         * @param val the object to get the size of.
    1167         * @return the size of val
    1168         */
    1169        private int sizeOf(JexlNode node, Object val) {
    1170            if (val instanceof Collection<?>) {
    1171                return ((Collection<?>) val).size();
    1172            } else if (val.getClass().isArray()) {
    1173                return Array.getLength(val);
    1174            } else if (val instanceof Map<?, ?>) {
    1175                return ((Map<?, ?>) val).size();
    1176            } else if (val instanceof String) {
    1177                return ((String) val).length();
    1178            } else {
    1179                // check if there is a size method on the object that returns an
    1180                // integer and if so, just use it
    1181                Object[] params = new Object[0];
    1182                JexlMethod vm = uberspect.getMethod(val, "size", EMPTY_PARAMS, node);
    1183                if (vm != null && vm.getReturnType() == Integer.TYPE) {
    1184                    Integer result;
    1185                    try {
    1186                        result = (Integer) vm.invoke(val, params);
    1187                    } catch (Exception e) {
    1188                        throw new JexlException(node, "size() : error executing", e);
    1189                    }
    1190                    return result.intValue();
    1191                }
    1192                throw new JexlException(node, "size() : unsupported type : " + val.getClass(), null);
    1193            }
    1194        }
    1195
    1196        /**
    1197         * Gets an attribute of an object.
    1198         *
    1199         * @param object to retrieve value from
    1200         * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
    1201         *            key for a map
    1202         * @return the attribute value
    1203         */
    1204        public Object getAttribute(Object object, Object attribute) {
    1205            return getAttribute(object, attribute, null);
    1206        }
    1207
    1208        /**
    1209         * Gets an attribute of an object.
    1210         *
    1211         * @param object to retrieve value from
    1212         * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
    1213         *            key for a map
    1214         * @param node the node that evaluated as the object
    1215         * @return the attribute value
    1216         */
    1217        protected Object getAttribute(Object object, Object attribute, JexlNode node) {
    1218            if (object == null) {
    1219                throw new JexlException(node, "object is null");
    1220            }
    1221            // attempt to reuse last executor cached in volatile JexlNode.value
    1222            if (node != null && cache) {
    1223                Object cached = node.jjtGetValue();
    1224                if (cached instanceof JexlPropertyGet) {
    1225                    JexlPropertyGet vg = (JexlPropertyGet) cached;
    1226                    Object value = vg.tryInvoke(object, attribute);
    1227                    if (!vg.tryFailed(value)) {
    1228                        return value;
    1229                    }
    1230                }
    1231            }
    1232            JexlPropertyGet vg = uberspect.getPropertyGet(object, attribute, node);
    1233            if (vg != null) {
    1234                try {
    1235                    Object value = vg.invoke(object);
    1236                    // cache executor in volatile JexlNode.value
    1237                    if (node != null && cache && vg.isCacheable()) {
    1238                        node.jjtSetValue(vg);
    1239                    }
    1240                    return value;
    1241                } catch (Exception xany) {
    1242                    if (node == null) {
    1243                        throw new RuntimeException(xany);
    1244                    } else {
    1245                        JexlException xjexl = new JexlException(node, "get object property error", xany);
    1246                        if (strict) {
    1247                            throw xjexl;
    1248                        }
    1249                        if (!silent) {
    1250                            logger.warn(xjexl.getMessage());
    1251                        }
    1252                    }
    1253                }
    1254            }
    1255
    1256            return null;
    1257        }
    1258
    1259        /**
    1260         * Sets an attribute of an object.
    1261         *
    1262         * @param object to set the value to
    1263         * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
    1264         *            key for a map
    1265         * @param value the value to assign to the object's attribute
    1266         */
    1267        public void setAttribute(Object object, Object attribute, Object value) {
    1268            setAttribute(object, attribute, value, null);
    1269        }
    1270
    1271        /**
    1272         * Sets an attribute of an object.
    1273         *
    1274         * @param object to set the value to
    1275         * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
    1276         *            key for a map
    1277         * @param value the value to assign to the object's attribute
    1278         * @param node the node that evaluated as the object
    1279         */
    1280        protected void setAttribute(Object object, Object attribute, Object value, JexlNode node) {
    1281            // attempt to reuse last executor cached in volatile JexlNode.value
    1282            if (node != null && cache) {
    1283                Object cached = node.jjtGetValue();
    1284                if (cached instanceof JexlPropertySet) {
    1285                    JexlPropertySet setter = (JexlPropertySet) cached;
    1286                    Object eval = setter.tryInvoke(object, attribute, value);
    1287                    if (!setter.tryFailed(eval)) {
    1288                        return;
    1289                    }
    1290                }
    1291            }
    1292            JexlException xjexl = null;
    1293            JexlPropertySet vs = uberspect.getPropertySet(object, attribute, value, node);
    1294            if (vs != null) {
    1295                try {
    1296                    // cache executor in volatile JexlNode.value
    1297                    vs.invoke(object, value);
    1298                    if (node != null && cache && vs.isCacheable()) {
    1299                        node.jjtSetValue(vs);
    1300                    }
    1301                    return;
    1302                } catch (RuntimeException xrt) {
    1303                    if (node == null) {
    1304                        throw xrt;
    1305                    }
    1306                    xjexl = new JexlException(node, "set object property error", xrt);
    1307                } catch (Exception xany) {
    1308                    if (node == null) {
    1309                        throw new RuntimeException(xany);
    1310                    }
    1311                    xjexl = new JexlException(node, "set object property error", xany);
    1312                }
    1313            }
    1314            if (xjexl == null) {
    1315                String error = "unable to set object property"
    1316                               + ", class: " + object.getClass().getName()
    1317                               + ", property: " + attribute;
    1318                if (node == null) {
    1319                    throw new UnsupportedOperationException(error);
    1320                }
    1321                xjexl = new JexlException(node, error, null);
    1322            }
    1323            if (strict) {
    1324                throw xjexl;
    1325            }
    1326            if (!silent) {
    1327                logger.warn(xjexl.getMessage());
    1328            }
    1329        }
    1330
    1331        /**
    1332         * Unused, satisfy ParserVisitor interface.
    1333         * @param node a node
    1334         * @param data the data
    1335         * @return does not return
    1336         */
    1337        public Object visit(SimpleNode node, Object data) {
    1338            throw new UnsupportedOperationException("Not supported yet.");
    1339        }
    1340
    1341        /**
    1342         * Unused, should throw in Parser.
    1343         * @param node a node
    1344         * @param data the data
    1345         * @return does not return
    1346         */
    1347        public Object visit(ASTAmbiguous node, Object data) {
    1348            throw new UnsupportedOperationException("unexpected type of node");
    1349        }
    1350    }