/*
 * 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.core.shared.GWT;
import com.google.gwt.user.client.rpc.CustomFieldSerializer;
import com.google.gwt.user.client.rpc.GwtTransient;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamReader;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import com.google.gwt.user.server.rpc.SerializationPolicy;
import com.google.gwt.user.server.rpc.ServerCustomFieldSerializer;

import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.CRC32;

/**
 * Serialization utility class used by the server-side RPC code.
 */
public class SerializabilityUtil {
  /**
   * Comparator used to sort fields.
   */
  public static final Comparator<Field> FIELD_COMPARATOR = new Comparator<Field>() {
    @Override
    public int compare(Field f1, Field f2) {
      return f1.getName().compareTo(f2.getName());
    }
  };

  /**
   * A permanent cache of all computed CRCs on classes. This is safe to do
   * because a Class is guaranteed not to change within the lifetime of a
   * ClassLoader (and thus, this Map).
   */
  private static final Map<Class<?>, String> classCRC32Cache =
      new ConcurrentHashMap<Class<?>, String>();

  /**
   * A permanent cache of all serializable fields on classes. This is safe to do
   * because a Class is guaranteed not to change within the lifetime of a
   * ClassLoader (and thus, this Map).
   */
  private static final Map<Class<?>, Field[]> classSerializableFieldsCache =
      new ConcurrentHashMap<Class<?>, Field[]>();

  /**
   * A permanent cache of all which classes onto custom field serializers. This
   * is safe to do because a Class is guaranteed not to change within the
   * lifetime of a ClassLoader (and thus, this Map).
   */
  private static final Map<Class<?>, Class<?>> classCustomSerializerCache =
      new ConcurrentHashMap<Class<?>, Class<?>>();

  /**
   * A permanent cache of all which classes onto server-side custom field
   * serializers. This is safe to do because a Class is guaranteed not to change
   * within the lifetime of a ClassLoader (and thus, this Map).
   */
  private static final Map<Class<?>, Class<?>> classServerCustomSerializerCache =
      new ConcurrentHashMap<Class<?>, Class<?>>();

  /**
   * Map of {@link Class} objects to singleton instances of that
   * {@link CustomFieldSerializer}.
   */
  private static final Map<Class<?>, CustomFieldSerializer<?>> CLASS_TO_SERIALIZER_INSTANCE =
      new ConcurrentHashMap<Class<?>, CustomFieldSerializer<?>>();

  private static final String JRE_SERVER_SERIALIZER_PACKAGE = "com.google.gwt.user.server.rpc.core";
  private static final String JRE_SERIALIZER_PACKAGE = "com.google.gwt.user.client.rpc.core";

  /**
   * A re-usable, non-functional {@link ServerCustomFieldSerializer} for when
   * the Server Custom Field Serializer does not implement the
   * {@link ServerCustomFieldSerializer} interface.
   */
  private static final ServerCustomFieldSerializer<?> NO_SUCH_SERIALIZER =
      new ServerCustomFieldSerializer<Object>() {
        @Override
        public void deserializeInstance(SerializationStreamReader streamReader, Object instance) {
          throw new AssertionError("This should never be called.");
        }

        @Override
        public void deserializeInstance(ServerSerializationStreamReader streamReader,
            Object instance, Type[] expectedParameterTypes,
            DequeMap<TypeVariable<?>, Type> resolvedTypes) throws SerializationException {
          throw new SerializationException("This should never be called.");
        }

        @Override
        public void serializeInstance(SerializationStreamWriter streamWriter, Object instance) {
          throw new AssertionError("This should never be called.");
        }
      };

  private static final Map<String, String> SERIALIZED_PRIMITIVE_TYPE_NAMES =
      new HashMap<String, String>();

  private static final Set<Class<?>> TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES =
      new HashSet<Class<?>>();

