Package com.google.gwt.user.rebind.rpc

Source Code of com.google.gwt.user.rebind.rpc.SerializableTypeOracleBuilder$TypeInfoComputed

/*
* 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.rebind.rpc;

import com.google.gwt.core.ext.PropertyOracle;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.typeinfo.JArrayType;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.JGenericType;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JRealClassType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.JTypeParameter;
import com.google.gwt.core.ext.typeinfo.JWildcardType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.google.gwt.user.rebind.rpc.TypeParameterExposureComputer.TypeParameterFlowInfo;
import com.google.gwt.user.rebind.rpc.TypePaths.TypePath;

import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.Map.Entry;

/**
* Builds a {@link SerializableTypeOracle} for a given set of root types.
*
* <p>
* There are two goals for this builder. First, discover the set of serializable
* types that can be serialized if you serialize one of the root types. Second,
* to make sure that all root types can actually be serialized by GWT.
* </p>
*
* <p>
* To find the serializable types, it includes the root types, and then it
* iteratively traverses the type hierarchy and the fields of any type already
* deemed serializable. To improve the accuracy of the traversal there is a
* computations of the exposure of type parameters. When the traversal reaches a
* parameterized type, these exposure values are used to determine how to treat
* the arguments.
* </p>
*
* <p>
* A type qualifies for serialization if it or one of its subtypes is
* automatically or manually serializable. Automatic serialization is selected
* if the type is assignable to {@link IsSerializable} or {@link Serializable}
* or if the type is a primitive type such as int, boolean, etc. Manual
* serialization is selected if there exists another type with the same fully
* qualified name concatenated with "_CustomFieldSerializer". If a type
* qualifies for both manual and automatic serialization, manual serialization
* is preferred.
* </p>
*/
public class SerializableTypeOracleBuilder {

  private class TypeInfoComputed {

    /**
     * <code>true</code> if the type is assignable to {@link IsSerializable}
     * or {@link java.io.Serializable Serializable}.
     */
    private final boolean autoSerializable;

    /**
     * <code>true</code> if the this type directly implements one of the
     * marker interfaces.
     */
    private final boolean directlyImplementsMarker;

    /**
     * <code>true</code> if the type is automatically or manually serializable
     * and the corresponding checks succeed.
     */
    private boolean fieldSerializable = false;

    /**
     * <code>true</code> if this type might be instantiated.
     */
    private boolean instantiable = false;

    /**
     * <code>true</code> if there are instantiable subtypes assignable to this
     * one.
     */
    private boolean instantiableSubtypes;

    /**
     * Custom field serializer or <code>null</code> if there isn't one.
     */
    private final JClassType manualSerializer;

    /**
     * Path used to discover this type.
     */
    private final TypePath path;

    /**
     * The state that this type is currently in.
     */
    private TypeState state = TypeState.NOT_CHECKED;

    /**
     * {@link JClassType} associated with this metadata.
     */
    private final JClassType type;

    public TypeInfoComputed(JClassType type, TypePath path) {
      this.type = type;
      this.path = path;
      autoSerializable = SerializableTypeOracleBuilder.isAutoSerializable(type);
      manualSerializer = findCustomFieldSerializer(typeOracle, type);
      directlyImplementsMarker = directlyImplementsMarkerInterface(type);
    }

    public JClassType getManualSerializer() {
      return manualSerializer;
    }

    public TypePath getPath() {
      return path;
    }

    public JClassType getType() {
      return type;
    }

    public boolean hasInstantiableSubtypes() {
      return isInstantiable() || instantiableSubtypes;
    }

    public boolean isAutoSerializable() {
      return autoSerializable;
    }

    public boolean isDeclaredSerializable() {
      return autoSerializable || isManuallySerializable();
    }

    public boolean isDirectlySerializable() {
      return directlyImplementsMarker || isManuallySerializable();
    }

    public boolean isDone() {
      return state == TypeState.CHECK_DONE;
    }

    public boolean isFieldSerializable() {
      return fieldSerializable;
    }

    public boolean isInstantiable() {
      return instantiable;
    }

    public boolean isManuallySerializable() {
      return manualSerializer != null;
    }

    public boolean isPendingInstantiable() {
      return state == TypeState.CHECK_IN_PROGRESS;
    }

    public void setFieldSerializable() {
      fieldSerializable = true;
    }

    public void setInstantiable(boolean instantiable) {
      this.instantiable = instantiable;
      if (instantiable) {
        fieldSerializable = true;
      }
      state = TypeState.CHECK_DONE;
    }

    public void setInstantiableSubytpes(boolean instantiableSubtypes) {
      this.instantiableSubtypes = instantiableSubtypes;
    }

    public void setPendingInstantiable() {
      state = TypeState.CHECK_IN_PROGRESS;
    }
  }

  private enum TypeState {
    /**
     * The instantiability of a type has been determined.
     */
    CHECK_DONE("Check succeeded"),
    /**
     * The instantiability of a type is being checked.
     */
    CHECK_IN_PROGRESS("Check in progress"),
    /**
     * The instantiability of a type has not been checked.
     */
    NOT_CHECKED("Not checked");

    private final String message;

    TypeState(String message) {
      this.message = message;
    }

    @Override
    public String toString() {
      return message;
    }
  }

  /**
   * Compares {@link JType}s according to their qualified source names.
   */
  static final Comparator<JType> JTYPE_COMPARATOR = new Comparator<JType>() {
    public int compare(JType t1, JType t2) {
      return t1.getQualifiedSourceName().compareTo(t2.getQualifiedSourceName());
    }
  };

  /**
   * No type filtering by default..
   */
  private static final TypeFilter DEFAULT_TYPE_FILTER = new TypeFilter() {
    public String getName() {
      return "Default";
    }

    public boolean isAllowed(JClassType type) {
      return true;
    }
  };

