/*
 * Copyright 2008 Google Inc.
 * 
 * Licensed 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 com.google.gwt.user.server.rpc.impl;

import com.google.gwt.user.client.rpc.CustomFieldSerializer;
import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializedTypeViolationException;
import com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamReader;
import com.google.gwt.user.server.Base64Utils;
import com.google.gwt.user.server.rpc.SerializationPolicy;
import com.google.gwt.user.server.rpc.SerializationPolicyProvider;
import com.google.gwt.user.server.rpc.ServerCustomFieldSerializer;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * For internal use only. Used for server call serialization. This class is
 * carefully matched with the client-side version.
 */
public final class ServerSerializationStreamReader extends AbstractSerializationStreamReader {

  /**
   * A basic sanity check for strong names. Only allow certain characters to protect serialization
   * policy providers that use it blindly to load files. (This is normally a hex string, but we
   * allow a few more safe characters in case it's useful for testing.)
   * @see com.google.gwt.dev.util.Util#computeStrongName
   */
  private static final Pattern ALLOWED_STRONG_NAME = Pattern.compile("[a-zA-Z0-9_]+");

  /**
   * Used to accumulate elements while deserializing array types. The generic
   * type of the BoundedList will vary from the component type of the array it
   * is intended to create when the array is of a primitive type.
   * 
   * @param <T> The type of object used to hold the data in the buffer
   */
  private static class BoundedList<T> extends LinkedList<T> {
    private final Class<?> componentType;
    private final int expectedSize;

    public BoundedList(Class<?> componentType, int expectedSize) {
      this.componentType = componentType;
      this.expectedSize = expectedSize;
    }

    @Override
    public boolean add(T o) {
      assert size() < getExpectedSize();
      return super.add(o);
    }

    public Class<?> getComponentType() {
      return componentType;
    }

    public int getExpectedSize() {
      return expectedSize;
    }
  }

  /**
   * Enumeration used to provided typed instance readers.
   */
  private enum ValueReader {
    BOOLEAN {
      @Override
      Object readValue(ServerSerializationStreamReader stream) throws SerializationException {
        return stream.readBoolean();
      }
    },
    BYTE {
      @Override
      Object readValue(ServerSerializationStreamReader stream) throws SerializationException {
        return stream.readByte();
      }
    },
    CHAR {
      @Override
      Object readValue(ServerSerializationStreamReader stream) throws SerializationException {
        return stream.readChar();
      }
    },
    DOUBLE {
      @Override
      Object readValue(ServerSerializationStreamReader stream) throws SerializationException {
        return stream.readDouble();
      }
    },
    FLOAT {
      @Override
      Object readValue(ServerSerializationStreamReader stream) throws SerializationException {
        return stream.readFloat();
      }
    },
    INT {
      @Override
      Object readValue(ServerSerializationStreamReader stream) throws SerializationException {
        return stream.readInt();
      }
    },
    LONG {
      @Override
      Object readValue(ServerSerializationStreamReader stream) throws SerializationException {
        return stream.readLong();
      }
    },
    OBJECT {
      @Override
      Object readValue(ServerSerializationStreamReader stream) throws SerializationException {
        return stream.readObject();
      }

      @Override
      Object readValue(ServerSerializationStreamReader stream, Type expectedType,
          DequeMap<TypeVariable<?>, Type> resolvedTypes) throws SerializationException {
        return stream.readObject(expectedType, resolvedTypes);
      }
    },
    SHORT {
      @Override
      Object readValue(ServerSerializationStreamReader stream) throws SerializationException {
        return stream.readShort();
      }
    },
    STRING {
      @Override
      Object readValue(ServerSerializationStreamReader stream) throws SerializationException {
        return stream.readString();
      }
    };

    abstract Object readValue(ServerSerializationStreamReader stream) throws SerializationException;

    @SuppressWarnings("unused")
    Object readValue(ServerSerializationStreamReader stream, Type expectedType,
        DequeMap<TypeVariable<?>, Type> resolvedTypes) throws SerializationException {
      return readValue(stream);
    }
  }