  static {

    SERIALIZED_PRIMITIVE_TYPE_NAMES.put(boolean.class.getName(), "Z");
    SERIALIZED_PRIMITIVE_TYPE_NAMES.put(byte.class.getName(), "B");
    SERIALIZED_PRIMITIVE_TYPE_NAMES.put(char.class.getName(), "C");
    SERIALIZED_PRIMITIVE_TYPE_NAMES.put(double.class.getName(), "D");
    SERIALIZED_PRIMITIVE_TYPE_NAMES.put(float.class.getName(), "F");
    SERIALIZED_PRIMITIVE_TYPE_NAMES.put(int.class.getName(), "I");
    SERIALIZED_PRIMITIVE_TYPE_NAMES.put(long.class.getName(), "J");
    SERIALIZED_PRIMITIVE_TYPE_NAMES.put(short.class.getName(), "S");

    TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Boolean.class);
    TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Byte.class);
    TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Character.class);
    TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Double.class);
    TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Exception.class);
    TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Float.class);
    TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Integer.class);
    TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Long.class);
    TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Object.class);
    TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Short.class);
    TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(String.class);
    TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Throwable.class);

    try {
      /*
       * Work around for incompatible type hierarchy (and therefore signature)
       * between JUnit3 and JUnit4. Do this via reflection so we don't force the
       * server to depend on JUnit.
       */
      Class<?> clazz = Class.forName("junit.framework.AssertionFailedError");
      TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(clazz);
    } catch (ClassNotFoundException dontCare) {
      // Empty because we don't care
    }
  }

  /**
   * @deprecated use {@link #applyFieldSerializationPolicy(Class, SerializationPolicy)} instead.
   */
  @Deprecated
  public static Field[] applyFieldSerializationPolicy(Class<?> clazz) {
    return applyFieldSerializationPolicy(clazz, false);
  }

  /**
   * Returns the fields of a particular class that can be considered for
   * serialization. The returned list will be sorted into a canonical order to
   * ensure consistent answers.
   */
  public static Field[] applyFieldSerializationPolicy(Class<?> clazz, SerializationPolicy policy) {
    return applyFieldSerializationPolicy(clazz, policy.shouldSerializeFinalFields());
  }

  private static Field[] applyFieldSerializationPolicy(Class<?> clazz, boolean includeFinalFields) {
    Field[] serializableFields = classSerializableFieldsCache.get(clazz);
    if (serializableFields == null) {
      ArrayList<Field> fieldList = new ArrayList<Field>();
      Field[] fields = clazz.getDeclaredFields();
      for (Field field : fields) {
        if (fieldQualifiesForSerialization(field, includeFinalFields)) {
          fieldList.add(field);
        }
      }
      serializableFields = fieldList.toArray(new Field[fieldList.size()]);

      // sort the fields by name
      Arrays.sort(serializableFields, 0, serializableFields.length, FIELD_COMPARATOR);

      classSerializableFieldsCache.put(clazz, serializableFields);
    }
    return serializableFields;
  }

  public static SerializedInstanceReference decodeSerializedInstanceReference(
      String encodedSerializedInstanceReference) {
    final String[] components =
        encodedSerializedInstanceReference
            .split(SerializedInstanceReference.SERIALIZED_REFERENCE_SEPARATOR);
    return new SerializedInstanceReference() {
      @Override
      public String getName() {
        return components.length > 0 ? components[0] : "";
      }

      @Override
      public String getSignature() {
        return components.length > 1 ? components[1] : "";
      }
    };
  }

  public static String encodeSerializedInstanceReference(Class<?> instanceType,
      SerializationPolicy policy) {
    return instanceType.getName() + SerializedInstanceReference.SERIALIZED_REFERENCE_SEPARATOR
        + getSerializationSignature(instanceType, policy);
  }

  /**
   * Resolve type variables to concrete types if possible. Otherwise, just return
   * the type variable.
   *
   * @param unresolved The type to resolve
   * @param resolvedTypes A map of generic types to actual types.
   * @return The actual type, which may be of any subclass of Type.
   */
  public static Type findActualType(Type unresolved,
      DequeMap<TypeVariable<?>, Type> resolvedTypes) {

    // Handle simple cases quickly.
    if (!(unresolved instanceof TypeVariable<?>)) {
      return unresolved;
    }
    TypeVariable<?> var = (TypeVariable<?>) unresolved;
    Type target = resolvedTypes.get(var);
    if (target == null || target == var) {
      return var;
    }
    if (!(target instanceof TypeVariable<?>)) {
      return target;
    }

    // Type variables that point to other type variables might form a cycle, which
    // means they're all equivalent. Keep track of visited type variables to detect this.
    Set<TypeVariable<?>> seen = new HashSet<TypeVariable<?>>();
    seen.add(var);
    var = (TypeVariable<?>) target;
    seen.add(var);

    while (true) {
      target = resolvedTypes.get(var);
      if (target == null || target == var) {
        return var;
      }
      if (!(target instanceof TypeVariable<?>)) {
        return target;
      }
      var = (TypeVariable<?>) target;
      if (!seen.add(var)) {
        // Cycle detected; returning an arbitrary var in the cycle.
        return var;
      }
    }
  }

  /**
   * Determine the expected types for any instance type parameters.
   *
   * This method also determines whether or not the instance can be assigned to
   * the expected type. We combine the tasks because they require traversing the
   * same data structures.
   *
   * @param instanceClass The instance for which we want generic parameter types
   * @param expectedType The type we are expecting this instance to be
   * @param resolvedTypes The types that have been resolved to actual values
   * @return The expected types of the instance class' parameters. If null, the
   *     instance class is not assignable to the expected type.
   */
  public static Type[] findExpectedParameterTypes(Class<?> instanceClass,
      Type expectedType, DequeMap<TypeVariable<?>, Type> resolvedTypes) {
    // Make a copy of the instance parameters from its class
    TypeVariable<?>[] instanceTypes = instanceClass.getTypeParameters();
    Type[] expectedParameterTypes = Arrays.copyOf(instanceTypes, instanceTypes.length,
        Type[].class);

    // Determine the type we are really expecting, to the best of our knowledge
    if (expectedType == null) {
      // Match up parameters assuming that the instance class is the best match
      // for the expected class, which we can only do if we have resolved types.
      if (resolvedTypes != null) {
        findInstanceParameters(instanceClass, resolvedTypes, expectedParameterTypes);
      }

      // With no expected type, the instance is assignable and we fall through
    } else {
      // First determine what type we are really expecting. The type may still
      // be a TypeVariable<?> at this time when we are deserializing class
      // fields or components of another data structure.
      Type actualType = findActualType(expectedType, resolvedTypes);

      // Try to match the instanceClass to the expected type, updating the
      // expectedParameterTypes so that we can track them back to the resolved
      // types once we determine what class to treat the instance as. In
      // this method, only type information from the instance is used to resolve
      // types because the information in the resolved types map is only
      // relevant to the expected type(s). We still pass in the resolvedTypes
      // because we may need to resolve expected types.
      // Note that certain expected types may require the instance to extend
      // or implement multiple classes or interfaces, so we may capture multiple
      // types here.
      Set<Class<?>> expectedInstanceClasses = new HashSet<Class<?>>();
      if (!findExpectedInstanceClass(instanceClass, actualType,
          resolvedTypes, expectedInstanceClasses, expectedParameterTypes)) {
        // If we could not match the instance to the expected, it is not assignable
        // and we are done.
        return null;
      }

      // Now that we know what class the instance should be,
      // get any remaining parameters using resolved types.
      if (resolvedTypes != null) {
        for (Class<?> expectedClass : expectedInstanceClasses) {
          findInstanceParameters(expectedClass, resolvedTypes, expectedParameterTypes);
        }
      }
    }

    return expectedParameterTypes;
  }

  /**
   * Find the Class that a given type refers to.
   *
   * @param type The type of interest
   * @return The Class that type represents
   */
  public static Class<?> getClassFromType(Type type,
      DequeMap<TypeVariable<?>, Type> resolvedTypes) {
    Type actualType = findActualType(type, resolvedTypes);
    if (actualType instanceof Class) {
      return (Class<?>) actualType;
    }
    if (type instanceof ParameterizedType) {
      return getClassFromType(((ParameterizedType) actualType).getRawType(), resolvedTypes);
    }
    return null;
  }

  public static String getSerializationSignature(Class<?> instanceType,
      SerializationPolicy policy) {
    String result;
    result = classCRC32Cache.get(instanceType);
    if (result == null) {
      CRC32 crc = new CRC32();
      try {
        generateSerializationSignature(instanceType, crc, policy);
      } catch (UnsupportedEncodingException e) {
        throw new RuntimeException("Could not compute the serialization signature", e);
      }
      result = Long.toString(crc.getValue());
      classCRC32Cache.put(instanceType, result);
    }
    return result;
  }

  public static String getSerializedTypeName(Class<?> instanceType) {
    if (instanceType.isPrimitive()) {
      return SERIALIZED_PRIMITIVE_TYPE_NAMES.get(instanceType.getName());
    }

    return instanceType.getName();
  }

  /**
   * Returns the {@link Class} which can serialize the given instance type, or
   * <code>null</code> if this class has no custom field serializer.
   *
   * Note that arrays never have custom field serializers.
   */
  public static Class<?> hasCustomFieldSerializer(Class<?> instanceType) {
    assert (instanceType != null);
    if (instanceType.isArray()) {
      return null;
    }

    Class<?> result;
    result = classCustomSerializerCache.get(instanceType);
    if (result == null) {
      result = computeHasCustomFieldSerializer(instanceType, false);
      if (result == null) {
        /*
         * Use (result == instanceType) as a sentinel value when the class has
         * no custom field serializer. We avoid using null as the sentinel
         * value, because that would necessitate an additional containsKey()
         * check in the most common case.
         */
        result = instanceType;
      }
      classCustomSerializerCache.put(instanceType, result);
    }
    return (result == instanceType) ? null : result;
  }

  /**
   * Returns the server-side {@link Class} which can serialize the given
   * instance type, or <code>null</code> if this class has no type-checking
   * custom field serializer.
   *
   * Note that arrays never have custom field serializers.
   */
  public static Class<?> hasServerCustomFieldSerializer(Class<?> instanceType) {
    assert (instanceType != null);
    if (instanceType.isArray()) {
      return null;
    }

    Class<?> result;
    result = classServerCustomSerializerCache.get(instanceType);
    if (result == null) {
      result = computeHasCustomFieldSerializer(instanceType, true);
      if (result == null) {
        /*
         * Use (result == instanceType) as a sentinel value when the class has
         * no custom field serializer. We avoid using null as the sentinel
         * value, because that would necessitate an additional containsKey()
         * check in the most common case.
         */
        result = instanceType;
      }
      classServerCustomSerializerCache.put(instanceType, result);
    }
    return (result == instanceType) ? null : result;
  }

  /**
   * Remove all of the actual types that arose from the given type.
   *
   * This method should always be called after a corresponding call to
   * resolveTypes.
   *
   * @param methodType The type we wish to assign this instance to
   * @param resolvedTypes The types that have been resolved to actual values
   */
  public static void releaseTypes(Type methodType, DequeMap<TypeVariable<?>, Type> resolvedTypes) {
    SerializabilityUtil.resolveTypesWorker(methodType, resolvedTypes, false);
  }

  /**
   * Find all the actual types we can from the information in the given type,
   * and put the mapping from TypeVariable objects to actual types into the
   * resolved types map.
   *
   * The method releaseTypes should always be called after a call to this
   * method, unless the resolved types map is about to be discarded.
   *
   * @param methodType The type we wish to assign this instance to
   * @param resolvedTypes The types that have been resolved to actual values
   */
  public static void resolveTypes(Type methodType, DequeMap<TypeVariable<?>, Type> resolvedTypes) {
    SerializabilityUtil.resolveTypesWorker(methodType, resolvedTypes, true);
  }

  /**
   * Returns true if this field has an annotation named "GwtTransient".
   */
  private static boolean hasGwtTransientAnnotation(Field field) {
    for (Annotation a : field.getAnnotations()) {
      if (a.annotationType().getSimpleName().equals(GwtTransient.class.getSimpleName())) {
        return true;
      }
    }
    return false;
  }

  static boolean isNotStaticOrTransient(Field field) {
    int fieldModifiers = field.getModifiers();
    return !Modifier.isStatic(fieldModifiers)
        && !Modifier.isTransient(fieldModifiers)
        && !hasGwtTransientAnnotation(field);
  }

  static boolean isNotFinal(Field field) {
    return !Modifier.isFinal(field.getModifiers());
  }

  /**
   * Loads a {@link CustomFieldSerializer} from a class that may implement that
   * interface.
   *
   * @param customSerializerClass the Custom Field Serializer class
   *
   * @return an instance the class provided if it implements
   *         {@link CustomFieldSerializer} or {@code null} if it does not
   *
   * @throws SerializationException if the load process encounters an unexpected
   *           problem
   */
  static CustomFieldSerializer<?> loadCustomFieldSerializer(final Class<?> customSerializerClass)
      throws SerializationException {
    CustomFieldSerializer<?> customFieldSerializer =
        CLASS_TO_SERIALIZER_INSTANCE.get(customSerializerClass);
    if (customFieldSerializer == null) {
      if (CustomFieldSerializer.class.isAssignableFrom(customSerializerClass)) {
        try {
          customFieldSerializer = (CustomFieldSerializer<?>) customSerializerClass.newInstance();
        } catch (InstantiationException e) {
          throw new SerializationException(e);

        } catch (IllegalAccessException e) {
          throw new SerializationException(e);
        }
      } else {
        customFieldSerializer = NO_SUCH_SERIALIZER;
      }
      CLASS_TO_SERIALIZER_INSTANCE.put(customSerializerClass, customFieldSerializer);
    }
    if (customFieldSerializer == NO_SUCH_SERIALIZER) {
      return null;
    } else {
      return customFieldSerializer;
    }
  }

  /**
   * This method treats arrays in a special way.
   */
  private static Class<?> computeHasCustomFieldSerializer(Class<?> instanceType,
      Boolean typeChecked) {
    assert (instanceType != null);
    String qualifiedTypeName = instanceType.getName();
    /*
     * This class is called from client code running in Development Mode as well
     * as server code running in the servlet container. In Development Mode, we
     * want to load classes through the
     * CompilingClassLoader$MultiParentClassLoader, not the system classloader.
     */
    ClassLoader classLoader =
        GWT.isClient() ? SerializabilityUtil.class.getClassLoader() : Thread.currentThread()
            .getContextClassLoader();

    if (typeChecked) {
      /*
       * Look for a server-specific version of the custom field serializer.
       * Server-side versions do additional type checking before deserializing a
       * class, offering protection against certain malicious attacks on the
       * server via RPC.
       */
      String serverSerializerName = qualifiedTypeName + "_ServerCustomFieldSerializer";
      serverSerializerName = serverSerializerName.replaceFirst("client", "server");
      Class<?> serverCustomSerializer = getCustomFieldSerializer(classLoader, serverSerializerName);
      if (serverCustomSerializer != null) {
        return serverCustomSerializer;
      }

      // Try with the regular name
      serverCustomSerializer =
          getCustomFieldSerializer(classLoader, JRE_SERVER_SERIALIZER_PACKAGE + "."
              + serverSerializerName);
      if (serverCustomSerializer != null) {
        return serverCustomSerializer;
      }
    }

    // Look for client-side serializers.
    String simpleSerializerName = qualifiedTypeName + "_CustomFieldSerializer";
    Class<?> customSerializer = getCustomFieldSerializer(classLoader, simpleSerializerName);
    if (customSerializer != null) {
      return customSerializer;
    }

    // Try with the regular name
    customSerializer =
        getCustomFieldSerializer(classLoader, JRE_SERIALIZER_PACKAGE + "." + simpleSerializerName);
    if (customSerializer != null) {
      return customSerializer;
    }

    return null;
  }

  private static boolean excludeImplementationFromSerializationSignature(Class<?> instanceType) {
    if (TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.contains(instanceType)) {
      return true;
    }
    return false;
  }

  private static boolean fieldQualifiesForSerialization(Field field, boolean includeFinal) {
    if (Throwable.class == field.getDeclaringClass()) {
      /**
       * Only serialize Throwable's detailMessage field; all others are ignored.
       *
       * NOTE: Changing the set of fields that we serialize for Throwable will
       * necessitate a change to our JRE emulation's version of Throwable.
       */
      if ("detailMessage".equals(field.getName())) {
        assert (isNotStaticOrTransient(field));
        return true;
      } else {
        return false;
      }
    } else {
      return isNotStaticOrTransient(field) && (includeFinal || isNotFinal(field));
    }
  }

  private static boolean findExpectedInstanceClass(Class<?> instanceClass,
      Type expectedType, DequeMap<TypeVariable<?>, Type> resolvedTypes,
      Set<Class<?>> expectedInstanceClasses, Type[] expectedParameterTypes) {
    // Check for an exact match for the instance class and the expected type.
    // If found, we can return. For expected types that allow one of several
    // possible class to match (wildcards, type variables) see if this
    // instance matches any of the allowed types.
    if (expectedType instanceof TypeVariable) {
      // Every bound must have a match
      Type[] typeVariableBounds = ((TypeVariable<?>) expectedType).getBounds();
      for (Type boundType : typeVariableBounds) {
        if (!findExpectedInstanceClass(instanceClass, boundType, resolvedTypes,
            expectedInstanceClasses, expectedParameterTypes)) {
          return false;
        }
      }
      return true;
    } else if (expectedType instanceof ParameterizedType) {
      ParameterizedType paramType = (ParameterizedType) expectedType;
      if (paramType.getRawType() == instanceClass) {
        expectedInstanceClasses.add(instanceClass);
        return true;
      }
    } else if (expectedType instanceof GenericArrayType) {
      if (instanceClass.isArray()) {
        expectedInstanceClasses.add(instanceClass);
        return true;
      }
    } else if (expectedType instanceof WildcardType) {
      WildcardType wildcardType = (WildcardType) expectedType;

      Type[] lowerBounds = wildcardType.getLowerBounds();
      for (Type type : lowerBounds) {
        /* Require instance to be a superclass of type, or type itself. */
        Class<?> boundClass = getClassFromType(type, resolvedTypes);

        while (boundClass != null) {
          if (instanceClass == boundClass) {
            expectedInstanceClasses.add(boundClass);
            break;
          }
          boundClass = boundClass.getSuperclass();
        }

        // We fail if the class does not meet any bound, as we should.
        if (boundClass == null) {
          return false;
        }
      }

      Type[] upperBounds = wildcardType.getUpperBounds();
      for (Type type : upperBounds) {
        /* Require instanceClass to be a subclass of type. */
        if (!findExpectedInstanceClass(instanceClass, type, resolvedTypes,
            expectedInstanceClasses, expectedParameterTypes)) {
          return false;
        }
      }
      return true;
    } else if (((Class<?>) expectedType).getComponentType() != null) {
      // Array types just pass through here, and we catch any problems when
      // we try to deserialize the entries.
      if (((Class<?>) expectedType).isAssignableFrom(instanceClass)) {
        expectedInstanceClasses.add(instanceClass);
        return true;
      } else {
        return false;
      }
    } else if (((Class<?>) expectedType) == instanceClass) {
      expectedInstanceClasses.add(instanceClass);
      return true;
    }

    // We know that the instance class does not exactly match the expected type,
    // so try its superclass and its interfaces. At the same time, update any
    // expected types to use the superclass or interface type. We know at this
    // point that the expected type does not involve wildcards or TypeVariables,
    // so we know that the first thing to return true indicates we are done,
    // and failure of anything to return true means the instance does not meet
    // the expected type.
    if (instanceClass.getGenericSuperclass() != null) {
      Type[] localTypes = expectedParameterTypes.clone();
      if (findExpectedInstanceClassFromSuper(instanceClass.getGenericSuperclass(), expectedType,
          resolvedTypes, expectedInstanceClasses, localTypes)) {
        for (int i = 0; i < expectedParameterTypes.length; ++i) {
          expectedParameterTypes[i] = localTypes[i];
        }
        return true;
      }
    }

    Type[] interfaces = instanceClass.getGenericInterfaces();
    for (Type interfaceType : interfaces) {
      Type[] localTypes = expectedParameterTypes.clone();
      if (findExpectedInstanceClassFromSuper(interfaceType, expectedType, resolvedTypes,
          expectedInstanceClasses, localTypes)) {
        for (int i = 0; i < expectedParameterTypes.length; ++i) {
          expectedParameterTypes[i] = localTypes[i];
        }
        return true;
      }
    }

    return false;
  }

  private static boolean findExpectedInstanceClassFromSuper(
      Type superType, Type expectedType, DequeMap<TypeVariable<?>, Type> resolvedTypes,
      Set<Class<?>> expectedInstanceClasses, Type[] expectedParameterTypes) {

    if (superType instanceof GenericArrayType) {
      // Can't use array types as supertypes or interfaces
      return false;
    } else if (superType instanceof Class) {
      // No additional type info from the superclass
      return findExpectedInstanceClass((Class<?>) superType, expectedType, resolvedTypes,
          expectedInstanceClasses, expectedParameterTypes);
    } else if (superType instanceof ParameterizedType) {
      ParameterizedType paramType = (ParameterizedType) superType;
      Type rawType = paramType.getRawType();

      if (rawType instanceof Class) {
        Class<?> rawClass = (Class<?>) rawType;
        TypeVariable<?>[] classGenericTypes = rawClass.getTypeParameters();
        Type[] actualTypes = paramType.getActualTypeArguments();

        for (int i = 0; i < actualTypes.length; ++i) {
          for (int j = 0; j < expectedParameterTypes.length; ++j) {
            if (actualTypes[i] == expectedParameterTypes[j]) {
              expectedParameterTypes[j] = classGenericTypes[i];
            }
          }
        }
        return findExpectedInstanceClass(rawClass, expectedType, resolvedTypes,
            expectedInstanceClasses, expectedParameterTypes);
      }
    } else if (superType instanceof WildcardType) {
      WildcardType wildcardType = (WildcardType) superType;
      Type[] upperBounds = wildcardType.getUpperBounds();
      for (Type boundType : upperBounds) {
        if (findExpectedInstanceClassFromSuper(boundType, expectedType,
            resolvedTypes, expectedInstanceClasses, expectedParameterTypes)) {
          return true;
        }
      }
    }

    return false;
  }

    /**
   * Attempt to find known type for TypeVariable type from an instance.
   *
   * @param foundParameter The currently known parameter, which must be of type
   *          TypeVariable
   * @param instanceType The instance that we need to check for information
   *          about the type
   * @param resolvedTypes The map of known relationships between Type objects
   * @return A new value for the foundParameter, if we find one
   */
  private static Type findInstanceParameter(Type foundParameter, Type instanceType,
      DequeMap<TypeVariable<?>, Type> resolvedTypes) {
    // See what we know about the types that are matched to this type.
    if (instanceType instanceof GenericArrayType) {
      return findInstanceParameter(foundParameter, ((GenericArrayType) instanceType)
          .getGenericComponentType(), resolvedTypes);
    } else if (instanceType instanceof ParameterizedType) {
      ParameterizedType paramType = (ParameterizedType) instanceType;
      Type rawType = paramType.getRawType();

      if (rawType instanceof Class) {
        Class<?> rawClass = (Class<?>) rawType;
        TypeVariable<?>[] classGenericTypes = rawClass.getTypeParameters();
        Type[] actualTypes = paramType.getActualTypeArguments();

        for (int i = 0; i < actualTypes.length; ++i) {
          if (actualTypes[i] == foundParameter) {
            // Check if we already know about this type.
            Type capturedType = findActualType(classGenericTypes[i], resolvedTypes);
            if (capturedType != classGenericTypes[i]) {
              return capturedType;
            }

            if (rawClass.getGenericSuperclass() != null) {
              Type superParameter =
                  findInstanceParameter(classGenericTypes[i], rawClass.getGenericSuperclass(),
                      resolvedTypes);
              if (!(superParameter instanceof TypeVariable)) {
                return superParameter;
              }
            }
            Type[] rawInterfaces = rawClass.getGenericInterfaces();
            for (Type interfaceType : rawInterfaces) {
              Type interfaceParameter =
                  findInstanceParameter(classGenericTypes[i], interfaceType, resolvedTypes);
              if (!(interfaceParameter instanceof TypeVariable)) {
                return interfaceParameter;
              }
            }
          }
        }
      }
    } else if (instanceType instanceof WildcardType) {
      WildcardType wildcardType = (WildcardType) instanceType;
      Type[] upperBounds = wildcardType.getUpperBounds();
      for (Type boundType : upperBounds) {
        Type wildcardParameter = findInstanceParameter(foundParameter, boundType, resolvedTypes);
        if (!(wildcardParameter instanceof TypeVariable)) {
          return wildcardParameter;
        }
      }
    }

    return foundParameter;
  }

  /**
   * Attempt to find the actual types for the generic parameters of an instance,
   * given the types we have resolved from the method signature or class field
   * declaration.
   *
   * @param instanceClass The instance for which we want actual generic
   *          parameter types.
   * @param resolvedTypes The types that have been resolved to actual values
   * @param expectedParameterTypes An array of types representing the actual
   *         declared types for the  parameters of the instanceClass. Some may
   *         be of type TypeVariable, in which case they need to be resolved, if
   *         possible, by this method.
   */
  private static void findInstanceParameters(Class<?> instanceClass,
      DequeMap<TypeVariable<?>, Type> resolvedTypes, Type[] expectedParameterTypes) {
    TypeVariable<?>[] instanceTypes = instanceClass.getTypeParameters();

    for (int i = 0; i < expectedParameterTypes.length; ++i) {
      if (!(expectedParameterTypes[i] instanceof TypeVariable)) {
        // We already have an actual type
        continue;
      }

      // Check if we already know about this type.
      boolean haveMatch = false;
      for (int j = 0; !haveMatch && j < instanceTypes.length; ++j) {
        if (expectedParameterTypes[i] == instanceTypes[j]) {
          Type capturedType = findActualType(instanceTypes[j], resolvedTypes);
          if (!(capturedType instanceof TypeVariable)) {
            expectedParameterTypes[i] = capturedType;
            haveMatch = true;
          }
        }
      }
      if (haveMatch) {
        continue;
      }

      // Check if it is defined by superclasses
      if (instanceClass.getGenericSuperclass() != null) {
        Type superParameter = findInstanceParameter(expectedParameterTypes[i],
                instanceClass.getGenericSuperclass(), resolvedTypes);
        if (!(superParameter instanceof TypeVariable)) {
          expectedParameterTypes[i] = superParameter;
          continue;
        }
      }

      // Check if it is defined by interfaces
      Type[] interfaceTypes = instanceClass.getGenericInterfaces();
      for (Type interfaceType : interfaceTypes) {
        Type interfaceParameter =
            findInstanceParameter(expectedParameterTypes[i], interfaceType, resolvedTypes);
        if (!(interfaceParameter instanceof TypeVariable)) {
          expectedParameterTypes[i] = interfaceParameter;
          break;
        }
      }
    }
  }

  private static void generateSerializationSignature(Class<?> instanceType, CRC32 crc,
      SerializationPolicy policy) throws UnsupportedEncodingException {
    crc.update(getSerializedTypeName(instanceType).getBytes(StandardCharsets.UTF_8));

    if (excludeImplementationFromSerializationSignature(instanceType)) {
      return;
    }

    Class<?> customSerializer = hasCustomFieldSerializer(instanceType);
    if (customSerializer != null) {
      generateSerializationSignature(customSerializer, crc, policy);
    } else if (instanceType.isArray()) {
      generateSerializationSignature(instanceType.getComponentType(), crc, policy);
    } else if (Enum.class.isAssignableFrom(instanceType) && !Enum.class.equals(instanceType)) {
      if (!instanceType.isEnum()) {
        instanceType = instanceType.getSuperclass();
      }
      Enum<?>[] constants = instanceType.asSubclass(Enum.class).getEnumConstants();
      for (Enum<?> constant : constants) {
        crc.update(constant.name().getBytes(StandardCharsets.UTF_8));
      }
    } else if (!instanceType.isPrimitive()) {
      Field[] fields = applyFieldSerializationPolicy(instanceType, policy);
      Set<String> clientFieldNames = policy.getClientFieldNamesForEnhancedClass(instanceType);
      for (Field field : fields) {
        assert (field != null);
        /**
         * If clientFieldNames is non-null, use only the fields listed there to
         * generate the signature. Otherwise, use all known fields.
         */
        if ((clientFieldNames == null) || clientFieldNames.contains(field.getName())) {
          crc.update(field.getName().getBytes(StandardCharsets.UTF_8));
          crc.update(getSerializedTypeName(field.getType()).getBytes(StandardCharsets.UTF_8));
        }
      }

      Class<?> superClass = instanceType.getSuperclass();
      if (superClass != null) {
        generateSerializationSignature(superClass, crc, policy);
      }
    }
  }

  private static Class<?> getCustomFieldSerializer(ClassLoader classLoader,
      String qualifiedSerialzierName) {
    try {
      Class<?> customSerializerClass = Class.forName(qualifiedSerialzierName, false, classLoader);
      return customSerializerClass;
    } catch (ClassNotFoundException e) {
      return null;
    }
  }

  private static void resolveTypesWorker(Type methodType,
      DequeMap<TypeVariable<?>, Type> resolvedTypes, boolean addTypes) {
    if (methodType instanceof GenericArrayType) {
      SerializabilityUtil.resolveTypesWorker(((GenericArrayType) methodType)
          .getGenericComponentType(), resolvedTypes, addTypes);
    } else if (methodType instanceof ParameterizedType) {
      ParameterizedType paramType = (ParameterizedType) methodType;
      Type rawType = paramType.getRawType();
      if (rawType instanceof Class) {
        Class<?> rawClass = (Class<?>) paramType.getRawType();
        TypeVariable<?>[] classGenericTypes = rawClass.getTypeParameters();
        Type[] actualTypes = paramType.getActualTypeArguments();

        for (int i = 0; i < actualTypes.length; ++i) {
          TypeVariable<?> variableType = classGenericTypes[i];
          if (addTypes) {
            resolvedTypes.add(variableType, actualTypes[i]);
          } else {
            resolvedTypes.remove(variableType);
          }
        }

        Class<?> superClass = rawClass.getSuperclass();
        if (superClass != null) {
          Type superGenericType = rawClass.getGenericSuperclass();
          SerializabilityUtil.resolveTypesWorker(superGenericType, resolvedTypes, addTypes);
        }

        Type[] interfaceTypes = rawClass.getGenericInterfaces();
        for (Type interfaceType : interfaceTypes) {
          SerializabilityUtil.resolveTypesWorker(interfaceType, resolvedTypes, addTypes);
        }
      }
    } else if (methodType instanceof WildcardType) {
      WildcardType wildcardType = (WildcardType) methodType;
      Type[] lowerBounds = wildcardType.getLowerBounds();
      for (Type type : lowerBounds) {
        SerializabilityUtil.resolveTypesWorker(type, resolvedTypes, addTypes);
      }
      Type[] upperBounds = wildcardType.getUpperBounds();
      for (Type type : upperBounds) {
        SerializabilityUtil.resolveTypesWorker(type, resolvedTypes, addTypes);
      }
    } else if (methodType instanceof TypeVariable) {
      Type[] bounds = ((TypeVariable<?>) methodType).getBounds();
      for (Type type : bounds) {
        SerializabilityUtil.resolveTypesWorker(type, resolvedTypes, addTypes);
      }
    } else if (methodType instanceof Class) {
      Class<?> classType = (Class<?>) methodType;

      // A type that is of instance Class, with TypeParameters, must be a raw
      // class, so strip off any parameters in the map.
      TypeVariable<?>[] classParams = classType.getTypeParameters();
      for (TypeVariable<?> classParamType : classParams) {
        if (addTypes) {
          resolvedTypes.add(classParamType, classParamType);
        } else {
          resolvedTypes.remove(classParamType);
        }
      }

      Type superGenericType = classType.getGenericSuperclass();
      if (superGenericType != null) {
        SerializabilityUtil.resolveTypesWorker(superGenericType, resolvedTypes, addTypes);
      }

      Type[] interfaceTypes = classType.getGenericInterfaces();
      for (Type interfaceType : interfaceTypes) {
        SerializabilityUtil.resolveTypesWorker(interfaceType, resolvedTypes, addTypes);
      }
    }
  }
}
