Updates RPC to allow the proper use of generics and enumerated types.  Also added a new unit test to cover enumerated types over RPC.

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1592 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java b/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java
index 345d3fa..5915306 100644
--- a/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java
+++ b/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java
@@ -147,7 +147,15 @@
 
   @Override
   protected String getObjectTypeSignature(Object o) {
-    String typeName = o.getClass().getName();
+    Class<?> clazz = o.getClass();
+
+    if (o instanceof Enum) {
+      Enum<?> e = (Enum<?>) o;
+      clazz = e.getDeclaringClass();
+    }
+
+    String typeName = clazz.getName();
+    
     String serializationSignature = serializer.getSerializationSignature(typeName);
     if (serializationSignature != null) {
       typeName += "/" + serializationSignature;
diff --git a/user/src/com/google/gwt/user/rebind/rpc/CustomFieldSerializerValidator.java b/user/src/com/google/gwt/user/rebind/rpc/CustomFieldSerializerValidator.java
index 55e272d..63c2c62 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/CustomFieldSerializerValidator.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/CustomFieldSerializerValidator.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameter;
 import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
 import com.google.gwt.core.ext.typeinfo.JType;
 
@@ -49,28 +50,37 @@
       JClassType streamWriterClass, JClassType serializer, JClassType serializee) {
     List<String> reasons = new ArrayList<String>();
 
-    JMethod deserialize = serializer.findMethod("deserialize", new JType[] {
-        streamReaderClass, serializee});
-    if (!isValidCustomFieldSerializerMethod(deserialize, JPrimitiveType.VOID)) {
+    if (serializee.isEnum() != null) {
+      /*
+       * Enumerated types cannot have custom field serializers because it would
+       * introduce shared state between the client and the server via the
+       * enumerated constants.
+       */
+      reasons.add("Enumerated types cannot have custom field serializers.");
+      return reasons;
+    }
+
+    if (!hasSerializationMethod(streamReaderClass, "deserialize", serializer,
+        serializee)) {
+      // No valid deserialize method was found.
       reasons.add(MessageFormat.format(NO_DESERIALIZE_METHOD,
           serializer.getQualifiedSourceName(),
           streamReaderClass.getQualifiedSourceName(),
           serializee.getQualifiedSourceName()));
     }
 
-    JMethod serialize = serializer.findMethod("serialize", new JType[] {
-        streamWriterClass, serializee});
-    if (!isValidCustomFieldSerializerMethod(serialize, JPrimitiveType.VOID)) {
+    if (!hasSerializationMethod(streamWriterClass, "serialize", serializer,
+        serializee)) {
+      // No valid serialize method was found.
       reasons.add(MessageFormat.format(NO_SERIALIZE_METHOD,
           serializer.getQualifiedSourceName(),
           streamWriterClass.getQualifiedSourceName(),
           serializee.getQualifiedSourceName()));
     }
 
-    if (!serializee.isAbstract() && !serializee.isDefaultInstantiable()) {
-      JMethod instantiate = serializer.findMethod("instantiate",
-          new JType[] {streamReaderClass});
-      if (!isValidCustomFieldSerializerMethod(instantiate, serializee)) {
+    if (!Shared.isDefaultInstantiable(serializee)) {
+      if (!hasInstantiationMethod(streamReaderClass, serializer, serializee)) {
+        // Not default instantiable and no instantiate method was found.
         reasons.add(MessageFormat.format(NO_INSTANTIATE_METHOD,
             serializer.getQualifiedSourceName(),
             serializee.getQualifiedSourceName(),
@@ -81,10 +91,88 @@
     return reasons;
   }
 
-  private static boolean isValidCustomFieldSerializerMethod(JMethod method,
-      JType returnType) {
-    if (method == null || method.getReturnType() != returnType
-        || !method.isPublic() || !method.isStatic()) {
+  private static boolean hasInstantiationMethod(JClassType streamReaderClass,
+      JClassType serializer, JClassType serializee) {
+    JMethod[] overloads = serializer.getOverloads("instantiate");
+    for (JMethod overload : overloads) {
+      JParameter[] parameters = overload.getParameters();
+
+      if (parameters.length != 1) {
+        // Different overload
+        continue;
+      }
+
+      if (parameters[0].getType() != streamReaderClass) {
+        // First param is not a stream class
+        continue;
+      }
+
+      if (!isValidCustomFieldSerializerMethod(overload)) {
+        continue;
+      }
+
+      JType type = overload.getReturnType();
+      if (type.isPrimitive() != null) {
+        // Primitives are auto serialized so this can't be the right method
+        continue;
+      }
+
+      // TODO: if isArray answered yes to isClass this cast would not be
+      // necessary
+      JClassType clazz = (JClassType) type;
+      return clazz.isAssignableFrom(serializee);
+    }
+
+    return false;
+  }
+
+  private static boolean hasSerializationMethod(JClassType streamClass,
+      String methodName, JClassType serializer, JClassType serializee) {
+    JMethod[] overloads = serializer.getOverloads(methodName);
+    for (JMethod overload : overloads) {
+      JParameter[] parameters = overload.getParameters();
+
+      if (parameters.length != 2) {
+        // Different overload
+        continue;
+      }
+
+      if (parameters[0].getType() != streamClass) {
+        // First param is not a stream class
+        continue;
+      }
+
+      JParameter serializeeParam = parameters[1];
+      JType type = serializeeParam.getType();
+      if (type.isPrimitive() != null) {
+        // Primitives are auto serialized so this can't be the right method
+        continue;
+      }
+
+      // TODO: if isArray answered yes to isClass this cast would not be
+      // necessary
+      JClassType clazz = (JClassType) type;
+      if (clazz.isAssignableFrom(serializee)) {
+        if (isValidCustomFieldSerializerMethod(overload)
+            && overload.getReturnType() == JPrimitiveType.VOID) {
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+
+  private static boolean isValidCustomFieldSerializerMethod(JMethod method) {
+    if (method == null) {
+      return false;
+    }
+
+    if (!method.isStatic()) {
+      return false;
+    }
+
+    if (!method.isPublic()) {
       return false;
     }
 
diff --git a/user/src/com/google/gwt/user/rebind/rpc/FieldSerializerCreator.java b/user/src/com/google/gwt/user/rebind/rpc/FieldSerializerCreator.java
index fbaa697..3f052be 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/FieldSerializerCreator.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/FieldSerializerCreator.java
@@ -19,6 +19,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.typeinfo.JArrayType;
 import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JEnumType;
 import com.google.gwt.core.ext.typeinfo.JField;
 import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.user.client.rpc.SerializationException;
@@ -274,6 +275,8 @@
     JArrayType isArray = serializableClass.isArray();
     if (isArray != null) {
       writeArrayDeserializationStatements(isArray);
+    } else if (serializableClass.isEnum() != null) {
+      writeEnumDeserializationStatements(serializableClass.isEnum());
     } else {
       writeClassDeserializationStatements();
     }
@@ -282,6 +285,15 @@
     sourceWriter.println();
   }
 
+  private void writeEnumDeserializationStatements(JEnumType enumType) {
+    sourceWriter.println("// Enum deserialization is handled via the instantiate method");
+  }
+
+  private void writeEnumSerializationStatements(JEnumType enumType) {
+    sourceWriter.println("assert (instance != null);");
+    sourceWriter.println("streamWriter.writeInt(instance.ordinal());");
+  }
+
   /**
    * This method will generate a native JSNI accessor method for every field
    * that is protected, private using the "Violator" pattern to allow an
@@ -357,12 +369,13 @@
   }
 
   private void writeInstatiateMethod() {
-    if (serializableClass.isAbstract()) {
+    if (!serializationOracle.maybeInstantiated(serializableClass)) {
       return;
     }
 
     sourceWriter.print("public static ");
-    sourceWriter.print(serializableClass.getQualifiedSourceName());
+    String qualifiedSourceName = serializableClass.getQualifiedSourceName();
+    sourceWriter.print(qualifiedSourceName);
     sourceWriter.print(" instantiate(");
     sourceWriter.print(SerializationStreamReader.class.getName());
     sourceWriter.println(" streamReader) throws "
@@ -374,9 +387,14 @@
       sourceWriter.println("int rank = streamReader.readInt();");
       sourceWriter.println("return "
           + createArrayInstantiationExpression(isArray) + ";");
+    } else if (serializableClass.isEnum() != null) {
+      sourceWriter.println("int ordinal = streamReader.readInt();");
+      sourceWriter.println(qualifiedSourceName + "[] values = "
+          + qualifiedSourceName + ".values();");
+      sourceWriter.println("assert (ordinal >= 0 && ordinal < values.length);");
+      sourceWriter.println("return values[ordinal];");
     } else {
-      sourceWriter.println("return new "
-          + serializableClass.getQualifiedSourceName() + "();");
+      sourceWriter.println("return new " + qualifiedSourceName + "();");
     }
 
     sourceWriter.outdent();
@@ -396,6 +414,8 @@
     JArrayType isArray = serializableClass.isArray();
     if (isArray != null) {
       writeArraySerializationStatements(isArray);
+    } else if (serializableClass.isEnum() != null) {
+      writeEnumSerializationStatements(serializableClass.isEnum());
     } else {
       writeClassSerializationStatements();
     }
diff --git a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
index 3fce25b..1ce4f21 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
@@ -21,8 +21,8 @@
 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.JBound;
 import com.google.gwt.core.ext.typeinfo.JClassType;
-import com.google.gwt.core.ext.typeinfo.JConstructor;
 import com.google.gwt.core.ext.typeinfo.JField;
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.core.ext.typeinfo.JPackage;
@@ -30,6 +30,7 @@
 import com.google.gwt.core.ext.typeinfo.JParameterizedType;
 import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
 import com.google.gwt.core.ext.typeinfo.JType;
+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.user.client.rpc.IncompatibleRemoteServiceException;
@@ -193,7 +194,8 @@
     /**
      * An issue that prevents a type from being serializable.
      */
-    private class SerializationIssue implements Comparable<SerializationIssue> {
+    private static class SerializationIssue implements
+        Comparable<SerializationIssue> {
       public final boolean isSpeculative;
       public final String issueMessage;
 
@@ -271,11 +273,11 @@
     private TypeState state = NOT_CHECKED;
 
     /**
-     * {@link JType} associated with this metadata.
+     * {@link JClassType} associated with this metadata.
      */
-    private final JType type;
+    private final JClassType type;
 
-    public TypeInfoComputed(JType type) {
+    public TypeInfoComputed(JClassType type) {
       this.type = type;
     }
 
@@ -284,7 +286,7 @@
           issueMessage));
     }
 
-    public JType getType() {
+    public JClassType getType() {
       return type;
     }
 
@@ -615,12 +617,12 @@
     try {
       // String is always instantiable.
       JClassType stringType = typeOracle.getType(String.class.getName());
-      if (!checkTypeInstantiable(rootLogger, stringType, false, false)) {
+      if (!checkTypeInstantiable(rootLogger, stringType, false)) {
         throw new UnableToCompleteException();
       }
       // IncompatibleRemoteServiceException is always serializable
       JClassType icseType = typeOracle.getType(IncompatibleRemoteServiceException.class.getName());
-      if (!checkTypeInstantiable(rootLogger, icseType, false, false)) {
+      if (!checkTypeInstantiable(rootLogger, icseType, false)) {
         throw new UnableToCompleteException();
       }
     } catch (NotFoundException e) {
@@ -667,22 +669,36 @@
       }
     }
 
-    Set<JType> possiblyInstantiatedTypes = new HashSet<JType>();
-    List<JType> serializableTypesList = new ArrayList<JType>();
+    Set<JClassType> possiblyInstantiatedTypes = new HashSet<JClassType>();
+    List<JClassType> serializableTypesList = new ArrayList<JClassType>();
     for (TypeInfoComputed tic : typeToTypeInfoComputed.values()) {
-      JType type = tic.getType();
+      JClassType type = tic.getType();
+
+      if (type.isTypeParameter() != null || type.isWildcard() != null) {
+        /*
+         * Wildcard and type parameters types are ignored here. Their
+         * corresponding subtypes will be part of the typeToTypeInfoComputed
+         * set. TypeParameters can reach here if there are methods on the
+         * RemoteService that declare their own type parameters.
+         */
+        continue;
+      }
+
       // Only record real types
-      if (type.isParameterized() == null) {
-        if (tic.isInstantiable()) {
-          possiblyInstantiatedTypes.add(type);
-        }
-        if (tic.isFieldSerializable()) {
-          serializableTypesList.add(type);
-        }
+      if (type.isParameterized() != null) {
+        type = type.isParameterized().getRawType();
+      }
+
+      if (tic.isInstantiable()) {
+        possiblyInstantiatedTypes.add(type);
+      }
+
+      if (tic.isFieldSerializable()) {
+        serializableTypesList.add(type);
       }
     }
 
-    JType[] serializableTypes = new JType[serializableTypesList.size()];
+    JClassType[] serializableTypes = new JClassType[serializableTypesList.size()];
     serializableTypesList.toArray(serializableTypes);
 
     Arrays.sort(serializableTypes, new Comparator<JType>() {
@@ -721,11 +737,21 @@
     JClassType[] allTypes = typeOracle.getJavaLangObject().getSubtypes();
     for (JClassType cls : allTypes) {
       if (getTypeInfo(cls).isDeclaredSerializable()) {
-        checkTypeInstantiable(localLogger, cls, true, true);
+        checkTypeInstantiable(localLogger, cls, true);
       }
     }
   }
 
+  private boolean checkArrayInstantiable(TreeLogger logger,
+      JArrayType arrayType, TypeInfoComputed tic, boolean isSpeculative) {
+    TreeLogger branch = logger.branch(TreeLogger.DEBUG,
+        "Analyzing component type:", null);
+    boolean success = checkTypeInstantiable(branch,
+        arrayType.getComponentType(), isSpeculative);
+    tic.setInstantiable(success);
+    return success;
+  }
+
   private boolean checkClassOrInterfaceInstantiable(TreeLogger logger,
       JClassType type, boolean isSpeculative) {
 
@@ -752,6 +778,32 @@
     } else {
       assert (typeInfo.isAutoSerializable());
 
+      if (type.isEnum() != null) {
+        if (type.isLocalType()) {
+          /*
+           * Quietly ignore local enum types.
+           */
+          tic.setInstantiable(false);
+          return false;
+        } else {
+          /*
+           * Enumerated types are serializable by default, but they do not have 
+           * their state automatically or manually serialized.  So, consider it 
+           * serializable but do not check its fields.
+           */
+          return true;
+        }
+      }
+      
+      if (type.isPrivate()) {
+        /*
+         * Quietly ignore private types since these cannot be instantiated
+         * from the generated field serializers.
+         */
+        tic.setInstantiable(false);
+        return false;
+      }
+      
       if (type.isLocalType()) {
         markAsUninstantiableAndLog(
             logger,
@@ -777,17 +829,7 @@
         return false;
       }
 
-      boolean isDefaultInstantiable = false;
-      if (type.getConstructors().length == 0) {
-        isDefaultInstantiable = true;
-      } else {
-        JConstructor ctor = type.findConstructor(new JType[0]);
-        if (ctor != null && !ctor.isPrivate()) {
-          isDefaultInstantiable = true;
-        }
-      }
-
-      if (!isDefaultInstantiable) {
+      if (!Shared.isDefaultInstantiable(type)) {
         // Warn and return false.
         logger.log(
             TreeLogger.WARN,
@@ -797,7 +839,6 @@
       }
     }
 
-    // Check all fields, including inherited fields
     if (!checkFields(logger, type, isSpeculative)) {
       return false;
     }
@@ -859,7 +900,7 @@
               "Object was reached from a manually serializable type", null));
         } else {
           allSucceeded &= checkTypeInstantiable(fieldLogger, fieldType,
-              isSpeculative, false);
+              isSpeculative);
         }
       }
     }
@@ -871,27 +912,6 @@
     return succeeded;
   }
 
-  private void checkForUnparameterizedType(TreeLogger logger, JType type) {
-    if (type.isParameterized() != null) {
-      return;
-    }
-
-    JClassType classOrInterface = type.isClassOrInterface();
-    if (classOrInterface != null) {
-      if (classOrInterface.isAssignableTo(collectionClass)
-          || classOrInterface.isAssignableTo(mapClass)) {
-        TreeLogger localLogger = logger.branch(
-            TreeLogger.WARN,
-            "Type '"
-                + type.getQualifiedSourceName()
-                + "' should be parameterized to help the compiler produce the smallest code size possible for your module. Since the gwt.typeArgs javadoc annotation is missing, all subtypes of Object will be analyzed for serializability even if they are not directly or indirectly used",
-            null);
-
-        checkAllSubtypesOfObject(localLogger);
-      }
-    }
-  }
-
   private void checkMethods(TreeLogger logger, JClassType classOrInterface) {
     if (isDefinedInJREEmulation(classOrInterface)) {
       // JRE emulation classes are never used on the server; skip the check
@@ -911,17 +931,42 @@
     }
   }
 
+  /**
+   * Returns <code>true</code> if all of the type arguments of the
+   * parameterized type are themselves instantiable.
+   */
+  private boolean checkTypeArgumentsInstantiable(TreeLogger logger,
+      JParameterizedType parameterizedType, boolean isSpeculative) {
+    TreeLogger branch = logger.branch(TreeLogger.DEBUG, "Analyzing type args",
+        null);
+    JClassType[] typeArgs = parameterizedType.getTypeArgs();
+    boolean allSucceeded = true;
+    for (JClassType typeArg : typeArgs) {
+      allSucceeded &= checkTypeInstantiable(branch, typeArg, isSpeculative);
+    }
+
+    return allSucceeded;
+  }
+
   private boolean checkTypeInstantiable(TreeLogger logger, JType type,
-      boolean isSpeculative, boolean rawTypeOk) {
+      boolean isSpeculative) {
     assert (type != null);
     if (type.isPrimitive() != null) {
       return true;
     }
 
-    TreeLogger localLogger = logger.branch(TreeLogger.DEBUG,
-        type.getParameterizedQualifiedSourceName(), null);
+    assert (type instanceof JClassType);
 
-    TypeInfoComputed tic = getTypeInfoComputed(type);
+    JClassType classType = (JClassType) type;
+    if (classType.isGenericType() != null) {
+      // Treat generic types as raw types.
+      classType = classType.isGenericType().getRawType();
+    }
+
+    TreeLogger localLogger = logger.branch(TreeLogger.DEBUG,
+        classType.getParameterizedQualifiedSourceName(), null);
+
+    TypeInfoComputed tic = getTypeInfoComputed(classType);
     if (tic.isPendingInstantiable()) {
       // just early out and pretend we will succeed
       return true;
@@ -931,7 +976,7 @@
 
     tic.setPendingInstantiable();
 
-    if (type.getLeafType() == typeOracle.getJavaLangObject()) {
+    if (classType.getLeafType() == typeOracle.getJavaLangObject()) {
       markAsUninstantiableAndLog(
           logger,
           isSpeculative,
@@ -940,30 +985,13 @@
       return false;
     }
 
-    if (type.isParameterized() != null) {
-      JParameterizedType parameterized = type.isParameterized();
-      boolean allSucceeded = checkTypeInstantiable(localLogger.branch(
-          TreeLogger.DEBUG, "Analyzing raw type", null),
-          parameterized.getRawType(), isSpeculative, true);
-
-      TreeLogger branch = localLogger.branch(TreeLogger.DEBUG,
-          "Analyzing type args", null);
-      JType[] typeArgs = parameterized.getTypeArgs();
-      for (int i = 0; i < typeArgs.length; ++i) {
-        allSucceeded &= checkTypeInstantiable(branch, typeArgs[i],
-            isSpeculative, false);
-      }
-      tic.setInstantiable(allSucceeded);
-      return allSucceeded;
-    } else if (type.isArray() != null) {
-      TreeLogger branch = localLogger.branch(TreeLogger.DEBUG,
-          "Analyzing component type:", null);
-      boolean success = checkTypeInstantiable(branch,
-          type.isArray().getComponentType(), isSpeculative, false);
-      tic.setInstantiable(success);
-      return success;
-    } else if (type.isClassOrInterface() != null) {
-      JClassType classType = type.isClassOrInterface();
+    if (classType.isArray() != null) {
+      return checkArrayInstantiable(logger, classType.isArray(), tic,
+          isSpeculative);
+    } else if (classType.isWildcard() != null) {
+      return checkWildcardInstantiable(logger, classType.isWildcard(), tic,
+          isSpeculative);
+    } else if (classType.isClassOrInterface() != null) {
       TypeInfo typeInfo = getTypeInfo(classType);
       if (isSpeculative && typeInfo.isDirectlySerializable()) {
         isSpeculative = false;
@@ -976,6 +1004,37 @@
         anySubtypes = true;
       }
 
+      if (classType.isParameterized() != null) {
+        /*
+         * Backwards compatibility. The number of parameterization arguments
+         * specified via gwt.typeArgs could exceed the number of formal
+         * parameters declared on the generic type. Therefore have to explicitly
+         * visit them here and they must all be instantiable.
+         */
+        JParameterizedType parameterizedType = classType.isParameterized();
+        if (!checkTypeArgumentsInstantiable(localLogger, parameterizedType,
+            isSpeculative)) {
+          return false;
+        }
+      } else if (classType.isRawType() != null) {
+        TreeLogger rawTypeLogger = logger.branch(
+            TreeLogger.WARN,
+            "Type '"
+                + classType.getQualifiedSourceName()
+                + "' should be parameterized to help the compiler produce the smallest code size possible for your module.",
+            null);
+
+        if (classType.isAssignableTo(collectionClass)
+            || classType.isAssignableTo(mapClass)) {
+          /*
+           * Backwards compatibility. Raw collections or maps force all object
+           * subtypes to be considered. Fall through to the normal class
+           * handling.
+           */
+          checkAllSubtypesOfObject(rawTypeLogger);
+        }
+      }
+
       // Speculatively check all subtypes.
       JClassType[] subtypes = classType.getSubtypes();
       if (subtypes.length > 0) {
@@ -983,30 +1042,26 @@
             "Analyzing subclasses:", null);
 
         for (JClassType subType : subtypes) {
-          if (checkClassOrInterfaceInstantiable(subLogger, subType, true)) {
+          if (checkClassOrInterfaceInstantiable(subLogger.branch(
+              TreeLogger.DEBUG, subType.getParameterizedQualifiedSourceName(),
+              null), subType, true)) {
             getTypeInfoComputed(subType).setInstantiable(true);
             anySubtypes = true;
           }
         }
       }
 
-      if (anySubtypes) {
-        if (!rawTypeOk) {
-          /*
-           * There is at least one instantiable type and raw types are not
-           * allowed; check if this type is an unparameterized type.
-           */
-          checkForUnparameterizedType(logger, classType);
-        }
-      } else if (!isSpeculative) {
+      if (!anySubtypes && !isSpeculative) {
         // No instantiable types were found
-        logger.log(
-            getLogLevel(isSpeculative),
+        markAsUninstantiableAndLog(
+            logger,
+            isSpeculative,
             "Type '"
-                + type.getParameterizedQualifiedSourceName()
+                + classType.getParameterizedQualifiedSourceName()
                 + "' was not serializable and has no concrete serializable subtypes",
-            null);
+            tic);
       }
+
       return anySubtypes;
     } else {
       assert (false);
@@ -1014,6 +1069,26 @@
     }
   }
 
+  private boolean checkWildcardInstantiable(TreeLogger logger,
+      JWildcardType wildcard, TypeInfoComputed tic, boolean isSpeculative) {
+    JBound bounds = wildcard.getBounds();
+    boolean success;
+    if (bounds.isLowerBound() != null) {
+      // Fail since ? super T for any T implies object also
+      markAsUninstantiableAndLog(logger, isSpeculative,
+          "In order to produce smaller client-side code, 'Object' is not allowed; '"
+              + wildcard.getQualifiedSourceName() + "' includes Object.", tic);
+
+      success = false;
+    } else {
+      JClassType firstBound = bounds.getFirstBound();
+      success = checkTypeInstantiable(logger, firstBound, isSpeculative);
+    }
+
+    tic.setInstantiable(success);
+    return success;
+  }
+
   private TypeInfo getTypeInfo(JClassType type) {
     TypeInfo ti = typeToTypeInfo.get(type);
     if (ti == null) {
@@ -1023,7 +1098,7 @@
     return ti;
   }
 
-  private TypeInfoComputed getTypeInfoComputed(JType type) {
+  private TypeInfoComputed getTypeInfoComputed(JClassType type) {
     TypeInfoComputed tic = typeToTypeInfoComputed.get(type);
     if (tic == null) {
       tic = new TypeInfoComputed(type);
@@ -1090,7 +1165,7 @@
             "Return type: " + returnType.getParameterizedQualifiedSourceName(),
             null);
         allSucceeded &= checkTypeInstantiable(returnTypeLogger, returnType,
-            false, false);
+            false);
       }
 
       JParameter[] params = method.getParameters();
@@ -1098,8 +1173,7 @@
         TreeLogger paramLogger = methodLogger.branch(TreeLogger.DEBUG,
             "Parameter: " + param.toString(), null);
         JType paramType = param.getType();
-        allSucceeded &= checkTypeInstantiable(paramLogger, paramType, false,
-            false);
+        allSucceeded &= checkTypeInstantiable(paramLogger, paramType, false);
       }
 
       JType[] exs = method.getThrows();
@@ -1116,7 +1190,7 @@
                 null);
           }
 
-          allSucceeded &= checkTypeInstantiable(throwsLogger, ex, false, false);
+          allSucceeded &= checkTypeInstantiable(throwsLogger, ex, false);
         }
       }
     }
@@ -1126,5 +1200,4 @@
       throw new UnableToCompleteException();
     }
   }
-
 }
diff --git a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleImpl.java b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleImpl.java
index cc91e55..efe05a9 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleImpl.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleImpl.java
@@ -68,14 +68,14 @@
     TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add("java.lang.Throwable");
   }
 
-  private final Set<JType> serializableTypesSet;
+  private final Set<JClassType> serializableTypesSet;
   private final TypeOracle typeOracle;
-  private final Set<JType> possiblyInstantiatedTypes;
+  private final Set<JClassType> possiblyInstantiatedTypes;
 
   public SerializableTypeOracleImpl(TypeOracle typeOracle,
-      JType[] serializableTypes, Set<JType> possiblyInstantiatedTypes) {
+      JClassType[] serializableTypes, Set<JClassType> possiblyInstantiatedTypes) {
 
-    serializableTypesSet = new TreeSet<JType>(TYPE_COMPARATOR);
+    serializableTypesSet = new TreeSet<JClassType>(TYPE_COMPARATOR);
     serializableTypesSet.addAll(Arrays.asList(serializableTypes));
     this.typeOracle = typeOracle;
 
diff --git a/user/src/com/google/gwt/user/rebind/rpc/Shared.java b/user/src/com/google/gwt/user/rebind/rpc/Shared.java
index 0c4d6b3..c263436 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/Shared.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/Shared.java
@@ -20,6 +20,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.typeinfo.JArrayType;
 import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JConstructor;
 import com.google.gwt.core.ext.typeinfo.JParameterizedType;
 import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
 import com.google.gwt.core.ext.typeinfo.JType;
@@ -53,6 +54,33 @@
   }
 
   /**
+   * Returns <code>true</code> if the type is a non-abstract class that either
+   * has no constructors or it has a non-private, no argument constructor.
+   * 
+   * @param type
+   * @return <code>true</code> if the type is a non-abstract class that either
+   *         has no constructors or it has a non-private, no argument
+   *         constructor
+   */
+  static boolean isDefaultInstantiable(JClassType type) {
+    if (type.isInterface() != null || type.isAbstract()) {
+      return false;
+    }
+
+    boolean isDefaultInstantiable = false;
+    if (type.getConstructors().length == 0) {
+      isDefaultInstantiable = true;
+    } else {
+      JConstructor ctor = type.findConstructor(new JType[0]);
+      if (ctor != null && !ctor.isPrivate()) {
+        isDefaultInstantiable = true;
+      }
+    }
+
+    return isDefaultInstantiable;
+  }
+
+  /**
    * Returns <code>true</code> if the generated code should enforce type
    * versioning.
    */
diff --git a/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java b/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java
index e30ac4e..0fa0d59 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java
@@ -204,19 +204,6 @@
     return composerFactory.createSourceWriter(ctx, printWriter);
   }
 
-  private boolean isAbstractType(JType type) {
-    JClassType classType = type.isClassOrInterface();
-    if (classType != null) {
-      if (classType.isAbstract()) {
-        return true;
-      }
-    }
-
-    // Primitives, arrays, and non-abstract classes fall-through to here.
-    //
-    return false;
-  }
-
   /**
    * Return <code>true</code> if this type is concrete and has a custom field
    * serializer that does not declare an instantiate method.
@@ -227,7 +214,7 @@
   private boolean needsCreateMethod(JType type) {
     // If this type is abstract it will not be serialized into the stream
     //
-    if (isAbstractType(type)) {
+    if (!serializationOracle.maybeInstantiated(type)) {
       return false;
     }
 
@@ -263,7 +250,7 @@
       boolean needComma = false;
       for (int index = 0; index < types.length; ++index) {
         JType type = types[index];
-        if (isAbstractType(type)) {
+        if (!serializationOracle.maybeInstantiated(type)) {
           continue;
         }
 
@@ -375,7 +362,7 @@
       boolean needComma = false;
       for (int index = 0; index < types.length; ++index) {
         JType type = types[index];
-        if (isAbstractType(type)) {
+        if (!serializationOracle.maybeInstantiated(type)) {
           continue;
         }
         if (needComma) {
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
index 55d89ac..2f56dc7 100644
--- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
+++ b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
@@ -467,6 +467,8 @@
           instance);
     } else if (instanceClass.isArray()) {
       deserializeArray(instanceClass, instance);
+    } else if (instanceClass.isEnum()) {
+      // Enums are deserialized when they are instantiated
     } else {
       deserializeClass(instanceClass, instance);
     }
@@ -512,6 +514,11 @@
       int length = readInt();
       Class<?> componentType = instanceClass.getComponentType();
       return Array.newInstance(componentType, length);
+    } else if (instanceClass.isEnum()) {
+      Enum[] enumConstants = (Enum[]) instanceClass.getEnumConstants();
+      int ordinal = readInt();
+      assert (ordinal >= 0 && ordinal < enumConstants.length);
+      return enumConstants[ordinal];
     } else {
       return instanceClass.newInstance();
     }
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java
index 9a0dd0e..231e454 100644
--- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java
+++ b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java
@@ -330,6 +330,22 @@
   }
 
   /**
+   * Returns the {@link Class} instance to use for serialization.  Enumerations 
+   * are serialized as their declaring class while all others are serialized 
+   * using their true class instance.
+   */
+  private static Class<?> getClassForSerialization(Object instance) {
+    assert (instance != null);
+    
+    if (instance instanceof Enum) {
+      Enum<?> e = (Enum<?>) instance;
+      return e.getDeclaringClass();
+    } else {
+      return instance.getClass();
+    }
+  }
+
+  /**
    * Returns <code>true</code> if the character requires the \\uXXXX unicode
    * character escape sequence. This is necessary if the raw character could be
    * consumed and/or interpreted as a special character when the JSON encoded
@@ -515,10 +531,13 @@
 
   @Override
   protected String getObjectTypeSignature(Object instance) {
+    assert (instance != null);
+    
+    Class<?> clazz = getClassForSerialization(instance);
     if (shouldEnforceTypeVersioning()) {
-      return SerializabilityUtil.encodeSerializedInstanceReference(instance.getClass());
+      return SerializabilityUtil.encodeSerializedInstanceReference(clazz);
     } else {
-      return SerializabilityUtil.getSerializedTypeName(instance.getClass());
+      return SerializabilityUtil.getSerializedTypeName(clazz);
     }
   }
 
@@ -532,7 +551,8 @@
       throws SerializationException {
     assert (instance != null);
 
-    Class<?> clazz = instance.getClass();
+    Class<?> clazz = getClassForSerialization(instance);
+
     serializationPolicy.validateSerialize(clazz);
 
     serializeImpl(instance, clazz);
@@ -609,6 +629,8 @@
       serializeWithCustomSerializer(customSerializer, instance, instanceClass);
     } else if (instanceClass.isArray()) {
       serializeArray(instanceClass, instance);
+    } else if (instanceClass.isEnum()) {
+      writeInt(((Enum<?>) instance).ordinal());
     } else {
       // Regular class instance
       serializeClass(instance, instanceClass);
diff --git a/user/test/com/google/gwt/user/client/rpc/EnumsTest.java b/user/test/com/google/gwt/user/client/rpc/EnumsTest.java
new file mode 100644
index 0000000..a3b2fde
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/rpc/EnumsTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2007 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.client.rpc;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.user.client.rpc.EnumsTestService.Basic;
+import com.google.gwt.user.client.rpc.EnumsTestService.Complex;
+import com.google.gwt.user.client.rpc.EnumsTestService.Subclassing;
+
+/**
+ * Tests enums over RPC.
+ */
+public class EnumsTest extends GWTTestCase {
+  private static final int TEST_DELAY = 5000;
+
+  private static EnumsTestServiceAsync getService() {
+    EnumsTestServiceAsync service = GWT.create(EnumsTestService.class);
+    ServiceDefTarget target = (ServiceDefTarget) service;
+    target.setServiceEntryPoint(GWT.getModuleBaseURL() + "enums");
+    return service;
+  }
+
+  private static void rethrowException(Throwable caught) {
+    if (caught instanceof RuntimeException) {
+      throw (RuntimeException) caught;
+    } else {
+      throw new RuntimeException(caught);
+    }
+  }
+  
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.user.RPCSuite";
+  }
+
+  /**
+   * Test that basic enums can be used over RPC.
+   */
+  public void testBasicEnums() {
+    delayTestFinish(TEST_DELAY);
+    getService().echo(Basic.A, new AsyncCallback<Basic>() {
+      public void onFailure(Throwable caught) {
+        rethrowException(caught);
+      }
+
+      public void onSuccess(Basic result) {
+        assertEquals(Basic.A, result);
+        finishTest();
+      }});
+  }
+  
+  /**
+   * Test that complex enums with state and non-default constructors can be used
+   * over RPC and that the client state does not change.
+   */
+  public void testComplexEnums() {
+    delayTestFinish(TEST_DELAY);
+    
+    Complex a = Complex.A;
+    a.value = "client";
+    
+    getService().echo(Complex.A, new AsyncCallback<Complex>() {
+      public void onFailure(Throwable caught) {
+        rethrowException(caught);
+      }
+
+      public void onSuccess(Complex result) {
+        assertEquals(Complex.A, result);
+        
+        // Ensure that the server's changes did not impact us.
+        assertEquals("client", result.value);
+        
+        finishTest();
+      }});
+  }
+  
+  /**
+   * Test that null can be used as an enumeration.
+   */
+  public void testNull() {
+    delayTestFinish(TEST_DELAY);
+    
+    getService().echo((Basic) null, new AsyncCallback<Basic>() {
+      public void onFailure(Throwable caught) {
+        rethrowException(caught);
+      }
+
+      public void onSuccess(Basic result) {
+        assertNull(result);
+        finishTest();
+      }});
+  }
+  
+  /**
+   * Test that enums with subclasses can be passed over RPC.
+   */
+  public void testSubclassingEnums() {
+    delayTestFinish(TEST_DELAY);
+    
+    getService().echo(Subclassing.A, new AsyncCallback<Subclassing>() {
+      public void onFailure(Throwable caught) {
+        rethrowException(caught);
+      }
+
+      public void onSuccess(Subclassing result) {
+        assertEquals(Subclassing.A, result);
+        finishTest();
+      }});
+  }
+}
diff --git a/user/test/com/google/gwt/user/client/rpc/EnumsTestService.java b/user/test/com/google/gwt/user/client/rpc/EnumsTestService.java
new file mode 100644
index 0000000..6bd7f1d
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/rpc/EnumsTestService.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2007 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.client.rpc;
+
+/**
+ * RemoteService used to test the use of enums over RPC.
+ */
+public interface EnumsTestService extends RemoteService {
+  /**
+   * Exception thrown when the enumeration state from the client makes it to the
+   * server.
+   */
+  public class EnumStateModificationException extends SerializableException {
+    public EnumStateModificationException() {
+    }
+    
+    public EnumStateModificationException(String msg) {
+      super(msg);
+    }
+  }
+  
+  /**
+   * Simplest enum possible; no subtypes or enum constant specific state.
+   */
+  public enum Basic {
+    A, B, C
+  }
+
+  /**
+   * Enum that has no default constructor and includes state.
+   */
+  public enum Complex {
+    A("X"), B("Y"), C("Z");
+
+    public String value;
+
+    Complex(String value) {
+      this.value = value;
+    }
+
+    public String value() {
+      return value;
+    }
+  }
+
+  /**
+   * Enum that has local subtypes.
+   */
+  public enum Subclassing {
+    A {
+      @Override
+      public String value() {
+        return "X";
+      }
+    },
+    B {
+      @Override
+      public String value() {
+        return "Y";
+      }
+    },
+    C {
+      @Override
+      public String value() {
+        return "Z";
+      }
+    };
+
+    public abstract String value();
+  }
+  
+  Basic echo(Basic value);
+  Complex echo(Complex value) throws EnumStateModificationException;
+  Subclassing echo(Subclassing value);
+}
diff --git a/user/test/com/google/gwt/user/client/rpc/EnumsTestServiceAsync.java b/user/test/com/google/gwt/user/client/rpc/EnumsTestServiceAsync.java
new file mode 100644
index 0000000..d011d4d
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/rpc/EnumsTestServiceAsync.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2007 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.client.rpc;
+
+import com.google.gwt.user.client.rpc.EnumsTestService.Basic;
+import com.google.gwt.user.client.rpc.EnumsTestService.Complex;
+import com.google.gwt.user.client.rpc.EnumsTestService.Subclassing;
+
+/**
+ *
+ */
+public interface EnumsTestServiceAsync {
+  void echo(Basic value, AsyncCallback<Basic> callback);
+  void echo(Complex value, AsyncCallback<Complex> callback);
+  void echo(Subclassing value, AsyncCallback<Subclassing> callback);
+}
diff --git a/user/test/com/google/gwt/user/server/rpc/EnumsTestServiceImpl.java b/user/test/com/google/gwt/user/server/rpc/EnumsTestServiceImpl.java
new file mode 100644
index 0000000..32fac31
--- /dev/null
+++ b/user/test/com/google/gwt/user/server/rpc/EnumsTestServiceImpl.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2007 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;
+
+import com.google.gwt.user.client.rpc.EnumsTestService;
+
+/**
+ * Simple remote service used to echo enumerated types.
+ */
+public class EnumsTestServiceImpl extends RemoteServiceServlet implements
+    EnumsTestService {
+
+  public Basic echo(Basic value) {
+    return value;
+  }
+
+  public Complex echo(Complex value) throws EnumStateModificationException {
+    if ("client".equals(value.value)) {
+      throw new EnumStateModificationException("Client-side enumeration state made it to the server");
+    }
+    
+    return value;
+  }
+
+  public Subclassing echo(Subclassing value) {
+    return value;
+  }
+}