  static boolean canBeInstantiated(TreeLogger logger, JClassType type,
      TreeLogger.Type logLevel) {
    if (type.isEnum() == null) {
      if (type.isAbstract()) {
        // Abstract types will be picked up if there is an instantiable
        // subtype.
        return false;
      }

      if (!type.isDefaultInstantiable() && !isManuallySerializable(type)) {
        // Warn and return false.
        logger.log(
            logLevel,
            type.getParameterizedQualifiedSourceName()
                + " was not default instantiable (it must have a zero-argument constructor or no constructors at all)",
            null);
        return false;
      }
    } else {
      /*
       * Enums are always instantiable regardless of abstract or default
       * instantiability.
       */
    }

    return true;
  }

  /**
   * Finds the custom field serializer for a given type.
   *
   * @param typeOracle
   * @param type
   * @return the custom field serializer for a type or <code>null</code> if
   *         there is not one
   */
  static JClassType findCustomFieldSerializer(TypeOracle typeOracle, JType type) {
    JClassType classOrInterface = type.isClassOrInterface();
    if (classOrInterface == null) {
      return null;
    }

    String customFieldSerializerName = type.getQualifiedSourceName()
        + "_CustomFieldSerializer";
    JClassType customSerializer = typeOracle.findType(customFieldSerializerName);
    if (customSerializer == null) {
      // If the type is in the java.lang or java.util packages then it will be
      // mapped into com.google.gwt.user.client.rpc.core package
      customSerializer = typeOracle.findType("com.google.gwt.user.client.rpc.core."
          + customFieldSerializerName);
    }

    return customSerializer;
  }

  static JRealClassType getBaseType(JClassType type) {
    if (type.isParameterized() != null) {
      return type.isParameterized().getBaseType();
    } else if (type.isRawType() != null) {
      return type.isRawType().getBaseType();
    }

    return (JRealClassType) type;
  }

  static boolean isAutoSerializable(JClassType type) {
    try {
      JClassType isSerializable = getIsSerializableMarkerInterface(type);
      JClassType serializable = getSerializableMarkerInterface(type);
      return type.isAssignableTo(isSerializable)
          || type.isAssignableTo(serializable);
    } catch (NotFoundException e) {
      return false;
    }
  }

  /**
   * Returns <code>true</code> if this type is part of the standard java
   * packages.
   *
   * NOTE: This code is copied from
   * {@link com.google.gwt.dev.shell.CompilingClassLoader CompilingClassLoader};
   * don't change one without changing the other.
   */
  static boolean isInStandardJavaPackage(String className) {
    if (className.startsWith("java.")) {
      return true;
    }

    if (className.startsWith("javax.")) {
      return true;
    }

    return false;
  }

  static void recordTypeParametersIn(JType type, Set<JTypeParameter> params) {
    JTypeParameter isTypeParameter = type.isTypeParameter();
    if (isTypeParameter != null) {
      params.add(isTypeParameter);
    }

    JArrayType isArray = type.isArray();
    if (isArray != null) {
      recordTypeParametersIn(isArray.getComponentType(), params);
    }

    JWildcardType isWildcard = type.isWildcard();
    if (isWildcard != null) {
      for (JClassType bound : isWildcard.getUpperBounds()) {
        recordTypeParametersIn(bound, params);
      }
    }

    JParameterizedType isParameterized = type.isParameterized();
    if (isParameterized != null) {
      for (JClassType arg : isParameterized.getTypeArgs()) {
        recordTypeParametersIn(arg, params);
      }
    }
  }

  /**
   * Return <code>true</code> if a class's fields should be considered for
   * serialization. If it returns <code>false</code> then none of the fields
   * of this class should be serialized.
   */
  static boolean shouldConsiderFieldsForSerialization(TreeLogger logger,
      JClassType type, boolean isSpeculative, TypeFilter filter) {
    if (!isAllowedByFilter(logger, filter, type, isSpeculative)) {
      return false;
    }

    if (!isDeclaredSerializable(type)) {
      logger.branch(TreeLogger.DEBUG, "Type '"
          + type.getParameterizedQualifiedSourceName()
          + "' is not assignable to '" + IsSerializable.class.getName()
          + "' or '" + Serializable.class.getName()
          + "' nor does it have a custom field serializer", null);
      return false;
    }

    if (isManuallySerializable(type)) {
      JClassType manualSerializer = findCustomFieldSerializer(type.getOracle(),
          type);
      assert (manualSerializer != null);

      List<String> problems = CustomFieldSerializerValidator.validate(
          manualSerializer, type);
      if (!problems.isEmpty()) {
        for (String problem : problems) {
          logger.branch(getLogLevel(isSpeculative), problem, null);
        }
        return false;
      }
    } else {
      assert (isAutoSerializable(type));

      /*
       * Speculative paths log at DEBUG level, non-speculative ones log at WARN
       * level.
       */
      TreeLogger.Type logLevel = isSpeculative ? TreeLogger.DEBUG
          : TreeLogger.WARN;

      if (!isAccessibleToSerializer(type)) {
        // Class is not visible to a serializer class in the same package
        logger.branch(
            logLevel,
            type.getParameterizedQualifiedSourceName()
                + " is not accessible from a class in its same package; it will be excluded from the set of serializable types",
            null);
        return false;
      }

      if (type.isMemberType() && !type.isStatic()) {
        // Non-static member types cannot be serialized
        logger.branch(
            logLevel,
            type.getParameterizedQualifiedSourceName()
                + " is nested but not static; it will be excluded from the set of serializable types",
            null);
        return false;
      }
    }

    return true;
  }

