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    }