  /**
   * Enumeration used to provided typed instance readers for vectors.
   */
  private enum VectorReader {
    BOOLEAN_VECTOR {
      @Override
      protected Object readSingleValue(ServerSerializationStreamReader stream)
          throws SerializationException {
        return stream.readBoolean();
      }

      @Override
      protected void setSingleValue(Object array, int index, Object value) {
        Array.setBoolean(array, index, (Boolean) value);
      }
    },
    BYTE_VECTOR {
      @Override
      protected Object readSingleValue(ServerSerializationStreamReader stream)
          throws SerializationException {
        return stream.readByte();
      }

      @Override
      protected void setSingleValue(Object array, int index, Object value) {
        Array.setByte(array, index, (Byte) value);
      }
    },
    CHAR_VECTOR {
      @Override
      protected Object readSingleValue(ServerSerializationStreamReader stream)
          throws SerializationException {
        return stream.readChar();
      }

      @Override
      protected void setSingleValue(Object array, int index, Object value) {
        Array.setChar(array, index, (Character) value);
      }
    },
    DOUBLE_VECTOR {
      @Override
      protected Object readSingleValue(ServerSerializationStreamReader stream)
          throws SerializationException {
        return stream.readDouble();
      }

      @Override
      protected void setSingleValue(Object array, int index, Object value) {
        Array.setDouble(array, index, (Double) value);
      }
    },
    FLOAT_VECTOR {
      @Override
      protected Object readSingleValue(ServerSerializationStreamReader stream)
          throws SerializationException {
        return stream.readFloat();
      }

      @Override
      protected void setSingleValue(Object array, int index, Object value) {
        Array.setFloat(array, index, (Float) value);
      }
    },
    INT_VECTOR {
      @Override
      protected Object readSingleValue(ServerSerializationStreamReader stream)
          throws SerializationException {
        return stream.readInt();
      }

      @Override
      protected void setSingleValue(Object array, int index, Object value) {
        Array.setInt(array, index, (Integer) value);
      }
    },
    LONG_VECTOR {
      @Override
      protected Object readSingleValue(ServerSerializationStreamReader stream)
          throws SerializationException {
        return stream.readLong();
      }

      @Override
      protected void setSingleValue(Object array, int index, Object value) {
        Array.setLong(array, index, (Long) value);
      }
    },
    OBJECT_VECTOR {
      @Override
      protected Object readSingleValue(ServerSerializationStreamReader stream)
          throws SerializationException {
        return stream.readObject();
      }

      @Override
      protected Object readSingleValue(ServerSerializationStreamReader stream, Type expectedType,
          DequeMap<TypeVariable<?>, Type> resolvedTypes) throws SerializationException {
        return stream.readObject(expectedType, resolvedTypes);
      }

      @Override
      protected void setSingleValue(Object array, int index, Object value) {
        Array.set(array, index, value);
      }
    },
    SHORT_VECTOR {
      @Override
      protected Object readSingleValue(ServerSerializationStreamReader stream)
          throws SerializationException {
        return stream.readShort();
      }

      @Override
      protected void setSingleValue(Object array, int index, Object value) {
        Array.setShort(array, index, (Short) value);
      }
    },
    STRING_VECTOR {
      @Override
      protected Object readSingleValue(ServerSerializationStreamReader stream)
          throws SerializationException {
        return stream.readString();
      }

      @Override
      protected void setSingleValue(Object array, int index, Object value) {
        Array.set(array, index, value);
      }
    };

    protected abstract Object readSingleValue(ServerSerializationStreamReader stream)
        throws SerializationException;

    @SuppressWarnings("unused")
    protected Object readSingleValue(ServerSerializationStreamReader stream, Type expectedType,
        DequeMap<TypeVariable<?>, Type> resolvedTypes) throws SerializationException {
      return readSingleValue(stream);
    }

    protected abstract void setSingleValue(Object array, int index, Object value);

    /**
     * Convert a BoundedList to an array of the correct type. This
     * implementation consumes the BoundedList.
     */
    protected Object toArray(Class<?> componentType, BoundedList<Object> buffer)
        throws SerializationException {
      if (buffer.getExpectedSize() != buffer.size()) {
        throw new SerializationException("Inconsistent number of elements received. Received "
            + buffer.size() + " but expecting " + buffer.getExpectedSize());
      }

      Object arr = Array.newInstance(componentType, buffer.size());

      for (int i = 0, n = buffer.size(); i < n; i++) {
        setSingleValue(arr, i, buffer.removeFirst());
      }

      return arr;
    }

    Object read(ServerSerializationStreamReader stream, BoundedList<Object> instance)
        throws SerializationException {
      for (int i = 0, n = instance.getExpectedSize(); i < n; ++i) {
        instance.add(readSingleValue(stream));
      }

      return toArray(instance.getComponentType(), instance);
    }

    Object read(ServerSerializationStreamReader stream, BoundedList<Object> instance,
        Type expectedType, DequeMap<TypeVariable<?>, Type> resolvedTypes) throws
        SerializationException {
      for (int i = 0, n = instance.getExpectedSize(); i < n; ++i) {
        instance.add(readSingleValue(stream, expectedType, resolvedTypes));
      }

      return toArray(instance.getComponentType(), instance);
    }
  }

  /**
   * Map of {@link Class} objects to {@link ValueReader}s.
   */
  private static final Map<Class<?>, ValueReader> CLASS_TO_VALUE_READER =
      new IdentityHashMap<Class<?>, ValueReader>();

  /**
   * Map of {@link Class} objects to {@link VectorReader}s.
   */
  private static final Map<Class<?>, VectorReader> CLASS_TO_VECTOR_READER =
      new IdentityHashMap<Class<?>, VectorReader>();

  private final ClassLoader classLoader;

  private SerializationPolicy serializationPolicy = LegacySerializationPolicy.getInstance();