  /**
   * Returns <code>true</code> if the field qualifies for serialization
   * without considering its type.
   */
  static boolean shouldConsiderForSerialization(TreeLogger logger,
      boolean suppressNonStaticFinalFieldWarnings, JField field) {
    if (field.isStatic() || field.isTransient()) {
      return false;
    }

    if (field.isFinal()) {
      logger.branch(suppressNonStaticFinalFieldWarnings ? TreeLogger.DEBUG
          : TreeLogger.WARN, "Field '" + field.toString()
          + "' will not be serialized because it is final", null);
      return false;
    }

    return true;
  }

  private static boolean directlyImplementsMarkerInterface(JClassType type) {
    try {
      return TypeHierarchyUtils.directlyImplementsInterface(type,
          getIsSerializableMarkerInterface(type))
          || TypeHierarchyUtils.directlyImplementsInterface(type,
              getSerializableMarkerInterface(type));
    } catch (NotFoundException e) {
      return false;
    }
  }

  private static JArrayType getArrayType(TypeOracle typeOracle, int rank,
      JType component) {
    assert (rank > 0);

    JArrayType array = null;
    JType currentComponent = component;
    for (int i = 0; i < rank; ++i) {
      array = typeOracle.getArrayType(currentComponent);
      currentComponent = array;
    }

    return array;
  }

  private static JClassType getIsSerializableMarkerInterface(JClassType type)
      throws NotFoundException {
    return type.getOracle().getType(IsSerializable.class.getName());
  }

  private static Type getLogLevel(boolean isSpeculative) {
    return isSpeculative ? TreeLogger.WARN : TreeLogger.ERROR;
  }

  private static JClassType getSerializableMarkerInterface(JClassType type)
      throws NotFoundException {
    return type.getOracle().getType(Serializable.class.getName());
  }

  /**
   * Returns <code>true</code> if a serializer class could access this type.
   */
  private static boolean isAccessibleToSerializer(JClassType type) {
    if (type.isPrivate() || type.isLocalType()) {
      return false;
    }

    if (isInStandardJavaPackage(type.getQualifiedSourceName())) {
      if (!type.isPublic()) {
        return false;
      }
    }

    if (type.isMemberType()) {
      return isAccessibleToSerializer(type.getEnclosingType());
    }

    return true;
  }

  private static boolean isAllowedByFilter(TreeLogger logger,
      TypeFilter filter, JClassType classType, boolean isSpeculative) {
    if (!filter.isAllowed(classType)) {
      logger.log(getLogLevel(isSpeculative), "Excluded by type filter ");
      return false;
    }

    return true;
  }

  private static boolean isDeclaredSerializable(JClassType type) {
    return isAutoSerializable(type) || isManuallySerializable(type);
  }

  private static boolean isDirectlySerializable(JClassType type) {
    return directlyImplementsMarkerInterface(type)
        || isManuallySerializable(type);
  }

  private static boolean isManuallySerializable(JClassType type) {
    return findCustomFieldSerializer(type.getOracle(), type) != null;
  }

  private static void logSerializableTypes(TreeLogger logger,
      Set<JClassType> fieldSerializableTypes) {
    TreeLogger localLogger = logger.branch(TreeLogger.DEBUG, "Identified "
        + fieldSerializableTypes.size() + " serializable type"
        + ((fieldSerializableTypes.size() == 1) ? "" : "s"), null);

    for (JClassType fieldSerializableType : fieldSerializableTypes) {
      localLogger.branch(TreeLogger.DEBUG,
          fieldSerializableType.getParameterizedQualifiedSourceName(), null);
    }
  }

  private boolean alreadyCheckedObject;

  /**
   * Cache of the {@link JClassType} for {@link Collection}.
   */
  private final JGenericType collectionClass;

  private OutputStream logOutputStream;

  /**
   * Cache of the {@link JClassType} for {@link Map}.
   */
  private final JGenericType mapClass;

  private final Map<JClassType, TreeLogger> rootTypes = new LinkedHashMap<JClassType, TreeLogger>();

  /**
   * If <code>true</code> we will not warn if a serializable type contains a
   * non-static final field. We warn because these fields are not serialized.
   */
  private final boolean suppressNonStaticFinalFieldWarnings;

  private final TypeConstrainer typeConstrainer;
  private TypeFilter typeFilter = DEFAULT_TYPE_FILTER;

  private final TypeOracle typeOracle;

  private final TypeParameterExposureComputer typeParameterExposureComputer = new TypeParameterExposureComputer(
      typeFilter);

  /**
   * The set of type parameters that appear in one of the root types.
   * TODO(spoon): It would be cleaner to delete this field, and instead to have
   * {@link #addRootType(TreeLogger, JType)} replace parameters with wildcard
   * types. Then the root types would not contain any parameters.
   */
  private Set<JTypeParameter> typeParametersInRootTypes = new HashSet<JTypeParameter>();

  /**
   * Map of {@link JType} to {@link TypeInfoComputed}.
   */
  private final Map<JType, TypeInfoComputed> typeToTypeInfoComputed = new HashMap<JType, TypeInfoComputed>();

  /**
   * Constructs a builder.
   *
   * @param logger
   * @param propertyOracle
   * @param typeOracle
   *
   * @throws UnableToCompleteException if we fail to find one of our special
   *           types
   */
  public SerializableTypeOracleBuilder(TreeLogger logger,
      PropertyOracle propertyOracle, TypeOracle typeOracle)
      throws UnableToCompleteException {
    this.typeOracle = typeOracle;
    typeConstrainer = new TypeConstrainer(typeOracle);

    try {
      collectionClass = typeOracle.getType(Collection.class.getName()).isGenericType();
      mapClass = typeOracle.getType(Map.class.getName()).isGenericType();
    } catch (NotFoundException e) {
      logger.log(TreeLogger.ERROR, null, e);
      throw new UnableToCompleteException();
    }

    suppressNonStaticFinalFieldWarnings = Shared.shouldSuppressNonStaticFinalFieldWarnings(
        logger, propertyOracle);
  }

