| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.jexl2; |
| |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Array; |
| import java.lang.reflect.InvocationTargetException; |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| import org.apache.commons.jexl2.parser.SimpleNode; |
| import org.apache.commons.logging.Log; |
| |
| import org.apache.commons.jexl2.parser.JexlNode; |
| import org.apache.commons.jexl2.parser.ASTAdditiveNode; |
| import org.apache.commons.jexl2.parser.ASTAdditiveOperator; |
| import org.apache.commons.jexl2.parser.ASTAndNode; |
| import org.apache.commons.jexl2.parser.ASTAmbiguous; |
| import org.apache.commons.jexl2.parser.ASTArrayAccess; |
| import org.apache.commons.jexl2.parser.ASTArrayLiteral; |
| import org.apache.commons.jexl2.parser.ASTAssignment; |
| import org.apache.commons.jexl2.parser.ASTBitwiseAndNode; |
| import org.apache.commons.jexl2.parser.ASTBitwiseComplNode; |
| import org.apache.commons.jexl2.parser.ASTBitwiseOrNode; |
| import org.apache.commons.jexl2.parser.ASTBitwiseXorNode; |
| import org.apache.commons.jexl2.parser.ASTBlock; |
| import org.apache.commons.jexl2.parser.ASTConstructorNode; |
| import org.apache.commons.jexl2.parser.ASTDivNode; |
| import org.apache.commons.jexl2.parser.ASTEQNode; |
| import org.apache.commons.jexl2.parser.ASTERNode; |
| import org.apache.commons.jexl2.parser.ASTEmptyFunction; |
| import org.apache.commons.jexl2.parser.ASTFalseNode; |
| import org.apache.commons.jexl2.parser.ASTFunctionNode; |
| import org.apache.commons.jexl2.parser.ASTFloatLiteral; |
| import org.apache.commons.jexl2.parser.ASTForeachStatement; |
| import org.apache.commons.jexl2.parser.ASTGENode; |
| import org.apache.commons.jexl2.parser.ASTGTNode; |
| import org.apache.commons.jexl2.parser.ASTIdentifier; |
| import org.apache.commons.jexl2.parser.ASTIfStatement; |
| import org.apache.commons.jexl2.parser.ASTIntegerLiteral; |
| import org.apache.commons.jexl2.parser.ASTJexlScript; |
| import org.apache.commons.jexl2.parser.ASTLENode; |
| import org.apache.commons.jexl2.parser.ASTLTNode; |
| import org.apache.commons.jexl2.parser.ASTMapEntry; |
| import org.apache.commons.jexl2.parser.ASTMapLiteral; |
| import org.apache.commons.jexl2.parser.ASTMethodNode; |
| import org.apache.commons.jexl2.parser.ASTModNode; |
| import org.apache.commons.jexl2.parser.ASTMulNode; |
| import org.apache.commons.jexl2.parser.ASTNENode; |
| import org.apache.commons.jexl2.parser.ASTNRNode; |
| import org.apache.commons.jexl2.parser.ASTNotNode; |
| import org.apache.commons.jexl2.parser.ASTNullLiteral; |
| import org.apache.commons.jexl2.parser.ASTOrNode; |
| import org.apache.commons.jexl2.parser.ASTReference; |
| import org.apache.commons.jexl2.parser.ASTSizeFunction; |
| import org.apache.commons.jexl2.parser.ASTSizeMethod; |
| import org.apache.commons.jexl2.parser.ASTStringLiteral; |
| import org.apache.commons.jexl2.parser.ASTTernaryNode; |
| import org.apache.commons.jexl2.parser.ASTTrueNode; |
| import org.apache.commons.jexl2.parser.ASTUnaryMinusNode; |
| import org.apache.commons.jexl2.parser.ASTWhileStatement; |
| import org.apache.commons.jexl2.parser.Node; |
| import org.apache.commons.jexl2.parser.ParserVisitor; |
| |
| import org.apache.commons.jexl2.introspection.Uberspect; |
| import org.apache.commons.jexl2.introspection.JexlMethod; |
| import org.apache.commons.jexl2.introspection.JexlPropertyGet; |
| import org.apache.commons.jexl2.introspection.JexlPropertySet; |
| |
| /** |
| * An interpreter of JEXL syntax. |
| * |
| * @since 2.0 |
| */ |
| public class Interpreter implements ParserVisitor { |
| /** The logger. */ |
| protected final Log logger; |
| /** The uberspect. */ |
| protected final Uberspect uberspect; |
| /** The arithmetic handler. */ |
| protected final JexlArithmetic arithmetic; |
| /** The map of registered functions. */ |
| protected final Map<String, Object> functions; |
| /** The map of registered functions. */ |
| protected Map<String, Object> functors; |
| /** The context to store/retrieve variables. */ |
| protected final JexlContext context; |
| /** Strict interpreter flag. */ |
| protected final boolean strict; |
| /** Silent intepreter flag. */ |
| protected boolean silent; |
| /** Cache executors. */ |
| protected final boolean cache; |
| /** Registers made of 2 pairs of {register-name, value}. */ |
| protected Object[] registers = null; |
| /** Empty parameters for method matching. */ |
| protected static final Object[] EMPTY_PARAMS = new Object[0]; |
| |
| /** |
| * Creates an interpreter. |
| * @param jexl the engine creating this interpreter |
| * @param aContext the context to evaluate expression |
| */ |
| public Interpreter(JexlEngine jexl, JexlContext aContext) { |
| this.logger = jexl.logger; |
| this.uberspect = jexl.uberspect; |
| this.arithmetic = jexl.arithmetic; |
| this.functions = jexl.functions; |
| this.strict = !this.arithmetic.isLenient(); |
| this.silent = jexl.silent; |
| this.cache = jexl.cache != null; |
| this.context = aContext; |
| this.functors = null; |
| } |
| |
| /** |
| * Sets whether this interpreter throws JexlException during evaluation. |
| * @param flag true means no JexlException will be thrown but will be logged |
| * as info through the Jexl engine logger, false allows them to be thrown. |
| */ |
| public void setSilent(boolean flag) { |
| this.silent = flag; |
| } |
| |
| /** |
| * Checks whether this interpreter throws JexlException during evaluation. |
| * @return true if silent, false otherwise |
| */ |
| public boolean isSilent() { |
| return this.silent; |
| } |
| |
| /** |
| * Interpret the given script/expression. |
| * <p> |
| * If the underlying JEXL engine is silent, errors will be logged through its logger as info. |
| * </p> |
| * @param node the script or expression to interpret. |
| * @return the result of the interpretation. |
| * @throws JexlException if any error occurs during interpretation. |
| */ |
| public Object interpret(JexlNode node) { |
| try { |
| return node.jjtAccept(this, null); |
| } catch (JexlException xjexl) { |
| if (silent) { |
| logger.warn(xjexl.getMessage(), xjexl.getCause()); |
| return null; |
| } |
| throw xjexl; |
| } |
| } |
| |
| /** |
| * Gets the uberspect. |
| * @return an {@link Uberspect} |
| */ |
| protected Uberspect getUberspect() { |
| return uberspect; |
| } |
| |
| /** |
| * Sets this interpreter registers for bean access/assign expressions. |
| * @param theRegisters the array of registers |
| */ |
| protected void setRegisters(Object[] theRegisters) { |
| this.registers = theRegisters; |
| } |
| |
| /** |
| * Finds the node causing a NPE for diadic operators. |
| * @param xrt the RuntimeException |
| * @param node the parent node |
| * @param left the left argument |
| * @param right the right argument |
| * @return the left, right or parent node |
| */ |
| protected JexlNode findNullOperand(RuntimeException xrt, JexlNode node, Object left, Object right) { |
| if (xrt instanceof NullPointerException |
| && JexlException.NULL_OPERAND == xrt.getMessage()) { |
| if (left == null) { |
| return node.jjtGetChild(0); |
| } |
| if (right == null) { |
| return node.jjtGetChild(1); |
| } |
| } |
| return node; |
| } |
| |
| /** |
| * Triggered when variable can not be resolved. |
| * @param xjexl the JexlException ("undefined variable " + variable) |
| * @return throws JexlException if strict, null otherwise |
| */ |
| protected Object unknownVariable(JexlException xjexl) { |
| if (strict) { |
| throw xjexl; |
| } |
| if (!silent) { |
| logger.warn(xjexl.getMessage()); |
| } |
| return null; |
| } |
| |
| /** |
| * Triggered when method, function or constructor invocation fails. |
| * @param xjexl the JexlException wrapping the original error |
| * @return throws JexlException if strict, null otherwise |
| */ |
| protected Object invocationFailed(JexlException xjexl) { |
| if (strict) { |
| throw xjexl; |
| } |
| if (!silent) { |
| logger.warn(xjexl.getMessage(), xjexl.getCause()); |
| } |
| return null; |
| } |
| |
| /** |
| * Resolves a namespace, eventually allocating an instance using context as constructor argument. |
| * The lifetime of such instances span the current expression or script evaluation. |
| * |
| * @param prefix the prefix name (may be null for global namespace) |
| * @param node the AST node |
| * @return the namespace instance |
| */ |
| protected Object resolveNamespace(String prefix, JexlNode node) { |
| Object namespace; |
| // check whether this namespace is a functor |
| if (functors != null) { |
| namespace = functors.get(prefix); |
| if (namespace != null) { |
| return namespace; |
| } |
| } |
| namespace = functions.get(prefix); |
| if (namespace == null) { |
| throw new JexlException(node, "no such function namespace " + prefix); |
| } |
| // allow namespace to be instantiated as functor with context |
| if (namespace instanceof Class<?>) { |
| Object[] args = new Object[]{context}; |
| Constructor<?> ctor = uberspect.getConstructor(namespace,args, node); |
| if (ctor != null) { |
| try { |
| namespace = ctor.newInstance(args); |
| if (functors == null) { |
| functors = new HashMap<String, Object>(); |
| } |
| functors.put(prefix, namespace); |
| } catch (Exception xinst) { |
| throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst); |
| } |
| } |
| } |
| return namespace; |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTAdditiveNode node, Object data) { |
| /** |
| * The pattern for exception mgmt is to let the child*.jjtAccept |
| * out of the try/catch loop so that if one fails, the ex will |
| * traverse up to the interpreter. |
| * In cases where this is not convenient/possible, JexlException must |
| * be caught explicitly and rethrown. |
| */ |
| Object left = node.jjtGetChild(0).jjtAccept(this, data); |
| for(int c = 2, size = node.jjtGetNumChildren(); c < size; c += 2) { |
| Object right = node.jjtGetChild(c).jjtAccept(this, data); |
| try { |
| JexlNode op = node.jjtGetChild(c - 1); |
| if (op instanceof ASTAdditiveOperator) { |
| String which = ((ASTAdditiveOperator) op).image; |
| if ("+".equals(which)) { |
| left = arithmetic.add(left, right); |
| continue; |
| } |
| if ("-".equals(which)) { |
| left = arithmetic.subtract(left, right); |
| continue; |
| } |
| throw new UnsupportedOperationException("unknown operator " + which); |
| } |
| throw new IllegalArgumentException("unknown operator " + op); |
| } catch (RuntimeException xrt) { |
| JexlNode xnode = findNullOperand(xrt, node, left, right); |
| throw new JexlException(xnode, "+/- error", xrt); |
| } |
| } |
| return left; |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTAdditiveOperator node, Object data) { |
| throw new UnsupportedOperationException("Shoud not be called."); |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTAndNode node, Object data) { |
| Object left = node.jjtGetChild(0).jjtAccept(this, data); |
| try { |
| boolean leftValue = arithmetic.toBoolean(left); |
| if (!leftValue) { |
| return Boolean.FALSE; |
| } |
| } catch (RuntimeException xrt) { |
| throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt); |
| } |
| Object right = node.jjtGetChild(1).jjtAccept(this, data); |
| try { |
| boolean rightValue = arithmetic.toBoolean(right); |
| if (!rightValue) { |
| return Boolean.FALSE; |
| } |
| } catch (RuntimeException xrt) { |
| throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt); |
| } |
| return Boolean.TRUE; |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTArrayAccess node, Object data) { |
| // first objectNode is the identifier |
| Object object = node.jjtGetChild(0).jjtAccept(this, data); |
| // can have multiple nodes - either an expression, integer literal or |
| // reference |
| int numChildren = node.jjtGetNumChildren(); |
| for (int i = 1; i < numChildren; i++) { |
| JexlNode nindex = node.jjtGetChild(i); |
| Object index = nindex.jjtAccept(this, null); |
| object = getAttribute(object, index, nindex); |
| } |
| |
| return object; |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTArrayLiteral node, Object data) { |
| int childCount = node.jjtGetNumChildren(); |
| Object[] array = new Object[childCount]; |
| for (int i = 0; i < childCount; i++) { |
| Object entry = node.jjtGetChild(i).jjtAccept(this, data); |
| array[i] = entry; |
| } |
| return arithmetic.narrowArrayType(array); |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTAssignment node, Object data) { |
| // left contains the reference to assign to |
| JexlNode left = node.jjtGetChild(0); |
| if (!(left instanceof ASTReference)) { |
| throw new JexlException(left, "illegal assignment form"); |
| } |
| // right is the value expression to assign |
| Object right = node.jjtGetChild(1).jjtAccept(this, data); |
| |
| // determine initial object & property: |
| JexlNode objectNode = null; |
| Object object = null; |
| JexlNode propertyNode = null; |
| Object property = null; |
| boolean isVariable = true; |
| int v = 0; |
| StringBuilder variableName = null; |
| // 1: follow children till penultimate |
| int last = left.jjtGetNumChildren() - 1; |
| for (int c = 0; c < last; ++c) { |
| objectNode = left.jjtGetChild(c); |
| // evaluate the property within the object |
| object = objectNode.jjtAccept(this, object); |
| if (object != null) { |
| continue; |
| } |
| isVariable &= objectNode instanceof ASTIdentifier; |
| // if we get null back as a result, check for an ant variable |
| if (isVariable) { |
| if (v == 0) { |
| variableName = new StringBuilder(left.jjtGetChild(0).image); |
| v = 1; |
| } |
| for(; v <= c; ++v) { |
| variableName.append('.'); |
| variableName.append(left.jjtGetChild(v).image); |
| } |
| object = context.get(variableName.toString()); |
| // disallow mixing ant & bean with same root; avoid ambiguity |
| if (object != null) { |
| isVariable = false; |
| } |
| } else { |
| throw new JexlException(objectNode, "illegal assignment form"); |
| } |
| } |
| // 2: last objectNode will perform assignement in all cases |
| propertyNode = left.jjtGetChild(last); |
| if (propertyNode instanceof ASTIdentifier) { |
| property = ((ASTIdentifier) propertyNode).image; |
| // deal with ant variable |
| if (isVariable && object == null) { |
| if (variableName != null) { |
| if (last > 0) { |
| variableName.append('.'); |
| } |
| variableName.append(property); |
| property = variableName.toString(); |
| } |
| context.set(String.valueOf(property), right); |
| return right; |
| } |
| } else if (propertyNode instanceof ASTIntegerLiteral) { |
| property = visit((ASTIntegerLiteral) propertyNode, null); |
| // deal with ant variable |
| if (isVariable && object == null) { |
| if (variableName != null) { |
| if (last > 0) { |
| variableName.append('.'); |
| } |
| variableName.append(property); |
| property = variableName.toString(); |
| } |
| context.set(String.valueOf(property), right); |
| return right; |
| } |
| } else if (propertyNode instanceof ASTArrayAccess) { |
| // first objectNode is the identifier |
| objectNode = propertyNode; |
| ASTArrayAccess narray = (ASTArrayAccess) objectNode; |
| Object nobject = narray.jjtGetChild(0).jjtAccept(this, object); |
| if (nobject == null) { |
| throw new JexlException(objectNode, "array element is null"); |
| } else { |
| object = nobject; |
| } |
| // can have multiple nodes - either an expression, integer literal or |
| // reference |
| last = narray.jjtGetNumChildren() - 1; |
| for (int i = 1; i < last; i++) { |
| objectNode = narray.jjtGetChild(i); |
| Object index = objectNode.jjtAccept(this, null); |
| object = getAttribute(object, index, objectNode); |
| } |
| property = narray.jjtGetChild(last).jjtAccept(this, null); |
| } else { |
| throw new JexlException(objectNode, "illegal assignment form"); |
| } |
| if (property == null) { |
| // no property, we fail |
| throw new JexlException(propertyNode, "property is null"); |
| } |
| if (object == null) { |
| // no object, we fail |
| throw new JexlException(objectNode, "bean is null"); |
| } |
| // one before last, assign |
| setAttribute(object, property, right, propertyNode); |
| return right; |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTBitwiseAndNode node, Object data) { |
| Object left = node.jjtGetChild(0).jjtAccept(this, data); |
| Object right = node.jjtGetChild(1).jjtAccept(this, data); |
| int n = 0; |
| // coerce these two values longs and 'and'. |
| try { |
| long l = arithmetic.toLong(left); |
| n = 1; |
| long r = arithmetic.toLong(right); |
| return Long.valueOf(l & r); |
| } catch (RuntimeException xrt) { |
| throw new JexlException(node.jjtGetChild(n), "long coercion error", xrt); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTBitwiseComplNode node, Object data) { |
| Object left = node.jjtGetChild(0).jjtAccept(this, data); |
| try { |
| long l = arithmetic.toLong(left); |
| return Long.valueOf(~l); |
| } catch (RuntimeException xrt) { |
| throw new JexlException(node.jjtGetChild(0), "long coercion error", xrt); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTBitwiseOrNode node, Object data) { |
| Object left = node.jjtGetChild(0).jjtAccept(this, data); |
| Object right = node.jjtGetChild(1).jjtAccept(this, data); |
| int n = 0; |
| // coerce these two values longs and 'or'. |
| try { |
| long l = arithmetic.toLong(left); |
| n = 1; |
| long r = arithmetic.toLong(right); |
| return Long.valueOf(l | r); |
| } catch (RuntimeException xrt) { |
| throw new JexlException(node.jjtGetChild(n), "long coercion error", xrt); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTBitwiseXorNode node, Object data) { |
| Object left = node.jjtGetChild(0).jjtAccept(this, data); |
| Object right = node.jjtGetChild(1).jjtAccept(this, data); |
| int n = 0; |
| // coerce these two values longs and 'xor'. |
| try { |
| long l = arithmetic.toLong(left); |
| n = 1; |
| long r = arithmetic.toLong(right); |
| return Long.valueOf(l ^ r); |
| } catch (RuntimeException xrt) { |
| throw new JexlException(node.jjtGetChild(n), "long coercion error", xrt); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTBlock node, Object data) { |
| int numChildren = node.jjtGetNumChildren(); |
| Object result = null; |
| for (int i = 0; i < numChildren; i++) { |
| result = node.jjtGetChild(i).jjtAccept(this, data); |
| } |
| return result; |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTDivNode node, Object data) { |
| Object left = node.jjtGetChild(0).jjtAccept(this, data); |
| Object right = node.jjtGetChild(1).jjtAccept(this, data); |
| try { |
| return arithmetic.divide(left, right); |
| } catch (RuntimeException xrt) { |
| if (!strict && xrt instanceof ArithmeticException) { |
| return new Double(0.0); |
| } |
| JexlNode xnode = findNullOperand(xrt, node, left, right); |
| throw new JexlException(xnode, "divide error", xrt); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTEmptyFunction node, Object data) { |
| Object o = node.jjtGetChild(0).jjtAccept(this, data); |
| if (o == null) { |
| return Boolean.TRUE; |
| } |
| if (o instanceof String && "".equals(o)) { |
| return Boolean.TRUE; |
| } |
| if (o.getClass().isArray() && ((Object[]) o).length == 0) { |
| return Boolean.TRUE; |
| } |
| if (o instanceof Collection<?> && ((Collection<?>) o).isEmpty()) { |
| return Boolean.TRUE; |
| } |
| // Map isn't a collection |
| if (o instanceof Map<?, ?> && ((Map<?, ?>) o).isEmpty()) { |
| return Boolean.TRUE; |
| } |
| return Boolean.FALSE; |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTEQNode node, Object data) { |
| Object left = node.jjtGetChild(0).jjtAccept(this, data); |
| Object right = node.jjtGetChild(1).jjtAccept(this, data); |
| try { |
| return arithmetic.equals(left, right) ? Boolean.TRUE : Boolean.FALSE; |
| } catch (RuntimeException xrt) { |
| throw new JexlException(node, "== error", xrt); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTFalseNode node, Object data) { |
| return Boolean.FALSE; |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTFloatLiteral node, Object data) { |
| Float value = (Float) node.jjtGetValue(); |
| if (value == null) { |
| value = Float.valueOf(node.image); |
| node.jjtSetValue(value); |
| } |
| return value; |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTForeachStatement node, Object data) { |
| Object result = null; |
| /* first objectNode is the loop variable */ |
| ASTReference loopReference = (ASTReference) node.jjtGetChild(0); |
| ASTIdentifier loopVariable = (ASTIdentifier) loopReference.jjtGetChild(0); |
| /* second objectNode is the variable to iterate */ |
| Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data); |
| // make sure there is a value to iterate on and a statement to execute |
| if (iterableValue != null && node.jjtGetNumChildren() >= 3) { |
| /* third objectNode is the statement to execute */ |
| JexlNode statement = node.jjtGetChild(2); |
| // get an iterator for the collection/array etc via the |
| // introspector. |
| Iterator<?> itemsIterator = getUberspect().getIterator(iterableValue, node); |
| if (itemsIterator != null) { |
| while (itemsIterator.hasNext()) { |
| // set loopVariable to value of iterator |
| Object value = itemsIterator.next(); |
| context.set(loopVariable.image, value); |
| // execute statement |
| result = statement.jjtAccept(this, data); |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTGENode node, Object data) { |
| Object left = node.jjtGetChild(0).jjtAccept(this, data); |
| Object right = node.jjtGetChild(1).jjtAccept(this, data); |
| try { |
| return arithmetic.greaterThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE; |
| } catch (RuntimeException xrt) { |
| throw new JexlException(node, ">= error", xrt); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTGTNode node, Object data) { |
| Object left = node.jjtGetChild(0).jjtAccept(this, data); |
| Object right = node.jjtGetChild(1).jjtAccept(this, data); |
| try { |
| return arithmetic.greaterThan(left, right) ? Boolean.TRUE : Boolean.FALSE; |
| } catch (RuntimeException xrt) { |
| throw new JexlException(node, "> error", xrt); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTERNode node, Object data) { |
| Object left = node.jjtGetChild(0).jjtAccept(this, data); |
| Object right = node.jjtGetChild(1).jjtAccept(this, data); |
| try { |
| return arithmetic.matches(left, right) ? Boolean.TRUE : Boolean.FALSE; |
| } catch (RuntimeException xrt) { |
| throw new JexlException(node, "=~ error", xrt); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTIdentifier node, Object data) { |
| String name = node.image; |
| if (data == null) { |
| if (registers != null) { |
| if (registers[0].equals(name)) { |
| return registers[1]; |
| } |
| if (registers[2].equals(name)) { |
| return registers[3]; |
| } |
| } |
| Object value = context.get(name); |
| if (value == null |
| && !(node.jjtGetParent() instanceof ASTReference) |
| && !context.has(name)) { |
| JexlException xjexl = new JexlException(node, "undefined variable " + name); |
| return unknownVariable(xjexl); |
| } |
| return value; |
| } else { |
| return getAttribute(data, name, node); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTIfStatement node, Object data) { |
| int n = 0; |
| try { |
| Object result = null; |
| /* first objectNode is the expression */ |
| Object expression = node.jjtGetChild(0).jjtAccept(this, data); |
| if (arithmetic.toBoolean(expression)) { |
| // first objectNode is true statement |
| n = 1; |
| result = node.jjtGetChild(1).jjtAccept(this, data); |
| } else { |
| // if there is a false, execute it. false statement is the second |
| // objectNode |
| if (node.jjtGetNumChildren() == 3) { |
| n = 2; |
| result = node.jjtGetChild(2).jjtAccept(this, data); |
| } |
| } |
| return result; |
| } catch (JexlException error) { |
| throw error; |
| } catch (RuntimeException xrt) { |
| throw new JexlException(node.jjtGetChild(n), "if error", xrt); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTIntegerLiteral node, Object data) { |
| if (data != null) { |
| Integer value = Integer.valueOf(node.image); |
| return getAttribute(data, value, node); |
| } |
| Integer value = (Integer) node.jjtGetValue(); |
| if (value == null) { |
| value = Integer.valueOf(node.image); |
| node.jjtSetValue(value); |
| } |
| return value; |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTJexlScript node, Object data) { |
| int numChildren = node.jjtGetNumChildren(); |
| Object result = null; |
| for (int i = 0; i < numChildren; i++) { |
| JexlNode child = node.jjtGetChild(i); |
| result = child.jjtAccept(this, data); |
| } |
| return result; |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTLENode node, Object data) { |
| Object left = node.jjtGetChild(0).jjtAccept(this, data); |
| Object right = node.jjtGetChild(1).jjtAccept(this, data); |
| try { |
| return arithmetic.lessThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE; |
| } catch (RuntimeException xrt) { |
| throw new JexlException(node, "<= error", xrt); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTLTNode node, Object data) { |
| Object left = node.jjtGetChild(0).jjtAccept(this, data); |
| Object right = node.jjtGetChild(1).jjtAccept(this, data); |
| try { |
| return arithmetic.lessThan(left, right) ? Boolean.TRUE : Boolean.FALSE; |
| } catch (RuntimeException xrt) { |
| throw new JexlException(node, "< error", xrt); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTMapEntry node, Object data) { |
| Object key = node.jjtGetChild(0).jjtAccept(this, data); |
| Object value = node.jjtGetChild(1).jjtAccept(this, data); |
| return new Object[]{key, value}; |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTMapLiteral node, Object data) { |
| int childCount = node.jjtGetNumChildren(); |
| Map<Object, Object> map = new HashMap<Object, Object>(); |
| |
| for (int i = 0; i < childCount; i++) { |
| Object[] entry = (Object[]) (node.jjtGetChild(i)).jjtAccept(this, data); |
| map.put(entry[0], entry[1]); |
| } |
| |
| return map; |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTMethodNode node, Object data) { |
| // the object to invoke the method on should be in the data argument |
| if (data == null) { |
| // if the first child of the (ASTReference) parent, |
| // it is considered as calling a 'top level' function |
| if (node.jjtGetParent().jjtGetChild(0) == node) { |
| data = resolveNamespace(null, node); |
| if (data == null) { |
| throw new JexlException(node, "no default function namespace"); |
| } |
| } else { |
| throw new JexlException(node, "attempting to call method on null"); |
| } |
| } |
| // objectNode 0 is the identifier (method name), the others are parameters. |
| String methodName = ((ASTIdentifier) node.jjtGetChild(0)).image; |
| |
| // get our arguments |
| int argc = node.jjtGetNumChildren() - 1; |
| Object[] argv = new Object[argc]; |
| for (int i = 0; i < argc; i++) { |
| argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, null); |
| } |
| |
| JexlException xjexl = null; |
| try { |
| // attempt to reuse last executor cached in volatile JexlNode.value |
| if (cache) { |
| Object cached = node.jjtGetValue(); |
| if (cached instanceof JexlMethod) { |
| JexlMethod me = (JexlMethod) cached; |
| Object eval = me.tryInvoke(methodName, data, argv); |
| if (!me.tryFailed(eval)) { |
| return eval; |
| } |
| } |
| } |
| JexlMethod vm = uberspect.getMethod(data, methodName, argv, node); |
| // DG: If we can't find an exact match, narrow the parameters and try again! |
| if (vm == null) { |
| if (arithmetic.narrowArguments(argv)) { |
| vm = uberspect.getMethod(data, methodName, argv, node); |
| } |
| if (vm == null) { |
| xjexl = new JexlException(node, "unknown or ambiguous method", null); |
| } |
| } |
| if (xjexl == null) { |
| Object eval = vm.invoke(data, argv); // vm cannot be null if xjexl is null |
| // cache executor in volatile JexlNode.value |
| if (cache && vm.isCacheable()) { |
| node.jjtSetValue(vm); |
| } |
| return eval; |
| } |
| } catch (InvocationTargetException e) { |
| xjexl = new JexlException(node, "method invocation error", e.getCause()); |
| } catch (Exception e) { |
| xjexl = new JexlException(node, "method error", e); |
| } |
| return invocationFailed(xjexl); |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTConstructorNode node, Object data) { |
| // first child is class or class name |
| Object cobject = node.jjtGetChild(0).jjtAccept(this, data); |
| // get the ctor args |
| int argc = node.jjtGetNumChildren() - 1; |
| Object[] argv = new Object[argc]; |
| for (int i = 0; i < argc; i++) { |
| argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, null); |
| } |
| |
| JexlException xjexl = null; |
| try { |
| Constructor<?> ctor = uberspect.getConstructor(cobject, argv, node); |
| // DG: If we can't find an exact match, narrow the parameters and |
| // try again! |
| if (ctor == null) { |
| if (arithmetic.narrowArguments(argv)) { |
| ctor = uberspect.getConstructor(cobject, argv, node); |
| } |
| if (ctor == null) { |
| xjexl = new JexlException(node, "unknown constructor", null); |
| } |
| } |
| if (xjexl == null) { |
| return ctor.newInstance(argv); |
| } |
| } catch (InvocationTargetException e) { |
| xjexl = new JexlException(node, "constructor invocation error", e.getCause()); |
| } catch (Exception e) { |
| xjexl = new JexlException(node, "constructor error", e); |
| } |
| return invocationFailed(xjexl); |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTFunctionNode node, Object data) { |
| // objectNode 0 is the prefix |
| String prefix = ((ASTIdentifier) node.jjtGetChild(0)).image; |
| Object namespace = resolveNamespace(prefix, node); |
| // objectNode 1 is the identifier , the others are parameters. |
| String function = ((ASTIdentifier) node.jjtGetChild(1)).image; |
| |
| // get our args |
| int argc = node.jjtGetNumChildren() - 2; |
| Object[] argv = new Object[argc]; |
| for (int i = 0; i < argc; i++) { |
| argv[i] = node.jjtGetChild(i + 2).jjtAccept(this, null); |
| } |
| |
| JexlException xjexl = null; |
| try { |
| // attempt to reuse last executor cached in volatile JexlNode.value |
| if (cache) { |
| Object cached = node.jjtGetValue(); |
| if (cached instanceof JexlMethod) { |
| JexlMethod me = (JexlMethod) cached; |
| Object eval = me.tryInvoke(function, namespace, argv); |
| if (!me.tryFailed(eval)) { |
| return eval; |
| } |
| } |
| } |
| JexlMethod vm = uberspect.getMethod(namespace, function, argv, node); |
| // DG: If we can't find an exact match, narrow the parameters and |
| // try again! |
| if (vm == null) { |
| // replace all numbers with the smallest type that will fit |
| if (arithmetic.narrowArguments(argv)) { |
| vm = uberspect.getMethod(namespace, function, argv, node); |
| } |
| if (vm == null) { |
| xjexl = new JexlException(node, "unknown function", null); |
| } |
| } |
| if (xjexl == null) { |
| Object eval = vm.invoke(namespace, argv); // vm cannot be null if xjexl is null |
| // cache executor in volatile JexlNode.value |
| if (cache && vm.isCacheable()) { |
| node.jjtSetValue(vm); |
| } |
| return eval; |
| } |
| } catch (InvocationTargetException e) { |
| xjexl = new JexlException(node, "function invocation error", e.getCause()); |
| } catch (Exception e) { |
| xjexl = new JexlException(node, "function error", e); |
| } |
| return invocationFailed(xjexl); |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTModNode node, Object data) { |
| Object left = node.jjtGetChild(0).jjtAccept(this, data); |
| Object right = node.jjtGetChild(1).jjtAccept(this, data); |
| try { |
| return arithmetic.mod(left, right); |
| } catch (RuntimeException xrt) { |
| if (!strict && xrt instanceof ArithmeticException) { |
| return new Double(0.0); |
| } |
| JexlNode xnode = findNullOperand(xrt, node, left, right); |
| throw new JexlException(xnode, "% error", xrt); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTMulNode node, Object data) { |
| Object left = node.jjtGetChild(0).jjtAccept(this, data); |
| Object right = node.jjtGetChild(1).jjtAccept(this, data); |
| try { |
| return arithmetic.multiply(left, right); |
| } catch (RuntimeException xrt) { |
| JexlNode xnode = findNullOperand(xrt, node, left, right); |
| throw new JexlException(xnode, "* error", xrt); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTNENode node, Object data) { |
| Object left = node.jjtGetChild(0).jjtAccept(this, data); |
| Object right = node.jjtGetChild(1).jjtAccept(this, data); |
| try { |
| return arithmetic.equals(left, right) ? Boolean.FALSE : Boolean.TRUE; |
| } catch (RuntimeException xrt) { |
| JexlNode xnode = findNullOperand(xrt, node, left, right); |
| throw new JexlException(xnode, "!= error", xrt); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTNRNode node, Object data) { |
| Object left = node.jjtGetChild(0).jjtAccept(this, data); |
| Object right = node.jjtGetChild(1).jjtAccept(this, data); |
| try { |
| return arithmetic.matches(left, right) ? Boolean.FALSE : Boolean.TRUE; |
| } catch (RuntimeException xrt) { |
| throw new JexlException(node, "!~ error", xrt); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTNotNode node, Object data) { |
| Object val = node.jjtGetChild(0).jjtAccept(this, data); |
| return arithmetic.toBoolean(val) ? Boolean.FALSE : Boolean.TRUE; |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTNullLiteral node, Object data) { |
| return null; |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTOrNode node, Object data) { |
| Object left = node.jjtGetChild(0).jjtAccept(this, data); |
| try { |
| boolean leftValue = arithmetic.toBoolean(left); |
| if (leftValue) { |
| return Boolean.TRUE; |
| } |
| } catch (RuntimeException xrt) { |
| throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt); |
| } |
| Object right = node.jjtGetChild(1).jjtAccept(this, data); |
| try { |
| boolean rightValue = arithmetic.toBoolean(right); |
| if (rightValue) { |
| return Boolean.TRUE; |
| } |
| } catch (RuntimeException xrt) { |
| throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt); |
| } |
| return Boolean.FALSE; |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTReference node, Object data) { |
| // could be array access, identifier or map literal |
| // followed by zero or more ("." and array access, method, size, |
| // identifier or integer literal) |
| |
| int numChildren = node.jjtGetNumChildren(); |
| |
| // pass first piece of data in and loop through children |
| Object result = null; |
| StringBuilder variableName = null; |
| boolean isVariable = true; |
| int v = 0; |
| for (int c = 0; c < numChildren; c++) { |
| JexlNode theNode = node.jjtGetChild(c); |
| isVariable &= (theNode instanceof ASTIdentifier); |
| result = theNode.jjtAccept(this, result); |
| // if we get null back a result, check for an ant variable |
| if (result == null && isVariable) { |
| if (v == 0) { |
| variableName = new StringBuilder(node.jjtGetChild(0).image); |
| v = 1; |
| } |
| for(; v <= c; ++v) { |
| variableName.append('.'); |
| variableName.append(node.jjtGetChild(v).image); |
| } |
| result = context.get(variableName.toString()); |
| } |
| } |
| if (result == null) { |
| if (isVariable |
| && !(node.jjtGetParent() instanceof ASTTernaryNode) |
| && !context.has(variableName.toString())) { |
| JexlException xjexl = new JexlException(node, "undefined variable " + variableName.toString()); |
| return unknownVariable(xjexl); |
| } |
| } |
| return result; |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTSizeFunction node, Object data) { |
| Object val = node.jjtGetChild(0).jjtAccept(this, data); |
| |
| if (val == null) { |
| throw new JexlException(node, "size() : argument is null", null); |
| } |
| |
| return Integer.valueOf(sizeOf(node, val)); |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTSizeMethod node, Object data) { |
| return Integer.valueOf(sizeOf(node, data)); |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTStringLiteral node, Object data) { |
| return node.image; |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTTernaryNode node, Object data) { |
| Object condition = node.jjtGetChild(0).jjtAccept(this, data); |
| if (node.jjtGetNumChildren() == 3) { |
| if (condition != null && arithmetic.toBoolean(condition)) { |
| return node.jjtGetChild(1).jjtAccept(this, data); |
| } else { |
| return node.jjtGetChild(2).jjtAccept(this, data); |
| } |
| } |
| if (condition != null && !Boolean.FALSE.equals(condition)) { |
| return condition; |
| } else { |
| return node.jjtGetChild(1).jjtAccept(this, data); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTTrueNode node, Object data) { |
| return Boolean.TRUE; |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTUnaryMinusNode node, Object data) { |
| JexlNode valNode = node.jjtGetChild(0); |
| Object val = valNode.jjtAccept(this, data); |
| if (val instanceof Byte) { |
| byte valueAsByte = ((Byte) val).byteValue(); |
| return Byte.valueOf((byte) -valueAsByte); |
| } else if (val instanceof Short) { |
| short valueAsShort = ((Short) val).shortValue(); |
| return Short.valueOf((short) -valueAsShort); |
| } else if (val instanceof Integer) { |
| int valueAsInt = ((Integer) val).intValue(); |
| return Integer.valueOf(-valueAsInt); |
| } else if (val instanceof Long) { |
| long valueAsLong = ((Long) val).longValue(); |
| return Long.valueOf(-valueAsLong); |
| } else if (val instanceof Float) { |
| float valueAsFloat = ((Float) val).floatValue(); |
| return new Float(-valueAsFloat); |
| } else if (val instanceof Double) { |
| double valueAsDouble = ((Double) val).doubleValue(); |
| return new Double(-valueAsDouble); |
| } else if (val instanceof BigDecimal) { |
| BigDecimal valueAsBigD = (BigDecimal) val; |
| return valueAsBigD.negate(); |
| } else if (val instanceof BigInteger) { |
| BigInteger valueAsBigI = (BigInteger) val; |
| return valueAsBigI.negate(); |
| } |
| throw new JexlException(valNode, "not a number"); |
| } |
| |
| /** {@inheritDoc} */ |
| public Object visit(ASTWhileStatement node, Object data) { |
| Object result = null; |
| /* first objectNode is the expression */ |
| Node expressionNode = node.jjtGetChild(0); |
| while (arithmetic.toBoolean(expressionNode.jjtAccept(this, data))) { |
| // execute statement |
| result = node.jjtGetChild(1).jjtAccept(this, data); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Calculate the <code>size</code> of various types: Collection, Array, |
| * Map, String, and anything that has a int size() method. |
| * @param node the node that gave the value to size |
| * @param val the object to get the size of. |
| * @return the size of val |
| */ |
| private int sizeOf(JexlNode node, Object val) { |
| if (val instanceof Collection<?>) { |
| return ((Collection<?>) val).size(); |
| } else if (val.getClass().isArray()) { |
| return Array.getLength(val); |
| } else if (val instanceof Map<?, ?>) { |
| return ((Map<?, ?>) val).size(); |
| } else if (val instanceof String) { |
| return ((String) val).length(); |
| } else { |
| // check if there is a size method on the object that returns an |
| // integer and if so, just use it |
| Object[] params = new Object[0]; |
| JexlMethod vm = uberspect.getMethod(val, "size", EMPTY_PARAMS, node); |
| if (vm != null && vm.getReturnType() == Integer.TYPE) { |
| Integer result; |
| try { |
| result = (Integer) vm.invoke(val, params); |
| } catch (Exception e) { |
| throw new JexlException(node, "size() : error executing", e); |
| } |
| return result.intValue(); |
| } |
| throw new JexlException(node, "size() : unsupported type : " + val.getClass(), null); |
| } |
| } |
| |
| /** |
| * Gets an attribute of an object. |
| * |
| * @param object to retrieve value from |
| * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or |
| * key for a map |
| * @return the attribute value |
| */ |
| public Object getAttribute(Object object, Object attribute) { |
| return getAttribute(object, attribute, null); |
| } |
| |
| /** |
| * Gets an attribute of an object. |
| * |
| * @param object to retrieve value from |
| * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or |
| * key for a map |
| * @param node the node that evaluated as the object |
| * @return the attribute value |
| */ |
| protected Object getAttribute(Object object, Object attribute, JexlNode node) { |
| if (object == null) { |
| throw new JexlException(node, "object is null"); |
| } |
| // attempt to reuse last executor cached in volatile JexlNode.value |
| if (node != null && cache) { |
| Object cached = node.jjtGetValue(); |
| if (cached instanceof JexlPropertyGet) { |
| JexlPropertyGet vg = (JexlPropertyGet) cached; |
| Object value = vg.tryInvoke(object, attribute); |
| if (!vg.tryFailed(value)) { |
| return value; |
| } |
| } |
| } |
| JexlPropertyGet vg = uberspect.getPropertyGet(object, attribute, node); |
| if (vg != null) { |
| try { |
| Object value = vg.invoke(object); |
| // cache executor in volatile JexlNode.value |
| if (node != null && cache && vg.isCacheable()) { |
| node.jjtSetValue(vg); |
| } |
| return value; |
| } catch (Exception xany) { |
| if (node == null) { |
| throw new RuntimeException(xany); |
| } else { |
| JexlException xjexl = new JexlException(node, "get object property error", xany); |
| if (strict) { |
| throw xjexl; |
| } |
| if (!silent) { |
| logger.warn(xjexl.getMessage()); |
| } |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Sets an attribute of an object. |
| * |
| * @param object to set the value to |
| * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or |
| * key for a map |
| * @param value the value to assign to the object's attribute |
| */ |
| public void setAttribute(Object object, Object attribute, Object value) { |
| setAttribute(object, attribute, value, null); |
| } |
| |
| /** |
| * Sets an attribute of an object. |
| * |
| * @param object to set the value to |
| * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or |
| * key for a map |
| * @param value the value to assign to the object's attribute |
| * @param node the node that evaluated as the object |
| */ |
| protected void setAttribute(Object object, Object attribute, Object value, JexlNode node) { |
| // attempt to reuse last executor cached in volatile JexlNode.value |
| if (node != null && cache) { |
| Object cached = node.jjtGetValue(); |
| if (cached instanceof JexlPropertySet) { |
| JexlPropertySet setter = (JexlPropertySet) cached; |
| Object eval = setter.tryInvoke(object, attribute, value); |
| if (!setter.tryFailed(eval)) { |
| return; |
| } |
| } |
| } |
| JexlException xjexl = null; |
| JexlPropertySet vs = uberspect.getPropertySet(object, attribute, value, node); |
| if (vs != null) { |
| try { |
| // cache executor in volatile JexlNode.value |
| vs.invoke(object, value); |
| if (node != null && cache && vs.isCacheable()) { |
| node.jjtSetValue(vs); |
| } |
| return; |
| } catch (RuntimeException xrt) { |
| if (node == null) { |
| throw xrt; |
| } |
| xjexl = new JexlException(node, "set object property error", xrt); |
| } catch (Exception xany) { |
| if (node == null) { |
| throw new RuntimeException(xany); |
| } |
| xjexl = new JexlException(node, "set object property error", xany); |
| } |
| } |
| if (xjexl == null) { |
| String error = "unable to set object property" |
| + ", class: " + object.getClass().getName() |
| + ", property: " + attribute; |
| if (node == null) { |
| throw new UnsupportedOperationException(error); |
| } |
| xjexl = new JexlException(node, error, null); |
| } |
| if (strict) { |
| throw xjexl; |
| } |
| if (!silent) { |
| logger.warn(xjexl.getMessage()); |
| } |
| } |
| |
| /** |
| * Unused, satisfy ParserVisitor interface. |
| * @param node a node |
| * @param data the data |
| * @return does not return |
| */ |
| public Object visit(SimpleNode node, Object data) { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| /** |
| * Unused, should throw in Parser. |
| * @param node a node |
| * @param data the data |
| * @return does not return |
| */ |
| public Object visit(ASTAmbiguous node, Object data) { |
| throw new UnsupportedOperationException("unexpected type of node"); |
| } |
| } |