  private final SerializationPolicyProvider serializationPolicyProvider;

  /**
   * Used to look up setter methods of the form 'void Class.setXXX(T value)'
   * given a Class type and a field name XXX corresponding to a field of type T.
   */
  private final Map<Class<?>, Map<String, Method>> settersByClass =
      new HashMap<Class<?>, Map<String, Method>>();

  private String[] stringTable;

  private final ArrayList<String> tokenList = new ArrayList<String>();

  private int tokenListIndex;

  {
    CLASS_TO_VECTOR_READER.put(boolean[].class, VectorReader.BOOLEAN_VECTOR);
    CLASS_TO_VECTOR_READER.put(byte[].class, VectorReader.BYTE_VECTOR);
    CLASS_TO_VECTOR_READER.put(char[].class, VectorReader.CHAR_VECTOR);
    CLASS_TO_VECTOR_READER.put(double[].class, VectorReader.DOUBLE_VECTOR);
    CLASS_TO_VECTOR_READER.put(float[].class, VectorReader.FLOAT_VECTOR);
    CLASS_TO_VECTOR_READER.put(int[].class, VectorReader.INT_VECTOR);
    CLASS_TO_VECTOR_READER.put(long[].class, VectorReader.LONG_VECTOR);
    CLASS_TO_VECTOR_READER.put(Object[].class, VectorReader.OBJECT_VECTOR);
    CLASS_TO_VECTOR_READER.put(short[].class, VectorReader.SHORT_VECTOR);
    CLASS_TO_VECTOR_READER.put(String[].class, VectorReader.STRING_VECTOR);

    CLASS_TO_VALUE_READER.put(boolean.class, ValueReader.BOOLEAN);
    CLASS_TO_VALUE_READER.put(byte.class, ValueReader.BYTE);
    CLASS_TO_VALUE_READER.put(char.class, ValueReader.CHAR);
    CLASS_TO_VALUE_READER.put(double.class, ValueReader.DOUBLE);
    CLASS_TO_VALUE_READER.put(float.class, ValueReader.FLOAT);
    CLASS_TO_VALUE_READER.put(int.class, ValueReader.INT);
    CLASS_TO_VALUE_READER.put(long.class, ValueReader.LONG);
    CLASS_TO_VALUE_READER.put(Object.class, ValueReader.OBJECT);
    CLASS_TO_VALUE_READER.put(short.class, ValueReader.SHORT);
    CLASS_TO_VALUE_READER.put(String.class, ValueReader.STRING);
  }

  public ServerSerializationStreamReader(ClassLoader classLoader,
      SerializationPolicyProvider serializationPolicyProvider) {
    this.classLoader = classLoader;
    this.serializationPolicyProvider = serializationPolicyProvider;
  }

  public Object deserializeValue(Class<?> rpcType) throws SerializationException {
    ValueReader valueReader = CLASS_TO_VALUE_READER.get(rpcType);
    if (valueReader != null) {
      return valueReader.readValue(this);
    } else {
      // Arrays of primitive or reference types need to go through readObject.
      return ValueReader.OBJECT.readValue(this);
    }
  }

  public Object deserializeValue(Class<?> rpcType, Type methodType,
      DequeMap<TypeVariable<?>, Type> resolvedTypes) throws SerializationException {
    ValueReader valueReader = CLASS_TO_VALUE_READER.get(rpcType);
    if (valueReader != null) {
      return valueReader.readValue(this, methodType, resolvedTypes);
    } else {
      // Arrays of primitive or reference types need to go through readObject.
      return ValueReader.OBJECT.readValue(this, methodType, resolvedTypes);
    }
  }

  public int getNumberOfTokens() {
    return tokenList.size();
  }

  public SerializationPolicy getSerializationPolicy() {
    return serializationPolicy;
  }