  public void addRootType(TreeLogger logger, JType type) {
    if (type.isPrimitive() != null) {
      return;
    }

    JClassType clazz = (JClassType) type;
    if (!rootTypes.containsKey(clazz)) {
      recordTypeParametersIn(type, typeParametersInRootTypes);

      rootTypes.put(clazz, logger);
    } else {
      logger.log(TreeLogger.TRACE, clazz.getParameterizedQualifiedSourceName()
          + " is already a root type.");
    }
  }

  /**
   * Builds a {@link SerializableTypeOracle} for a given set of root types.
   *
   * @param logger
   *
   * @return a {@link SerializableTypeOracle} for the specified set of root
   *         types
   *
   * @throws UnableToCompleteException if there was not at least one
   *           instantiable type assignable to each of the specified root types
   */
  public SerializableTypeOracle build(TreeLogger logger)
      throws UnableToCompleteException {
    alreadyCheckedObject = false;

    boolean allSucceeded = true;
    for (Entry<JClassType, TreeLogger> entry : rootTypes.entrySet()) {
      allSucceeded &= checkTypeInstantiable(entry.getValue(), entry.getKey(),
          false, TypePaths.createRootPath(entry.getKey()));
    }

    if (!allSucceeded) {
      // the validation code has already logged why
      throw new UnableToCompleteException();
    }

    for (TypeInfoComputed tic : typeToTypeInfoComputed.values()) {
      assert (!tic.isPendingInstantiable());
    }

    pruneUnreachableTypes();

    logReachableTypes(logger);

    Set<JClassType> possiblyInstantiatedTypes = new TreeSet<JClassType>(
        JTYPE_COMPARATOR);

    Set<JClassType> fieldSerializableTypes = new TreeSet<JClassType>(
        JTYPE_COMPARATOR);

    for (TypeInfoComputed tic : typeToTypeInfoComputed.values()) {
      JClassType type = tic.getType();

      type = type.getErasedType();

      if (tic.isInstantiable()) {
        assert (!type.isAbstract() || type.isEnum() != null);

        possiblyInstantiatedTypes.add(type);
      }

      if (tic.isFieldSerializable()) {
        assert (type.isInterface() == null);

        fieldSerializableTypes.add(type);
      }
    }

    logSerializableTypes(logger, fieldSerializableTypes);

    return new SerializableTypeOracleImpl(typeOracle, fieldSerializableTypes,
        possiblyInstantiatedTypes);
  }

  /**
   * Set the {@link OutputStream} which will receive a detailed log of the types
   * which were examined in order to determine serializability.
   */
  public void setLogOutputStream(OutputStream logOutputStream) {
    this.logOutputStream = logOutputStream;
  }

  public void setTypeFilter(TypeFilter typeFilter) {
    this.typeFilter = typeFilter;
    typeParameterExposureComputer.setTypeFilter(typeFilter);
  }

  /**
   * This method determines whether a type can be serialized by GWT. To do so,
   * it must traverse all subtypes as well as all field types of those types,
   * transitively.
   *
   * It returns a boolean indicating whether this type or any of its subtypes
   * are instantiable.
   *
   * As a side effect, all types needed--plus some--to serialize this type are
   * accumulated in {@link #typeToTypeInfoComputed}.
   *
   * The method is exposed using default access to enable testing.
   */
  final boolean checkTypeInstantiable(TreeLogger logger, JType type,
      boolean isSpeculative, TypePath path) {
    return checkTypeInstantiable(logger, type, isSpeculative, path,
        new HashSet<JClassType>());
  }

  /**
   * Same as
   * {@link #checkTypeInstantiable(TreeLogger, JType, boolean, com.google.gwt.user.rebind.rpc.SerializableTypeOracleBuilder.TypePath)},
   * except that returns the set of instantiable subtypes.
   */
  boolean checkTypeInstantiable(TreeLogger logger, JType type,
      boolean isSpeculative, TypePath path, Set<JClassType> instSubtypes) {
    assert (type != null);
    if (type.isPrimitive() != null) {
      return true;
    }

    assert (type instanceof JClassType);

    JClassType classType = (JClassType) type;

    TreeLogger localLogger = logger.branch(TreeLogger.DEBUG,
        classType.getParameterizedQualifiedSourceName(), null);

    JTypeParameter isTypeParameter = classType.isTypeParameter();
    if (isTypeParameter != null) {
      if (typeParametersInRootTypes.contains(isTypeParameter)) {
        return checkTypeInstantiable(localLogger,
            isTypeParameter.getFirstBound(), isSpeculative,
            TypePaths.createTypeParameterInRootPath(path, isTypeParameter),
            instSubtypes);
      }

      /*
       * This type parameter was not in a root type and therefore it is the
       * caller's responsibility to deal with it. We assume that it is
       * instantiable here.
       */
      return true;
    }

    JWildcardType isWildcard = classType.isWildcard();
    if (isWildcard != null) {
      boolean success = true;
      for (JClassType bound : isWildcard.getUpperBounds()) {
        success &= checkTypeInstantiable(localLogger, bound, isSpeculative,
            path);
      }
      return success;
    }

    JArrayType isArray = classType.isArray();
    if (isArray != null) {
      return checkArrayInstantiable(localLogger, isArray, isSpeculative, path);
    }

    if (classType == typeOracle.getJavaLangObject()) {
      /*
       * Report an error if the type is or erases to Object since this violates
       * our restrictions on RPC.
       */
      localLogger.branch(
          getLogLevel(isSpeculative),
          "In order to produce smaller client-side code, 'Object' is not allowed; consider using a more specific type",
          null);
      return false;
    }

    if (classType.isRawType() != null) {
      localLogger.branch(
          TreeLogger.DEBUG,
          "Type '"
              + classType.getQualifiedSourceName()
              + "' should be parameterized to help the compiler produce the smallest code size possible for your module",
          null);
    }

    JClassType originalType = (JClassType) type;
    if (isSpeculative && isDirectlySerializable(originalType)) {
      // TODO: Our speculative tracking is off.
      isSpeculative = false;
    }
    //
    // TreeLogger subtypesLogger = localLogger.branch(TreeLogger.DEBUG,
    // "Analyzing subclasses:", null);
    boolean anySubtypes = checkSubtypes(localLogger, originalType,
        instSubtypes, path);

    if (!anySubtypes && !isSpeculative) {
      // No instantiable types were found
      localLogger.branch(getLogLevel(isSpeculative), "Type '"
          + classType.getParameterizedQualifiedSourceName()
          + "' was not serializable and has no concrete serializable subtypes",
          null);
    }

    return anySubtypes;
  }

