/******************************************************************************* * Copyright (c) 2012-2016 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.ide.ext.java.jdi.server.expression; import com.sun.jdi.AbsentInformationException; import com.sun.jdi.ArrayReference; import com.sun.jdi.ArrayType; import com.sun.jdi.BooleanType; import com.sun.jdi.BooleanValue; import com.sun.jdi.ClassNotLoadedException; import com.sun.jdi.ClassNotPreparedException; import com.sun.jdi.ClassType; import com.sun.jdi.DoubleValue; import com.sun.jdi.Field; import com.sun.jdi.FloatValue; import com.sun.jdi.IncompatibleThreadStateException; import com.sun.jdi.IntegerValue; import com.sun.jdi.InterfaceType; import com.sun.jdi.InvalidStackFrameException; import com.sun.jdi.InvalidTypeException; import com.sun.jdi.InvocationException; import com.sun.jdi.LocalVariable; import com.sun.jdi.LongValue; import com.sun.jdi.Method; import com.sun.jdi.NativeMethodException; import com.sun.jdi.ObjectReference; import com.sun.jdi.PrimitiveType; import com.sun.jdi.PrimitiveValue; import com.sun.jdi.ReferenceType; import com.sun.jdi.StackFrame; import com.sun.jdi.StringReference; import com.sun.jdi.ThreadReference; import com.sun.jdi.Type; import com.sun.jdi.Value; import com.sun.jdi.VirtualMachine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; /** @author andrew00x */ public class Evaluator { private static final Logger LOG = LoggerFactory.getLogger(Evaluator.class); private static final Set<String> PRIMITIVE_TYPES = new HashSet<>(8); static { PRIMITIVE_TYPES.add("boolean"); PRIMITIVE_TYPES.add("byte"); PRIMITIVE_TYPES.add("char"); PRIMITIVE_TYPES.add("short"); PRIMITIVE_TYPES.add("int"); PRIMITIVE_TYPES.add("long"); PRIMITIVE_TYPES.add("float"); PRIMITIVE_TYPES.add("double"); } private final VirtualMachine vm; private final ThreadReference thread; public Evaluator(VirtualMachine vm, ThreadReference thread) { this.vm = vm; this.thread = thread; } private static boolean isPrimitive(Type type) { return PRIMITIVE_TYPES.contains(type.name()); } public ThreadReference getThread() { return thread; } public ExpressionValue booleanValue(String text) { return value(Boolean.parseBoolean(text)); } public ExpressionValue byteValue(String text) { return value(Byte.parseByte(text)); } public ExpressionValue shortValue(String text) { return value(Short.parseShort(text)); } public ExpressionValue intValue(String text) { return value(Integer.parseInt(text)); } public ExpressionValue hexValue(String text) { int length = text.length(); if (length > 1 && ('l' == text.charAt(length - 1) || 'L' == text.charAt(length - 1))) { return value(Long.parseLong(text.substring(2, length - 1), 16)); } return value(Integer.parseInt(text.substring(2), 16)); } public ExpressionValue octalValue(String text) { int length = text.length(); if (length > 1 && ('l' == text.charAt(length - 1) || 'L' == text.charAt(length - 1))) { return value(Long.parseLong(text.substring(1, length - 1), 8)); } return value(Integer.parseInt(text.substring(1), 8)); } public ExpressionValue decimalValue(String text) { int length = text.length(); if (length > 1 && ('l' == text.charAt(length - 1) || 'L' == text.charAt(length - 1))) { return value(Long.parseLong(text.substring(0, length - 1))); } return value(Integer.parseInt(text)); } public ExpressionValue floating_pointValue(String text) { int length = text.length(); if ('f' == text.charAt(length - 1) || 'F' == text.charAt(length - 1)) { return value(Float.parseFloat(text)); } return value(Double.parseDouble(text)); } // public ExpressionValue charValue(String text) { return value(text.charAt(0)); } public ExpressionValue stringValue(String text) { return value(text.substring(1, text.length() - 1)); } public ExpressionValue nullValue() { return new ReadOnlyValue(null); } public ExpressionValue value(boolean v) { return new ReadOnlyValue(vm.mirrorOf(v)); } public ExpressionValue value(byte v) { return new ReadOnlyValue(vm.mirrorOf(v)); } public ExpressionValue value(short v) { return new ReadOnlyValue(vm.mirrorOf(v)); } public ExpressionValue value(int v) { return new ReadOnlyValue(vm.mirrorOf(v)); } public ExpressionValue value(long v) { return new ReadOnlyValue(vm.mirrorOf(v)); } public ExpressionValue value(float v) { return new ReadOnlyValue(vm.mirrorOf(v)); } // public ExpressionValue value(double v) { return new ReadOnlyValue(vm.mirrorOf(v)); } /* public ExpressionValue getField(String text) { ExpressionValue value = null; try { StackFrame frame = thread.frame(0); ObjectReference object = frame.thisObject(); if (object == null) { ReferenceType type = frame.location().declaringType(); Field field = type.fieldByName(text); if (field != null) { value = new StaticValue(type, field); } } else { Field field = object.referenceType().fieldByName(text); if (field != null) { value = new InstanceValue(object, field); } } } catch (IncompatibleThreadStateException e) { throw new ExpressionException(e.getMessage(), e); } catch (InvalidStackFrameException e) { throw new ExpressionException(e.getMessage(), e); } catch (ClassNotPreparedException e) { throw new ExpressionException(e.getMessage(), e); } LOG.debug("GET field {} {} ", text, value); return value; } */ public ExpressionValue value(char v) { return new ReadOnlyValue(vm.mirrorOf(v)); } public ExpressionValue value(String v) { return new ReadOnlyValue(vm.mirrorOf(v)); } public ExpressionValue getThisObject() { try { return new ReadOnlyValue(thread.frame(0).thisObject()); } catch (IncompatibleThreadStateException e) { throw new ExpressionException(e.getMessage(), e); } } public ExpressionValue getField(Value parent, String name) { if (!(parent instanceof ObjectReference)) { throw new ExpressionException("Value is not object. Cannot invoke method " + name); } ExpressionValue value = null; try { ObjectReference object = (ObjectReference)parent; Field field = object.referenceType().fieldByName(name); if (field != null) { value = new InstanceValue(object, field); } } catch (ClassNotPreparedException e) { throw new ExpressionException(e.getMessage(), e); } LOG.debug("GET field {} {} ", name, value); return value; } public ExpressionValue getLocalVariable(String text) { ExpressionValue value = null; try { StackFrame frame = thread.frame(0); LocalVariable var = frame.visibleVariableByName(text); if (var != null) { value = new LocalValue(thread, var); } } catch (IncompatibleThreadStateException | AbsentInformationException | InvalidStackFrameException | NativeMethodException e) { throw new ExpressionException(e.getMessage(), e); } LOG.debug("GET local variable {} {} ", text, value); return value; } public ExpressionValue getArrayElement(Value arrayValue, Value indexValue) { if (!(arrayValue instanceof ArrayReference)) { throw new ExpressionException("Cannot get array element. Object is not array. "); } if (!(indexValue instanceof IntegerValue)) { throw new ExpressionException("Invalid array index. "); } return new ArrayElement((ArrayReference)arrayValue, ((IntegerValue)indexValue).value()); } private Method findMethod(List<Method> methods, List<Value> arguments) { Method m = null; for (Method mm : methods) { List<Type> argumentTypes; try { argumentTypes = mm.argumentTypes(); } catch (ClassNotLoadedException e) { continue; } ARGUMENT_MATCHING argumentMatching = argumentsMatching(argumentTypes, arguments); if (argumentMatching == ARGUMENT_MATCHING.MATCH) { m = mm; break; } else if (argumentMatching == ARGUMENT_MATCHING.ASSIGNABLE) { if (m == null) { m = mm; } else { throw new ExpressionException("Multiple methods with name " + mm.name() + " matched to specified arguments. "); } } } return m; } private ARGUMENT_MATCHING argumentsMatching(List<Type> argumentTypes, List<Value> arguments) { if (argumentTypes.size() == arguments.size()) { Iterator<Value> argumentIterator = arguments.iterator(); Iterator<Type> argumentTypesIterator = argumentTypes.iterator(); ARGUMENT_MATCHING result = ARGUMENT_MATCHING.MATCH; while (argumentIterator.hasNext() && result != ARGUMENT_MATCHING.NOT_MATCH) { Value argumentValue = argumentIterator.next(); Type argumentType = argumentTypesIterator.next(); if (argumentValue == null) { if (isPrimitive(argumentType)) { // Null may not be used as value if argument type is primitive. result = ARGUMENT_MATCHING.NOT_MATCH; } } else { if (!(argumentValue.type().equals(argumentType))) { result = isAssignable(argumentValue.type(), argumentType) ? ARGUMENT_MATCHING.ASSIGNABLE : ARGUMENT_MATCHING.NOT_MATCH; } } } return result; } return ARGUMENT_MATCHING.NOT_MATCH; } private boolean isAssignable(Type from, Type to) { if (from.equals(to)) { return true; } if (from instanceof BooleanType) { return to instanceof BooleanType; } if (to instanceof BooleanType) { return false; } if (from instanceof PrimitiveType) { return to instanceof PrimitiveType; } if (to instanceof PrimitiveType) { return false; } if (from instanceof ArrayType) { if (to instanceof ArrayType) { Type fromArrayComponent; Type toArrayComponent; try { fromArrayComponent = ((ArrayType)from).componentType(); toArrayComponent = ((ArrayType)to).componentType(); } catch (ClassNotLoadedException e) { return false; } if (fromArrayComponent instanceof PrimitiveType) { return fromArrayComponent.equals(toArrayComponent); } return !(toArrayComponent instanceof PrimitiveType) && isAssignable(fromArrayComponent, toArrayComponent); } return to.name().equals("java.lang.Object"); } if (from instanceof ClassType) { ClassType superClass = ((ClassType)from).superclass(); if (superClass != null && isAssignable(superClass, to)) { return true; } for (InterfaceType interfaceType : ((ClassType)from).interfaces()) { if (isAssignable(interfaceType, to)) { return true; } } } for (InterfaceType interfaceType : ((InterfaceType)from).subinterfaces()) { if (isAssignable(interfaceType, to)) { return true; } } return false; } public ExpressionValue invokeMethod(Value value, String name, List<Value> arguments) { if (!(value instanceof ObjectReference)) { throw new ExpressionException("Value is not object. Cannot invoke method " + name); } ObjectReference object = (ObjectReference)value; ReferenceType type = object.referenceType(); List<Method> methods = type.methodsByName(name); Method method = findMethod(methods, arguments); if (method == null) { throw new ExpressionException("No method with name " + name + " matched to specified arguments for " + type.name()); } try { return new ReadOnlyValue(object.invokeMethod(thread, method, arguments, 0)); } catch (InvalidTypeException | ClassNotLoadedException | IncompatibleThreadStateException | InvocationException e) { throw new ExpressionException(e.getMessage(), e); } } public ExpressionValue unaryOperation(ExpressionValue expression, int op) { Value value = expression.getValue(); if (!(value instanceof PrimitiveValue)) { throw new ExpressionException("Unsupported operation " + JavaParser.tokenNames[op] + " for " + value); } if (value instanceof BooleanValue) { if (op == JavaParser.LOGICAL_NOT) { return value(!((BooleanValue)value).value()); } } // TODO add support for other unary operations PrimitiveValue pv = (PrimitiveValue)value; if (pv instanceof DoubleValue) { switch (op) { case JavaParser.UNARY_PLUS: return value(pv.doubleValue()); case JavaParser.UNARY_MINUS: return value(-pv.doubleValue()); default: throw new ExpressionException("Unsupported operation " + JavaParser.tokenNames[op] + " for " + value); } } if (pv instanceof FloatValue) { switch (op) { case JavaParser.UNARY_PLUS: return value(pv.floatValue()); case JavaParser.UNARY_MINUS: return value(-pv.floatValue()); default: throw new ExpressionException("Unsupported operation " + JavaParser.tokenNames[op] + " for " + value); } } if (pv instanceof LongValue) { switch (op) { case JavaParser.NOT: return value(~pv.longValue()); case JavaParser.UNARY_PLUS: return value(pv.longValue()); case JavaParser.UNARY_MINUS: return value(-pv.longValue()); default: throw new ExpressionException("Unsupported operation " + JavaParser.tokenNames[op] + " for " + value); } } switch (op) { case JavaParser.NOT: return value(~pv.intValue()); case JavaParser.UNARY_PLUS: return value(pv.intValue()); case JavaParser.UNARY_MINUS: return value(-pv.intValue()); default: throw new ExpressionException("Unsupported operation " + JavaParser.tokenNames[op] + " for " + value); } } public ExpressionValue operation(ExpressionValue leftExpression, ExpressionValue rightExpression, int op) { if (JavaParser.ASSIGN == op) { leftExpression.setValue(rightExpression.getValue()); return leftExpression; } Value leftValue = leftExpression.getValue(); Value rightValue = rightExpression.getValue(); if (leftValue instanceof StringReference || rightValue instanceof StringReference) { if (JavaParser.PLUS == op) { return value(valueToString(leftValue) + valueToString(rightValue)); } } if (leftValue instanceof ObjectReference || rightValue instanceof ObjectReference) { switch (op) { case JavaParser.EQUAL: return value(leftValue != null ? leftValue.equals(rightValue) : rightValue == null); case JavaParser.NOT_EQUAL: return value(!(leftValue != null ? leftValue.equals(rightValue) : rightValue == null)); default: throw new ExpressionException("Unsupported operation " + JavaParser.tokenNames[op] + " for " + leftValue + " and " + rightValue); } } if (leftValue == null || rightValue == null) { // Neither one is object and operation is not assignation. throw new ExpressionException("Unsupported operation " + JavaParser.tokenNames[op] + " for " + leftValue + " and " + rightValue); } PrimitiveValue lp = (PrimitiveValue)leftValue; PrimitiveValue rp = (PrimitiveValue)rightValue; if (lp instanceof BooleanValue && rp instanceof BooleanValue) { switch (op) { case JavaParser.LOGICAL_AND: return value(lp.booleanValue() && rp.booleanValue()); case JavaParser.LOGICAL_OR: return value(lp.booleanValue() || rp.booleanValue()); case JavaParser.EQUAL: return value(lp.booleanValue() == rp.booleanValue()); case JavaParser.NOT_EQUAL: return value(lp.booleanValue() != rp.booleanValue()); case JavaParser.OR: return value(lp.booleanValue() | rp.booleanValue()); case JavaParser.XOR: return value(lp.booleanValue() ^ rp.booleanValue()); case JavaParser.AND: return value(lp.booleanValue() & rp.booleanValue()); case JavaParser.OR_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.booleanValue() | rp.booleanValue())); return leftExpression; case JavaParser.XOR_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.booleanValue() ^ rp.booleanValue())); return leftExpression; case JavaParser.AND_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.booleanValue() & rp.booleanValue())); return leftExpression; default: throw new ExpressionException("Unsupported operation " + JavaParser.tokenNames[op] + " for " + leftValue + " and " + rightValue); } } switch (op) { case JavaParser.EQUAL: return value(lp.doubleValue() == rp.doubleValue()); case JavaParser.NOT_EQUAL: return value(lp.doubleValue() != rp.doubleValue()); case JavaParser.GREATER_THAN: return value(lp.doubleValue() > rp.doubleValue()); case JavaParser.LESS_THAN: return value(lp.doubleValue() < rp.doubleValue()); case JavaParser.GREATER_OR_EQUAL: return value(lp.doubleValue() >= rp.doubleValue()); case JavaParser.LESS_OR_EQUAL: return value(lp.doubleValue() <= rp.doubleValue()); } if (lp instanceof DoubleValue || rp instanceof DoubleValue) { switch (op) { case JavaParser.PLUS: return value(lp.doubleValue() + rp.doubleValue()); case JavaParser.MINUS: return value(lp.doubleValue() - rp.doubleValue()); case JavaParser.STAR: return value(lp.doubleValue() * rp.doubleValue()); case JavaParser.DIV: return value(lp.doubleValue() / rp.doubleValue()); case JavaParser.MOD: return value(lp.doubleValue() % rp.doubleValue()); case JavaParser.PLUS_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.doubleValue() + rp.doubleValue())); return leftExpression; case JavaParser.MINUS_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.doubleValue() - rp.doubleValue())); return leftExpression; case JavaParser.STAR_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.doubleValue() * rp.doubleValue())); return leftExpression; case JavaParser.DIV_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.doubleValue() / rp.doubleValue())); return leftExpression; case JavaParser.MOD_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.doubleValue() % rp.doubleValue())); return leftExpression; default: throw new ExpressionException("Unsupported operation " + JavaParser.tokenNames[op] + " for " + leftValue + " and " + rightValue); } } if (lp instanceof FloatValue || rp instanceof FloatValue) { switch (op) { case JavaParser.PLUS: return value(lp.floatValue() + rp.floatValue()); case JavaParser.MINUS: return value(lp.floatValue() - rp.floatValue()); case JavaParser.STAR: return value(lp.floatValue() * rp.floatValue()); case JavaParser.DIV: return value(lp.floatValue() / rp.floatValue()); case JavaParser.MOD: return value(lp.floatValue() % rp.floatValue()); case JavaParser.PLUS_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.floatValue() + rp.floatValue())); return leftExpression; case JavaParser.MINUS_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.floatValue() - rp.floatValue())); return leftExpression; case JavaParser.STAR_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.floatValue() * rp.longValue())); return leftExpression; case JavaParser.DIV_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.floatValue() / rp.floatValue())); return leftExpression; case JavaParser.MOD_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.floatValue() % rp.floatValue())); return leftExpression; default: throw new ExpressionException("Unsupported operation " + JavaParser.tokenNames[op] + " for " + leftValue + " and " + rightValue); } } if (lp instanceof LongValue || rp instanceof LongValue) { switch (op) { case JavaParser.PLUS: return value(lp.longValue() + rp.longValue()); case JavaParser.MINUS: return value(lp.longValue() - rp.longValue()); case JavaParser.STAR: return value(lp.longValue() * rp.longValue()); case JavaParser.DIV: return value(lp.longValue() / rp.longValue()); case JavaParser.MOD: return value(lp.longValue() % rp.longValue()); case JavaParser.SHIFT_LEFT: return value(lp.longValue() << rp.longValue()); case JavaParser.SHIFT_RIGHT: return value(lp.longValue() >> rp.longValue()); case JavaParser.BIT_SHIFT_RIGHT: return value(lp.longValue() >>> rp.longValue()); case JavaParser.OR: return value(lp.longValue() | rp.longValue()); case JavaParser.XOR: return value(lp.longValue() ^ rp.longValue()); case JavaParser.AND: return value(lp.longValue() & rp.longValue()); case JavaParser.PLUS_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.longValue() + rp.longValue())); return leftExpression; case JavaParser.MINUS_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.longValue() - rp.longValue())); return leftExpression; case JavaParser.STAR_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.longValue() * rp.longValue())); return leftExpression; case JavaParser.DIV_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.longValue() / rp.longValue())); return leftExpression; case JavaParser.OR_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.longValue() | rp.longValue())); return leftExpression; case JavaParser.XOR_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.longValue() ^ rp.longValue())); return leftExpression; case JavaParser.AND_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.longValue() & rp.longValue())); return leftExpression; case JavaParser.SHIFT_LEFT_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.longValue() << rp.longValue())); return leftExpression; case JavaParser.SHIFT_RIGHT_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.longValue() >> rp.longValue())); return leftExpression; case JavaParser.BIT_SHIFT_RIGHT_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.longValue() >>> rp.longValue())); return leftExpression; default: throw new ExpressionException("Unsupported operation " + JavaParser.tokenNames[op] + " for " + leftValue + " and " + rightValue); } } switch (op) { case JavaParser.PLUS: return value(lp.intValue() + rp.intValue()); case JavaParser.MINUS: return value(lp.intValue() - rp.intValue()); case JavaParser.STAR: return value(lp.intValue() * rp.intValue()); case JavaParser.DIV: return value(lp.intValue() / rp.intValue()); case JavaParser.MOD: return value(lp.intValue() % rp.intValue()); case JavaParser.SHIFT_LEFT: return value(lp.intValue() << rp.intValue()); case JavaParser.SHIFT_RIGHT: return value(lp.intValue() >> rp.intValue()); case JavaParser.BIT_SHIFT_RIGHT: return value(lp.intValue() >>> rp.intValue()); case JavaParser.OR: return value(lp.intValue() | rp.intValue()); case JavaParser.XOR: return value(lp.intValue() ^ rp.intValue()); case JavaParser.AND: return value(lp.intValue() & rp.intValue()); case JavaParser.PLUS_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.intValue() + rp.intValue())); return leftExpression; case JavaParser.MINUS_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.intValue() - rp.intValue())); return leftExpression; case JavaParser.STAR_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.intValue() * rp.intValue())); return leftExpression; case JavaParser.DIV_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.intValue() / rp.intValue())); return leftExpression; case JavaParser.OR_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.intValue() | rp.intValue())); return leftExpression; case JavaParser.XOR_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.intValue() ^ rp.intValue())); return leftExpression; case JavaParser.AND_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.intValue() & rp.intValue())); return leftExpression; case JavaParser.SHIFT_LEFT_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.intValue() << rp.intValue())); return leftExpression; case JavaParser.SHIFT_RIGHT_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.intValue() >> rp.intValue())); return leftExpression; case JavaParser.BIT_SHIFT_RIGHT_ASSIGN: leftExpression.setValue(vm.mirrorOf(lp.intValue() >>> rp.intValue())); return leftExpression; default: throw new ExpressionException("Unsupported operation " + JavaParser.tokenNames[op] + " for " + leftValue + " and " + rightValue); } } public ExpressionValue ternaryOperator(ExpressionValue testCondition, ExpressionValue value1, ExpressionValue value2) { Value test = testCondition.getValue(); if (!(test instanceof BooleanValue)) { throw new ExpressionException("Invalid ternary operator. "); } return ((BooleanValue)test).booleanValue() ? value1 : value2; } private String valueToString(Value value) { if (value == null) { return "null"; } if (value instanceof StringReference) { return ((StringReference)value).value(); } if (value instanceof ObjectReference) { StringReference stringValue = (StringReference)invokeMethod(value, "toString", Collections.<Value>emptyList()).getValue(); // XXX Can result be null ? return stringValue == null ? "null" : stringValue.value(); } return value.toString(); } private enum ARGUMENT_MATCHING { MATCH, ASSIGNABLE, NOT_MATCH } }