  @Override
  public void prepareToRead(String encodedTokens) throws SerializationException {
    tokenList.clear();
    tokenListIndex = 0;
    stringTable = null;

    int idx = 0, nextIdx;
    while (-1 != (nextIdx = encodedTokens.indexOf(RPC_SEPARATOR_CHAR, idx))) {
      String current = encodedTokens.substring(idx, nextIdx);
      tokenList.add(current);
      idx = nextIdx + 1;
    }
    if (idx == 0) {
      // Didn't find any separator, assume an older version with different
      // separators and get the version as the sequence of digits at the
      // beginning of the encoded string.
      while (idx < encodedTokens.length() && Character.isDigit(encodedTokens.charAt(idx))) {
        ++idx;
      }
      if (idx == 0) {
        throw new IncompatibleRemoteServiceException(
            "Malformed or old RPC message received - expecting version between "
                + SERIALIZATION_STREAM_MIN_VERSION + " and " + SERIALIZATION_STREAM_MAX_VERSION);
      } else {
        int version = Integer.valueOf(encodedTokens.substring(0, idx));
        throw new IncompatibleRemoteServiceException("Expecting version between "
            + SERIALIZATION_STREAM_MIN_VERSION + " and " + SERIALIZATION_STREAM_MAX_VERSION
            + " from client, got " + version + ".");
      }
    }

    super.prepareToRead(encodedTokens);

    // Check the RPC version number sent by the client
    if (getVersion() < SERIALIZATION_STREAM_MIN_VERSION
        || getVersion() > SERIALIZATION_STREAM_MAX_VERSION) {
      throw new IncompatibleRemoteServiceException("Expecting version between "
          + SERIALIZATION_STREAM_MIN_VERSION + " and " + SERIALIZATION_STREAM_MAX_VERSION
          + " from client, got " + getVersion() + ".");
    }

    // Check the flags
    if (!areFlagsValid()) {
      throw new IncompatibleRemoteServiceException("Got an unknown flag from " + "client: "
          + getFlags());
    }

    // Read the type name table
    deserializeStringTable();

    // Load the serialization policy
    String moduleBaseURL = readString();
    String strongName = readString();
    if (serializationPolicyProvider != null) {
      if (strongName != null && !ALLOWED_STRONG_NAME.matcher(strongName).matches()) {
        throw new SerializationException(
            "GWT-RPC request is invalid because the strong name contains invalid characters");
      }

      serializationPolicy =
          serializationPolicyProvider.getSerializationPolicy(moduleBaseURL, strongName);

      if (serializationPolicy == null) {
        throw new NullPointerException("serializationPolicyProvider.getSerializationPolicy()");
      }
    }
  }

  @Override
  public boolean readBoolean() throws SerializationException {
    return !extract().equals("0");
  }

  @Override
  public byte readByte() throws SerializationException {
    String value = extract();
    try {
      return Byte.parseByte(value);
    } catch (NumberFormatException e) {
      throw getNumberFormatException(value, "byte", Byte.MIN_VALUE, Byte.MAX_VALUE);
    }
  }

  @Override
  public char readChar() throws SerializationException {
    // just use an int, it's more foolproof
    return (char) readInt();
  }

  @Override
  public double readDouble() throws SerializationException {
    return Double.parseDouble(extract());
  }

  @Override
  public float readFloat() throws SerializationException {
    return (float) Double.parseDouble(extract());
  }

  @Override
  public int readInt() throws SerializationException {
    String value = extract();
    try {
      return Integer.parseInt(value);
    } catch (NumberFormatException e) {
      throw getNumberFormatException(value, "int", Integer.MIN_VALUE, Integer.MAX_VALUE);
    }
  }

  @Override
  public long readLong() throws SerializationException {
    if (getVersion() == SERIALIZATION_STREAM_MIN_VERSION) {
      return (long) readDouble() + (long) readDouble();
    } else {
      return Base64Utils.longFromBase64(extract());
    }
  }

  public Object readObject(Type expectedType, DequeMap<TypeVariable<?>, Type> resolvedTypes)
      throws SerializationException {
    int token = readInt();

    if (token < 0) {
      // Negative means a previous object
      // Transform negative 1-based to 0-based.
      return getDecodedObject(-token);
    }

    // Positive means a new object
    String typeSignature = getString(token);
    if (typeSignature == null) {
      // a null string means a null instance
      return null;
    }

    if (isSimpleClass(expectedType) && !resolvedTypes.isEmpty()) {
      // Start a new scope for resolving type variables. This is a workaround for false sharing
      // because the type checker doesn't implement type variables properly.
      //
      // For example, if expectedType is Serializable and the actual value is a LinkedHashMap, we
      // don't want any of LinkedHashMap, HashMap, or Map to inherit any bindings for their K,V
      // variables from the surrounding content.
      // Fixes issue 7628.
      resolvedTypes = new DequeMap<TypeVariable<?>, Type>();
    }

    return deserialize(typeSignature, expectedType, resolvedTypes);
  }

  @Override
  public short readShort() throws SerializationException {
    String value = extract();
    try {
      return Short.parseShort(value);
    } catch (NumberFormatException e) {
      throw getNumberFormatException(value, "short", Short.MIN_VALUE, Short.MAX_VALUE);
    }
  }

  @Override
  public String readString() throws SerializationException {
    return getString(readInt());
  }

  @Override
  protected Object deserialize(String typeSignature) throws SerializationException {
    return deserialize(typeSignature, null, null);
  }