  int getTypeParameterExposure(JGenericType type, int index) {
    return getFlowInfo(type, index).getExposure();
  }

  /**
   * Returns <code>true</code> if the fields of the type should be considered
   * for serialization.
   *
   * Default access to allow for testing.
   */
  boolean shouldConsiderFieldsForSerialization(TreeLogger logger,
      JClassType type, boolean isSpeculative) {
    return shouldConsiderFieldsForSerialization(logger, type, isSpeculative,
        typeFilter);
  }

  /**
   * Consider any subtype of java.lang.Object which qualifies for serialization.
   *
   * @param logger
   */
  private void checkAllSubtypesOfObject(TreeLogger logger, TypePath parent) {
    if (alreadyCheckedObject) {
      return;
    }
    alreadyCheckedObject = true;

    /*
     * This will pull in the world and the set of serializable types will be
     * larger than it needs to be. We exclude types that do not qualify for
     * serialization to avoid generating false errors due to types that do not
     * qualify for serialization and have no serializable subtypes.
     */
    TreeLogger localLogger = logger.branch(TreeLogger.WARN,
        "Checking all subtypes of Object which qualify for serialization", null);
    JClassType[] allTypes = typeOracle.getJavaLangObject().getSubtypes();
    for (JClassType cls : allTypes) {
      if (isDeclaredSerializable(cls)) {
        checkTypeInstantiable(localLogger, cls, true,
            TypePaths.createSubtypePath(parent, cls,
                typeOracle.getJavaLangObject()));
      }
    }
  }

  private boolean checkArrayInstantiable(TreeLogger logger, JArrayType array,
      boolean isSpeculative, TypePath path) {

    JType leafType = array.getLeafType();
    JWildcardType leafWild = leafType.isWildcard();
    if (leafWild != null) {
      JArrayType arrayType = getArrayType(typeOracle, array.getRank(),
          leafWild.getUpperBound());
      return checkArrayInstantiable(logger, arrayType, isSpeculative, path);
    }

    JClassType leafClass = leafType.isClassOrInterface();
    JTypeParameter isLeafTypeParameter = leafType.isTypeParameter();
    if (isLeafTypeParameter != null
        && !typeParametersInRootTypes.contains(isLeafTypeParameter)) {
      // Don't deal with non root type parameters
      return true;
    }

    if (!isAllowedByFilter(logger, array, isSpeculative)) {
      return false;
    }

    TypeInfoComputed tic = getTypeInfoComputed(array, path);
    if (tic.isDone()) {
      return tic.hasInstantiableSubtypes();
    } else if (tic.isPendingInstantiable()) {
      return true;
    }
    tic.setPendingInstantiable();

    TreeLogger branch = logger.branch(TreeLogger.DEBUG,
        "Analyzing component type:", null);
    Set<JClassType> instantiableTypes = new HashSet<JClassType>();

    boolean succeeded = checkTypeInstantiable(branch, leafType, isSpeculative,
        TypePaths.createArrayComponentPath(array, path), instantiableTypes);
    if (succeeded) {
      if (leafClass == null) {
        assert leafType.isPrimitive() != null;
        markArrayTypesInstantiable(leafType, array.getRank(), path);
      } else {
        TreeLogger covariantArrayLogger = logger.branch(TreeLogger.DEBUG,
            "Covariant array types");

        /*
         * Compute covariant arrays for arrays of reference types.
         */
        for (JClassType instantiableType : TypeHierarchyUtils.getAllTypesBetweenRootTypeAndLeaves(
            leafClass, instantiableTypes)) {
          if (!isAccessibleToSerializer(instantiableType)) {
            // Skip types that are not accessible from a serializer
            continue;
          }

          covariantArrayLogger.branch(
              TreeLogger.DEBUG,
              getArrayType(typeOracle, array.getRank(), instantiableType).getParameterizedQualifiedSourceName());

          markArrayTypesInstantiable(instantiableType, array.getRank(), path);
        }
      }
    }

    tic.setInstantiable(succeeded);
    return succeeded;
  }

