Reworked error logging in STOB, coincidentally gaining a 22% compile-time improvement in DynaTable sample by improving short-circuiting of already-examined types and avoiding "speculative" STOB processing. Basic idea is that errors are accumulated in a ProblemReport until we later know the overall result and can decide whether and how to emit them.
Issues: 3461
Review by: spoon
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5256 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/rebind/rpc/ProblemReport.java b/user/src/com/google/gwt/user/rebind/rpc/ProblemReport.java
new file mode 100644
index 0000000..56b000b
--- /dev/null
+++ b/user/src/com/google/gwt/user/rebind/rpc/ProblemReport.java
@@ -0,0 +1,223 @@
+/*
+ * 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.TreeLogger;
+import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * A collection of reported problems; these are accumulated during the
+ * SerializableTypeOracleBuilder's isSerializable analysis, and what to do about
+ * the problems is decided only later.
+ */
+public class ProblemReport {
+
+ /**
+ * Priority of problems. {@link #FATAL} problems will fail a build that
+ * would otherwise have succeeded, for example because of a bad custom
+ * serializer used only as a subclass of a superclass with other viable
+ * subtypes. {@link #DEFAULT} problems might or might not be fatal,
+ * depending on overall results accumulated later. {@link #AUXILIARY}
+ * problems are not fatal, and often not even problems by themselves, but
+ * diagnostics related to default problems (e.g. type filtration, which
+ * might suppress an intended-to-serialize class).
+ */
+ public enum Priority { FATAL, DEFAULT, AUXILIARY}
+
+ private Map<JClassType, List<String>> allProblems;
+ private Map<JClassType, List<String>> auxiliaries;
+ private Map<JClassType, List<String>> fatalProblems;
+ private JClassType contextType;
+
+ /**
+ * Creates a new, empty, context-less ProblemReport.
+ */
+ public ProblemReport() {
+ Comparator<JClassType> comparator = new Comparator<JClassType>() {
+ public int compare(JClassType o1, JClassType o2) {
+ assert o1 != null;
+ assert o2 != null;
+ return o1.getParameterizedQualifiedSourceName().compareTo(
+ o2.getParameterizedQualifiedSourceName());
+ }
+ };
+ allProblems = new TreeMap<JClassType, List<String>>(comparator);
+ auxiliaries = new TreeMap<JClassType, List<String>>(comparator);
+ fatalProblems = new TreeMap<JClassType, List<String>>(comparator);
+ contextType = null;
+ }
+
+ /**
+ * Adds a problem for a given type. This also sorts the problems into
+ * collections by priority.
+ *
+ * @param type the problematic type
+ * @param message the description of the problem
+ * @param priority priority of the problem.
+ * @param extraLines additional continuation lines for the message, usually
+ * for additional explanations.
+ */
+ public void add(JClassType type, String message, Priority priority,
+ String... extraLines) {
+ String contextString = "";
+ if (contextType != null) {
+ contextString = " (reached via " +
+ contextType.getParameterizedQualifiedSourceName() + ")";
+ }
+ message = message + contextString;
+ for (String line : extraLines) {
+ message = message + "\n " + line;
+ }
+ if (priority == Priority.AUXILIARY) {
+ addToMap(type, message, auxiliaries);
+ return;
+ }
+
+ // both FATAL and DEFAULT problems go in allProblems...
+ addToMap(type, message, allProblems);
+
+ // only FATAL problems go in fatalProblems...
+ if (priority == Priority.FATAL) {
+ addToMap(type, message, fatalProblems);
+ }
+ }
+
+ /**
+ * Returns list of auxiliary "problems" logged against a given type.
+ *
+ * @param type type to fetch problems for
+ * @return {@code null} if no auxiliaries were logged. Otherwise, a list
+ * of strings describing messages, including the context in which the
+ * problem was found.
+ */
+ public List<String> getAuxiliaryMessagesForType(JClassType type) {
+ List<String> list = auxiliaries.get(type);
+ if (list == null) {
+ list = new ArrayList<String>(0);
+ }
+ return list;
+ }
+
+ /**
+ * Returns list of problems logged against a given type.
+ *
+ * @param type type to fetch problems for
+ * @return {@code null} if no problems were logged. Otherwise, a list
+ * of strings describing problems, including the context in which the
+ * problem was found.
+ */
+ public List<String> getProblemsForType(JClassType type) {
+ List<String> list = allProblems.get(type);
+ if (list == null) {
+ list = new ArrayList<String>(0);
+ }
+ return list;
+ }
+
+ /**
+ * Returns the number of types against which problems were reported.
+ *
+ * @return number of problematic types
+ */
+ public int getProblemTypeCount() {
+ return allProblems.size();
+ }
+
+ /**
+ * Were any problems reported as "fatal"?
+ */
+ public boolean hasFatalProblems() {
+ return !fatalProblems.isEmpty();
+ }
+
+ /**
+ * Reports all problems to the logger supplied, at the log level supplied.
+ * The problems are assured of being reported in lexographic order of
+ * type names.
+ *
+ * @param logger logger to receive problem reports
+ * @param problemLevel severity level at which to report problems.
+ * @param auxLevel severity level at which to report any auxiliary messages.
+ */
+ public void report(TreeLogger logger, TreeLogger.Type problemLevel,
+ TreeLogger.Type auxLevel) {
+ doReport(logger, auxLevel, auxiliaries);
+ doReport(logger, problemLevel, allProblems);
+ }
+
+ /**
+ * Reports only urgent problems to the logger supplied, at the log level
+ * supplied. The problems are assured of being reported in lexographic
+ * order of type names.
+ *
+ * @param logger logger to receive problem reports
+ * @param level severity level at which to report problems.
+ */
+ public void reportFatalProblems(TreeLogger logger, TreeLogger.Type level) {
+ doReport(logger, level, fatalProblems);
+ }
+
+ /**
+ * Sets the context type currently being analyzed. Problems found will
+ * include reference to this context, until reset with another call to this
+ * method. Context may be cancelled with a {@code null} value here.
+ *
+ * @param newContext the type under analysis
+ */
+ public void setContextType(JClassType newContext) {
+ contextType = newContext;
+ }
+
+ /**
+ * Adds an entry to one of the problem maps.
+ *
+ * @param type the type to add
+ * @param message the message to add for {@code type}
+ * @param map the map to add to
+ */
+ private void addToMap(JClassType type, String message,
+ Map<JClassType, List<String>> map) {
+ List<String> entry = map.get(type);
+ if (entry == null) {
+ entry = new ArrayList<String>();
+ map.put(type, entry);
+ }
+ entry.add(message);
+ }
+
+ /**
+ * Logs all of the problems from one of the problem maps.
+ *
+ * @param logger the logger to log to
+ * @param level the level for messages
+ * @param problems the problems to log
+ */
+ private void doReport(TreeLogger logger, Type level,
+ Map<JClassType, List<String>> problems) {
+ for (JClassType type : problems.keySet()) {
+ for (String message : problems.get(type)) {
+ logger.log(level, message);
+ }
+ }
+ }
+}
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 fa8eb9e..616367a 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
@@ -33,6 +33,7 @@
import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
import com.google.gwt.user.client.rpc.GwtTransient;
import com.google.gwt.user.client.rpc.IsSerializable;
+import com.google.gwt.user.rebind.rpc.ProblemReport.Priority;
import com.google.gwt.user.rebind.rpc.TypeParameterExposureComputer.TypeParameterFlowInfo;
import com.google.gwt.user.rebind.rpc.TypePaths.TypePath;
@@ -56,13 +57,13 @@
/**
* 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
@@ -71,7 +72,7 @@
* 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
@@ -204,7 +205,7 @@
state = TypeState.CHECK_DONE;
}
- public void setInstantiableSubytpes(boolean instantiableSubtypes) {
+ public void setInstantiableSubtypes(boolean instantiableSubtypes) {
this.instantiableSubtypes = instantiableSubtypes;
}
@@ -256,13 +257,12 @@
return "Default";
}
- public boolean isAllowed(JClassType type) {
+ public boolean isAllowed(@SuppressWarnings("unused") JClassType type) {
return true;
}
};
- static boolean canBeInstantiated(TreeLogger logger, JClassType type,
- TreeLogger.Type logLevel) {
+ static boolean canBeInstantiated(JClassType type, ProblemReport problems) {
if (type.isEnum() == null) {
if (type.isAbstract()) {
// Abstract types will be picked up if there is an instantiable
@@ -272,11 +272,10 @@
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);
+ problems.add(type, type.getParameterizedQualifiedSourceName() +
+ " is not default instantiable (it must have a zero-argument "
+ + "constructor or no constructors at all) and has no custom "
+ + "serializer.", Priority.DEFAULT);
return false;
}
} else {
@@ -291,7 +290,7 @@
/**
* 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
@@ -384,18 +383,17 @@
* 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)) {
+ static boolean shouldConsiderFieldsForSerialization(JClassType type,
+ TypeFilter filter, ProblemReport problems) {
+ if (!isAllowedByFilter(filter, type, problems)) {
return false;
}
if (!isDeclaredSerializable(type)) {
- logger.branch(TreeLogger.DEBUG, "Type '"
- + type.getParameterizedQualifiedSourceName()
- + "' is not assignable to '" + IsSerializable.class.getName()
+ problems.add(type, type.getParameterizedQualifiedSourceName()
+ + " is not assignable to '" + IsSerializable.class.getName()
+ "' or '" + Serializable.class.getName()
- + "' nor does it have a custom field serializer", null);
+ + "' nor does it have a custom field serializer", Priority.DEFAULT);
return false;
}
@@ -404,41 +402,32 @@
type);
assert (manualSerializer != null);
- List<String> problems = CustomFieldSerializerValidator.validate(
+ List<String> fieldProblems = CustomFieldSerializerValidator.validate(
manualSerializer, type);
- if (!problems.isEmpty()) {
- for (String problem : problems) {
- logger.branch(getLogLevel(isSpeculative), problem, null);
+ if (!fieldProblems.isEmpty()) {
+ for (String problem : fieldProblems) {
+ problems.add(type, problem, Priority.FATAL);
}
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);
+ problems.add(type, type.getParameterizedQualifiedSourceName()
+ + " is not accessible from a class in its same package; it "
+ + "will be excluded from the set of serializable types",
+ Priority.DEFAULT);
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);
+ problems.add(type,
+ type.getParameterizedQualifiedSourceName() + " is nested but " +
+ "not static; it will be excluded from the set of serializable " +
+ "types", Priority.DEFAULT);
return false;
}
}
@@ -461,8 +450,18 @@
}
if (field.isFinal()) {
+ Type logLevel;
+ if (isManuallySerializable(field.getEnclosingType())) {
+ /*
+ * If the type has a custom serializer, assume the programmer knows
+ * best.
+ */
+ logLevel = TreeLogger.DEBUG;
+ } else {
+ logLevel = TreeLogger.WARN;
+ }
logger.branch(suppressNonStaticFinalFieldWarnings ? TreeLogger.DEBUG
- : TreeLogger.WARN, "Field '" + field.toString()
+ : logLevel, "Field '" + field.toString()
+ "' will not be serialized because it is final", null);
return false;
}
@@ -500,10 +499,6 @@
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());
@@ -530,10 +525,12 @@
return true;
}
- private static boolean isAllowedByFilter(TreeLogger logger,
- TypeFilter filter, JClassType classType, boolean isSpeculative) {
+ private static boolean isAllowedByFilter(TypeFilter filter,
+ JClassType classType, ProblemReport problems) {
if (!filter.isAllowed(classType)) {
- logger.log(getLogLevel(isSpeculative), "Excluded by type filter ");
+ problems.add(classType,
+ classType.getParameterizedQualifiedSourceName()
+ + " is excluded by type filter ", Priority.AUXILIARY);
return false;
}
@@ -610,11 +607,11 @@
/**
* Constructs a builder.
- *
+ *
* @param logger
* @param propertyOracle
* @param typeOracle
- *
+ *
* @throws UnableToCompleteException if we fail to find one of our special
* types
*/
@@ -654,12 +651,12 @@
/**
* 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
*/
@@ -668,13 +665,31 @@
alreadyCheckedObject = false;
boolean allSucceeded = true;
+
for (Entry<JClassType, TreeLogger> entry : rootTypes.entrySet()) {
- allSucceeded &= checkTypeInstantiable(entry.getValue(), entry.getKey(),
- false, TypePaths.createRootPath(entry.getKey()));
+ ProblemReport problems = new ProblemReport();
+ problems.setContextType(entry.getKey());
+ boolean entrySucceeded = checkTypeInstantiable(entry.getValue(),
+ entry.getKey(), TypePaths.createRootPath(entry.getKey()), problems);
+ if (!entrySucceeded) {
+ problems.report(logger, TreeLogger.ERROR, TreeLogger.INFO);
+ } else {
+ if (problems.hasFatalProblems()) {
+ entrySucceeded = false;
+ problems.reportFatalProblems(logger, TreeLogger.ERROR);
+ }
+ // if entrySucceeded, there may still be "warning" problems, but too
+ // often they're expected (e.g. non-instantiable subtypes of List), so
+ // we log them at DEBUG.
+ // TODO(fabbott): we could blacklist or graylist those types here, so
+ // instantiation during code generation would flag them for us.
+ problems.report(logger, TreeLogger.DEBUG, TreeLogger.DEBUG);
+ }
+
+ allSucceeded &= entrySucceeded;
}
if (!allSucceeded) {
- // the validation code has already logged why
throw new UnableToCompleteException();
}
@@ -733,19 +748,21 @@
* 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}.
- *
+ * accumulated in {@link #typeToTypeInfoComputed}. In particular, there
+ * will be an entry for any type that has been validated by this method, as
+ * a shortcircuit to avoid recomputation.
+ *
* 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>());
+ TypePath path, ProblemReport problems) {
+ return checkTypeInstantiable(logger, type, path, new HashSet<JClassType>(),
+ problems);
}
/**
@@ -754,7 +771,7 @@
* , except that returns the set of instantiable subtypes.
*/
boolean checkTypeInstantiable(TreeLogger logger, JType type,
- boolean isSpeculative, TypePath path, Set<JClassType> instSubtypes) {
+ TypePath path, Set<JClassType> instSubtypes, ProblemReport problems) {
assert (type != null);
if (type.isPrimitive() != null) {
return true;
@@ -764,6 +781,12 @@
JClassType classType = (JClassType) type;
+ TypeInfoComputed tic = getTypeInfoComputed(classType, path, false);
+ if (tic != null && tic.isDone()) {
+ // we have an answer already; use it.
+ return tic.hasInstantiableSubtypes();
+ }
+
TreeLogger localLogger = logger.branch(TreeLogger.DEBUG,
classType.getParameterizedQualifiedSourceName(), null);
@@ -771,16 +794,19 @@
if (isTypeParameter != null) {
if (typeParametersInRootTypes.contains(isTypeParameter)) {
return checkTypeInstantiable(localLogger,
- isTypeParameter.getFirstBound(), isSpeculative,
+ isTypeParameter.getFirstBound(),
TypePaths.createTypeParameterInRootPath(path, isTypeParameter),
- instSubtypes);
+ instSubtypes, problems);
}
/*
* 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.
+ * indirectly instantiable here.
*/
+ tic = getTypeInfoComputed(classType, path, true);
+ tic.setInstantiableSubtypes(true);
+ tic.setInstantiable(false);
return true;
}
@@ -788,31 +814,38 @@
if (isWildcard != null) {
boolean success = true;
for (JClassType bound : isWildcard.getUpperBounds()) {
- success &= checkTypeInstantiable(localLogger, bound, isSpeculative,
- path);
+ success &= checkTypeInstantiable(localLogger, bound, path, problems);
}
+ tic = getTypeInfoComputed(classType, path, true);
+ tic.setInstantiableSubtypes(success);
+ tic.setInstantiable(false);
return success;
}
JArrayType isArray = classType.isArray();
if (isArray != null) {
- return checkArrayInstantiable(localLogger, isArray, isSpeculative, path);
+ boolean success = checkArrayInstantiable(localLogger, isArray, path,
+ problems);
+ assert getTypeInfoComputed(classType, path, false) != null;
+ return success;
}
if (classType == typeOracle.getJavaLangObject()) {
/*
* Report an error if the type is or erases to Object since this violates
- * our restrictions on RPC.
+ * our restrictions on RPC. Should be fatal, but I worry users may have
+ * Object-using code they can't readily get out of the class hierarchy.
*/
- localLogger.branch(
- getLogLevel(isSpeculative),
- "In order to produce smaller client-side code, 'Object' is not allowed; consider using a more specific type",
- null);
+ problems.add(classType,
+ "In order to produce smaller client-side code, 'Object' is not " +
+ "allowed; please use a more specific type", Priority.DEFAULT);
+ tic = getTypeInfoComputed(classType, path, true);
+ tic.setInstantiable(false);
return false;
}
if (classType.isRawType() != null) {
- localLogger.branch(
+ localLogger.log(
TreeLogger.DEBUG,
"Type '"
+ classType.getQualifiedSourceName()
@@ -821,24 +854,16 @@
}
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);
+ instSubtypes, path, problems);
+ tic = getTypeInfoComputed(classType, path, true);
+ if (!tic.isDone()) {
+ tic.setInstantiableSubtypes(anySubtypes);
+ tic.setInstantiable(false);
}
-
return anySubtypes;
}
@@ -849,21 +874,22 @@
/**
* 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);
+ boolean shouldConsiderFieldsForSerialization(JClassType type,
+ ProblemReport problems) {
+ return shouldConsiderFieldsForSerialization(type, typeFilter,
+ problems);
}
/**
* Consider any subtype of java.lang.Object which qualifies for serialization.
- *
+ *
* @param logger
*/
- private void checkAllSubtypesOfObject(TreeLogger logger, TypePath parent) {
+ private void checkAllSubtypesOfObject(TreeLogger logger, TypePath parent,
+ ProblemReport problems) {
if (alreadyCheckedObject) {
return;
}
@@ -880,37 +906,45 @@
JClassType[] allTypes = typeOracle.getJavaLangObject().getSubtypes();
for (JClassType cls : allTypes) {
if (isDeclaredSerializable(cls)) {
- checkTypeInstantiable(localLogger, cls, true,
+ checkTypeInstantiable(localLogger, cls,
TypePaths.createSubtypePath(parent, cls,
- typeOracle.getJavaLangObject()));
+ typeOracle.getJavaLangObject()), problems);
}
}
}
private boolean checkArrayInstantiable(TreeLogger logger, JArrayType array,
- boolean isSpeculative, TypePath path) {
+ TypePath path, ProblemReport problems) {
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);
+ return checkArrayInstantiable(logger, arrayType, path, problems);
}
JClassType leafClass = leafType.isClassOrInterface();
JTypeParameter isLeafTypeParameter = leafType.isTypeParameter();
if (isLeafTypeParameter != null
&& !typeParametersInRootTypes.contains(isLeafTypeParameter)) {
- // Don't deal with non root type parameters
+ // Don't deal with non root type parameters, but make a TIC entry to
+ // save time if it recurs. We assume they're indirectly instantiable.
+ TypeInfoComputed tic = getTypeInfoComputed(array, path, true);
+ tic.setInstantiableSubtypes(true);
+ tic.setInstantiable(false);
return true;
}
- if (!isAllowedByFilter(logger, array, isSpeculative)) {
+ if (!isAllowedByFilter(array, problems)) {
+ // Don't deal with filtered out types either, but make a TIC entry to
+ // save time if it recurs. We assume they're not instantiable.
+ TypeInfoComputed tic = getTypeInfoComputed(array, path, true);
+ tic.setInstantiable(false);
return false;
}
- TypeInfoComputed tic = getTypeInfoComputed(array, path);
+ TypeInfoComputed tic = getTypeInfoComputed(array, path, true);
if (tic.isDone()) {
return tic.hasInstantiableSubtypes();
} else if (tic.isPendingInstantiable()) {
@@ -922,8 +956,9 @@
"Analyzing component type:", null);
Set<JClassType> instantiableTypes = new HashSet<JClassType>();
- boolean succeeded = checkTypeInstantiable(branch, leafType, isSpeculative,
- TypePaths.createArrayComponentPath(array, path), instantiableTypes);
+ boolean succeeded = checkTypeInstantiable(branch, leafType,
+ TypePaths.createArrayComponentPath(array, path), instantiableTypes,
+ problems);
if (succeeded) {
if (leafClass == null) {
assert leafType.isPrimitive() != null;
@@ -961,7 +996,7 @@
* necessary types.
*/
private boolean checkDeclaredFields(TreeLogger logger,
- TypeInfoComputed typeInfo, boolean isSpeculative, TypePath parent) {
+ TypeInfoComputed typeInfo, TypePath parent, ProblemReport problems) {
JClassType classOrInterface = typeInfo.getType();
if (classOrInterface.isEnum() != null) {
@@ -969,9 +1004,6 @@
return true;
}
- // All fields on a manual serializable are considered speculative.
- isSpeculative |= typeInfo.isManuallySerializable();
-
JClassType baseType = getBaseType(classOrInterface);
boolean allSucceeded = true;
@@ -999,10 +1031,10 @@
&& fieldType.getLeafType() == typeOracle.getJavaLangObject()) {
checkAllSubtypesOfObject(fieldLogger.branch(TreeLogger.WARN,
"Object was reached from a manually serializable type", null),
- path);
+ path, problems);
} else {
- allSucceeded &= checkTypeInstantiable(fieldLogger, fieldType,
- isSpeculative, path);
+ allSucceeded &= checkTypeInstantiable(fieldLogger, fieldType, path,
+ problems);
}
}
}
@@ -1016,7 +1048,7 @@
}
private boolean checkSubtype(TreeLogger logger, JClassType classOrInterface,
- JClassType originalType, boolean isSpeculative, TypePath parent) {
+ JClassType originalType, TypePath parent, ProblemReport problems) {
if (classOrInterface.isEnum() != null) {
// The fields of an enum are never serialized; they are always okay.
return true;
@@ -1029,7 +1061,7 @@
* Backwards compatibility. Raw collections or maps force all object
* subtypes to be considered.
*/
- checkAllSubtypesOfObject(logger, parent);
+ checkAllSubtypesOfObject(logger, parent, problems);
} else {
TreeLogger paramsLogger = logger.branch(TreeLogger.DEBUG,
"Checking parameters of '"
@@ -1038,7 +1070,8 @@
for (JTypeParameter param : isParameterized.getBaseType().getTypeParameters()) {
if (!checkTypeArgument(paramsLogger, isParameterized.getBaseType(),
param.getOrdinal(),
- isParameterized.getTypeArgs()[param.getOrdinal()], true, parent)) {
+ isParameterized.getTypeArgs()[param.getOrdinal()], parent,
+ problems)) {
return false;
}
}
@@ -1060,8 +1093,8 @@
boolean superTypeOk = false;
if (superType != null) {
superTypeOk = checkSubtype(logger, superType, originalType,
- isSpeculative, TypePaths.createSupertypePath(parent, superType,
- classOrInterface));
+ TypePaths.createSupertypePath(parent, superType, classOrInterface),
+ problems);
}
/*
@@ -1074,8 +1107,8 @@
}
}
- TypeInfoComputed tic = getTypeInfoComputed(classOrInterface, parent);
- return checkDeclaredFields(logger, tic, isSpeculative, parent);
+ TypeInfoComputed tic = getTypeInfoComputed(classOrInterface, parent, true);
+ return checkDeclaredFields(logger, tic, parent, problems);
}
/**
@@ -1083,12 +1116,12 @@
* instantiable relative to a known base type.
*/
private boolean checkSubtypes(TreeLogger logger, JClassType originalType,
- Set<JClassType> instSubtypes, TypePath path) {
+ Set<JClassType> instSubtypes, TypePath path, ProblemReport problems) {
JClassType baseType = getBaseType(originalType);
TreeLogger computationLogger = logger.branch(TreeLogger.DEBUG,
"Finding possibly instantiable subtypes");
List<JClassType> candidates = getPossiblyInstantiableSubtypes(
- computationLogger, baseType);
+ computationLogger, baseType, problems);
boolean anySubtypes = false;
TreeLogger verificationLogger = logger.branch(TreeLogger.DEBUG,
@@ -1105,13 +1138,13 @@
}
}
- if (!isAllowedByFilter(verificationLogger, candidate, true)) {
+ if (!isAllowedByFilter(candidate, problems)) {
continue;
}
TypePath subtypePath = TypePaths.createSubtypePath(path, candidate,
originalType);
- TypeInfoComputed tic = getTypeInfoComputed(candidate, subtypePath);
+ TypeInfoComputed tic = getTypeInfoComputed(candidate, subtypePath, true);
if (tic.isDone()) {
if (tic.isInstantiable()) {
anySubtypes = true;
@@ -1128,7 +1161,7 @@
TreeLogger subtypeLogger = verificationLogger.branch(TreeLogger.DEBUG,
candidate.getParameterizedQualifiedSourceName());
boolean instantiable = checkSubtype(subtypeLogger, candidate,
- originalType, true, subtypePath);
+ originalType, subtypePath, problems);
anySubtypes |= instantiable;
tic.setInstantiable(instantiable);
@@ -1152,25 +1185,25 @@
* 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) {
+ int paramIndex, JClassType typeArg, TypePath parent,
+ ProblemReport problems) {
JWildcardType isWildcard = typeArg.isWildcard();
if (isWildcard != null) {
return checkTypeArgument(logger, baseType, paramIndex,
- isWildcard.getUpperBound(), isSpeculative, parent);
+ isWildcard.getUpperBound(), parent, problems);
}
JArrayType typeArgAsArray = typeArg.isArray();
@@ -1185,13 +1218,13 @@
paramIndex);
if (otherFlowInfo.getExposure() >= 0
&& otherFlowInfo.isTransitivelyAffectedBy(flowInfoForArrayParam)) {
- logger.branch(
- getLogLevel(isSpeculative),
+ problems.add(baseType,
"Cannot serialize type '"
+ baseType.getParameterizedQualifiedSourceName()
+ "' when given an argument of type '"
+ typeArg.getParameterizedQualifiedSourceName()
- + "' because it appears to require serializing arrays of unbounded dimension");
+ + "' because it appears to require serializing arrays "
+ + "of unbounded dimension", Priority.DEFAULT);
return false;
}
}
@@ -1210,28 +1243,28 @@
+ " of type '"
+ baseType.getParameterizedQualifiedSourceName()
+ "' because it is directly exposed in this type or in one of its subtypes");
- return checkTypeInstantiable(branch, typeArg, true, path)
+ return checkTypeInstantiable(branch, typeArg, path, problems)
|| mightNotBeExposed(baseType, paramIndex);
}
case TypeParameterExposureComputer.EXPOSURE_NONE:
// Ignore this argument
- logger.branch(TreeLogger.DEBUG, "Ignoring type argument " + paramIndex
+ logger.log(Type.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,
+ problems.add(getArrayType(typeOracle, exposure, typeArg),
"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)
+ + exposure + " in this type or one of its subtypes",
+ Priority.AUXILIARY);
+ return checkTypeInstantiable(logger, getArrayType(typeOracle, exposure,
+ typeArg), path, problems)
|| mightNotBeExposed(baseType, paramIndex);
}
}
@@ -1257,7 +1290,7 @@
* Returns the subtypes of a given base type as parameterized by wildcards.
*/
private List<JClassType> getPossiblyInstantiableSubtypes(TreeLogger logger,
- JClassType baseType) {
+ JClassType baseType, ProblemReport problems) {
assert (baseType == getBaseType(baseType));
List<JClassType> possiblyInstantiableTypes = new ArrayList<JClassType>();
@@ -1267,11 +1300,12 @@
List<JClassType> candidates = new ArrayList<JClassType>();
candidates.add(baseType);
- candidates.addAll(Arrays.asList(baseType.getSubtypes()));
+ List<JClassType> subtypes = Arrays.asList(baseType.getSubtypes());
+ candidates.addAll(subtypes);
for (JClassType subtype : candidates) {
JClassType subtypeBase = getBaseType(subtype);
- if (maybeInstantiable(logger, subtypeBase)) {
+ if (maybeInstantiable(logger, subtypeBase, problems)) {
/*
* Convert the generic type into a parameterization that only includes
* wildcards.
@@ -1287,21 +1321,51 @@
}
}
+ if (possiblyInstantiableTypes.size() == 0) {
+ String possibilities[] = new String[candidates.size()];
+ for (int i = 0; i < possibilities.length; i++) {
+ JClassType subtype = candidates.get(i);
+ List<String> auxiliaries = problems.getAuxiliaryMessagesForType(subtype);
+ List<String> errors = problems.getProblemsForType(subtype);
+ if (errors.isEmpty()) {
+ if (auxiliaries.isEmpty()) {
+ possibilities[i] = " subtype " +
+ subtype.getParameterizedQualifiedSourceName() +
+ " is not instantiable";
+ } else {
+ // message with have the form "FQCN some-problem-description"
+ possibilities[i] = " subtype " + auxiliaries.get(0);
+ if (auxiliaries.size() > 1) {
+ possibilities[i] = possibilities[i] + ", etc.";
+ }
+ }
+ } else {
+ possibilities[i] = " subtype " + errors.get(0);
+ if (errors.size() > 1 || !auxiliaries.isEmpty()) {
+ possibilities[i] = possibilities[i] + ", etc.";
+ }
+ }
+ }
+ problems.add(baseType, baseType.getParameterizedQualifiedSourceName()
+ + " has no available instantiable subtypes.", Priority.DEFAULT,
+ possibilities);
+ }
return possiblyInstantiableTypes;
}
- private TypeInfoComputed getTypeInfoComputed(JClassType type, TypePath path) {
+ private TypeInfoComputed getTypeInfoComputed(JClassType type, TypePath path,
+ boolean createIfNeeded) {
TypeInfoComputed tic = typeToTypeInfoComputed.get(type);
- if (tic == null) {
+ if (tic == null && createIfNeeded) {
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);
+ private boolean isAllowedByFilter(JClassType classType,
+ ProblemReport problems) {
+ return isAllowedByFilter(typeFilter, classType, problems);
}
/**
@@ -1385,16 +1449,17 @@
JArrayType covariantArray = getArrayType(typeOracle, rank, leafType);
TypeInfoComputed covariantArrayTic = getTypeInfoComputed(covariantArray,
- path);
+ path, true);
covariantArrayTic.setInstantiable(true);
}
}
- private boolean maybeInstantiable(TreeLogger logger, JClassType type) {
- boolean success = canBeInstantiated(logger, type, TreeLogger.DEBUG)
- && shouldConsiderFieldsForSerialization(logger, type, true);
+ private boolean maybeInstantiable(TreeLogger logger, JClassType type,
+ ProblemReport problems) {
+ boolean success = canBeInstantiated(type, problems)
+ && shouldConsiderFieldsForSerialization(type, problems);
if (success) {
- logger.branch(TreeLogger.DEBUG,
+ logger.log(TreeLogger.DEBUG,
type.getParameterizedQualifiedSourceName() + " might be instantiable");
}
return success;
@@ -1408,7 +1473,7 @@
/**
* 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.
*/
diff --git a/user/src/com/google/gwt/user/rebind/rpc/TypeParameterExposureComputer.java b/user/src/com/google/gwt/user/rebind/rpc/TypeParameterExposureComputer.java
index aab0345..e9d2874 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/TypeParameterExposureComputer.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/TypeParameterExposureComputer.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -82,8 +82,10 @@
boolean didChange = false;
JClassType type = baseType;
while (type != null) {
+ // any problems should already have been captured by our caller, so we
+ // make a throw-away ProblemReport here.
if (SerializableTypeOracleBuilder.shouldConsiderFieldsForSerialization(
- TreeLogger.NULL, type, true, typeFilter)) {
+ type, typeFilter, new ProblemReport())) {
JField[] fields = type.getFields();
for (JField field : fields) {
if (!SerializableTypeOracleBuilder.shouldConsiderForSerialization(
@@ -249,8 +251,10 @@
continue;
}
+ // any problems should already have been captured by our caller, so we
+ // make a throw-away ProblemReport here.
if (!SerializableTypeOracleBuilder.shouldConsiderFieldsForSerialization(
- TreeLogger.NULL, subtype, true, typeFilter)) {
+ subtype, typeFilter, new ProblemReport())) {
continue;
}
@@ -267,7 +271,7 @@
JClassType type = baseType;
while (type != null) {
if (SerializableTypeOracleBuilder.shouldConsiderFieldsForSerialization(
- TreeLogger.NULL, type, true, typeFilter)) {
+ type, typeFilter, new ProblemReport())) {
JField[] fields = type.getFields();
for (JField field : fields) {
if (!SerializableTypeOracleBuilder.shouldConsiderForSerialization(
@@ -367,7 +371,7 @@
/**
* Computes flow information for the specified type parameter. If it has
* already been computed just return the value of the previous computation.
- *
+ *
* @param type the generic type whose type parameter flow we are interested in
* @param index the index of the type parameter whose flow we want to compute
*/
diff --git a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
index a7285fb..2cd14c3 100644
--- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
+++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -320,7 +320,7 @@
/**
* Test with a generic class whose type parameter is exposed only in certain
* subclasses.
- *
+ *
* NOTE: This test has been disabled because it requires a better pruner in
* STOB. See SerializableTypeOracleBuilder.pruneUnreachableTypes().
*/
@@ -381,7 +381,7 @@
/**
* Tests abstract root types that are field serializable.
- *
+ *
* @throws UnableToCompleteException
* @throws NotFoundException
*/
@@ -682,7 +682,7 @@
/**
* Tests the rules that govern whether a type qualifies for serialization.
- *
+ *
* @throws UnableToCompleteException
* @throws NotFoundException
*/
@@ -803,65 +803,81 @@
// Does not qualify because it is not declared to be auto or manually
// serializable
JClassType notSerializable = to.getType("NotSerializable");
- assertFalse(sob.shouldConsiderFieldsForSerialization(logger,
- notSerializable, false));
+ ProblemReport problems = new ProblemReport();
+ assertFalse(sob.shouldConsiderFieldsForSerialization(notSerializable,
+ problems));
// Local types should not qualify for serialization
JClassType iFoo = to.getType("AutoSerializable.IFoo");
- assertFalse(sob.shouldConsiderFieldsForSerialization(logger,
- iFoo.getSubtypes()[0], false));
+ problems = new ProblemReport();
+ assertFalse(sob.shouldConsiderFieldsForSerialization(
+ iFoo.getSubtypes()[0], problems));
// Static nested types qualify for serialization
JClassType staticNested = to.getType("OuterClass.StaticNested");
- assertTrue(sob.shouldConsiderFieldsForSerialization(logger, staticNested,
- false));
+ problems = new ProblemReport();
+ assertTrue(sob.shouldConsiderFieldsForSerialization(staticNested,
+ problems));
// Non-static nested types do not qualify for serialization
JClassType nonStaticNested = to.getType("OuterClass.NonStaticNested");
- assertFalse(sob.shouldConsiderFieldsForSerialization(logger,
- nonStaticNested, false));
+ problems = new ProblemReport();
+ assertFalse(sob.shouldConsiderFieldsForSerialization(
+ nonStaticNested, problems));
// Abstract classes that implement Serializable should not qualify
JClassType abstractSerializableClass = to.getType("AbstractSerializableClass");
- assertTrue(sob.shouldConsiderFieldsForSerialization(logger,
- abstractSerializableClass, false));
+ problems = new ProblemReport();
+ assertTrue(sob.shouldConsiderFieldsForSerialization(
+ abstractSerializableClass, problems));
+
+ problems = new ProblemReport();
assertFalse(SerializableTypeOracleBuilder.canBeInstantiated(
- TreeLogger.NULL, abstractSerializableClass, TreeLogger.DEBUG));
+ abstractSerializableClass, problems));
// Non-default instantiable types should not qualify
JClassType nonDefaultInstantiableSerializable = to.getType("NonDefaultInstantiableSerializable");
- assertTrue(sob.shouldConsiderFieldsForSerialization(logger,
- nonDefaultInstantiableSerializable, false));
+ problems = new ProblemReport();
+ assertTrue(sob.shouldConsiderFieldsForSerialization(
+ nonDefaultInstantiableSerializable, problems));
+
+ problems = new ProblemReport();
assertFalse(SerializableTypeOracleBuilder.canBeInstantiated(
- TreeLogger.NULL, nonDefaultInstantiableSerializable, TreeLogger.DEBUG));
+ nonDefaultInstantiableSerializable, problems));
// SPublicStaticInnerInner is not accessible to classes in its package
JClassType publicStaticInnerInner = to.getType("PublicOuterClass.PrivateStaticInner.PublicStaticInnerInner");
- assertFalse(sob.shouldConsiderFieldsForSerialization(logger,
- publicStaticInnerInner, false));
+ problems = new ProblemReport();
+ assertFalse(sob.shouldConsiderFieldsForSerialization(
+ publicStaticInnerInner, problems));
// DefaultStaticInnerInner is visible to classes in its package
JClassType defaultStaticInnerInner = to.getType("PublicOuterClass.DefaultStaticInner.DefaultStaticInnerInner");
- assertTrue(sob.shouldConsiderFieldsForSerialization(logger,
- defaultStaticInnerInner, false));
+ problems = new ProblemReport();
+ assertTrue(sob.shouldConsiderFieldsForSerialization(
+ defaultStaticInnerInner, problems));
// Enum with subclasses should qualify, but their subtypes should not
JClassType enumWithSubclasses = to.getType("EnumWithSubclasses");
- assertTrue(sob.shouldConsiderFieldsForSerialization(logger,
- enumWithSubclasses, false));
- assertFalse(sob.shouldConsiderFieldsForSerialization(logger,
- enumWithSubclasses.getSubtypes()[0], false));
+ problems = new ProblemReport();
+ assertTrue(sob.shouldConsiderFieldsForSerialization(
+ enumWithSubclasses, problems));
+
+ problems = new ProblemReport();
+ assertFalse(sob.shouldConsiderFieldsForSerialization(
+ enumWithSubclasses.getSubtypes()[0], problems));
// Enum that are not default instantiable should qualify
JClassType enumWithNonDefaultCtors = to.getType("EnumWithNonDefaultCtors");
- assertTrue(sob.shouldConsiderFieldsForSerialization(logger,
- enumWithNonDefaultCtors, false));
+ problems = new ProblemReport();
+ assertTrue(sob.shouldConsiderFieldsForSerialization(
+ enumWithNonDefaultCtors, problems));
}
/**
* Tests that both the generic and raw forms of type that has a type parameter
* that erases to object are not serializable.
- *
+ *
* @throws NotFoundException
*/
public void testClassWithTypeParameterThatErasesToObject()
@@ -886,7 +902,7 @@
/**
* Test the situation where an abstract class has an unconstrained type
* parameter but all of its concrete subclasses add helpful constraints to it.
- *
+ *
* @throws NotFoundException
* @throws UnableToCompleteException
*/
@@ -1022,7 +1038,7 @@
/**
* If the query type extends a raw type, be sure to pick up the parameters of
* the raw subertype.
- *
+ *
* @throws UnableToCompleteException
* @throws NotFoundException
*/
@@ -1095,7 +1111,7 @@
* If a subtype of a root type extends from the raw version of that root type,
* then when visiting the fields of the raw version, take advantage of
* information from the original root type.
- *
+ *
* @throws UnableToCompleteException
* @throws NotFoundException
*/
@@ -1656,8 +1672,143 @@
}
/**
+ * Tests a hierarchy blending various serializable and non-serializable
+ * types.
+ */
+ public void testProblemReporting() throws UnableToCompleteException,
+ NotFoundException {
+ Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+ addStandardClasses(units);
+
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("public interface TopInterface {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("TopInterface", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public abstract class AbstractSerializable implements\n");
+ code.append(" Serializable, TopInterface {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("AbstractSerializable", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public interface PureAbstractSerializable extends \n");
+ code.append(" Serializable, TopInterface {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("PureAbstractSerializable", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("public abstract class PureAbstractClass implements \n");
+ code.append(" PureAbstractSerializable {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("PureAbstractClass", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("public abstract class PureAbstractClassTwo extends \n");
+ code.append(" PureAbstractClass {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("PureAbstractClassTwo", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("public class ConcreteNonSerializable implements\n");
+ code.append(" TopInterface {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("ConcreteNonSerializable", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class ConcreteSerializable implements\n");
+ code.append(" Serializable, TopInterface {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("ConcreteSerializable", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("import java.io.Serializable;\n");
+ code.append("public class SubSerializable extends\n");
+ code.append(" ConcreteNonSerializable implements Serializable {\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("SubSerializable", code));
+ }
+ {
+ StringBuilder code = new StringBuilder();
+ code.append("public class ConcreteBadCtor extends\n");
+ code.append(" AbstractSerializable {\n");
+ code.append(" public ConcreteBadCtor(int i) {\n");
+ code.append(" }\n");
+ code.append("}\n");
+ units.add(createMockCompilationUnit("ConcreteBadCtor", code));
+ }
+ TreeLogger logger = createLogger();
+ TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+ SerializableTypeOracleBuilder stob = createSerializableTypeOracleBuilder(
+ logger, to);
+ JClassType topInterface = to.getType("TopInterface");
+ stob.addRootType(logger, topInterface);
+
+ ProblemReport problems = new ProblemReport();
+ assertTrue("TopInterface should be (partially) serializable",
+ stob.checkTypeInstantiable(logger, topInterface, null,
+ problems));
+ assertTrue("TopInterface should be a serializable type",
+ problems.getProblemsForType(topInterface).isEmpty());
+ assertTrue("AbstractSerializable should not be reported on",
+ problems.getProblemsForType(
+ to.getType("AbstractSerializable")).isEmpty());
+ assertTrue("PureAbstractSerializable should not be reported on",
+ problems.getProblemsForType(
+ to.getType("PureAbstractSerializable")).isEmpty());
+ assertTrue("PureAbstractClass should not be reported on",
+ problems.getProblemsForType(
+ to.getType("PureAbstractClass")).isEmpty());
+ assertFalse("ConcreteBadCtor should not be a serializable type",
+ problems.getProblemsForType(to.getType("ConcreteBadCtor")).isEmpty());
+ assertFalse("ConcreteNonSerializable should not be a serializable type",
+ problems.getProblemsForType(
+ to.getType("ConcreteNonSerializable")).isEmpty());
+ assertTrue("ConcreteSerializable should be a serializable type",
+ problems.getProblemsForType(to.getType("ConcreteSerializable")).isEmpty());
+ assertTrue("SubSerializable should be a serializable type",
+ problems.getProblemsForType(to.getType("SubSerializable")).isEmpty());
+
+ problems = new ProblemReport();
+ assertFalse("PureAbstractClass should have no possible concrete implementation",
+ stob.checkTypeInstantiable(logger, to.getType("PureAbstractClass"), null,
+ problems));
+ assertTrue("PureAbstractClass should have a problem entry as the tested class",
+ null != problems.getProblemsForType(to.getType("PureAbstractClass")));
+
+ problems = new ProblemReport();
+ assertFalse("PureAbstractSerializable should have no possible concrete implementation",
+ stob.checkTypeInstantiable(logger, to.getType("PureAbstractSerializable"), null,
+ problems));
+ assertFalse("PureAbstractSerializable should have a problem entry",
+ problems.getProblemsForType(to.getType("PureAbstractSerializable")).isEmpty());
+ assertTrue("PureAbstractClassTwo should not have a problem entry as the middle class",
+ problems.getProblemsForType(to.getType("PureAbstractClassTwo")).isEmpty());
+ assertTrue("PureAbstractClassTwo should not have an auxiliary entry as the middle class",
+ problems.getAuxiliaryMessagesForType(to.getType("PureAbstractClassTwo")).
+ isEmpty());
+ assertTrue("PureAbstractClass should not have a problem entry as the child class",
+ problems.getProblemsForType(to.getType("PureAbstractClass")).isEmpty());
+ assertTrue("PureAbstractClass should not have an auxiliary entry as the child class",
+ problems.getAuxiliaryMessagesForType(to.getType("PureAbstractClass")).
+ isEmpty());
+ }
+
+ /**
* Tests that adding a raw collection as a root type pulls in the world.
- *
+ *
* @throws UnableToCompleteException
* @throws NotFoundException
*/
@@ -1715,7 +1866,7 @@
/**
* Tests that raw type with type parameters that are instantiable are
* themselves instantiable.
- *
+ *
* @throws UnableToCompleteException
* @throws NotFoundException
*/
@@ -1761,7 +1912,7 @@
/**
* Tests that a type paramter that occurs within its bounds will not result in
* infinite recursion.
- *
+ *
* @throws UnableToCompleteException
* @throws NotFoundException
*/
@@ -1868,7 +2019,7 @@
/**
* Tests subtypes that introduce new instantiable type parameters.
- *
+ *
* @throws UnableToCompleteException
* @throws NotFoundException
*/
@@ -1921,7 +2072,7 @@
/**
* Tests subtypes that introduce new uninstantiable type parameters as
* compared to an implemented interface, where the root type is the interface.
- *
+ *
* @throws UnableToCompleteException
* @throws NotFoundException
*/
@@ -1990,7 +2141,7 @@
/**
* Tests subtypes that introduce new uninstantiable type parameters.
- *
+ *
* @throws UnableToCompleteException
* @throws NotFoundException
*/
@@ -2085,7 +2236,7 @@
/**
* Miscellaneous direct tests of {@link TypeConstrainer}.
- *
+ *
* @throws UnableToCompleteException
* @throws NotFoundException
*/
@@ -2209,7 +2360,7 @@
/**
* Tests root types that have type parameters.
- *
+ *
* @throws UnableToCompleteException
* @throws NotFoundException
*/
@@ -2262,7 +2413,7 @@
/**
* Tests root types that <em>are</em> type parameters.
- *
+ *
* @throws UnableToCompleteException
* @throws NotFoundException
*/