  protected Object deserialize(String typeSignature, Type expectedType,
      DequeMap<TypeVariable<?>, Type> resolvedTypes) throws SerializationException {
    Object instance = null;
    try {
      Class<?> instanceClass;
      if (hasFlags(FLAG_ELIDE_TYPE_NAMES)) {
        if (getSerializationPolicy() instanceof TypeNameObfuscator) {
          TypeNameObfuscator obfuscator = (TypeNameObfuscator) getSerializationPolicy();
          String instanceClassName = obfuscator.getClassNameForTypeId(typeSignature);
          instanceClass = Class.forName(instanceClassName, false, classLoader);
        } else {
          throw new SerializationException(
              "The GWT module was compiled with RPC type name elision enabled, but "
                  + getSerializationPolicy().getClass().getName() + " does not implement "
                  + TypeNameObfuscator.class.getName());
        }
      } else {
        SerializedInstanceReference serializedInstRef =
            SerializabilityUtil.decodeSerializedInstanceReference(typeSignature);
        instanceClass = Class.forName(serializedInstRef.getName(), false, classLoader);
        validateTypeVersions(instanceClass, serializedInstRef);
      }

      if (resolvedTypes == null) {
        // We can find ourselves with a null resolvedTypes map if a class
        // has a non-type-checking serializer that tries to deserialize a field.
        // In such cases there is field type information from the class, but no
        // resolvedTypes because we did not pass it through the non-type
        // checking serializer. Create a map, so that from this point forward
        // we have some way of type checking.
        resolvedTypes = new DequeMap<TypeVariable<?>, Type>();
      }
      
      // Try to determine the type for the generic type parameters of the
      // instance class, based upon the expected type and any class hierarchy
      // type information.
      // In looking for the expected parameter types, we also find out the
      // way in which the instance meets the expected type, and hence can find
      // out when it does not meet expectations.
      TypeVariable<?>[] instanceParameterTypes = instanceClass.getTypeParameters();
      Type[] expectedParameterTypes = null;
      if (expectedType != null) {
        SerializabilityUtil.resolveTypes(expectedType, resolvedTypes);
        expectedParameterTypes = SerializabilityUtil.findExpectedParameterTypes(
            instanceClass, expectedType, resolvedTypes);
        if (expectedParameterTypes == null) {
          throw new SerializedTypeViolationException("Attempt to deserialize an object of type "
              + instanceClass.toString() + " when an object of type "
              + SerializabilityUtil.findActualType(expectedType, resolvedTypes).toString()
              + " is expected");
        }
        
        // Add mappings from the instance type variables to the resolved types
        // map.
        for (int i = 0; i < instanceParameterTypes.length; ++i) {
          resolvedTypes.add(instanceParameterTypes[i], expectedParameterTypes[i]);
        }
      }

      assert (serializationPolicy != null);

      serializationPolicy.validateDeserialize(instanceClass);

      Class<?> customSerializer = SerializabilityUtil.hasServerCustomFieldSerializer(instanceClass);

      int index = reserveDecodedObjectIndex();

      instance = instantiate(customSerializer, instanceClass, expectedParameterTypes,
          resolvedTypes);

      rememberDecodedObject(index, instance);

      Object replacement = deserializeImpl(customSerializer, instanceClass, instance, expectedType,
          expectedParameterTypes, resolvedTypes);

      // Remove resolved types that were added for this instance.
      if (expectedParameterTypes != null) {
        for (int i = 0; i < instanceParameterTypes.length; ++i) {
          resolvedTypes.remove(instanceParameterTypes[i]);
        }
      }
      SerializabilityUtil.releaseTypes(expectedType, resolvedTypes);

      // It's possible that deserializing an object requires the original proxy
      // object to be replaced.
      if (instance != replacement) {
        rememberDecodedObject(index, replacement);
        instance = replacement;
      }

      return instance;

    } catch (ClassNotFoundException e) {
      throw new SerializationException(e);
    } catch (InstantiationException e) {
      throw new SerializationException(e);
    } catch (IllegalAccessException e) {
      throw new SerializationException(e);
    } catch (IllegalArgumentException e) {
      throw new SerializationException(e);
    } catch (InvocationTargetException e) {
      throw new SerializationException(e.getTargetException());
    } catch (NoSuchMethodException e) {
      throw new SerializationException(e);
    }
  }

  @Override
  protected String getString(int index) {
    if (index == 0) {
      return null;
    }
    // index is 1-based
    assert (index > 0);
    assert (index <= stringTable.length);
    return stringTable[index - 1];
  }

  /**
   * Deserialize an instance that is an array. Will default to deserializing as
   * an Object vector if the instance is not a primitive vector.
   * 
   * @param instanceClass the class we are deserializing
   * @param instance the object to deserialize into
   * @throws SerializationException
   */
  @SuppressWarnings("unchecked")
  private Object deserializeArray(Class<?> instanceClass, Object instance)
      throws SerializationException {
    assert (instanceClass.isArray());

    BoundedList<Object> buffer = (BoundedList<Object>) instance;
    VectorReader instanceReader = CLASS_TO_VECTOR_READER.get(instanceClass);
    if (instanceReader != null) {
      return instanceReader.read(this, buffer);
    } else {
      return VectorReader.OBJECT_VECTOR.read(this, buffer);
    }
  }