  /**
   * Returns <code>true</code> if the declared fields of this type are all
   * instantiable. As a side-effect it fills in {@link TypeInfoComputed} for all
   * necessary types.
   */
  private boolean checkDeclaredFields(TreeLogger logger,
      TypeInfoComputed typeInfo, boolean isSpeculative, TypePath parent) {

    JClassType classOrInterface = typeInfo.getType();
    if (classOrInterface.isEnum() != null) {
      // The fields of an enum are never serialized; they are always okay.
      return true;
    }

    // All fields on a manual serializable are considered speculative.
    isSpeculative |= typeInfo.isManuallySerializable();

    JClassType baseType = getBaseType(classOrInterface);

    boolean allSucceeded = true;
    // TODO: Propagating the constraints will produce better results as long
    // as infinite expansion can be avoided in the process.
    JField[] fields = baseType.getFields();
    if (fields.length > 0) {
      TreeLogger localLogger = logger.branch(TreeLogger.DEBUG,
          "Analyzing the fields of type '"
              + classOrInterface.getParameterizedQualifiedSourceName()
              + "' that qualify for serialization", null);

      for (JField field : fields) {
        if (!shouldConsiderForSerialization(localLogger,
            suppressNonStaticFinalFieldWarnings, field)) {
          continue;
        }

        TreeLogger fieldLogger = localLogger.branch(TreeLogger.DEBUG,
            field.toString(), null);
        JType fieldType = field.getType();

        TypePath path = TypePaths.createFieldPath(parent, field);
        if (typeInfo.isManuallySerializable()
            && fieldType.getLeafType() == typeOracle.getJavaLangObject()) {
          checkAllSubtypesOfObject(fieldLogger.branch(TreeLogger.WARN,
              "Object was reached from a manually serializable type", null),
              path);
        } else {
          allSucceeded &= checkTypeInstantiable(fieldLogger, fieldType,
              isSpeculative, path);
        }
      }
    }

    boolean succeeded = allSucceeded || typeInfo.isManuallySerializable();
    if (succeeded) {
      typeInfo.setFieldSerializable();
    }

    return succeeded;
  }

  private boolean checkSubtype(TreeLogger logger, JClassType classOrInterface,
      JClassType originalType, boolean isSpeculative, TypePath parent) {
    if (classOrInterface.isEnum() != null) {
      // The fields of an enum are never serialized; they are always okay.
      return true;
    }

    JParameterizedType isParameterized = classOrInterface.isParameterized();
    if (isParameterized != null) {
      if (isRawMapOrRawCollection(classOrInterface)) {
        /*
         * Backwards compatibility. Raw collections or maps force all object
         * subtypes to be considered.
         */
        checkAllSubtypesOfObject(logger, parent);
      } else {
        TreeLogger paramsLogger = logger.branch(TreeLogger.DEBUG,
            "Checking parameters of '"
                + isParameterized.getParameterizedQualifiedSourceName() + "'");

        for (JTypeParameter param : isParameterized.getBaseType().getTypeParameters()) {
          if (!checkTypeArgument(paramsLogger, isParameterized.getBaseType(),
              param.getOrdinal(),
              isParameterized.getTypeArgs()[param.getOrdinal()], true, parent)) {
            return false;
          }
        }
      }
    }

    // Check all super type fields first (recursively).
    JClassType superType = classOrInterface.getSuperclass();
    if (superType != null && superType.isRawType() != null) {
      superType = superType.isRawType().asParameterizedByWildcards();
    }

    if (superType != null && isDeclaredSerializable(superType)) {
      superType = constrainTypeBy(superType, originalType);
      if (superType == null) {
        return false;
      }

      boolean superTypeOk = false;
      if (superType != null) {
        superTypeOk = checkSubtype(logger, superType, originalType,
            isSpeculative, TypePaths.createSupertypePath(parent, superType,
                classOrInterface));
      }

      /*
       * If my super type did not check out, then I am not instantiable and we
       * should error out... UNLESS I am *directly* serializable myself, in
       * which case it's ok for me to be the root of a new instantiable
       * hierarchy.
       */
      if (!superTypeOk && !isDirectlySerializable(classOrInterface)) {
        return false;
      }
    }

    TypeInfoComputed tic = getTypeInfoComputed(classOrInterface, parent);
    return checkDeclaredFields(logger, tic, isSpeculative, parent);
  }

  /**
   * Returns <code>true</code> if this type or one of its subtypes is
   * instantiable relative to a known base type.
   */
  private boolean checkSubtypes(TreeLogger logger, JClassType originalType,
      Set<JClassType> instSubtypes, TypePath path) {
    JClassType baseType = getBaseType(originalType);
    TreeLogger computationLogger = logger.branch(TreeLogger.DEBUG,
        "Finding possibly instantiable subtypes");
    List<JClassType> candidates = getPossiblyInstantiableSubtypes(
        computationLogger, baseType);
    boolean anySubtypes = false;

    TreeLogger verificationLogger = logger.branch(TreeLogger.DEBUG,
        "Verifying instantiability");
    for (JClassType candidate : candidates) {
      if (getBaseType(candidate) == baseType
          && originalType.isRawType() == null) {
        // Don't rely on the constrainer when we have perfect information.
        candidate = originalType;
      } else {
        candidate = constrainTypeBy(candidate, originalType);
        if (candidate == null) {
          continue;
        }
      }

      if (!isAllowedByFilter(verificationLogger, candidate, true)) {
        continue;
      }

      TypePath subtypePath = TypePaths.createSubtypePath(path, candidate,
          originalType);
      TypeInfoComputed tic = getTypeInfoComputed(candidate, subtypePath);
      if (tic.isDone()) {
        if (tic.isInstantiable()) {
          anySubtypes = true;
          instSubtypes.add(candidate);
        }
        continue;
      } else if (tic.isPendingInstantiable()) {
        anySubtypes = true;
        instSubtypes.add(candidate);
        continue;
      }
      tic.setPendingInstantiable();

      TreeLogger subtypeLogger = verificationLogger.branch(TreeLogger.DEBUG,
          candidate.getParameterizedQualifiedSourceName());
      boolean instantiable = checkSubtype(subtypeLogger, candidate,
          originalType, true, subtypePath);
      anySubtypes |= instantiable;
      tic.setInstantiable(instantiable);

      if (instantiable) {
        subtypeLogger.branch(TreeLogger.DEBUG, "Is instantiable");
      }

      // Note we are leaving hasInstantiableSubtypes() as false which might be
      // wrong but it is only used by arrays and thus it will never be looked at
      // for this tic.
      if (instantiable && instSubtypes != null) {
        instSubtypes.add(candidate);
      }
    }

    return anySubtypes;
  }

