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