  /**
   * Deserialize an instance that is an array, with type checking.
   * 
   * Will default to deserializing as an Object vector if the instance is not a
   * primitive vector.
   * 
   * @param instanceClass the class we are deserializing
   * @param instance the object to deserialize into
   * @param expectedType the parameterized type the system is expected to find
   * @throws SerializationException
   */
  @SuppressWarnings("unchecked")
  private Object deserializeArray(Class<?> instanceClass, Object instance, Type expectedType,
      DequeMap<TypeVariable<?>, Type> resolvedTypes) throws SerializationException {
    assert (instanceClass.isArray());

    BoundedList<Object> buffer = (BoundedList<Object>) instance;
    VectorReader instanceReader = CLASS_TO_VECTOR_READER.get(instanceClass);
    if (instanceReader != null) {
      return instanceReader.read(this, buffer, expectedType, resolvedTypes);
    } else {
      return VectorReader.OBJECT_VECTOR.read(this, buffer, expectedType, resolvedTypes);
    }
  }

  private void deserializeClass(Class<?> instanceClass, Object instance, Type expectedType,
      Type[] expectedParameterTypes, DequeMap<TypeVariable<?>, Type> resolvedTypes) throws
      SerializationException, IllegalAccessException, NoSuchMethodException,
      InvocationTargetException, ClassNotFoundException {
    /**
     * A map from field names to corresponding setter methods. The reference
     * will be null for classes that do not require special handling for
     * server-only fields.
     */
    Map<String, Method> setters = null;

    /**
     * A list of fields of this class known to the client. If null, assume the
     * class is not enhanced and don't attempt to deal with server-only fields.
     */
    Set<String> clientFieldNames =
        serializationPolicy.getClientFieldNamesForEnhancedClass(instanceClass);
    if (clientFieldNames != null) {
      // Read and set server-only instance fields encoded in the RPC data
      try {
        String encodedData = readString();
        if (encodedData != null) {
          byte[] serializedData = Base64Utils.fromBase64(encodedData);
          ByteArrayInputStream baos = new ByteArrayInputStream(serializedData);
          ObjectInputStream ois = new ObjectInputStream(baos);

          int count = ois.readInt();
          for (int i = 0; i < count; i++) {
            String fieldName = (String) ois.readObject();
            Object fieldValue = ois.readObject();
            Field field = instanceClass.getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(instance, fieldValue);
          }
        }
      } catch (IOException e) {
        throw new SerializationException(e);
      } catch (NoSuchFieldException e) {
        throw new SerializationException(e);
      }

      setters = getSetters(instanceClass);
    }

    Field[] serializableFields = SerializabilityUtil.applyFieldSerializationPolicy(instanceClass,
        serializationPolicy);
    for (Field declField : serializableFields) {
      assert (declField != null);
      if ((clientFieldNames != null) && !clientFieldNames.contains(declField.getName())) {
        continue;
      }

      Type declGenericType = declField.getGenericType();
      Object value = deserializeValue(declField.getType(), declGenericType, resolvedTypes);

      String fieldName = declField.getName();
      Method setter;
      /*
       * If setters is non-null and there is a setter method for the given
       * field, call the setter. Otherwise, set the field value directly. For
       * persistence APIs such as JDO, the setter methods have been enhanced to
       * manipulate additional object state, causing direct field writes to fail
       * to update the object state properly.
       */
      if ((setters != null) && ((setter = setters.get(fieldName)) != null)) {
        setter.invoke(instance, value);
      } else {
        boolean isAccessible = declField.isAccessible();
        boolean needsAccessOverride = (!isAccessible
            && !Modifier.isPublic(declField.getModifiers()))
            || Modifier.isFinal(declField.getModifiers());
        if (needsAccessOverride) {
          // Override access restrictions
          declField.setAccessible(true);
        }

        declField.set(instance, value);
      }
    }

    Class<?> superClass = instanceClass.getSuperclass();
    if (serializationPolicy.shouldDeserializeFields(superClass)) {
      Type[] superParameterTypes = SerializabilityUtil.findExpectedParameterTypes(
          superClass, superClass, resolvedTypes);
      deserializeImpl(SerializabilityUtil.hasServerCustomFieldSerializer(superClass), superClass,
          instance, expectedType, superParameterTypes, resolvedTypes);
    }
  }