  /**
   * Check the argument to a parameterized type to see if it will make the type
   * it is applied to be serializable. As a side effect, populates
   * {@link #typeToTypeInfoComputed} in the same way as
   * {@link #checkTypeInstantiable(TreeLogger, JType, boolean)}.
   *
   * @param logger
   * @param baseType - The generic type the parameter is on
   * @param paramIndex - The index of the parameter in the generic type
   * @param typeArg - An upper bound on the actual argument being applied to the
   *          generic type
   * @param isSpeculative
   *
   * @return Whether the a parameterized type can be serializable if
   *         <code>baseType</code> is the base type and the
   *         <code>paramIndex</code>th type argument is a subtype of
   *         <code>typeArg</code>.
   */
  private boolean checkTypeArgument(TreeLogger logger, JGenericType baseType,
      int paramIndex, JClassType typeArg, boolean isSpeculative, TypePath parent) {
    JWildcardType isWildcard = typeArg.isWildcard();
    if (isWildcard != null) {
      return checkTypeArgument(logger, baseType, paramIndex,
          isWildcard.getUpperBound(), isSpeculative, parent);
    }

    JArrayType typeArgAsArray = typeArg.isArray();
    if (typeArgAsArray != null) {
      JTypeParameter parameterOfTypeArgArray = typeArgAsArray.getLeafType().isTypeParameter();
      if (parameterOfTypeArgArray != null) {
        JGenericType declaringClass = parameterOfTypeArgArray.getDeclaringClass();
        if (declaringClass != null) {
          TypeParameterFlowInfo flowInfoForArrayParam = getFlowInfo(
              declaringClass, parameterOfTypeArgArray.getOrdinal());
          TypeParameterFlowInfo otherFlowInfo = getFlowInfo(baseType,
              paramIndex);
          if (otherFlowInfo.getExposure() >= 0
              && otherFlowInfo.isTransitivelyAffectedBy(flowInfoForArrayParam)) {
            logger.branch(
                getLogLevel(isSpeculative),
                "Cannot serialize type '"
                    + baseType.getParameterizedQualifiedSourceName()
                    + "' when given an argument of type '"
                    + typeArg.getParameterizedQualifiedSourceName()
                    + "' because it appears to require serializing arrays of unbounded dimension");
            return false;
          }
        }
      }
    }

    TypePath path = TypePaths.createTypeArgumentPath(parent, baseType,
        paramIndex, typeArg);
    int exposure = getTypeParameterExposure(baseType, paramIndex);
    switch (exposure) {
      case TypeParameterExposureComputer.EXPOSURE_DIRECT: {
        TreeLogger branch = logger.branch(
            TreeLogger.DEBUG,
            "Checking type argument "
                + paramIndex
                + " of type '"
                + baseType.getParameterizedQualifiedSourceName()
                + "' because it is directly exposed in this type or in one of its subtypes");
        return checkTypeInstantiable(branch, typeArg, true, path)
            || mightNotBeExposed(baseType, paramIndex);
      }
      case TypeParameterExposureComputer.EXPOSURE_NONE:
        // Ignore this argument
        logger.branch(TreeLogger.DEBUG, "Ignoring type argument " + paramIndex
            + " of type '" + baseType.getParameterizedQualifiedSourceName()
            + "' because it is not exposed in this or any subtype");
        return true;

      default: {
        assert (exposure >= TypeParameterExposureComputer.EXPOSURE_MIN_BOUNDED_ARRAY);
        TreeLogger branch = logger.branch(
            TreeLogger.DEBUG,
            "Checking type argument "
                + paramIndex
                + " of type '"
                + baseType.getParameterizedQualifiedSourceName()
                + "' because it is exposed as an array with a maximum dimension of "
                + exposure + " in this type or one of its subtypes");
        return checkTypeInstantiable(branch, getArrayType(typeOracle, exposure,
            typeArg), true, path)
            || mightNotBeExposed(baseType, paramIndex);
      }
    }
  }

  /**
   * If <code>type</code>'s base class does not inherit from
   * <code>superType</code>'s base class, then return <code>type</code>
   * unchanged. If it does, then return a subtype of <code>type</code> that
   * includes all values in both <code>type</code> and <code>superType</code>.
   * If there are definitely no such values, return <code>null</code>.
   */
  private JClassType constrainTypeBy(JClassType type, JClassType superType) {
    return typeConstrainer.constrainTypeBy(type, superType);
  }

  private TypeParameterFlowInfo getFlowInfo(JGenericType type, int index) {
    return typeParameterExposureComputer.computeTypeParameterExposure(type,
        index);
  }

  /**
   * Returns the subtypes of a given base type as parameterized by wildcards.
   */
  private List<JClassType> getPossiblyInstantiableSubtypes(TreeLogger logger,
      JClassType baseType) {
    assert (baseType == getBaseType(baseType));

    List<JClassType> possiblyInstantiableTypes = new ArrayList<JClassType>();
    if (baseType == typeOracle.getJavaLangObject()) {
      return possiblyInstantiableTypes;
    }

    List<JClassType> candidates = new ArrayList<JClassType>();
    candidates.add(baseType);
    candidates.addAll(Arrays.asList(baseType.getSubtypes()));

    for (JClassType subtype : candidates) {
      JClassType subtypeBase = getBaseType(subtype);
      if (maybeInstantiable(logger, subtypeBase)) {
        /*
         * Convert the generic type into a parameterization that only includes
         * wildcards.
         */
        JGenericType isGeneric = subtype.isGenericType();
        if (isGeneric != null) {
          subtype = isGeneric.asParameterizedByWildcards();
        } else {
          assert (subtype instanceof JRealClassType);
        }

        possiblyInstantiableTypes.add(subtype);
      }
    }

    return possiblyInstantiableTypes;
  }

