/******************************************************************************* * Copyright (c) 2004, 2005 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation *******************************************************************************/ /* */ package org.eclipse.jem.internal.proxy.vm.remote; import java.io.DataInputStream; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import org.eclipse.jem.internal.proxy.common.*; import org.eclipse.jem.internal.proxy.common.remote.Commands; import org.eclipse.jem.internal.proxy.common.remote.ExpressionCommands; import org.eclipse.jem.internal.proxy.initParser.tree.*; /** * This processes the commands for expression processing and sends them over * to the common expression processer. * * This will be instantiated on the start of an expression. And then * each expression request from the IDE will be sent into here. The * reason this guy doesn't hold onto the connection and process the * entire expression is because we need to return to the connection * handler to keep the connection live (there is timeouts and stuff * in there that we don't want to duplicate here). * <p> * If there are any expression processing errors (versus hard io errors) we * will save up the error but don't do any more processing other than to make * sure we read the complete subcommand. This is so that the inputstream is left * in a valid state without standed data. * <p> * The at the sync point (either get value or sync subcommand) we will send back * the error. * * @since 1.0.0 */ public class ExpressionProcesserController { protected final RemoteVMServerThread server; protected final ConnectionHandler connHandler; protected final ExpressionProcesser exp; protected Commands.ValueObject workerValue = new Commands.ValueObject(); // A worker value object so we don't need to keep creating them and releasing them. private ClassLoader classLoader; /** * Create with a default expression processer and use default flag for trace. * @param server * * @since 1.0.0 */ public ExpressionProcesserController(RemoteVMServerThread server, ConnectionHandler connHandler) { this(server, connHandler, new ExpressionProcesser(Boolean.getBoolean(ExpressionCommands.EXPRESSIONTRACE), Long.getLong(ExpressionCommands.EXPRESSIONTRACE_TIMER_THRESHOLD, -1L).longValue())); } /** * Construct with a default expression processer. * @param server * @param connHandler * @param trace * * @since 1.1.0 */ public ExpressionProcesserController(RemoteVMServerThread server, ConnectionHandler connHandler, boolean trace) { this(server, connHandler, new ExpressionProcesser(trace, Long.getLong(ExpressionCommands.EXPRESSIONTRACE_TIMER_THRESHOLD, -1L).longValue())); } /** * Create from a subclass with a given expression processer. * * @param server * @param exp * * @since 1.0.0 */ protected ExpressionProcesserController(RemoteVMServerThread server, ConnectionHandler connHandler, ExpressionProcesser exp) { this.server = server; this.connHandler = connHandler; this.exp = exp; } /** * Set the class loader to use for finding classes. If never set, or if <code>null</code>, then * <code>Class.forName</code> will be used. * * @param classLoader * * @since 1.0.0 */ public void setClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } /* * Array of primitive type names. Used to look up primtive types in primitive types array. * * @since 1.0.0 */ private static final List PRIMITIVE_NAMES = Arrays.asList(new String[] {"byte", "char", "short", "int", "long", "float", "double"}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ private static final Class[] PRIMITIVE_TYPES = new Class[] {Byte.TYPE, Character.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE}; /** * Load the class given the name. If not found, return null. * * @param className * @return * * @since 1.0.0 */ protected Class loadClass(String className) throws ClassNotFoundException { if (className == null) return null; else if (className.endsWith("[]")) { //$NON-NLS-1$ // We have an array request instead. This is trickier. return loadClass(MapTypes.getJNIFormatName(className)); } else { int primIndex = PRIMITIVE_NAMES.indexOf(className); if (primIndex >= 0) return PRIMITIVE_TYPES[primIndex]; else if (classLoader == null) { return Class.forName(className); } else { return classLoader.loadClass(className); } } } /** * Now process the input stream. If either throws occurs, this is a hard error and we must terminate * the entire connection. The input stream is in an unknown state. * * @param in The input stream to get the data for the current sub-command. * * @throws CommandException * @throws IOException * @since 1.0.0 */ public void process(DataInputStream in) throws CommandException, IOException { // In the following subcommand processing, we always read the entire subcommand from the stream. // This is so that any errors during processing will not mess up the stream with unread data. // // Then we check if an error has occurred in the past. If it has, we simply break. This is because // once an error occurred we don't want to continue wasting time evaluating, however we need to make // sure that the stream is read completely so that we don't have a corrupted input stream. That way // when all is done we can return the error and still have a valid connection socket. byte subcommand = in.readByte(); try { switch (subcommand) { case InternalExpressionTypes.PUSH_TO_PROXY_EXPRESSION_VALUE: // Getting a proxy push. The value is sent as valueObject, so use that to read it in. Commands.readValue(in, workerValue); Object value = connHandler.getInvokableObject(workerValue); if (value == null) exp.pushExpression(null, MethodHelper.NULL_TYPE); else if (workerValue.isPrimitive()) exp.pushExpression(value, workerValue.getPrimitiveType()); else exp.pushExpression(value, value.getClass()); break; case InternalExpressionTypes.CAST_EXPRESSION_VALUE: // Get a cast request. The type is sent as valueObject. Commands.readValue(in, workerValue); try { Class classValue = getBeanTypeValue(workerValue); exp.pushCast(classValue); } catch (FailedProxyException e) { exp.processException(e.getCause()); // Let the processor know we have a stopping error. } break; case InternalExpressionTypes.INSTANCEOF_EXPRESSION_VALUE: // Get a instanceof request. The type is sent as valueObject. Commands.readValue(in, workerValue); try { Class classValue = getBeanTypeValue(workerValue); exp.pushInstanceof(classValue); } catch (FailedProxyException e) { exp.processException(e.getCause()); // Let the processor know we have a stopping error. } break; case InternalExpressionTypes.INFIX_EXPRESSION_VALUE: // Get an infix request. The operator and operand type are sent as bytes. byte infix_operator = in.readByte(); byte infix_operandType = in.readByte(); exp.pushInfix(InfixOperator.get(infix_operator), InternalInfixOperandType.get(infix_operandType)); break; case InternalExpressionTypes.PREFIX_EXPRESSION_VALUE: // Get a prefix request. The operator is sent as byte. byte prefix_operandType = in.readByte(); exp.pushPrefix(PrefixOperator.get(prefix_operandType)); break; case InternalExpressionTypes.ARRAY_ACCESS_EXPRESSION_VALUE: // Get an array access request. The index cound is sent as int. int arrayAccess_Indexcount = in.readInt(); exp.pushArrayAccess(arrayAccess_Indexcount); break; case InternalExpressionTypes.ARRAY_CREATION_EXPRESSION_VALUE: // Get an array creation request. The type is sent as valueObject, followed by int dimension count. Commands.readValue(in, workerValue); int arrayCreation_dimCount = in.readInt(); try { Class classValue = getBeanTypeValue(workerValue); exp.pushArrayCreation(classValue, arrayCreation_dimCount); } catch (FailedProxyException e) { exp.processException(e.getCause()); // Let the processor know we have a stopping error. } break; case InternalExpressionTypes.ARRAY_INITIALIZER_EXPRESSION_VALUE: // Get an array initializer request. The type is sent as valueObject, followed by int expression count. Commands.readValue(in, workerValue); int stripCount = in.readInt(); int arrayInitializer_expressionCount = in.readInt(); try { Class classValue = getBeanTypeValue(workerValue); exp.pushArrayInitializer(classValue, stripCount, arrayInitializer_expressionCount); } catch (FailedProxyException e) { exp.processException(e.getCause()); // Let the processor know we have a stopping error. } break; case InternalExpressionTypes.CLASS_INSTANCE_CREATION_EXPRESSION_VALUE: // Get a class instance creation request. The type is sent as valueObject, followed by int argument count. Commands.readValue(in, workerValue); int newInstance_argCount = in.readInt(); try { Class classValue = getBeanTypeValue(workerValue); exp.pushClassInstanceCreation(classValue, newInstance_argCount); } catch (FailedProxyException e) { exp.processException(e.getCause()); // Let the processor know we have a stopping error. } break; case InternalExpressionTypes.TYPERECEIVER_EXPRESSION_VALUE: // Get a type receiver request. The type is sent as valueObject. Commands.readValue(in, workerValue); try { Class classValue = getBeanTypeValue(workerValue); exp.pushExpression(classValue, classValue); } catch (FailedProxyException e) { exp.processException(e.getCause()); // Let the processor know we have a stopping error. } break; case InternalExpressionTypes.FIELD_ACCESS_EXPRESSION_VALUE: // Get a field access request. Command.ValueObject, followed by hasReceiver as boolean. Commands.readValue(in, workerValue); boolean has_fieldAccess_receiver = in.readBoolean(); try { Object fieldAccess = getFieldValue(workerValue); exp.pushFieldAccess(fieldAccess, workerValue.getType() == Commands.STRING, has_fieldAccess_receiver); } catch (ClassCastException e) { exp.processException(e); // Let the processor know we have a stopping error. } catch (FailedProxyException e) { exp.processException(e.getCause()); // Let the processor know we have a stopping error. } break; case InternalExpressionTypes.METHOD_EXPRESSION_VALUE: // Get a method invocation request. Sent as Commands.ValueObject, followed by hasReceiver as boolean., and argCount as int. Commands.readValue(in, workerValue); boolean has_method_receiver = in.readBoolean(); int method_argCount = in.readInt(); try { Object method = getMethodValue(workerValue); exp.pushMethodInvocation(method, workerValue.getType() == Commands.STRING, has_method_receiver, method_argCount); } catch (FailedProxyException e) { exp.processException(e.getCause()); // Let the processor know we have a stopping error. } break; case InternalExpressionTypes.CONDITIONAL_EXPRESSION_VALUE: // Get a conditional expression request. The expression type (ie. condition/true/false) is sent as a byte exp.pushConditional(InternalConditionalOperandType.get(in.readByte())); break; case InternalExpressionTypes.ASSIGNMENT_PROXY_EXPRESSION_VALUE: // Get an assignment expression request. The proxy id is sent as an int. int proxyid = in.readInt(); exp.pushAssignment(new RemoteExpressionProxy(proxyid)); break; case InternalExpressionTypes.ASSIGNMENT_EXPRESSION_VALUE: // Get an assignment expression request. Nothing else to read from stream. exp.pushAssignment(); break; case InternalExpressionTypes.PUSH_TO_EXPRESSION_PROXY_EXPRESSION_VALUE: // Get a push expression proxy expression. The proxy id is sent as an int. // First test if a possible FailedExpressionProxy because we could of been pushing // a failed reflection proxy. proxyid = in.readInt(); try { exp.getExpressionProxy(proxyid, new Object[] {null, null}); } catch (NoExpressionValueException e1) { if (e1.getProxy() != null) { FailedRemoteExpressionProxy failure = (FailedRemoteExpressionProxy) e1.getProxy(); exp.processException((Throwable) failure.getValue()); break; // Don't go on, we processed it. A standard no expression value should be passed on and let following code handle it. } } exp.pushExpressionProxy(proxyid); break; case InternalExpressionTypes.BLOCK_BEGIN_EXPRESSION_VALUE: // Get a begin block proxy expression. The block id is sent as an int. exp.pushBlockBegin(in.readInt()); break; case InternalExpressionTypes.BLOCK_BREAK_EXPRESSION_VALUE: // Get a break block proxy expression. The block id is sent as an int. exp.pushBlockBreak(in.readInt()); break; case InternalExpressionTypes.BLOCK_END_EXPRESSION_VALUE: // Get a end block proxy expression. The block id is sent as an int. exp.pushBlockEnd(in.readInt()); break; case InternalExpressionTypes.TRY_BEGIN_EXPRESSION_VALUE: // Get a try begin proxy expression. The try id is sent as an int. exp.pushTryBegin(in.readInt()); break; case InternalExpressionTypes.TRY_CATCH_EXPRESSION_VALUE: int tryNumber = in.readInt(); Commands.readValue(in, workerValue); proxyid = in.readInt(); try { Class classValue = getBeanTypeValue(workerValue); exp.pushTryCatchClause(tryNumber, classValue, proxyid != -1 ? new RemoteExpressionProxy(proxyid) : null); } catch (FailedProxyException e) { exp.processException(e.getCause()); // Let the processor know we have a stopping error. } break; case InternalExpressionTypes.TRY_FINALLY_EXPRESSION_VALUE: // Get a try finally proxy expression. The try id is sent as an int. exp.pushTryFinallyClause(in.readInt()); break; case InternalExpressionTypes.TRY_END_EXPRESSION_VALUE: // Get a try end proxy expression. The try id is sent as an int. exp.pushTryEnd(in.readInt()); break; case InternalExpressionTypes.THROW_EXPRESSION_VALUE: exp.pushThrowException(); break; case InternalExpressionTypes.RETHROW_EXPRESSION_VALUE: // Get a rethrow proxy expression. The try id is sent as an int. exp.pushTryRethrow(in.readInt()); break; case InternalExpressionTypes.PUSH_BEANTYPE_EXPRESSIONPROXY_EXPRESSION_VALUE: // Get the beantype expression proxy and resolve it. proxyid = in.readInt(); String typeName = Commands.readStringData(in); try { Class classValue = loadClass(typeName); RemoteExpressionProxy rep = new RemoteExpressionProxy(proxyid); rep.setProxy(classValue, Class.class); exp.allocateExpressionProxy(rep); } catch (ClassNotFoundException e) { FailedRemoteExpressionProxy rep = new FailedRemoteExpressionProxy(proxyid); rep.setProxy(e, e.getClass()); exp.allocateExpressionProxy(rep); } catch (LinkageError e) { FailedRemoteExpressionProxy rep = new FailedRemoteExpressionProxy(proxyid); rep.setProxy(e, e.getClass()); exp.allocateExpressionProxy(rep); } break; case InternalExpressionTypes.PUSH_METHOD_EXPRESSIONPROXY_EXPRESSION_VALUE: // Get the Method expression proxy and resolve it. // Comes over as: // int for proxy id for the method // beanTypeValue for declaring class (either beantype or expression proxy) // string for method name // int for arg count // beanTypeValue(s) for arg types (as many as arg count). proxyid = in.readInt(); Commands.ValueObject decClassValue = Commands.readValue(in, new Commands.ValueObject()); String methodName = Commands.readStringData(in); int argCount = in.readInt(); Commands.ValueObject[] args = null; if (argCount > 0) { args = new Commands.ValueObject[argCount]; for (int i = 0; i < argCount; i++) { args[i] = Commands.readValue(in, new Commands.ValueObject()); } } Class decClass = null; Class[] argClasses = null; try { decClass = getBeanTypeValue(decClassValue); argClasses = null; if (argCount>0) { argClasses = new Class[argCount]; for (int i = 0; i < argCount; i++) { argClasses[i] = getBeanTypeValue(args[i]); } } // Now get the method itself. Method m = decClass.getMethod(methodName, argClasses); RemoteExpressionProxy rep = new RemoteExpressionProxy(proxyid); rep.setProxy(m, Method.class); exp.allocateExpressionProxy(rep); } catch (FailedProxyException e) { FailedRemoteExpressionProxy rep = new FailedRemoteExpressionProxy(proxyid); rep.setProxy(e.getCause(), e.getCause().getClass()); exp.allocateExpressionProxy(rep); } catch (NoSuchMethodException e) { // The default trace doesn't show what method was being searched for, so recreate with that. StringBuffer s = new StringBuffer(); s.append(decClass.getName()); s.append('.'); s.append(methodName); s.append('('); if (argClasses != null) { for (int i = 0; i < argClasses.length; i++) { if (i > 0) s.append(','); s.append(argClasses[i].getName()); } } s.append(')'); NoSuchMethodException ne = new NoSuchMethodException(s.toString()); ne.setStackTrace(e.getStackTrace()); FailedRemoteExpressionProxy rep = new FailedRemoteExpressionProxy(proxyid); rep.setProxy(ne, ne.getClass()); exp.allocateExpressionProxy(rep); } break; case InternalExpressionTypes.PUSH_FIELD_EXPRESSIONPROXY_EXPRESSION_VALUE: // Get the Filed expression proxy and resolve it. // Comes over as: // int for proxy id for the field // beanTypeValue for declaring class (either beantype or expression proxy) // string for field name proxyid = in.readInt(); decClassValue = Commands.readValue(in, new Commands.ValueObject()); String fieldName = Commands.readStringData(in); try { decClass = getBeanTypeValue(decClassValue); // Now get the field itself. Field f = decClass.getField(fieldName); RemoteExpressionProxy rep = new RemoteExpressionProxy(proxyid); rep.setProxy(f, Method.class); exp.allocateExpressionProxy(rep); } catch (FailedProxyException e) { FailedRemoteExpressionProxy rep = new FailedRemoteExpressionProxy(proxyid); rep.setProxy(e.getCause(), e.getCause().getClass()); exp.allocateExpressionProxy(rep); } catch (NoSuchFieldException e) { FailedRemoteExpressionProxy rep = new FailedRemoteExpressionProxy(proxyid); rep.setProxy(e, e.getClass()); exp.allocateExpressionProxy(rep); } break; case InternalExpressionTypes.IF_TEST_EXPRESSION_VALUE: // Get a if test expression request. exp.pushIfElse(); break; case InternalExpressionTypes.IF_ELSE_EXPRESSION_VALUE: // Get a if/else expression clause request. The clause type (ie. true/false) is sent as a byte exp.pushIfElse(InternalIfElseOperandType.get(in.readByte())); break; case InternalExpressionTypes.NEW_INSTANCE_VALUE: // Get a new instance from string. String initString = Commands.readStringData(in); workerValue = Commands.readValue(in, new Commands.ValueObject()); try { Class classValue = getBeanTypeValue(workerValue); exp.pushNewInstanceFromString(initString, classValue, classLoader); } catch (FailedProxyException e) { exp.processException(e.getCause()); // Let the processor know we have a stopping error. } break; case InternalExpressionTypes.MARK_VALUE: // Do a mark. int markID = in.readInt(); exp.pushMark(markID); break; case InternalExpressionTypes.ENDMARK_VALUE: // Do an end mark. markID = in.readInt(); boolean restore = in.readBoolean(); exp.pushEndmark(markID, restore); break; case InternalExpressionTypes.SUBEXPRESSION_BEGIN_EXPRESSION_VALUE: // Get a begin subexpression proxy expression. The subexpression id is sent as an int. exp.pushSubexpressionBegin(in.readInt()); break; case InternalExpressionTypes.SUBEXPRESSION_END_EXPRESSION_VALUE: // Get a end subexpression proxy expression. The subexpression id is sent as an int. exp.pushSubexpressionEnd(in.readInt()); break; } } catch (RuntimeException e) { exp.processException(e); } workerValue.set(); // Clear it out so nothing being held onto. } /** * This is an exception that is thrown from the getBeanType, field, method (reflect type stuff) * that wrappers the real throwable that occurred during the previous reflection. This is * because reflection is done out of sequence with the use of the reflect value. So we need * to store the reflection exception in this exception and then store this exception in * a {@link FailedRemoteExpressionProxy}. This will then be picked up when trying to be * used and it will get the true exception out of the {@link Throwable#getCause()}. * * @since 1.1.0 */ private static class FailedProxyException extends Exception { /** * Comment for <code>serialVersionUID</code> * * @since 1.1.0 */ private static final long serialVersionUID = 2872325672166348923L; public FailedProxyException(Throwable realThrowable) { super(realThrowable); } } /** * Get the beantype (class) out of the value object sent in. It can handle the beantype sent or * as an expression proxy to a beantype expression proxy. * * @param value * @return * @throws FailedProxyException Wrappers the real throwable that caused the bean type to not be found. * * @since 1.1.0 */ protected Class getBeanTypeValue(Commands.ValueObject value) throws FailedProxyException { Object beantype = connHandler.getInvokableObject(value); // It is either a type directly or is an expression proxy. if (value.type == Commands.INT) { // It is an expression proxy request. Object[] expvalue = new Object[2]; try { exp.getExpressionProxy(((Integer) beantype).intValue(), expvalue); beantype = expvalue[0]; } catch (NoExpressionValueException e) { // See if there is a failed proxy. if (e.getProxy() != null) { FailedRemoteExpressionProxy failure = (FailedRemoteExpressionProxy) e.getProxy(); throw new FailedProxyException((Throwable) failure.getValue()); } else throw new FailedProxyException(new ClassNotFoundException()); // This shouldn't of occurred. } } return (Class) beantype; } /** * Get the method out of the value object sent in. It can handle the method sent or * as an expression proxy to a method expression proxy. * @param value * @return method if a method or string if a string or get the method if an expression proxy. * @throws FailedProxyException Wrappers the real Throwable that caused the method to not be found. * * @since 1.1.0 */ protected Object getMethodValue(Commands.ValueObject value) throws FailedProxyException { Object method = connHandler.getInvokableObject(value); // It is either a method directly or is an expression proxy. if (value.type == Commands.INT) { // It is an expression proxy request. Object[] expvalue = new Object[2]; try { exp.getExpressionProxy(((Integer) method).intValue(), expvalue); method = expvalue[0]; } catch (NoExpressionValueException e) { // See if there is a failed proxy. if (e.getProxy() != null) { FailedRemoteExpressionProxy failure = (FailedRemoteExpressionProxy) e.getProxy(); throw new FailedProxyException((Throwable) failure.getValue()); } else throw new FailedProxyException(new NoSuchMethodException()); // This shouldn't of occurred. } } return method; } /** * Get the field out of the value object sent in. It can handle the field sent or * as an expression proxy to a field expression proxy. * @param value * @return field if a field or string if a string or get the field if an expression proxy. * @throws FailedProxyException Wrappers the real throwable that caused the field to not be found. * * @since 1.1.0 */ protected Object getFieldValue(Commands.ValueObject value) throws FailedProxyException { Object field = connHandler.getInvokableObject(value); // It is either a field directly or is an expression proxy. if (value.type == Commands.INT) { // It is an expression proxy request. Object[] expvalue = new Object[2]; try { exp.getExpressionProxy(((Integer) field).intValue(), expvalue); field = expvalue[0]; } catch (NoExpressionValueException e) { // See if there is a failed proxy. if (e.getProxy() != null) { FailedRemoteExpressionProxy failure = (FailedRemoteExpressionProxy) e.getProxy(); throw new FailedProxyException((Throwable) failure.getValue()); } else throw new FailedProxyException(new NoSuchFieldException()); // This shouldn't of occurred. } } return field; } /** * Pull the Expression Proxy value into the result object. * @param proxyID * @param result * @return <code>true</code> if value could be returned, <code>false</code> if it was no expression value assigned. * * @since 1.1.0 */ public boolean pullExpressionProxyValue(int proxyID, Object[] result) { try { exp.pullExpressionProxyValue(proxyID, result); return true; } catch (NoExpressionValueException e) { } return false; } private static class RemoteExpressionProxy implements InternalExpressionProxy { private final int proxyID; private Object value; private Class type; private boolean set; public RemoteExpressionProxy(int proxyID) { this.proxyID = proxyID; } /* (non-Javadoc) * @see org.eclipse.jem.internal.proxy.initParser.tree.InternalExpressionProxy#getProxyID() */ public int getProxyID() { return proxyID; } /* (non-Javadoc) * @see org.eclipse.jem.internal.proxy.initParser.tree.InternalExpressionProxy#getType() */ public Class getType() { return type; } /* (non-Javadoc) * @see org.eclipse.jem.internal.proxy.initParser.tree.InternalExpressionProxy#getValue() */ public Object getValue() { return value; } /* (non-Javadoc) * @see org.eclipse.jem.internal.proxy.initParser.tree.InternalExpressionProxy#isSet() */ public boolean isSet() { return set; } /* (non-Javadoc) * @see org.eclipse.jem.internal.proxy.initParser.tree.InternalExpressionProxy#setProxy(java.lang.Object, java.lang.Class) */ public void setProxy(Object value, Class type) { this.value = value; this.type = type; set = true; } } /** * Used for the java.lang.reflect things (class, field, method) to indicate * why they aren't found. * <p> * The exception will be placed into value, BUT this should never be sent * back to the caller. It is intended only as a place-holder for subsequent * usage. Use the {@link RemoteExpressionProxy#isFailedExpression()} method * to determine if it is a failed one. * <p> * This is used because the reflect calls are done out of sequence from where * they are used and so if there was an error, the error is reported in the * wrong place. * * @since 1.1.0 */ private static class FailedRemoteExpressionProxy extends RemoteExpressionProxy { public FailedRemoteExpressionProxy(int proxyID) { super(proxyID); } public boolean isSet() { return false; // This should never be considered to be set. It is holder of info only. } } /** * Pull the value. * * @return r[0] is the value, r[1] is the type of the value. * @throws NoExpressionValueException * * @since 1.0.0 */ public Object[] pullValue() throws NoExpressionValueException { Object[] result = new Object[2]; exp.pullValue(result); return result; } /** * Close out things. * * @since 1.0.0 */ public void close() { exp.close(); } /** * Get the throwable error. * @return * * @since 1.1.0 */ public Throwable getErrorThrowable() { return exp.getErrorThrowable(); } /** * Return whether there were no errors or not. * @return * * @since 1.1.0 */ public boolean noErrors() { return exp.noErrors(); } /** * Return whether there is a no expression value exception or not. * @return * * @since 1.1.0 */ public boolean isNoExpressionValue() { return exp.isNoExpressionValue(); } }