  private Object deserializeImpl(Class<?> customSerializer, Class<?> instanceClass,
      Object instance, Type expectedType, Type[] expectedParameterTypes,
      DequeMap<TypeVariable<?>, Type> resolvedTypes)
      throws NoSuchMethodException, IllegalArgumentException, IllegalAccessException,
      InvocationTargetException, SerializationException, ClassNotFoundException {

    if (customSerializer != null) {
      @SuppressWarnings("unchecked")
      CustomFieldSerializer<Object> customFieldSerializer =
          (CustomFieldSerializer<Object>) SerializabilityUtil
              .loadCustomFieldSerializer(customSerializer);
      if (customFieldSerializer == null) {
        deserializeWithCustomFieldDeserializer(customSerializer, instanceClass, instance,
            expectedParameterTypes, resolvedTypes);
      } else if (customFieldSerializer instanceof ServerCustomFieldSerializer &&
          expectedParameterTypes != null) {
        ServerCustomFieldSerializer<Object> serverCFS =
            (ServerCustomFieldSerializer<Object>) customFieldSerializer;
        serverCFS.deserializeInstance(this, instance, expectedParameterTypes, resolvedTypes);
      } else {
        customFieldSerializer.deserializeInstance(this, instance);
      }
    } else if (instanceClass.isArray()) {
      if (expectedType == null) {
        return deserializeArray(instanceClass, instance);
      }
      Type actualExpectedType = SerializabilityUtil.findActualType(expectedType, resolvedTypes);
      if (actualExpectedType instanceof GenericArrayType) {
        Type arrayType = ((GenericArrayType) actualExpectedType).getGenericComponentType();
        return deserializeArray(instanceClass, instance, arrayType, resolvedTypes);
      } else if (((Class<?>) actualExpectedType).getComponentType() != null) {
        Class<?> arrayType = ((Class<?>) actualExpectedType).getComponentType();
        return deserializeArray(instanceClass, instance, arrayType, resolvedTypes);
      }
    } else if (instanceClass.isEnum()) {
      // Enums are deserialized when they are instantiated
    } else {
      deserializeClass(instanceClass, instance, expectedType, expectedParameterTypes,
          resolvedTypes);
    }

    return instance;
  }

  private void deserializeStringTable() throws SerializationException {
    int typeNameCount = readInt();
    BoundedList<String> buffer = new BoundedList<String>(String.class, typeNameCount);
    for (int typeNameIndex = 0; typeNameIndex < typeNameCount; ++typeNameIndex) {
      String str = extract();
      // Change quoted characters back.
      int idx = str.indexOf('\\');
      if (idx >= 0) {
        StringBuilder buf = new StringBuilder();
        int pos = 0;
        while (idx >= 0) {
          buf.append(str.substring(pos, idx));
          if (++idx == str.length()) {
            throw new SerializationException("Unmatched backslash: \"" + str + "\"");
          }
          char ch = str.charAt(idx);
          pos = idx + 1;
          switch (ch) {
            case '0':
              buf.append('\u0000');
              break;
            case '!':
              buf.append(RPC_SEPARATOR_CHAR);
              break;
            case '\\':
              buf.append(ch);
              break;
            case 'u':
              try {
                ch = (char) Integer.parseInt(str.substring(idx + 1, idx + 5), 16);
              } catch (NumberFormatException e) {
                throw new SerializationException("Invalid Unicode escape sequence in \"" + str
                    + "\"");
              }
              buf.append(ch);
              pos += 4;
              break;
            default:
              throw new SerializationException("Unexpected escape character " + ch
                  + " after backslash: \"" + str + "\"");
          }
          idx = str.indexOf('\\', pos);
        }
        buf.append(str.substring(pos));
        str = buf.toString();
      }
      buffer.add(str);
    }

    if (buffer.size() != buffer.getExpectedSize()) {
      throw new SerializationException("Expected " + buffer.getExpectedSize()
          + " string table elements; received " + buffer.size());
    }

    stringTable = buffer.toArray(new String[buffer.getExpectedSize()]);
  }

  private void deserializeWithCustomFieldDeserializer(Class<?> customSerializer,
      Class<?> instanceClass, Object instance, Type[] expectedParameterTypes,
      DequeMap<TypeVariable<?>, Type> resolvedTypes)
      throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    assert (!instanceClass.isArray());