  private TypeInfoComputed getTypeInfoComputed(JClassType type, TypePath path) {
    TypeInfoComputed tic = typeToTypeInfoComputed.get(type);
    if (tic == null) {
      tic = new TypeInfoComputed(type, path);
      typeToTypeInfoComputed.put(type, tic);
    }
    return tic;
  }

  private boolean isAllowedByFilter(TreeLogger logger, JClassType classType,
      boolean isSpeculative) {
    return isAllowedByFilter(logger, typeFilter, classType, isSpeculative);
  }

  /**
   * Returns <code>true</code> if the type is Collection<? extends Object> or
   * Map<? extends Object, ? extends Object>.
   */
  private boolean isRawMapOrRawCollection(JClassType type) {
    if (type.asParameterizationOf(collectionClass) == collectionClass.asParameterizedByWildcards()) {
      return true;
    }

    if (type.asParameterizationOf(mapClass) == mapClass.asParameterizedByWildcards()) {
      return true;
    }

    return false;
  }

  private void logPath(TreeLogger logger, TypePath path) {
    if (path == null) {
      return;
    }

    logger.log(TreeLogger.INFO, path.toString());
    logPath(logger, path.getParent());
  }

  private void logReachableTypes(TreeLogger logger) {
    PrintWriter printWriter = null;
    if (logOutputStream != null) {
      // Route the TreeLogger output to an output stream.
      printWriter = new PrintWriter(logOutputStream);
      PrintWriterTreeLogger printWriterTreeLogger = new PrintWriterTreeLogger(
          printWriter);
      printWriterTreeLogger.setMaxDetail(TreeLogger.ALL);
      logger = printWriterTreeLogger;
    }

    logger.log(TreeLogger.INFO, "Reachable types computed on: "
        + new Date().toString());
    Set<JType> keySet = typeToTypeInfoComputed.keySet();
    JType[] types = keySet.toArray(new JType[0]);
    Arrays.sort(types, JTYPE_COMPARATOR);

    for (JType type : types) {
      TypeInfoComputed tic = typeToTypeInfoComputed.get(type);
      assert (tic != null);

      TreeLogger typeLogger = logger.branch(TreeLogger.INFO,
          tic.getType().getParameterizedQualifiedSourceName());
      TreeLogger serializationStatus = typeLogger.branch(TreeLogger.INFO,
          "Serialization status");
      if (tic.isInstantiable()) {
        serializationStatus.branch(TreeLogger.INFO, "Instantiable");
      } else {
        if (tic.isFieldSerializable()) {
          serializationStatus.branch(TreeLogger.INFO, "Field serializable");
        } else {
          serializationStatus.branch(TreeLogger.INFO, "Not serializable");
        }
      }

      TreeLogger pathLogger = typeLogger.branch(TreeLogger.INFO, "Path");

      logPath(pathLogger, tic.getPath());
      logger.log(TreeLogger.INFO, "");
    }

    if (printWriter != null) {
      printWriter.flush();
    }
  }

  /**
   * Mark arrays of <code>leafType</code> as instantiable, for arrays of
   * dimension up to <code>maxRank</code>.
   */
  private void markArrayTypesInstantiable(JType leafType, int maxRank,
      TypePath path) {
    for (int rank = 1; rank <= maxRank; ++rank) {
      JArrayType covariantArray = getArrayType(typeOracle, rank, leafType);

      TypeInfoComputed covariantArrayTic = getTypeInfoComputed(covariantArray,
          path);
      covariantArrayTic.setInstantiable(true);
    }
  }

  private boolean maybeInstantiable(TreeLogger logger, JClassType type) {
    boolean success = canBeInstantiated(logger, type, TreeLogger.DEBUG)
        && shouldConsiderFieldsForSerialization(logger, type, true);
    if (success) {
      logger.branch(TreeLogger.DEBUG,
          type.getParameterizedQualifiedSourceName() + " might be instantiable");
    }
    return success;
  }

  private boolean mightNotBeExposed(JGenericType baseType, int paramIndex) {
    TypeParameterFlowInfo flowInfo = getFlowInfo(baseType, paramIndex);
    return flowInfo.getMightNotBeExposed() || isManuallySerializable(baseType);
  }

  /**
   * Remove serializable types that were visited due to speculative paths but
   * are not really needed for serialization.
   *
   * NOTE: This is currently much more limited than it should be. For example, a
   * path sensitive prune could remove instantiable types also.
   */
  private void pruneUnreachableTypes() {
    /*
     * Record all supertypes of any instantiable type, whether or not they are
     * field serialziable.
     */
    Set<JType> supersOfInstantiableTypes = new LinkedHashSet<JType>();
    for (TypeInfoComputed tic : typeToTypeInfoComputed.values()) {
      if (tic.isInstantiable()) {
        JClassType type = tic.getType().getErasedType();
        JClassType sup = type;
        while (sup != null) {
          supersOfInstantiableTypes.add(sup.getErasedType());
          sup = sup.getErasedType().getSuperclass();
        }
      }
    }

    /*
     * Record any field serializable type that is not in the supers of any
     * instantiable type.
     */
    Set<JType> toKill = new LinkedHashSet<JType>();
    for (TypeInfoComputed tic : typeToTypeInfoComputed.values()) {
      if (tic.isFieldSerializable()
          && !supersOfInstantiableTypes.contains(tic.getType().getErasedType())) {
        toKill.add(tic.getType());
      }
    }

    /*
     * Remove any field serializable supers that cannot be reached from an
     * instantiable type.
     */
    for (JType type : toKill) {
      typeToTypeInfoComputed.remove(type);
    }
  }
}
TOP

Related Classes of com.google.gwt.user.rebind.rpc.SerializableTypeOracleBuilder$TypeInfoComputed

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.