    if (expectedParameterTypes != null) {
      for (Method method : customSerializer.getMethods()) {
        if ("deserializeChecked".equals(method.getName())) {
          method.invoke(null, this, instance, expectedParameterTypes, resolvedTypes);
          return;
        }
      }
    }
    for (Method method : customSerializer.getMethods()) {
      if ("deserialize".equals(method.getName())) {
        method.invoke(null, this, instance);
        return;
      }
    }
    throw new NoSuchMethodException("deserialize");
  }

  private String extract() throws SerializationException {
    try {
      return tokenList.get(tokenListIndex++);
    } catch (IndexOutOfBoundsException e) {
      throw new SerializationException("Too few tokens in RPC request", e);
    }
  }

  /**
   * Returns a suitable NumberFormatException with an explanatory message when a
   * numerical value cannot be parsed according to its expected type.
   * 
   * @param value the value as read from the RPC stream
   * @param type the name of the expected type
   * @param minValue the smallest valid value for the expected type
   * @param maxValue the largest valid value for the expected type
   * @return a NumberFormatException with an explanatory message
   */
  private NumberFormatException getNumberFormatException(String value, String type,
      double minValue, double maxValue) {
    String message = "a non-numerical value";
    try {
      // Check the field contents in order to produce a more comprehensible
      // error message
      double d = Double.parseDouble(value);
      if (d < minValue || d > maxValue) {
        message = "an out-of-range value";
      } else if (d != Math.floor(d)) {
        message = "a fractional value";
      }
    } catch (NumberFormatException e2) {
      // Fall through with the default message.
    }

    return new NumberFormatException("Expected type '" + type + "' but received " + message + ": "
        + value);
  }

  /**
   * Returns a Map from a field name to the setter method for that field, for a
   * given class. The results are computed once for each class and cached.
   * 
   * @param instanceClass the class to query
   * @return a Map from Strings to Methods such that the name <code>XXX</code>
   *         (corresponding to the field <code>T XXX</code>) maps to the method
   *         <code>void setXXX(T value)</code>, or null if no such method
   *         exists.
   */
  private Map<String, Method> getSetters(Class<?> instanceClass) {
    synchronized (settersByClass) {
      Map<String, Method> setters = settersByClass.get(instanceClass);
      if (setters == null) {
        setters = new HashMap<String, Method>();

        // Iterate over each field and locate a suitable setter method
        Field[] fields = instanceClass.getDeclaredFields();
        for (Field field : fields) {
          // Consider non-static, non-transient (or @GwtTransient) fields only
          if (SerializabilityUtil.isNotStaticOrTransient(field)
              && SerializabilityUtil.isNotFinal(field)) {
            String fieldName = field.getName();
            String setterName =
                "set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
            try {
              Method setter = instanceClass.getMethod(setterName, field.getType());
              setters.put(fieldName, setter);
            } catch (NoSuchMethodException e) {
              // Just leave this field out of the map
            }
          }
        }

        settersByClass.put(instanceClass, setters);
      }

      return setters;
    }
  }

  private Object instantiate(Class<?> customSerializer, Class<?> instanceClass,
      Type[] expectedParameterTypes, DequeMap<TypeVariable<?>, Type> resolvedTypes) throws
      InstantiationException, IllegalAccessException, IllegalArgumentException,
      InvocationTargetException, NoSuchMethodException, SerializationException {
    if (customSerializer != null) {
      CustomFieldSerializer<?> customFieldSerializer =
          SerializabilityUtil.loadCustomFieldSerializer(customSerializer);
      if (customFieldSerializer == null) {
        Object result = instantiateWithCustomFieldInstantiator(customSerializer,
            expectedParameterTypes, resolvedTypes);
        if (result != null) {
          return result;
        }
        // Ok to not have a custom instantiate.
      } else if (customFieldSerializer.hasCustomInstantiateInstance()) {
        if (expectedParameterTypes != null &&
            (customFieldSerializer instanceof ServerCustomFieldSerializer)) {
          ServerCustomFieldSerializer<?> serverCFS =
              (ServerCustomFieldSerializer<?>) customFieldSerializer;
          return serverCFS.instantiateInstance(this, expectedParameterTypes, resolvedTypes);
        } else {
          return customFieldSerializer.instantiateInstance(this);
        }
      }
    }

    if (instanceClass.isArray()) {
      int length = readInt();
      // We don't pre-allocate the array; this prevents an allocation attack
      return new BoundedList<Object>(instanceClass.getComponentType(), length);
    } else if (instanceClass.isEnum()) {
      Enum<?>[] enumConstants = (Enum[]) instanceClass.getEnumConstants();
      int ordinal = readInt();
      assert (ordinal >= 0 && ordinal < enumConstants.length);
      return enumConstants[ordinal];
    } else {
      Constructor<?> constructor = instanceClass.getDeclaredConstructor();
      constructor.setAccessible(true);
      return constructor.newInstance();
    }
  }

  private Object instantiateWithCustomFieldInstantiator(Class<?> customSerializer,
      Type[] expectedParameterTypes, DequeMap<TypeVariable<?>, Type> resolvedTypes)
      throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
    if (expectedParameterTypes != null) {
      for (Method method : customSerializer.getMethods()) {
        if ("instantiateChecked".equals(method.getName())) {
          return method.invoke(null, this, expectedParameterTypes, resolvedTypes);
        }
      }
    }

    for (Method method : customSerializer.getMethods()) {
      if ("instantiate".equals(method.getName())) {
        return method.invoke(null, this);
      }
    }
    return null;
  }

  /**
   * Returns true if the given type is a top-level class or interface with no type parameters.
   */
  private boolean isSimpleClass(Type t) {
    if (!(t instanceof Class)) {
      return false;
    }
    Class cl = (Class) t;
    return cl.getTypeParameters().length == 0 && cl.getEnclosingClass() == null;
  }

  private void validateTypeVersions(Class<?> instanceClass,
      SerializedInstanceReference serializedInstRef) throws SerializationException {
    String clientTypeSignature = serializedInstRef.getSignature();
    if (clientTypeSignature.length() == 0) {
      throw new SerializationException("Missing type signature for " + instanceClass.getName());
    }

    String serverTypeSignature =
        SerializabilityUtil.getSerializationSignature(instanceClass, serializationPolicy);

    if (!clientTypeSignature.equals(serverTypeSignature)) {
      throw new SerializationException("Invalid type signature for " + instanceClass.getName());
    }
  }
}
