From 3b7a305de6a42914bfcddfb0611e8ba76bfd3587 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 25 Nov 2025 21:33:19 +0100 Subject: [PATCH 01/18] fixing issue #10181: don't call setInstantiable(...) for class still having a chance of instantiability --- .../gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 34f28fea57a..67eab44ca72 100644 --- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java +++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java @@ -1272,7 +1272,9 @@ private boolean checkSubtypes(TreeLogger logger, JClassType originalType, boolean instantiable = checkSubtype(subtypeLogger, candidate, originalType, subtypePath, problems); anySubtypes |= instantiable; - tic.setInstantiable(instantiable); + if (instantiable) { + tic.setInstantiable(true); + } if (instantiable) { subtypeLogger.branch(TreeLogger.DEBUG, "Is instantiable"); @@ -1285,6 +1287,9 @@ private boolean checkSubtypes(TreeLogger logger, JClassType originalType, instSubtypes.add(candidate); } } + if (!anySubtypes) { + tic.setInstantiable(false); + } return anySubtypes; } From 9f025152668e67767deabce1535aa2c624f90e9b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 26 Nov 2025 11:09:03 +0100 Subject: [PATCH 02/18] issue-10181: working towards a test case for reproduction --- eclipse/dev/.classpath | 21 ++-- eclipse/user/.classpath | 10 +- .../rpc/SerializableTypeOracleBuilder.java | 7 +- .../SerializableTypeOracleBuilderTest.java | 15 +++ .../RecursiveTypeGraphInstantiability.java | 100 ++++++++++++++++++ 5 files changed, 136 insertions(+), 17 deletions(-) create mode 100644 user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java diff --git a/eclipse/dev/.classpath b/eclipse/dev/.classpath index 075c1983641..51f950d18c3 100644 --- a/eclipse/dev/.classpath +++ b/eclipse/dev/.classpath @@ -2,16 +2,16 @@ - - - + + + + + + - - - @@ -30,7 +30,12 @@ - - + + + + + + + diff --git a/eclipse/user/.classpath b/eclipse/user/.classpath index 1fa63d0ffcc..3a668a55bf7 100644 --- a/eclipse/user/.classpath +++ b/eclipse/user/.classpath @@ -6,7 +6,11 @@ - + + + + + @@ -61,9 +65,9 @@ - - + + 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 67eab44ca72..34f28fea57a 100644 --- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java +++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java @@ -1272,9 +1272,7 @@ private boolean checkSubtypes(TreeLogger logger, JClassType originalType, boolean instantiable = checkSubtype(subtypeLogger, candidate, originalType, subtypePath, problems); anySubtypes |= instantiable; - if (instantiable) { - tic.setInstantiable(true); - } + tic.setInstantiable(instantiable); if (instantiable) { subtypeLogger.branch(TreeLogger.DEBUG, "Is instantiable"); @@ -1287,9 +1285,6 @@ private boolean checkSubtypes(TreeLogger logger, JClassType originalType, instSubtypes.add(candidate); } } - if (!anySubtypes) { - tic.setInstantiable(false); - } return anySubtypes; } 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 aef3e8b1302..2026bf3c5ce 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java +++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java @@ -49,6 +49,7 @@ import com.google.gwt.user.rebind.rpc.testcases.client.NotAllSubtypesAreSerializable; import com.google.gwt.user.rebind.rpc.testcases.client.ParameterizedTypeInList; import com.google.gwt.user.rebind.rpc.testcases.client.RawTypeInList; +import com.google.gwt.user.rebind.rpc.testcases.client.RecursiveTypeGraphInstantiability; import com.google.gwt.user.rebind.rpc.testcases.client.SubclassUsedInArray; import junit.framework.TestCase; @@ -1521,6 +1522,20 @@ public void testNotAllSubtypesAreSerializable() throws UnableToCompleteException validateSTO(sto, expected); } + public void testInstantiabilityDetectionOnRecursiveTypeGraph() throws UnableToCompleteException, + NotFoundException { + TreeLogger logger = createLogger(); + TypeOracle typeOracle = getTestTypeOracle(); + JClassType a = typeOracle.getType(RecursiveTypeGraphInstantiability.A.class.getCanonicalName()); + SerializableTypeOracleBuilder stob = createSerializableTypeOracleBuilder(logger, typeOracle); + stob.addRootType(logger, a); + SerializableTypeOracle sto = stob.build(logger); + TypeInfo[] expected = new TypeInfo[] { + new TypeInfo(makeSourceName(NotAllSubtypesAreSerializable.A.class.getName()), true), + new TypeInfo(makeSourceName(NotAllSubtypesAreSerializable.B.class.getName()), true)}; + validateSTO(sto, expected); + } + /** * Tests that Object[] is not instantiable. */ diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java new file mode 100644 index 00000000000..c8b1749235a --- /dev/null +++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java @@ -0,0 +1,100 @@ +package com.google.gwt.user.rebind.rpc.testcases.client; + +import com.google.gwt.user.client.rpc.IsSerializable; + +public interface RecursiveTypeGraphInstantiability extends IsSerializable { + /** + * Not serializable. + */ + interface A extends IsSerializable { + } + + /** + * Auto serializable, but with back-reference to A for which the question of instantiable subtypes + * depends on the serializability of this class; all other sub-classes are not instantiable. + */ + class B implements A { + private A a; + + public A getA() { + return a; + } + + public void setA(A a) { + this.a = a; + } + } + + /** + * Not serializable due to Object field. + */ + class C extends B { + Object field; + } + + /** + * Not instantiable either, due to non-default constructor and final field + */ + class D implements A { + private final int i; + + public D(int i) { + super(); + this.i = i; + } + + public int getI() { + return i; + } + } + + /** + * Not instantiable; see {@link D} + */ + class E implements A { + private final int i; + + public E(int i) { + super(); + this.i = i; + } + + public int getI() { + return i; + } + } + + /** + * Not instantiable; see {@link D} + */ + class F implements A { + private final int i; + + public F(int i) { + super(); + this.i = i; + } + + public int getI() { + return i; + } + } + + /** + * Not instantiable; see {@link D} + */ + class G implements A { + private final int i; + + public G(int i) { + super(); + this.i = i; + } + + public int getI() { + return i; + } + } + + A getA(); +} From a8e6fe3df9c2f58a35515e6a9ecb54bd616d49c8 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 26 Nov 2025 13:42:06 +0100 Subject: [PATCH 03/18] issue-10181: fixed test case for recursive graph instantiability --- .../gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 2026bf3c5ce..4eb79de4752 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java +++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java @@ -1531,8 +1531,7 @@ public void testInstantiabilityDetectionOnRecursiveTypeGraph() throws UnableToCo stob.addRootType(logger, a); SerializableTypeOracle sto = stob.build(logger); TypeInfo[] expected = new TypeInfo[] { - new TypeInfo(makeSourceName(NotAllSubtypesAreSerializable.A.class.getName()), true), - new TypeInfo(makeSourceName(NotAllSubtypesAreSerializable.B.class.getName()), true)}; + new TypeInfo(makeSourceName(RecursiveTypeGraphInstantiability.B.class.getName()), true)}; validateSTO(sto, expected); } From 024b6882978c20f0dcf82d8bcfdebe18d7596659 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 27 Nov 2025 17:38:28 +0100 Subject: [PATCH 04/18] issue-10181: trying to replicate the type graph leading to a 20% error probability --- .../SerializableTypeOracleBuilderTest.java | 4 +- .../RecursiveTypeGraphInstantiability.java | 945 +++++++++++++++++- 2 files changed, 933 insertions(+), 16 deletions(-) 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 4eb79de4752..c1aa8ee48cf 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java +++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java @@ -1526,12 +1526,12 @@ public void testInstantiabilityDetectionOnRecursiveTypeGraph() throws UnableToCo NotFoundException { TreeLogger logger = createLogger(); TypeOracle typeOracle = getTestTypeOracle(); - JClassType a = typeOracle.getType(RecursiveTypeGraphInstantiability.A.class.getCanonicalName()); + JClassType a = typeOracle.getType(RecursiveTypeGraphInstantiability.StoredDataMiningReportDTO.class.getCanonicalName()); SerializableTypeOracleBuilder stob = createSerializableTypeOracleBuilder(logger, typeOracle); stob.addRootType(logger, a); SerializableTypeOracle sto = stob.build(logger); TypeInfo[] expected = new TypeInfo[] { - new TypeInfo(makeSourceName(RecursiveTypeGraphInstantiability.B.class.getName()), true)}; + new TypeInfo(makeSourceName(RecursiveTypeGraphInstantiability.StoredDataMiningReportDTOImpl.class.getName()), true)}; validateSTO(sto, expected); } diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java index c8b1749235a..ac1ace829e7 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java +++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java @@ -1,30 +1,925 @@ package com.google.gwt.user.rebind.rpc.testcases.client; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Set; +import java.util.UUID; + import com.google.gwt.user.client.rpc.IsSerializable; public interface RecursiveTypeGraphInstantiability extends IsSerializable { - /** - * Not serializable. - */ - interface A extends IsSerializable { + public interface StoredDataMiningReportDTO extends NamedWithUUID, Renamable { + DataMiningReportDTO getReport(); } - /** - * Auto serializable, but with back-reference to A for which the question of instantiable subtypes - * depends on the serializability of this class; all other sub-classes are not instantiable. - */ - class B implements A { - private A a; + public static interface NamedWithUUID extends NamedWithID { + @Override + UUID getId(); + } - public A getA() { - return a; + public static interface NamedWithID extends Named, WithID { + } + + public static interface Named extends Serializable { + String getName(); + } + public static interface WithID { + /** + * Something that uniquely identifies this object beyond its name + */ + Serializable getId(); + } + + public static interface Renamable extends Named { + void setName(String newName); + } + + public static interface DataMiningReportDTO extends Serializable { + } + + public static class RenamableImpl implements Renamable { + private static final long serialVersionUID = -4815125282671451300L; + private String name; + + public RenamableImpl(String name) { + super(); + this.name = name; } - public void setA(A a) { - this.a = a; + @Override + public String getName() { + return name; + } + + @Override + public String toString() { + return getName(); + } + + @Override + public void setName(String newName) { + this.name = newName; + } + } + + public static class StoredDataMiningReportDTOImpl extends RenamableImpl implements + StoredDataMiningReportDTO { + private static final long serialVersionUID = 9218620680326470175L; + + private UUID id; + private DataMiningReportDTO report; + + @Deprecated // for GWT serialization only + StoredDataMiningReportDTOImpl() { + super(null); + } + + public StoredDataMiningReportDTOImpl(UUID id, String name, DataMiningReportDTO report) { + super(name); + this.id = id; + this.report = report; + } + + @Override + public UUID getId() { + return id; + } + + @Override + public DataMiningReportDTO getReport() { + return report; + } + } + + public static interface FilterDimensionParameter extends NamedWithUUID { + } + + public static interface StatisticQueryDefinitionDTO extends Serializable { + } + + public interface ParameterModelListener { + } + + public class LocalizedTypeDTO implements Serializable { + private static final long serialVersionUID = -5976605483497225403L; + + private String typeName; + private String displayName; + + /** + * Constructor for the GWT-Serialization. Don't use this! + */ + @Deprecated + LocalizedTypeDTO() { + } + + public LocalizedTypeDTO(String typeName, String displayName) { + this.typeName = typeName; + this.displayName = displayName; + } + + public String getTypeName() { + return typeName; + } + + public String getDisplayName() { + return displayName; + } + } + + public static abstract class SerializableSettings extends AbstractSettings implements + Serializable { + private static final long serialVersionUID = -2710627501473421333L; + } + + public abstract class PolarDataMiningSettings extends SerializableSettings { + + private static final long serialVersionUID = 3670315004615987482L; + + public abstract Integer getMinimumDataCountPerGraph(); + + public abstract double getMinimumWindConfidence(); + + public abstract boolean applyMinimumWindConfidence(); + + public abstract Integer getMinimumDataCountPerAngle(); + + public abstract int getNumberOfHistogramColumns(); + + public abstract boolean useOnlyWindGaugesForWindSpeed(); + + public abstract boolean useOnlyEstimatedForWindDirection(); + + public abstract WindSpeedSteppingWithMaxDistance getWindSpeedStepping(); + + public abstract boolean areDefault(); + + } + + public interface WindSpeedStepping extends Serializable { + + public abstract int getLevelIndexForValue(double speed); + + public abstract Double getSteppedValueForValue(double speed); + + double[] getRawStepping(); + + public abstract int getLevelIndexFloorForValue(double speed); + + public abstract int getLevelIndexCeilingForValue(double speed); + + double getDistanceToLevelFloor(double speed); + + int hashCode(); + + boolean equals(Object obj); + + } + + public class WindSpeedSteppingImpl implements WindSpeedStepping { + + // For GWT Serialization + protected WindSpeedSteppingImpl() { + }; + + private static final long serialVersionUID = 2215693490331489508L; + protected double[] levels; + + public WindSpeedSteppingImpl(double[] levels) { + this.levels = levels; + } + + public int getNumberOfLevels() { + return levels.length; + } + + @Override + public int getLevelIndexForValue(double speed) { + return 0; + } + + @Override + public Double getSteppedValueForValue(double speed) { + return null; + } + + @Override + public double[] getRawStepping() { + return null; + } + + @Override + public int getLevelIndexFloorForValue(double speed) { + return 0; + } + + @Override + public int getLevelIndexCeilingForValue(double speed) { + return 0; + } + + @Override + public double getDistanceToLevelFloor(double speed) { + return 0; + } + } + + public class WindSpeedSteppingWithMaxDistance extends WindSpeedSteppingImpl { + + private static final long serialVersionUID = -2207840179212727591L; + private double maxDistance; + + // For GWT Serialization + WindSpeedSteppingWithMaxDistance() { + super(); + }; + + public WindSpeedSteppingWithMaxDistance(double[] levels, double maxDistance) { + super(levels); + this.maxDistance = maxDistance; + } + + @Override + public int getLevelIndexForValue(double speed) { + int result = -1; + for (int i = 0; i < levels.length - 1; i++) { + double threshold = levels[i] + ((levels[i + 1] - levels[i]) / 2.); + if (speed < threshold) { + if (threshold - speed <= maxDistance * 2) { + result = i; + } + break; + } + } + if (result == -1) { + if (Math.abs(levels[levels.length - 1] - speed) <= maxDistance) { + result = levels.length - 1; + } + } + return result; + } + + public double getMaxDistance() { + return maxDistance; + } + } + public class PolarDataMiningSettingsImpl extends PolarDataMiningSettings { + + public static PolarDataMiningSettingsImpl createStandardPolarSettings() { + double[] levels = {4., 6., 8., 10., 12., 14., 16., 20., 25., 30.}; + WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, + 2.5); + return new PolarDataMiningSettingsImpl(1000, 0.01, true, 100, 20, true, true, windStepping); + } + + private static final long serialVersionUID = 2731616509404813790L; + private Integer minimumDataCountPerGraph; + private double minimumWindConfidence; + private Integer minimumDataCountPerAngle; + private Integer numberOfHistogramColumns; + private boolean useOnlyWindGaugesForWindSpeed; + private boolean useOnlyEstimationForWindDirection; + private WindSpeedSteppingWithMaxDistance windStepping; + private boolean applyMinimumWindConfidence; + + // GWT + PolarDataMiningSettingsImpl() { + }; + + public PolarDataMiningSettingsImpl(Integer minimumDataCountPerGraph, + double minimumWindConfidence, boolean applyMinimumWindConfidence, + Integer minimumDataCountPerAngle, Integer numberOfHistogramColumns, + boolean useOnlyWindGaugesForWindSpeed, boolean useOnlyEstimationForWindDirection, + WindSpeedSteppingWithMaxDistance windStepping) { + this.minimumDataCountPerGraph = minimumDataCountPerGraph; + this.minimumWindConfidence = minimumWindConfidence; + this.applyMinimumWindConfidence = applyMinimumWindConfidence; + this.minimumDataCountPerAngle = minimumDataCountPerAngle; + this.numberOfHistogramColumns = numberOfHistogramColumns; + this.useOnlyWindGaugesForWindSpeed = useOnlyWindGaugesForWindSpeed; + this.useOnlyEstimationForWindDirection = useOnlyEstimationForWindDirection; + this.windStepping = windStepping; + } + + @Override + public Integer getMinimumDataCountPerGraph() { + return minimumDataCountPerGraph; + } + + @Override + public double getMinimumWindConfidence() { + return minimumWindConfidence; + } + + @Override + public Integer getMinimumDataCountPerAngle() { + return minimumDataCountPerAngle; + } + + @Override + public int getNumberOfHistogramColumns() { + return numberOfHistogramColumns; + } + + @Override + public boolean useOnlyWindGaugesForWindSpeed() { + return useOnlyWindGaugesForWindSpeed; + } + + @Override + public boolean useOnlyEstimatedForWindDirection() { + return useOnlyEstimationForWindDirection; + } + + @Override + public WindSpeedSteppingWithMaxDistance getWindSpeedStepping() { + return windStepping; + } + + @Override + public boolean applyMinimumWindConfidence() { + return applyMinimumWindConfidence; + } + + @Override + public boolean areDefault() { + return false; + } + } + + public static class DataRetrieverLevelDTO implements Serializable, + Comparable { + private static final long serialVersionUID = 6911713148350359643L; + + /** + * The index of this retriever level in the retriever chain. + */ + private int retrieverLevel; + /** + * The fully qualified name of the Processor performing the retrieval of this level. + */ + private String retrieverTypeName; + /** + * The type of the retrieved data elements in form of a {@link LocalizedTypeDTO}. Its type name + * is the fully qualified name of the retrieved data type and is used for identification + * purposes. + * + * The display name is used as human readable string representation of this retriever level and + * should be omitted when persisting a DataRetrieverLevelDTO. + */ + private LocalizedTypeDTO retrievedDataType; + + /** + * The default settings for this retriever level or null, if the level doesn't have + * settings. Should be omitted when persisting a DataRetrieverLevelDTO. + */ + private SerializableSettings defaultSettings; + + /** + * Constructor for the GWT-Serialization. Don't use this! + */ + @Deprecated + DataRetrieverLevelDTO() { + } + + public DataRetrieverLevelDTO(int retrieverLevel, String retrieverTypeName, + LocalizedTypeDTO retrievedDataType, SerializableSettings defaultSettings) { + this.retrieverLevel = retrieverLevel; + this.retrieverTypeName = retrieverTypeName; + this.retrievedDataType = retrievedDataType; + this.defaultSettings = defaultSettings; + } + + public int getLevel() { + return retrieverLevel; + } + + public String getRetrieverTypeName() { + return retrieverTypeName; + } + + public LocalizedTypeDTO getRetrievedDataType() { + return retrievedDataType; + } + + public boolean hasSettings() { + return getDefaultSettings() != null; + } + + public SerializableSettings getDefaultSettings() { + return defaultSettings; + } + + @Override + public int compareTo(DataRetrieverLevelDTO o) { + return 0; + } + } + + public static class FilterDimensionIdentifier implements Serializable { + private static final long serialVersionUID = -4824907338023614296L; + private DataRetrieverLevelDTO retrieverLevel; + private FunctionDTO dimensionFunction; + + @Deprecated // for GWT serialization only + FilterDimensionIdentifier() { + } + + public FilterDimensionIdentifier(DataRetrieverLevelDTO retrieverLevel, + FunctionDTO dimensionFunction) { + super(); + this.retrieverLevel = retrieverLevel; + this.dimensionFunction = dimensionFunction; + } + + public DataRetrieverLevelDTO getRetrieverLevel() { + return retrieverLevel; + } + + public FunctionDTO getDimensionFunction() { + return dimensionFunction; + } + } + + public static class ModifiableDataMiningReportDTO implements DataMiningReportDTO { + private static final long serialVersionUID = -6512175470789118223L; + + /** + * Handled by identity; remove and contains checks don't use query equality because modifiable + * queries can change their equality/hashCode over their life cycle + */ + private ArrayList queryDefinitions; + private HashSet parameters; + private IdentityHashMap> parameterUsages; + + @SuppressWarnings("unused") // only to force the type to be instantiable for GWT serialization + private ParameterModelListener _serializationDummy = null; + + /** + * Objects of this type entertain one value change listener for each parameter in + * {@link #getParameters()} so that when a parameter value changes, all + * {@link #getQueryDefinitions() queries in this report} using this parameter will have their + * {@link StatisticQueryDefinitionDTO#getFilterSelection() filter selections} adjusted + * accordingly. + */ + private HashMap parameterValueChangeListeners; + + private transient Set parameterModelListeners; + + /** + * Creates an empty report with no queries and hence no parameter usages. + */ + public ModifiableDataMiningReportDTO() { + this(Collections.emptySet(), Collections.emptySet()); + } + + public ModifiableDataMiningReportDTO( + Iterable queryDefinitions, + Iterable parameters) { + } + + public HashMap getParameterValueChangeListeners() { + return parameterValueChangeListeners; + } + + public void setParameterValueChangeListeners( + HashMap parameterValueChangeListeners) { + this.parameterValueChangeListeners = parameterValueChangeListeners; + } + + public Set getParameterModelListeners() { + return parameterModelListeners; + } + + public void setParameterModelListeners(Set parameterModelListeners) { + this.parameterModelListeners = parameterModelListeners; + } + + public ArrayList getQueryDefinitions() { + return queryDefinitions; + } + + public void setQueryDefinitions( + ArrayList queryDefinitions) { + this.queryDefinitions = queryDefinitions; + } + + public HashSet getParameters() { + return parameters; + } + + public void setParameters(HashSet parameters) { + this.parameters = parameters; + } + + public IdentityHashMap> getParameterUsages() { + return parameterUsages; + } + + public void setParameterUsages( + IdentityHashMap> parameterUsages) { + this.parameterUsages = parameterUsages; + } + + } + + public class DataRetrieverChainDefinitionDTO implements Serializable, + Comparable { + private static final long serialVersionUID = 7806173601799997214L; + + /** + * The fully qualified name of the initial data source type. + */ + private String dataSourceTypeName; + /** + * The ordered list of retriever levels. + */ + private ArrayList retrieverLevels; + + /** + * A human readable string representation. Should be omitted when persisting a + * DataRetrieverChainDefinition. + */ + private String displayName; + + /** + * Constructor for the GWT-Serialization. Don't use this! + */ + @Deprecated + DataRetrieverChainDefinitionDTO() { + } + + public DataRetrieverChainDefinitionDTO(String name, String dataSourceTypeName, + ArrayList retrieverLevels) { + this.displayName = name; + this.dataSourceTypeName = dataSourceTypeName; + this.retrieverLevels = new ArrayList<>(retrieverLevels); + } + + public String getName() { + return displayName; + } + + public String getDataSourceTypeName() { + return dataSourceTypeName; + } + + public String getRetrievedDataTypeName() { + return retrieverLevels.get(retrieverLevels.size() - 1).getRetrievedDataType().getTypeName(); + } + + public ArrayList getRetrieverLevels() { + return retrieverLevels; + } + + public int getLevelAmount() { + return retrieverLevels.size(); + } + + @Override + public int compareTo(DataRetrieverChainDefinitionDTO o) { + return 0; + } + } + + public static class ModifiableStatisticQueryDefinitionDTO implements StatisticQueryDefinitionDTO { + private static final long serialVersionUID = -6438771277564908352L; + + /** + * The extraction function used to get the statistic from the retrieved data elements. + */ + private FunctionDTO statisticToCalculate; + /** + * The aggregator used to aggregate the extracted statistics. + */ + private AggregationProcessorDefinitionDTO aggregatorDefinition; + /** + * The list of dimensions to group the results by. + */ + private ArrayList dimensionsToGroupBy; + /** + * The retriever chain used to retrieve the data elements. + */ + private DataRetrieverChainDefinitionDTO dataRetrieverChainDefinition; + /** + * The settings to be used for the retriever levels. Can be empty, if no retriever level has + * settings. + */ + private HashMap retrieverSettings; + /** + * The values used to filter the data elements during the retrieval process. Each set of values + * is mapped by the dimension used to extract the value from a data element and the retriever + * level the dimension belongs to. The dimensions are declared by the data type retrieved by the + * corresponding retriever level and not by the data type the query is based on (return type of + * the retriever chain). + * + * The actual filter values can be anything that is returned by a dimension, which will be + * mostly simple data like Strings or Enums, but can also be more complex data structures like + * {@link ClusterDTO}. + */ + private HashMap>> filterSelection; + + /** + * The LocalInfo name that will be used for internationalization. Should be omitted when + * persisting a ModifiableStatisticQueryDefinitionDTO. + */ + private String localeInfoName; + + /** + * Used to record whether this query has changed since last run. When, for example, a parameter + * change triggers the modification of this query's dimension filter(s), the flag should be + * {@link #setQueryChangedSinceLastRun(boolean) set}. It must be un-set when the query is run. + */ + private transient boolean queryChangedSinceLastRun; + + /** + * Constructor for the GWT-Serialization. Don't use this! + */ + @Deprecated + ModifiableStatisticQueryDefinitionDTO() { + } + + public ModifiableStatisticQueryDefinitionDTO(String localeInfoName, + FunctionDTO statisticToCalculate, AggregationProcessorDefinitionDTO aggregatorDefinition, + DataRetrieverChainDefinitionDTO dataRetrieverChainDefinition) { + this.localeInfoName = localeInfoName; + this.statisticToCalculate = statisticToCalculate; + this.aggregatorDefinition = aggregatorDefinition; + this.dataRetrieverChainDefinition = dataRetrieverChainDefinition; + this.retrieverSettings = new HashMap<>(); + this.filterSelection = new HashMap<>(); + this.dimensionsToGroupBy = new ArrayList(); + } + + public ModifiableStatisticQueryDefinitionDTO(StatisticQueryDefinitionDTO definition) { + } + + public FunctionDTO getStatisticToCalculate() { + return statisticToCalculate; + } + + public AggregationProcessorDefinitionDTO getAggregatorDefinition() { + return aggregatorDefinition; + } + + public DataRetrieverChainDefinitionDTO getDataRetrieverChainDefinition() { + return dataRetrieverChainDefinition; + } + + public String getLocaleInfoName() { + return localeInfoName; + } + + public boolean isQueryChangedSinceLastRun() { + return queryChangedSinceLastRun; + } + + public void setQueryChangedSinceLastRun(boolean queryChangedSinceLastRun) { + this.queryChangedSinceLastRun = queryChangedSinceLastRun; + } + + public void setDataRetrieverChainDefinition( + DataRetrieverChainDefinitionDTO dataRetrieverChainDefinition) { + if (dataRetrieverChainDefinition == null) { + throw new NullPointerException("The data retriever chain definition mustn't be null"); + } + this.dataRetrieverChainDefinition = dataRetrieverChainDefinition; + } + + public void setRetrieverSettings(DataRetrieverLevelDTO retrieverLevel, + SerializableSettings settings) { + if (retrieverLevel == null) { + throw new NullPointerException("The retriever level mustn't be null"); + } + retrieverSettings.put(retrieverLevel, settings); + } + + public void setFilterSelectionFor(DataRetrieverLevelDTO retrieverLevel, + HashMap> levelFilterSelection) { + if (retrieverLevel == null) { + throw new NullPointerException("The retriever level mustn't be null"); + } + if (levelFilterSelection == null) { + throw new NullPointerException("The level filter selection mustn't be null"); + } + filterSelection.put(retrieverLevel, levelFilterSelection); + } + + public void appendDimensionToGroupBy(FunctionDTO dimensionToGroupBy) { + if (dimensionToGroupBy == null) { + throw new NullPointerException("The dimension mustn't be null"); + } + dimensionsToGroupBy.add(dimensionToGroupBy); + } + + public void setStatisticToCalculate(FunctionDTO statisticToCalculate) { + if (statisticToCalculate == null) { + throw new NullPointerException("The statistic to calculate mustn't be null"); + } + this.statisticToCalculate = statisticToCalculate; + } + + public void setAggregatorDefinition(AggregationProcessorDefinitionDTO aggregatorDefinition) { + if (aggregatorDefinition == null) { + throw new NullPointerException("The aggregator definition mustn't be null"); + } + this.aggregatorDefinition = aggregatorDefinition; + } + + public void setLocaleInfoName(String localeInfoName) { + if (localeInfoName == null) { + throw new NullPointerException("The locale info name mustn't be null"); + } + this.localeInfoName = localeInfoName; + } + } + + public static class AggregationProcessorDefinitionDTO implements Serializable, + Comparable { + private static final long serialVersionUID = -434497637456305118L; + + /** + * Additional identification feature, since the extracted and aggregated type wouldn't suffice. + * Has to be included when persisting a AggregationProcessorDefinitionDTO. + */ + private String messageKey; + /** + * The fully qualified name of the aggregators input type. + */ + private String extractedTypeName; + /** + * The fully qualified name of the aggregators result type. + */ + private String aggregatedTypeName; + + /** + * A human readable string representation. Should be omitted when persisting a + * AggregationProcessorDefinitionDTO. + */ + private String displayName; + + /** + * Constructor for the GWT-Serialization. Don't use this! + */ + @Deprecated + AggregationProcessorDefinitionDTO() { + } + + public AggregationProcessorDefinitionDTO(String messageKey, String extractedTypeName, + String aggregatedTypeName, String displayName) { + this.messageKey = messageKey; + this.extractedTypeName = extractedTypeName; + this.aggregatedTypeName = aggregatedTypeName; + this.displayName = displayName; + } + + public String getMessageKey() { + return messageKey; + } + + public String getExtractedTypeName() { + return extractedTypeName; + } + + public String getAggregatedTypeName() { + return aggregatedTypeName; + } + + public String getDisplayName() { + return displayName; + } + + @Override + public String toString() { + return getExtractedTypeName() + " -> " + getAggregatedTypeName() + "[messageKey: " + + messageKey + "]"; + } + + @Override + public int compareTo(AggregationProcessorDefinitionDTO aggregatorDefinitionDTO) { + return getDisplayName().compareTo(aggregatorDefinitionDTO.getDisplayName()); + } + } + + public static interface Settings { + } + + public static abstract class AbstractSettings implements Settings { + } + + public static class FunctionDTO implements Serializable, Comparable { + private static final long serialVersionUID = 4587389541910498505L; + + private final boolean isDimension; + /** + * The function's name including its parameters in parenthesis or empty parenthesis, if this + * function doesn't have parameters. Can consist of multiple concatenated function names, if the + * backend Function encapsulated multiple data mining functions. + */ + private final String functionName; + /** + * The fully qualified name of the type that declares this function. + */ + private final String sourceTypeName; + /** + * The fully qualified name of the type returned by this function. + */ + private final String returnTypeName; + /** + * The fully qualified names of the functions parameter types. + */ + private final List parameterTypeNames; + + /** + * Meta-data for the natural ordering of FunctionDTOs. Should be omitted when persisting a + * FunctionDTO. + */ + private final int ordinal; + /** + * A human readable string representation. Should be omitted when persisting a FunctionDTO. + */ + private String displayName; + /** + * If the {@link #displayName} is not set and this provider is valid, the {@F + */ + private transient DisplayNameProvider displayNameProvider; + + public static interface DisplayNameProvider { + String getDisplayName(); + } + + /** + * Makes the construction of the {@link #displayName} lazy, using the + * {@code displayNameProvider}. + */ + public FunctionDTO(boolean isDimension, String functionName, String sourceTypeName, + String returnTypeName, List parameterTypeNames, + DisplayNameProvider displayNameProvider, int ordinal) { + this(isDimension, functionName, sourceTypeName, returnTypeName, parameterTypeNames, + /* displayName is constructed lazily using the displayNameProvider */ (String) null, + ordinal); + this.displayNameProvider = displayNameProvider; + } + + public FunctionDTO(boolean isDimension, String functionName, String sourceTypeName, + String returnTypeName, List parameterTypeNames, String displayName, int ordinal) { + this.isDimension = isDimension; + this.functionName = functionName; + this.sourceTypeName = sourceTypeName; + this.returnTypeName = returnTypeName; + this.parameterTypeNames = new ArrayList(parameterTypeNames); + this.displayName = displayName; + this.ordinal = ordinal; + } + + public String getSourceTypeName() { + return sourceTypeName; + } + + public String getReturnTypeName() { + return returnTypeName; + } + + public List getParameterTypeNames() { + return parameterTypeNames; + } + + public String getFunctionName() { + return functionName; + } + + public String getDisplayName() { + if (displayName == null && displayNameProvider != null) { + displayName = displayNameProvider.getDisplayName(); + } + return displayName; + } + + public boolean isDimension() { + return isDimension; + } + + public int getOrdinal() { + return ordinal; + } + + @Override + public int compareTo(FunctionDTO f) { + return Integer.compare(this.getOrdinal(), f.getOrdinal()); } } + /** + * Not serializable. + */ + interface A extends IsSerializable { + } + /** * Not serializable due to Object field. */ @@ -86,6 +981,10 @@ public int getI() { class G implements A { private final int i; + public G() { + i = 0; + } + public G(int i) { super(); this.i = i; @@ -96,5 +995,23 @@ public int getI() { } } + /** + * Auto serializable, but with back-reference to A for which the question of instantiable subtypes + * depends on the serializability of this class; all other sub-classes are not instantiable. + */ + class B implements A { + private A a; + + public A getA() { + return a; + } + + public void setA(A a) { + this.a = a; + } + } + A getA(); + + StoredDataMiningReportDTO getStoredDataMiningReportDTO(); } From 13923afb3ce88747ec9e29a034ce80aae1703c83 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 27 Nov 2025 18:00:37 +0100 Subject: [PATCH 05/18] issue-10181: reproduced the problem by replacing UUID by String; stderr has [ERROR] 'com.google.gwt.user.rebind.rpc.testcases.client.RecursiveTypeGraphInstantiability.StoredDataMiningReportDTO' has no instantiable subtypes which was the expected result. --- .../client/RecursiveTypeGraphInstantiability.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java index ac1ace829e7..fa1645967b1 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java +++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java @@ -8,18 +8,17 @@ import java.util.IdentityHashMap; import java.util.List; import java.util.Set; -import java.util.UUID; import com.google.gwt.user.client.rpc.IsSerializable; public interface RecursiveTypeGraphInstantiability extends IsSerializable { - public interface StoredDataMiningReportDTO extends NamedWithUUID, Renamable { + public static interface StoredDataMiningReportDTO extends NamedWithStringID, Renamable { DataMiningReportDTO getReport(); } - public static interface NamedWithUUID extends NamedWithID { + public static interface NamedWithStringID extends NamedWithID { @Override - UUID getId(); + String getId(); } public static interface NamedWithID extends Named, WithID { @@ -71,7 +70,7 @@ public static class StoredDataMiningReportDTOImpl extends RenamableImpl implemen StoredDataMiningReportDTO { private static final long serialVersionUID = 9218620680326470175L; - private UUID id; + private String id; private DataMiningReportDTO report; @Deprecated // for GWT serialization only @@ -79,14 +78,14 @@ public static class StoredDataMiningReportDTOImpl extends RenamableImpl implemen super(null); } - public StoredDataMiningReportDTOImpl(UUID id, String name, DataMiningReportDTO report) { + public StoredDataMiningReportDTOImpl(String id, String name, DataMiningReportDTO report) { super(name); this.id = id; this.report = report; } @Override - public UUID getId() { + public String getId() { return id; } @@ -96,7 +95,7 @@ public DataMiningReportDTO getReport() { } } - public static interface FilterDimensionParameter extends NamedWithUUID { + public static interface FilterDimensionParameter extends NamedWithStringID { } public static interface StatisticQueryDefinitionDTO extends Serializable { From 48c7bc786f4d37f10208179e785ad5ff91bbb56c Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 27 Nov 2025 18:07:00 +0100 Subject: [PATCH 06/18] issue-10181: simplified type graph slightly --- .../RecursiveTypeGraphInstantiability.java | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java index fa1645967b1..b894a649d6c 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java +++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java @@ -245,22 +245,7 @@ public WindSpeedSteppingWithMaxDistance(double[] levels, double maxDistance) { @Override public int getLevelIndexForValue(double speed) { - int result = -1; - for (int i = 0; i < levels.length - 1; i++) { - double threshold = levels[i] + ((levels[i + 1] - levels[i]) / 2.); - if (speed < threshold) { - if (threshold - speed <= maxDistance * 2) { - result = i; - } - break; - } - } - if (result == -1) { - if (Math.abs(levels[levels.length - 1] - speed) <= maxDistance) { - result = levels.length - 1; - } - } - return result; + return -1; } public double getMaxDistance() { From e861951c6f904ee6a38a43aa7d408dbcb96c637e Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 28 Nov 2025 15:09:32 +0100 Subject: [PATCH 07/18] issue-10181: successful test for serializability of recursive test graph --- .../rpc/SerializableTypeOracleBuilder.java | 11 +- .../SerializableTypeOracleBuilderTest.java | 5 +- .../RecursiveTypeGraphInstantiability.java | 161 +++++++++++++++++- 3 files changed, 162 insertions(+), 15 deletions(-) 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 34f28fea57a..e604d978dbe 100644 --- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java +++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java @@ -1147,9 +1147,14 @@ private boolean checkDeclaredFields(TreeLogger logger, TypeInfoComputed typeInfo checkAllSubtypesOfObject(fieldLogger.branch(TreeLogger.WARN, "Object was reached from a manually serializable type", null), path, problems); } else { - allSucceeded &= - computeTypeInstantiability(fieldLogger, fieldType, path, problems) - .hasInstantiableSubtypes(); + boolean hasInstantiableSubtypes = computeTypeInstantiability(fieldLogger, fieldType, path, problems) + .hasInstantiableSubtypes(); + allSucceeded &= hasInstantiableSubtypes; + if (!hasInstantiableSubtypes) { + fieldLogger.branch(TreeLogger.WARN, "Field type '" + + fieldType.getParameterizedQualifiedSourceName() + + "' has no instantiable subtypes"); + } } } } 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 c1aa8ee48cf..ab4c89584c4 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java +++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java @@ -1530,9 +1530,8 @@ public void testInstantiabilityDetectionOnRecursiveTypeGraph() throws UnableToCo SerializableTypeOracleBuilder stob = createSerializableTypeOracleBuilder(logger, typeOracle); stob.addRootType(logger, a); SerializableTypeOracle sto = stob.build(logger); - TypeInfo[] expected = new TypeInfo[] { - new TypeInfo(makeSourceName(RecursiveTypeGraphInstantiability.StoredDataMiningReportDTOImpl.class.getName()), true)}; - validateSTO(sto, expected); + assertTrue(Arrays.asList(getActualTypeInfo(sto)).contains( + new TypeInfo(makeSourceName(RecursiveTypeGraphInstantiability.StoredDataMiningReportDTOImpl.class.getName()), true))); } /** diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java index b894a649d6c..2849f9474ef 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java +++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java @@ -9,7 +9,11 @@ import java.util.List; import java.util.Set; +import com.google.gwt.user.client.rpc.CustomFieldSerializer; import com.google.gwt.user.client.rpc.IsSerializable; +import com.google.gwt.user.client.rpc.SerializationException; +import com.google.gwt.user.client.rpc.SerializationStreamReader; +import com.google.gwt.user.client.rpc.SerializationStreamWriter; public interface RecursiveTypeGraphInstantiability extends IsSerializable { public static interface StoredDataMiningReportDTO extends NamedWithStringID, Renamable { @@ -27,6 +31,7 @@ public static interface NamedWithID extends Named, WithID { public static interface Named extends Serializable { String getName(); } + public static interface WithID { /** * Something that uniquely identifies this object beyond its name @@ -96,6 +101,80 @@ public DataMiningReportDTO getReport() { } public static interface FilterDimensionParameter extends NamedWithStringID { + String getTypeName(); + } + + public abstract class AbstractParameterizedDimensionFilter extends RenamableImpl implements + FilterDimensionParameter { + private static final long serialVersionUID = 3853015601496471357L; + + private String typeName; + private String id; + + @Deprecated // GWT serialization only + AbstractParameterizedDimensionFilter() { + super(null); + } + + public AbstractParameterizedDimensionFilter(String name, String typeName) { + super(name); + this.id = "abc"; + this.typeName = typeName; + } + + @Override + public String getId() { + return id; + } + + @Override + public String getTypeName() { + return typeName; + } + } + + public class ValueListFilterParameter extends AbstractParameterizedDimensionFilter { + private static final long serialVersionUID = -8440835683986197499L; + + private HashSet values; + + private transient Set parameterModelListeners; + + private HashSet nonTransientParameterModelListeners; + + @Deprecated // GWT serialization only + ValueListFilterParameter() { + } + + public ValueListFilterParameter(String name, String typeName, + Iterable values) { + super(name, typeName); + final HashSet set = new HashSet<>(); + this.values = set; + this.parameterModelListeners = new HashSet<>(); + this.nonTransientParameterModelListeners = new HashSet<>(); + } + + public Iterable getValues() { + return new HashSet<>(values); + } + + public Set getParameterModelListeners() { + return parameterModelListeners; + } + + public void setParameterModelListeners(Set parameterModelListeners) { + this.parameterModelListeners = parameterModelListeners; + } + + public HashSet getNonTransientParameterModelListeners() { + return nonTransientParameterModelListeners; + } + + public void setNonTransientParameterModelListeners( + HashSet nonTransientParameterModelListeners) { + this.nonTransientParameterModelListeners = nonTransientParameterModelListeners; + } } public static interface StatisticQueryDefinitionDTO extends Serializable { @@ -104,6 +183,29 @@ public static interface StatisticQueryDefinitionDTO extends Serializable { public interface ParameterModelListener { } + public class ParameterValueChangeListener implements ParameterModelListener, Serializable { + private static final long serialVersionUID = 402977347893177440L; + + private ModifiableDataMiningReportDTO report; + + @Deprecated // for GWT serialization only + ParameterValueChangeListener() { + } + + public ParameterValueChangeListener(ModifiableDataMiningReportDTO report) { + super(); + this.report = report; + } + + public ModifiableDataMiningReportDTO getReport() { + return report; + } + + public void setReport(ModifiableDataMiningReportDTO report) { + this.report = report; + } + } + public class LocalizedTypeDTO implements Serializable { private static final long serialVersionUID = -5976605483497225403L; @@ -253,14 +355,6 @@ public double getMaxDistance() { } } public class PolarDataMiningSettingsImpl extends PolarDataMiningSettings { - - public static PolarDataMiningSettingsImpl createStandardPolarSettings() { - double[] levels = {4., 6., 8., 10., 12., 14., 16., 20., 25., 30.}; - WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, - 2.5); - return new PolarDataMiningSettingsImpl(1000, 0.01, true, 100, 20, true, true, windStepping); - } - private static final long serialVersionUID = 2731616509404813790L; private Integer minimumDataCountPerGraph; private double minimumWindConfidence; @@ -509,7 +603,6 @@ public void setParameterUsages( IdentityHashMap> parameterUsages) { this.parameterUsages = parameterUsages; } - } public class DataRetrieverChainDefinitionDTO implements Serializable, @@ -898,6 +991,56 @@ public int compareTo(FunctionDTO f) { } } + public class FunctionDTO_CustomFieldSerializer extends CustomFieldSerializer { + @Override + public void serializeInstance(SerializationStreamWriter streamWriter, FunctionDTO instance) + throws SerializationException { + serialize(streamWriter, instance); + } + + public static void serialize(SerializationStreamWriter streamWriter, FunctionDTO instance) + throws SerializationException { + streamWriter.writeBoolean(instance.isDimension()); + streamWriter.writeString(instance.getFunctionName()); + streamWriter.writeString(instance.getSourceTypeName()); + streamWriter.writeString(instance.getReturnTypeName()); + streamWriter.writeObject(instance.getParameterTypeNames()); + streamWriter.writeString(instance.getDisplayName()); + streamWriter.writeInt(instance.getOrdinal()); + } + + @Override + public boolean hasCustomInstantiateInstance() { + return true; + } + + @Override + public FunctionDTO instantiateInstance(SerializationStreamReader streamReader) + throws SerializationException { + return instantiate(streamReader); + } + + @SuppressWarnings("unchecked") // the cast to List is the problem here + public static FunctionDTO instantiate(SerializationStreamReader streamReader) + throws SerializationException { + return new FunctionDTO(/* isDimension */ streamReader.readBoolean(), + /* function name */ streamReader.readString(), /* source type name */ streamReader + .readString(), /* return type name */ streamReader.readString(), + /* parameter type names */ (List) streamReader.readObject(), + /* display name */ streamReader.readString(), /* ordinal */ streamReader.readInt()); + } + + @Override + public void deserializeInstance(SerializationStreamReader streamReader, FunctionDTO instance) + throws SerializationException { + deserialize(streamReader, instance); + } + + public static void deserialize(SerializationStreamReader streamReader, FunctionDTO instance) { + // Done by instantiateInstance + } + } + /** * Not serializable. */ From 4e2d09240d86990cd60bda2d3c55baf1e04274b1 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sat, 29 Nov 2025 13:33:25 +0100 Subject: [PATCH 08/18] issue-10181: added an unserializable subclass to the recursive graph --- .../RecursiveTypeGraphInstantiability.java | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java index 2849f9474ef..339f2a6a239 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java +++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java @@ -180,10 +180,31 @@ public void setNonTransientParameterModelListeners( public static interface StatisticQueryDefinitionDTO extends Serializable { } - public interface ParameterModelListener { + public static interface ParameterModelListener { } + + /** an intentionally non-serializable implementation of ParameterModelListener */ + public static class PMLImpl implements ParameterModelListener { + private Object nonSerializableField = new Object(); - public class ParameterValueChangeListener implements ParameterModelListener, Serializable { + /** + * Only a non-default constructor + */ + public PMLImpl(Object nonSerializableField) { + super(); + this.nonSerializableField = nonSerializableField; + } + + public Object getNonSerializableField() { + return nonSerializableField; + } + + public void setNonSerializableField(Object nonSerializableField) { + this.nonSerializableField = nonSerializableField; + } + } + + public static class ParameterValueChangeListener implements ParameterModelListener, Serializable { private static final long serialVersionUID = 402977347893177440L; private ModifiableDataMiningReportDTO report; From 452be5794d785f04c46b93e34f68627156f4de8a Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 1 Dec 2025 21:35:14 +0100 Subject: [PATCH 09/18] issue-10181: reproducing multiple setInstantiable calls for same TIC --- .../user/rebind/rpc/SerializableTypeOracleBuilderTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 ab4c89584c4..c2d7621517e 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java +++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java @@ -1526,12 +1526,13 @@ public void testInstantiabilityDetectionOnRecursiveTypeGraph() throws UnableToCo NotFoundException { TreeLogger logger = createLogger(); TypeOracle typeOracle = getTestTypeOracle(); - JClassType a = typeOracle.getType(RecursiveTypeGraphInstantiability.StoredDataMiningReportDTO.class.getCanonicalName()); + JClassType a = typeOracle.getType( + RecursiveTypeGraphInstantiability.ModifiableDataMiningReportDTO.class.getCanonicalName()); SerializableTypeOracleBuilder stob = createSerializableTypeOracleBuilder(logger, typeOracle); stob.addRootType(logger, a); SerializableTypeOracle sto = stob.build(logger); - assertTrue(Arrays.asList(getActualTypeInfo(sto)).contains( - new TypeInfo(makeSourceName(RecursiveTypeGraphInstantiability.StoredDataMiningReportDTOImpl.class.getName()), true))); + assertTrue(Arrays.asList(getActualTypeInfo(sto)).contains(new TypeInfo(makeSourceName( + RecursiveTypeGraphInstantiability.ModifiableDataMiningReportDTO.class.getName()), true))); } /** From bf1d9aa03ecc0f3e0cd2292faeb1945628279b71 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 1 Dec 2025 22:51:43 +0100 Subject: [PATCH 10/18] issue-10181: stripped-down failing test case --- .../SerializableTypeOracleBuilderTest.java | 11 +- .../RecursiveTypeGraphInstantiability.java | 1162 +---------------- 2 files changed, 44 insertions(+), 1129 deletions(-) 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 c2d7621517e..2ddfdf5723c 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java +++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java @@ -1527,12 +1527,17 @@ public void testInstantiabilityDetectionOnRecursiveTypeGraph() throws UnableToCo TreeLogger logger = createLogger(); TypeOracle typeOracle = getTestTypeOracle(); JClassType a = typeOracle.getType( - RecursiveTypeGraphInstantiability.ModifiableDataMiningReportDTO.class.getCanonicalName()); + RecursiveTypeGraphInstantiability.B.class.getCanonicalName()); SerializableTypeOracleBuilder stob = createSerializableTypeOracleBuilder(logger, typeOracle); stob.addRootType(logger, a); SerializableTypeOracle sto = stob.build(logger); - assertTrue(Arrays.asList(getActualTypeInfo(sto)).contains(new TypeInfo(makeSourceName( - RecursiveTypeGraphInstantiability.ModifiableDataMiningReportDTO.class.getName()), true))); + TypeInfo[] expected = + new TypeInfo[] { + new TypeInfo(makeSourceName(NotAllSubtypesAreSerializable.B.class.getName()), true), + new TypeInfo(makeSourceName(NotAllSubtypesAreSerializable.C.class.getName()), true)}; + validateSTO(sto, expected); +// assertTrue(Arrays.asList(getActualTypeInfo(sto)).contains(new TypeInfo(makeSourceName( +// RecursiveTypeGraphInstantiability.ParameterValueChangeListener.class.getName()), true))); } /** diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java index 339f2a6a239..20b24754714 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java +++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java @@ -1,1165 +1,75 @@ package com.google.gwt.user.rebind.rpc.testcases.client; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Set; - -import com.google.gwt.user.client.rpc.CustomFieldSerializer; import com.google.gwt.user.client.rpc.IsSerializable; -import com.google.gwt.user.client.rpc.SerializationException; -import com.google.gwt.user.client.rpc.SerializationStreamReader; -import com.google.gwt.user.client.rpc.SerializationStreamWriter; public interface RecursiveTypeGraphInstantiability extends IsSerializable { - public static interface StoredDataMiningReportDTO extends NamedWithStringID, Renamable { - DataMiningReportDTO getReport(); - } - - public static interface NamedWithStringID extends NamedWithID { - @Override - String getId(); - } - - public static interface NamedWithID extends Named, WithID { - } - - public static interface Named extends Serializable { - String getName(); - } - - public static interface WithID { - /** - * Something that uniquely identifies this object beyond its name - */ - Serializable getId(); - } - - public static interface Renamable extends Named { - void setName(String newName); - } - - public static interface DataMiningReportDTO extends Serializable { - } - - public static class RenamableImpl implements Renamable { - private static final long serialVersionUID = -4815125282671451300L; - private String name; - - public RenamableImpl(String name) { - super(); - this.name = name; - } - - @Override - public String getName() { - return name; - } - - @Override - public String toString() { - return getName(); - } - - @Override - public void setName(String newName) { - this.name = newName; - } - } - - public static class StoredDataMiningReportDTOImpl extends RenamableImpl implements - StoredDataMiningReportDTO { - private static final long serialVersionUID = 9218620680326470175L; - - private String id; - private DataMiningReportDTO report; - - @Deprecated // for GWT serialization only - StoredDataMiningReportDTOImpl() { - super(null); - } - - public StoredDataMiningReportDTOImpl(String id, String name, DataMiningReportDTO report) { - super(name); - this.id = id; - this.report = report; - } - - @Override - public String getId() { - return id; - } - - @Override - public DataMiningReportDTO getReport() { - return report; - } - } - - public static interface FilterDimensionParameter extends NamedWithStringID { - String getTypeName(); - } - - public abstract class AbstractParameterizedDimensionFilter extends RenamableImpl implements - FilterDimensionParameter { - private static final long serialVersionUID = 3853015601496471357L; - - private String typeName; - private String id; - - @Deprecated // GWT serialization only - AbstractParameterizedDimensionFilter() { - super(null); - } - - public AbstractParameterizedDimensionFilter(String name, String typeName) { - super(name); - this.id = "abc"; - this.typeName = typeName; - } - - @Override - public String getId() { - return id; - } - - @Override - public String getTypeName() { - return typeName; - } - } - - public class ValueListFilterParameter extends AbstractParameterizedDimensionFilter { - private static final long serialVersionUID = -8440835683986197499L; - - private HashSet values; - - private transient Set parameterModelListeners; - - private HashSet nonTransientParameterModelListeners; - - @Deprecated // GWT serialization only - ValueListFilterParameter() { - } - - public ValueListFilterParameter(String name, String typeName, - Iterable values) { - super(name, typeName); - final HashSet set = new HashSet<>(); - this.values = set; - this.parameterModelListeners = new HashSet<>(); - this.nonTransientParameterModelListeners = new HashSet<>(); - } - - public Iterable getValues() { - return new HashSet<>(values); - } - - public Set getParameterModelListeners() { - return parameterModelListeners; - } - - public void setParameterModelListeners(Set parameterModelListeners) { - this.parameterModelListeners = parameterModelListeners; - } - - public HashSet getNonTransientParameterModelListeners() { - return nonTransientParameterModelListeners; - } - - public void setNonTransientParameterModelListeners( - HashSet nonTransientParameterModelListeners) { - this.nonTransientParameterModelListeners = nonTransientParameterModelListeners; - } - } - - public static interface StatisticQueryDefinitionDTO extends Serializable { - } - - public static interface ParameterModelListener { - } - - /** an intentionally non-serializable implementation of ParameterModelListener */ - public static class PMLImpl implements ParameterModelListener { - private Object nonSerializableField = new Object(); - - /** - * Only a non-default constructor - */ - public PMLImpl(Object nonSerializableField) { - super(); - this.nonSerializableField = nonSerializableField; - } - - public Object getNonSerializableField() { - return nonSerializableField; - } - - public void setNonSerializableField(Object nonSerializableField) { - this.nonSerializableField = nonSerializableField; - } - } - - public static class ParameterValueChangeListener implements ParameterModelListener, Serializable { - private static final long serialVersionUID = 402977347893177440L; - - private ModifiableDataMiningReportDTO report; - - @Deprecated // for GWT serialization only - ParameterValueChangeListener() { - } - - public ParameterValueChangeListener(ModifiableDataMiningReportDTO report) { - super(); - this.report = report; - } - - public ModifiableDataMiningReportDTO getReport() { - return report; - } - - public void setReport(ModifiableDataMiningReportDTO report) { - this.report = report; - } - } - - public class LocalizedTypeDTO implements Serializable { - private static final long serialVersionUID = -5976605483497225403L; - - private String typeName; - private String displayName; - - /** - * Constructor for the GWT-Serialization. Don't use this! - */ - @Deprecated - LocalizedTypeDTO() { - } - - public LocalizedTypeDTO(String typeName, String displayName) { - this.typeName = typeName; - this.displayName = displayName; - } - - public String getTypeName() { - return typeName; - } - - public String getDisplayName() { - return displayName; - } - } - - public static abstract class SerializableSettings extends AbstractSettings implements - Serializable { - private static final long serialVersionUID = -2710627501473421333L; - } - - public abstract class PolarDataMiningSettings extends SerializableSettings { - - private static final long serialVersionUID = 3670315004615987482L; - - public abstract Integer getMinimumDataCountPerGraph(); - - public abstract double getMinimumWindConfidence(); - - public abstract boolean applyMinimumWindConfidence(); - - public abstract Integer getMinimumDataCountPerAngle(); - - public abstract int getNumberOfHistogramColumns(); - - public abstract boolean useOnlyWindGaugesForWindSpeed(); - - public abstract boolean useOnlyEstimatedForWindDirection(); - - public abstract WindSpeedSteppingWithMaxDistance getWindSpeedStepping(); - - public abstract boolean areDefault(); - - } - - public interface WindSpeedStepping extends Serializable { - - public abstract int getLevelIndexForValue(double speed); - - public abstract Double getSteppedValueForValue(double speed); - - double[] getRawStepping(); - - public abstract int getLevelIndexFloorForValue(double speed); - - public abstract int getLevelIndexCeilingForValue(double speed); - - double getDistanceToLevelFloor(double speed); - - int hashCode(); - - boolean equals(Object obj); - - } - - public class WindSpeedSteppingImpl implements WindSpeedStepping { - - // For GWT Serialization - protected WindSpeedSteppingImpl() { - }; - - private static final long serialVersionUID = 2215693490331489508L; - protected double[] levels; - - public WindSpeedSteppingImpl(double[] levels) { - this.levels = levels; - } - - public int getNumberOfLevels() { - return levels.length; - } - - @Override - public int getLevelIndexForValue(double speed) { - return 0; - } - - @Override - public Double getSteppedValueForValue(double speed) { - return null; - } - - @Override - public double[] getRawStepping() { - return null; - } - - @Override - public int getLevelIndexFloorForValue(double speed) { - return 0; - } - - @Override - public int getLevelIndexCeilingForValue(double speed) { - return 0; - } - - @Override - public double getDistanceToLevelFloor(double speed) { - return 0; - } - } - - public class WindSpeedSteppingWithMaxDistance extends WindSpeedSteppingImpl { - - private static final long serialVersionUID = -2207840179212727591L; - private double maxDistance; - - // For GWT Serialization - WindSpeedSteppingWithMaxDistance() { - super(); - }; - - public WindSpeedSteppingWithMaxDistance(double[] levels, double maxDistance) { - super(levels); - this.maxDistance = maxDistance; - } - - @Override - public int getLevelIndexForValue(double speed) { - return -1; - } - - public double getMaxDistance() { - return maxDistance; - } - } - public class PolarDataMiningSettingsImpl extends PolarDataMiningSettings { - private static final long serialVersionUID = 2731616509404813790L; - private Integer minimumDataCountPerGraph; - private double minimumWindConfidence; - private Integer minimumDataCountPerAngle; - private Integer numberOfHistogramColumns; - private boolean useOnlyWindGaugesForWindSpeed; - private boolean useOnlyEstimationForWindDirection; - private WindSpeedSteppingWithMaxDistance windStepping; - private boolean applyMinimumWindConfidence; - - // GWT - PolarDataMiningSettingsImpl() { - }; - - public PolarDataMiningSettingsImpl(Integer minimumDataCountPerGraph, - double minimumWindConfidence, boolean applyMinimumWindConfidence, - Integer minimumDataCountPerAngle, Integer numberOfHistogramColumns, - boolean useOnlyWindGaugesForWindSpeed, boolean useOnlyEstimationForWindDirection, - WindSpeedSteppingWithMaxDistance windStepping) { - this.minimumDataCountPerGraph = minimumDataCountPerGraph; - this.minimumWindConfidence = minimumWindConfidence; - this.applyMinimumWindConfidence = applyMinimumWindConfidence; - this.minimumDataCountPerAngle = minimumDataCountPerAngle; - this.numberOfHistogramColumns = numberOfHistogramColumns; - this.useOnlyWindGaugesForWindSpeed = useOnlyWindGaugesForWindSpeed; - this.useOnlyEstimationForWindDirection = useOnlyEstimationForWindDirection; - this.windStepping = windStepping; - } - - @Override - public Integer getMinimumDataCountPerGraph() { - return minimumDataCountPerGraph; - } - - @Override - public double getMinimumWindConfidence() { - return minimumWindConfidence; - } - - @Override - public Integer getMinimumDataCountPerAngle() { - return minimumDataCountPerAngle; - } - - @Override - public int getNumberOfHistogramColumns() { - return numberOfHistogramColumns; - } - - @Override - public boolean useOnlyWindGaugesForWindSpeed() { - return useOnlyWindGaugesForWindSpeed; - } - - @Override - public boolean useOnlyEstimatedForWindDirection() { - return useOnlyEstimationForWindDirection; - } - - @Override - public WindSpeedSteppingWithMaxDistance getWindSpeedStepping() { - return windStepping; - } - - @Override - public boolean applyMinimumWindConfidence() { - return applyMinimumWindConfidence; - } - - @Override - public boolean areDefault() { - return false; - } - } - - public static class DataRetrieverLevelDTO implements Serializable, - Comparable { - private static final long serialVersionUID = 6911713148350359643L; - - /** - * The index of this retriever level in the retriever chain. - */ - private int retrieverLevel; - /** - * The fully qualified name of the Processor performing the retrieval of this level. - */ - private String retrieverTypeName; - /** - * The type of the retrieved data elements in form of a {@link LocalizedTypeDTO}. Its type name - * is the fully qualified name of the retrieved data type and is used for identification - * purposes. - * - * The display name is used as human readable string representation of this retriever level and - * should be omitted when persisting a DataRetrieverLevelDTO. - */ - private LocalizedTypeDTO retrievedDataType; - - /** - * The default settings for this retriever level or null, if the level doesn't have - * settings. Should be omitted when persisting a DataRetrieverLevelDTO. - */ - private SerializableSettings defaultSettings; - - /** - * Constructor for the GWT-Serialization. Don't use this! - */ - @Deprecated - DataRetrieverLevelDTO() { - } - - public DataRetrieverLevelDTO(int retrieverLevel, String retrieverTypeName, - LocalizedTypeDTO retrievedDataType, SerializableSettings defaultSettings) { - this.retrieverLevel = retrieverLevel; - this.retrieverTypeName = retrieverTypeName; - this.retrievedDataType = retrievedDataType; - this.defaultSettings = defaultSettings; - } - - public int getLevel() { - return retrieverLevel; - } - - public String getRetrieverTypeName() { - return retrieverTypeName; - } - - public LocalizedTypeDTO getRetrievedDataType() { - return retrievedDataType; - } - - public boolean hasSettings() { - return getDefaultSettings() != null; - } - - public SerializableSettings getDefaultSettings() { - return defaultSettings; - } - - @Override - public int compareTo(DataRetrieverLevelDTO o) { - return 0; - } - } - - public static class FilterDimensionIdentifier implements Serializable { - private static final long serialVersionUID = -4824907338023614296L; - private DataRetrieverLevelDTO retrieverLevel; - private FunctionDTO dimensionFunction; - - @Deprecated // for GWT serialization only - FilterDimensionIdentifier() { - } - - public FilterDimensionIdentifier(DataRetrieverLevelDTO retrieverLevel, - FunctionDTO dimensionFunction) { - super(); - this.retrieverLevel = retrieverLevel; - this.dimensionFunction = dimensionFunction; - } - - public DataRetrieverLevelDTO getRetrieverLevel() { - return retrieverLevel; - } - - public FunctionDTO getDimensionFunction() { - return dimensionFunction; - } - } - - public static class ModifiableDataMiningReportDTO implements DataMiningReportDTO { - private static final long serialVersionUID = -6512175470789118223L; - - /** - * Handled by identity; remove and contains checks don't use query equality because modifiable - * queries can change their equality/hashCode over their life cycle - */ - private ArrayList queryDefinitions; - private HashSet parameters; - private IdentityHashMap> parameterUsages; - - @SuppressWarnings("unused") // only to force the type to be instantiable for GWT serialization - private ParameterModelListener _serializationDummy = null; - - /** - * Objects of this type entertain one value change listener for each parameter in - * {@link #getParameters()} so that when a parameter value changes, all - * {@link #getQueryDefinitions() queries in this report} using this parameter will have their - * {@link StatisticQueryDefinitionDTO#getFilterSelection() filter selections} adjusted - * accordingly. - */ - private HashMap parameterValueChangeListeners; - - private transient Set parameterModelListeners; - - /** - * Creates an empty report with no queries and hence no parameter usages. - */ - public ModifiableDataMiningReportDTO() { - this(Collections.emptySet(), Collections.emptySet()); - } - - public ModifiableDataMiningReportDTO( - Iterable queryDefinitions, - Iterable parameters) { - } - - public HashMap getParameterValueChangeListeners() { - return parameterValueChangeListeners; - } - - public void setParameterValueChangeListeners( - HashMap parameterValueChangeListeners) { - this.parameterValueChangeListeners = parameterValueChangeListeners; - } - - public Set getParameterModelListeners() { - return parameterModelListeners; - } - - public void setParameterModelListeners(Set parameterModelListeners) { - this.parameterModelListeners = parameterModelListeners; - } - - public ArrayList getQueryDefinitions() { - return queryDefinitions; - } - - public void setQueryDefinitions( - ArrayList queryDefinitions) { - this.queryDefinitions = queryDefinitions; - } - - public HashSet getParameters() { - return parameters; - } - - public void setParameters(HashSet parameters) { - this.parameters = parameters; - } - - public IdentityHashMap> getParameterUsages() { - return parameterUsages; - } - - public void setParameterUsages( - IdentityHashMap> parameterUsages) { - this.parameterUsages = parameterUsages; - } - } - - public class DataRetrieverChainDefinitionDTO implements Serializable, - Comparable { - private static final long serialVersionUID = 7806173601799997214L; - - /** - * The fully qualified name of the initial data source type. - */ - private String dataSourceTypeName; - /** - * The ordered list of retriever levels. - */ - private ArrayList retrieverLevels; - - /** - * A human readable string representation. Should be omitted when persisting a - * DataRetrieverChainDefinition. - */ - private String displayName; - - /** - * Constructor for the GWT-Serialization. Don't use this! - */ - @Deprecated - DataRetrieverChainDefinitionDTO() { - } - - public DataRetrieverChainDefinitionDTO(String name, String dataSourceTypeName, - ArrayList retrieverLevels) { - this.displayName = name; - this.dataSourceTypeName = dataSourceTypeName; - this.retrieverLevels = new ArrayList<>(retrieverLevels); - } - - public String getName() { - return displayName; - } - - public String getDataSourceTypeName() { - return dataSourceTypeName; - } - - public String getRetrievedDataTypeName() { - return retrieverLevels.get(retrieverLevels.size() - 1).getRetrievedDataType().getTypeName(); - } - - public ArrayList getRetrieverLevels() { - return retrieverLevels; - } - - public int getLevelAmount() { - return retrieverLevels.size(); - } - - @Override - public int compareTo(DataRetrieverChainDefinitionDTO o) { - return 0; - } - } - - public static class ModifiableStatisticQueryDefinitionDTO implements StatisticQueryDefinitionDTO { - private static final long serialVersionUID = -6438771277564908352L; - - /** - * The extraction function used to get the statistic from the retrieved data elements. - */ - private FunctionDTO statisticToCalculate; - /** - * The aggregator used to aggregate the extracted statistics. - */ - private AggregationProcessorDefinitionDTO aggregatorDefinition; - /** - * The list of dimensions to group the results by. - */ - private ArrayList dimensionsToGroupBy; - /** - * The retriever chain used to retrieve the data elements. - */ - private DataRetrieverChainDefinitionDTO dataRetrieverChainDefinition; - /** - * The settings to be used for the retriever levels. Can be empty, if no retriever level has - * settings. - */ - private HashMap retrieverSettings; - /** - * The values used to filter the data elements during the retrieval process. Each set of values - * is mapped by the dimension used to extract the value from a data element and the retriever - * level the dimension belongs to. The dimensions are declared by the data type retrieved by the - * corresponding retriever level and not by the data type the query is based on (return type of - * the retriever chain). - * - * The actual filter values can be anything that is returned by a dimension, which will be - * mostly simple data like Strings or Enums, but can also be more complex data structures like - * {@link ClusterDTO}. - */ - private HashMap>> filterSelection; - - /** - * The LocalInfo name that will be used for internationalization. Should be omitted when - * persisting a ModifiableStatisticQueryDefinitionDTO. - */ - private String localeInfoName; - - /** - * Used to record whether this query has changed since last run. When, for example, a parameter - * change triggers the modification of this query's dimension filter(s), the flag should be - * {@link #setQueryChangedSinceLastRun(boolean) set}. It must be un-set when the query is run. - */ - private transient boolean queryChangedSinceLastRun; - - /** - * Constructor for the GWT-Serialization. Don't use this! - */ - @Deprecated - ModifiableStatisticQueryDefinitionDTO() { - } - - public ModifiableStatisticQueryDefinitionDTO(String localeInfoName, - FunctionDTO statisticToCalculate, AggregationProcessorDefinitionDTO aggregatorDefinition, - DataRetrieverChainDefinitionDTO dataRetrieverChainDefinition) { - this.localeInfoName = localeInfoName; - this.statisticToCalculate = statisticToCalculate; - this.aggregatorDefinition = aggregatorDefinition; - this.dataRetrieverChainDefinition = dataRetrieverChainDefinition; - this.retrieverSettings = new HashMap<>(); - this.filterSelection = new HashMap<>(); - this.dimensionsToGroupBy = new ArrayList(); - } - - public ModifiableStatisticQueryDefinitionDTO(StatisticQueryDefinitionDTO definition) { - } - - public FunctionDTO getStatisticToCalculate() { - return statisticToCalculate; - } - - public AggregationProcessorDefinitionDTO getAggregatorDefinition() { - return aggregatorDefinition; - } - - public DataRetrieverChainDefinitionDTO getDataRetrieverChainDefinition() { - return dataRetrieverChainDefinition; - } - - public String getLocaleInfoName() { - return localeInfoName; - } - - public boolean isQueryChangedSinceLastRun() { - return queryChangedSinceLastRun; - } - - public void setQueryChangedSinceLastRun(boolean queryChangedSinceLastRun) { - this.queryChangedSinceLastRun = queryChangedSinceLastRun; - } - - public void setDataRetrieverChainDefinition( - DataRetrieverChainDefinitionDTO dataRetrieverChainDefinition) { - if (dataRetrieverChainDefinition == null) { - throw new NullPointerException("The data retriever chain definition mustn't be null"); - } - this.dataRetrieverChainDefinition = dataRetrieverChainDefinition; - } - - public void setRetrieverSettings(DataRetrieverLevelDTO retrieverLevel, - SerializableSettings settings) { - if (retrieverLevel == null) { - throw new NullPointerException("The retriever level mustn't be null"); - } - retrieverSettings.put(retrieverLevel, settings); - } - - public void setFilterSelectionFor(DataRetrieverLevelDTO retrieverLevel, - HashMap> levelFilterSelection) { - if (retrieverLevel == null) { - throw new NullPointerException("The retriever level mustn't be null"); - } - if (levelFilterSelection == null) { - throw new NullPointerException("The level filter selection mustn't be null"); - } - filterSelection.put(retrieverLevel, levelFilterSelection); - } - - public void appendDimensionToGroupBy(FunctionDTO dimensionToGroupBy) { - if (dimensionToGroupBy == null) { - throw new NullPointerException("The dimension mustn't be null"); - } - dimensionsToGroupBy.add(dimensionToGroupBy); - } - - public void setStatisticToCalculate(FunctionDTO statisticToCalculate) { - if (statisticToCalculate == null) { - throw new NullPointerException("The statistic to calculate mustn't be null"); - } - this.statisticToCalculate = statisticToCalculate; - } - - public void setAggregatorDefinition(AggregationProcessorDefinitionDTO aggregatorDefinition) { - if (aggregatorDefinition == null) { - throw new NullPointerException("The aggregator definition mustn't be null"); - } - this.aggregatorDefinition = aggregatorDefinition; - } - - public void setLocaleInfoName(String localeInfoName) { - if (localeInfoName == null) { - throw new NullPointerException("The locale info name mustn't be null"); - } - this.localeInfoName = localeInfoName; - } - } - - public static class AggregationProcessorDefinitionDTO implements Serializable, - Comparable { - private static final long serialVersionUID = -434497637456305118L; - - /** - * Additional identification feature, since the extracted and aggregated type wouldn't suffice. - * Has to be included when persisting a AggregationProcessorDefinitionDTO. - */ - private String messageKey; - /** - * The fully qualified name of the aggregators input type. - */ - private String extractedTypeName; - /** - * The fully qualified name of the aggregators result type. - */ - private String aggregatedTypeName; - - /** - * A human readable string representation. Should be omitted when persisting a - * AggregationProcessorDefinitionDTO. - */ - private String displayName; - - /** - * Constructor for the GWT-Serialization. Don't use this! - */ - @Deprecated - AggregationProcessorDefinitionDTO() { - } - - public AggregationProcessorDefinitionDTO(String messageKey, String extractedTypeName, - String aggregatedTypeName, String displayName) { - this.messageKey = messageKey; - this.extractedTypeName = extractedTypeName; - this.aggregatedTypeName = aggregatedTypeName; - this.displayName = displayName; - } - - public String getMessageKey() { - return messageKey; - } - - public String getExtractedTypeName() { - return extractedTypeName; - } - - public String getAggregatedTypeName() { - return aggregatedTypeName; - } - - public String getDisplayName() { - return displayName; - } - - @Override - public String toString() { - return getExtractedTypeName() + " -> " + getAggregatedTypeName() + "[messageKey: " - + messageKey + "]"; - } - - @Override - public int compareTo(AggregationProcessorDefinitionDTO aggregatorDefinitionDTO) { - return getDisplayName().compareTo(aggregatorDefinitionDTO.getDisplayName()); - } - } - - public static interface Settings { - } - - public static abstract class AbstractSettings implements Settings { - } - - public static class FunctionDTO implements Serializable, Comparable { - private static final long serialVersionUID = 4587389541910498505L; - - private final boolean isDimension; - /** - * The function's name including its parameters in parenthesis or empty parenthesis, if this - * function doesn't have parameters. Can consist of multiple concatenated function names, if the - * backend Function encapsulated multiple data mining functions. - */ - private final String functionName; - /** - * The fully qualified name of the type that declares this function. - */ - private final String sourceTypeName; - /** - * The fully qualified name of the type returned by this function. - */ - private final String returnTypeName; - /** - * The fully qualified names of the functions parameter types. - */ - private final List parameterTypeNames; - - /** - * Meta-data for the natural ordering of FunctionDTOs. Should be omitted when persisting a - * FunctionDTO. - */ - private final int ordinal; - /** - * A human readable string representation. Should be omitted when persisting a FunctionDTO. - */ - private String displayName; - /** - * If the {@link #displayName} is not set and this provider is valid, the {@F - */ - private transient DisplayNameProvider displayNameProvider; - - public static interface DisplayNameProvider { - String getDisplayName(); - } - - /** - * Makes the construction of the {@link #displayName} lazy, using the - * {@code displayNameProvider}. - */ - public FunctionDTO(boolean isDimension, String functionName, String sourceTypeName, - String returnTypeName, List parameterTypeNames, - DisplayNameProvider displayNameProvider, int ordinal) { - this(isDimension, functionName, sourceTypeName, returnTypeName, parameterTypeNames, - /* displayName is constructed lazily using the displayNameProvider */ (String) null, - ordinal); - this.displayNameProvider = displayNameProvider; - } - - public FunctionDTO(boolean isDimension, String functionName, String sourceTypeName, - String returnTypeName, List parameterTypeNames, String displayName, int ordinal) { - this.isDimension = isDimension; - this.functionName = functionName; - this.sourceTypeName = sourceTypeName; - this.returnTypeName = returnTypeName; - this.parameterTypeNames = new ArrayList(parameterTypeNames); - this.displayName = displayName; - this.ordinal = ordinal; - } - - public String getSourceTypeName() { - return sourceTypeName; - } - - public String getReturnTypeName() { - return returnTypeName; - } - - public List getParameterTypeNames() { - return parameterTypeNames; - } - - public String getFunctionName() { - return functionName; - } - - public String getDisplayName() { - if (displayName == null && displayNameProvider != null) { - displayName = displayNameProvider.getDisplayName(); - } - return displayName; - } - - public boolean isDimension() { - return isDimension; - } - - public int getOrdinal() { - return ordinal; - } - - @Override - public int compareTo(FunctionDTO f) { - return Integer.compare(this.getOrdinal(), f.getOrdinal()); - } - } - - public class FunctionDTO_CustomFieldSerializer extends CustomFieldSerializer { - @Override - public void serializeInstance(SerializationStreamWriter streamWriter, FunctionDTO instance) - throws SerializationException { - serialize(streamWriter, instance); - } - - public static void serialize(SerializationStreamWriter streamWriter, FunctionDTO instance) - throws SerializationException { - streamWriter.writeBoolean(instance.isDimension()); - streamWriter.writeString(instance.getFunctionName()); - streamWriter.writeString(instance.getSourceTypeName()); - streamWriter.writeString(instance.getReturnTypeName()); - streamWriter.writeObject(instance.getParameterTypeNames()); - streamWriter.writeString(instance.getDisplayName()); - streamWriter.writeInt(instance.getOrdinal()); - } - - @Override - public boolean hasCustomInstantiateInstance() { - return true; - } - - @Override - public FunctionDTO instantiateInstance(SerializationStreamReader streamReader) - throws SerializationException { - return instantiate(streamReader); - } - - @SuppressWarnings("unchecked") // the cast to List is the problem here - public static FunctionDTO instantiate(SerializationStreamReader streamReader) - throws SerializationException { - return new FunctionDTO(/* isDimension */ streamReader.readBoolean(), - /* function name */ streamReader.readString(), /* source type name */ streamReader - .readString(), /* return type name */ streamReader.readString(), - /* parameter type names */ (List) streamReader.readObject(), - /* display name */ streamReader.readString(), /* ordinal */ streamReader.readInt()); - } - - @Override - public void deserializeInstance(SerializationStreamReader streamReader, FunctionDTO instance) - throws SerializationException { - deserialize(streamReader, instance); - } - - public static void deserialize(SerializationStreamReader streamReader, FunctionDTO instance) { - // Done by instantiateInstance - } - } - /** - * Not serializable. + * Not serializable; interface only */ interface A extends IsSerializable { } - + /** - * Not serializable due to Object field. + * Not serializable; interface only, the second interface implemented by {@link B} */ - class C extends B { - Object field; + interface D extends IsSerializable { } /** - * Not instantiable either, due to non-default constructor and final field - */ - class D implements A { - private final int i; - - public D(int i) { - super(); - this.i = i; - } - - public int getI() { - return i; - } - } - - /** - * Not instantiable; see {@link D} + * Auto serializable, but with back-reference to A for which the question of instantiable subtypes + * depends on the serializability of this class. The reference to {@link C} which + * has a back-reference to {@link B} and B being its only subtypes candidate + * helps reproduce issue 10181. */ - class E implements A { - private final int i; - - public E(int i) { - super(); - this.i = i; - } + class B implements A, D { + private A a; + private B b; + private C c; - public int getI() { - return i; + public A getA() { + return a; } - } - - /** - * Not instantiable; see {@link D} - */ - class F implements A { - private final int i; - public F(int i) { - super(); - this.i = i; + public void setA(A a) { + this.a = a; } - public int getI() { - return i; + public B getB() { + return b; } - } - - /** - * Not instantiable; see {@link D} - */ - class G implements A { - private final int i; - public G() { - i = 0; + public void setB(B b) { + this.b = b; } - public G(int i) { - super(); - this.i = i; + public C getC() { + return c; } - public int getI() { - return i; + public void setC(C c) { + this.c = c; } } /** - * Auto serializable, but with back-reference to A for which the question of instantiable subtypes - * depends on the serializability of this class; all other sub-classes are not instantiable. + * Auto-serializable with back-reference to B through D, a different interface, triggering another + * descent through checkSubtypes, finding an already "done" TIC for {@link B} with + * {@code instantiable==false}. Being "done", it is used to decide instantiability of the only + * subclass candidate of {@link D}, which is {@link B}, resulting in {@code false} for + * the instantiability of {@link D}, and thus of {@link C}, and thus of {@link B}, causing + * the test case to fail prior to a fix for issue 10181. */ - class B implements A { - private A a; + class C implements IsSerializable { + private D d; - public A getA() { - return a; + public D getD() { + return d; } - public void setA(A a) { - this.a = a; + public void setD(D d) { + this.d = d; } } A getA(); - - StoredDataMiningReportDTO getStoredDataMiningReportDTO(); } From 1d430616bc317c80aedf51121ba54efd567055e7 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 1 Dec 2025 23:10:56 +0100 Subject: [PATCH 11/18] issue-10181: streamlined test case --- .../SerializableTypeOracleBuilderTest.java | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) 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 2ddfdf5723c..928ae6b0d46 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java +++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java @@ -15,6 +15,18 @@ */ package com.google.gwt.user.rebind.rpc; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + import com.google.gwt.core.ext.PropertyOracle; import com.google.gwt.core.ext.StubGeneratorContext; import com.google.gwt.core.ext.TreeLogger; @@ -54,18 +66,6 @@ import junit.framework.TestCase; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; - /** * Used to test the {@link SerializableTypeOracleBuilder}. */ @@ -1526,18 +1526,18 @@ public void testInstantiabilityDetectionOnRecursiveTypeGraph() throws UnableToCo NotFoundException { TreeLogger logger = createLogger(); TypeOracle typeOracle = getTestTypeOracle(); - JClassType a = typeOracle.getType( - RecursiveTypeGraphInstantiability.B.class.getCanonicalName()); + JClassType a = typeOracle.getType(RecursiveTypeGraphInstantiability.B.class.getCanonicalName()); SerializableTypeOracleBuilder stob = createSerializableTypeOracleBuilder(logger, typeOracle); stob.addRootType(logger, a); SerializableTypeOracle sto = stob.build(logger); - TypeInfo[] expected = - new TypeInfo[] { - new TypeInfo(makeSourceName(NotAllSubtypesAreSerializable.B.class.getName()), true), - new TypeInfo(makeSourceName(NotAllSubtypesAreSerializable.C.class.getName()), true)}; + assertTrue("Expected type info for B to be present and marked instantiable but found " + Arrays + .asList(getActualTypeInfo(sto)) + ".", Arrays.asList(getActualTypeInfo(sto)).contains( + new TypeInfo(makeSourceName(RecursiveTypeGraphInstantiability.B.class.getName()), + true))); + TypeInfo[] expected = new TypeInfo[] { + new TypeInfo(makeSourceName(NotAllSubtypesAreSerializable.B.class.getName()), true), + new TypeInfo(makeSourceName(NotAllSubtypesAreSerializable.C.class.getName()), true)}; validateSTO(sto, expected); -// assertTrue(Arrays.asList(getActualTypeInfo(sto)).contains(new TypeInfo(makeSourceName( -// RecursiveTypeGraphInstantiability.ParameterValueChangeListener.class.getName()), true))); } /** From 90a639b9ed508a5c763d21ff793ce6dac78f9e04 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 2 Dec 2025 17:52:29 +0100 Subject: [PATCH 12/18] issue-10181: start type analysis from A; remove unused getters/setters --- .../SerializableTypeOracleBuilderTest.java | 6 +-- .../RecursiveTypeGraphInstantiability.java | 40 ++----------------- 2 files changed, 7 insertions(+), 39 deletions(-) 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 928ae6b0d46..a3fb0e5ea29 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java +++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java @@ -1526,7 +1526,7 @@ public void testInstantiabilityDetectionOnRecursiveTypeGraph() throws UnableToCo NotFoundException { TreeLogger logger = createLogger(); TypeOracle typeOracle = getTestTypeOracle(); - JClassType a = typeOracle.getType(RecursiveTypeGraphInstantiability.B.class.getCanonicalName()); + JClassType a = typeOracle.getType(RecursiveTypeGraphInstantiability.A.class.getCanonicalName()); SerializableTypeOracleBuilder stob = createSerializableTypeOracleBuilder(logger, typeOracle); stob.addRootType(logger, a); SerializableTypeOracle sto = stob.build(logger); @@ -1535,8 +1535,8 @@ public void testInstantiabilityDetectionOnRecursiveTypeGraph() throws UnableToCo new TypeInfo(makeSourceName(RecursiveTypeGraphInstantiability.B.class.getName()), true))); TypeInfo[] expected = new TypeInfo[] { - new TypeInfo(makeSourceName(NotAllSubtypesAreSerializable.B.class.getName()), true), - new TypeInfo(makeSourceName(NotAllSubtypesAreSerializable.C.class.getName()), true)}; + new TypeInfo(makeSourceName(RecursiveTypeGraphInstantiability.B.class.getName()), true), + new TypeInfo(makeSourceName(RecursiveTypeGraphInstantiability.C.class.getName()), true)}; validateSTO(sto, expected); } diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java index 20b24754714..ab8445e9a70 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java +++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java @@ -22,33 +22,9 @@ interface D extends IsSerializable { * helps reproduce issue 10181. */ class B implements A, D { - private A a; - private B b; - private C c; - - public A getA() { - return a; - } - - public void setA(A a) { - this.a = a; - } - - public B getB() { - return b; - } - - public void setB(B b) { - this.b = b; - } - - public C getC() { - return c; - } - - public void setC(C c) { - this.c = c; - } + A a; + B b; + C c; } /** @@ -60,15 +36,7 @@ public void setC(C c) { * the test case to fail prior to a fix for issue 10181. */ class C implements IsSerializable { - private D d; - - public D getD() { - return d; - } - - public void setD(D d) { - this.d = d; - } + D d; } A getA(); From 67816eeda35ce43f6b53829ceb2506da6112b803 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 3 Dec 2025 15:32:14 +0100 Subject: [PATCH 13/18] issue-10181: added simplified version of recursive type graph plus tests --- .../rpc/SerializableTypeOracleBuilder.java | 4 +- .../SerializableTypeOracleBuilderTest.java | 59 ++++++++++++++++++- .../RecursiveTypeGraphInstantiability.java | 4 +- ...fiedRecursiveTypeGraphInstantiability.java | 28 +++++++++ 4 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 user/test/com/google/gwt/user/rebind/rpc/testcases/client/SimplifiedRecursiveTypeGraphInstantiability.java 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 e604d978dbe..a0b61179e64 100644 --- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java +++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java @@ -1003,7 +1003,9 @@ TypeInfoComputed computeTypeInstantiability(TreeLogger logger, JType type, TypeP Set instantiableTypes = new HashSet(); boolean anySubtypes = checkSubtypes(localLogger, originalType, instantiableTypes, path, problems); - if (!tic.isDone()) { + if (tic.isPendingInstantiable()) { + tic.setInstantiableSubtypes(anySubtypes); + } else if (!tic.isDone()) { tic.setInstantiableSubtypes(anySubtypes); tic.setInstantiable(false); } 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 a3fb0e5ea29..f30ac9c03a0 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java +++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java @@ -62,6 +62,7 @@ import com.google.gwt.user.rebind.rpc.testcases.client.ParameterizedTypeInList; import com.google.gwt.user.rebind.rpc.testcases.client.RawTypeInList; import com.google.gwt.user.rebind.rpc.testcases.client.RecursiveTypeGraphInstantiability; +import com.google.gwt.user.rebind.rpc.testcases.client.SimplifiedRecursiveTypeGraphInstantiability; import com.google.gwt.user.rebind.rpc.testcases.client.SubclassUsedInArray; import junit.framework.TestCase; @@ -1522,8 +1523,8 @@ public void testNotAllSubtypesAreSerializable() throws UnableToCompleteException validateSTO(sto, expected); } - public void testInstantiabilityDetectionOnRecursiveTypeGraph() throws UnableToCompleteException, - NotFoundException { + public void testInstantiabilityDetectionOnRecursiveTypeGraphStartingAtA() + throws UnableToCompleteException, NotFoundException { TreeLogger logger = createLogger(); TypeOracle typeOracle = getTestTypeOracle(); JClassType a = typeOracle.getType(RecursiveTypeGraphInstantiability.A.class.getCanonicalName()); @@ -1540,6 +1541,60 @@ public void testInstantiabilityDetectionOnRecursiveTypeGraph() throws UnableToCo validateSTO(sto, expected); } + public void testInstantiabilityDetectionOnRecursiveTypeGraphStartingAtB() + throws UnableToCompleteException, NotFoundException { + TreeLogger logger = createLogger(); + TypeOracle typeOracle = getTestTypeOracle(); + JClassType b = typeOracle.getType(RecursiveTypeGraphInstantiability.B.class.getCanonicalName()); + SerializableTypeOracleBuilder stob = createSerializableTypeOracleBuilder(logger, typeOracle); + stob.addRootType(logger, b); + SerializableTypeOracle sto = stob.build(logger); + assertTrue("Expected type info for B to be present and marked instantiable but found " + Arrays + .asList(getActualTypeInfo(sto)) + ".", Arrays.asList(getActualTypeInfo(sto)).contains( + new TypeInfo(makeSourceName(RecursiveTypeGraphInstantiability.B.class.getName()), + true))); + TypeInfo[] expected = new TypeInfo[] { + new TypeInfo(makeSourceName(RecursiveTypeGraphInstantiability.B.class.getName()), true), + new TypeInfo(makeSourceName(RecursiveTypeGraphInstantiability.C.class.getName()), true)}; + validateSTO(sto, expected); + } + + public void testInstantiabilityDetectionOnSimplifiedRecursiveTypeGraphStartingAtA() + throws UnableToCompleteException, NotFoundException { + TreeLogger logger = createLogger(); + TypeOracle typeOracle = getTestTypeOracle(); + JClassType a = typeOracle.getType(SimplifiedRecursiveTypeGraphInstantiability.A.class.getCanonicalName()); + SerializableTypeOracleBuilder stob = createSerializableTypeOracleBuilder(logger, typeOracle); + stob.addRootType(logger, a); + SerializableTypeOracle sto = stob.build(logger); + assertTrue("Expected type info for B to be present and marked instantiable but found " + Arrays + .asList(getActualTypeInfo(sto)) + ".", Arrays.asList(getActualTypeInfo(sto)).contains( + new TypeInfo(makeSourceName(SimplifiedRecursiveTypeGraphInstantiability.B.class.getName()), + true))); + TypeInfo[] expected = new TypeInfo[] { + new TypeInfo(makeSourceName(SimplifiedRecursiveTypeGraphInstantiability.B.class.getName()), true), + new TypeInfo(makeSourceName(SimplifiedRecursiveTypeGraphInstantiability.C.class.getName()), true)}; + validateSTO(sto, expected); + } + + public void testInstantiabilityDetectionOnSimplifiedRecursiveTypeGraphStartingAtB() + throws UnableToCompleteException, NotFoundException { + TreeLogger logger = createLogger(); + TypeOracle typeOracle = getTestTypeOracle(); + JClassType b = typeOracle.getType(SimplifiedRecursiveTypeGraphInstantiability.B.class.getCanonicalName()); + SerializableTypeOracleBuilder stob = createSerializableTypeOracleBuilder(logger, typeOracle); + stob.addRootType(logger, b); + SerializableTypeOracle sto = stob.build(logger); + assertTrue("Expected type info for B to be present and marked instantiable but found " + Arrays + .asList(getActualTypeInfo(sto)) + ".", Arrays.asList(getActualTypeInfo(sto)).contains( + new TypeInfo(makeSourceName(SimplifiedRecursiveTypeGraphInstantiability.B.class.getName()), + true))); + TypeInfo[] expected = new TypeInfo[] { + new TypeInfo(makeSourceName(SimplifiedRecursiveTypeGraphInstantiability.B.class.getName()), true), + new TypeInfo(makeSourceName(SimplifiedRecursiveTypeGraphInstantiability.C.class.getName()), true)}; + validateSTO(sto, expected); + } + /** * Tests that Object[] is not instantiable. */ diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java index ab8445e9a70..703a096dbd4 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java +++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java @@ -16,7 +16,7 @@ interface D extends IsSerializable { } /** - * Auto serializable, but with back-reference to A for which the question of instantiable subtypes + * Default-serializable, but with back-reference to A for which the question of instantiable subtypes * depends on the serializability of this class. The reference to {@link C} which * has a back-reference to {@link B} and B being its only subtypes candidate * helps reproduce issue 10181. @@ -28,7 +28,7 @@ class B implements A, D { } /** - * Auto-serializable with back-reference to B through D, a different interface, triggering another + * Default-serializable with back-reference to B through D, a different interface, triggering another * descent through checkSubtypes, finding an already "done" TIC for {@link B} with * {@code instantiable==false}. Being "done", it is used to decide instantiability of the only * subclass candidate of {@link D}, which is {@link B}, resulting in {@code false} for diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/SimplifiedRecursiveTypeGraphInstantiability.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/SimplifiedRecursiveTypeGraphInstantiability.java new file mode 100644 index 00000000000..f5ee4f4ec3d --- /dev/null +++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/SimplifiedRecursiveTypeGraphInstantiability.java @@ -0,0 +1,28 @@ +package com.google.gwt.user.rebind.rpc.testcases.client; + +import com.google.gwt.user.client.rpc.IsSerializable; + +public interface SimplifiedRecursiveTypeGraphInstantiability extends IsSerializable { + /** + * Not serializable; interface only + */ + interface A extends IsSerializable { + } + + /** + * Default serializable, but with back-reference to B for which the question of instantiable subtypes + * depends on the serializability of this class. The reference to {@link C} which + * has a back-reference to {@link A} and B being its only subtypes candidate + * helps reproduce issue 10181. + */ + class B implements A { + B b; + C c; + } + + class C implements IsSerializable { + A A; + } + + A getA(); +} From 8ab92dad08790d75a1d4975cfc11f994d03c6c0a Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 3 Dec 2025 15:57:48 +0100 Subject: [PATCH 14/18] issue-10181: fixed import order again in SerializableTypeOracleBuilderTest --- .../SerializableTypeOracleBuilderTest.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) 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 f30ac9c03a0..84c3ec45534 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java +++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java @@ -15,18 +15,6 @@ */ package com.google.gwt.user.rebind.rpc; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; - import com.google.gwt.core.ext.PropertyOracle; import com.google.gwt.core.ext.StubGeneratorContext; import com.google.gwt.core.ext.TreeLogger; @@ -67,6 +55,18 @@ import junit.framework.TestCase; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + /** * Used to test the {@link SerializableTypeOracleBuilder}. */ From a309d4dd879ead1fb5a8811329fe2ffc64491d81 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 4 Dec 2025 11:39:13 +0100 Subject: [PATCH 15/18] issue-10181: revert .classpath changes to keep diff minimal --- eclipse/dev/.classpath | 21 ++++++++------------- eclipse/user/.classpath | 10 +++------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/eclipse/dev/.classpath b/eclipse/dev/.classpath index 51f950d18c3..075c1983641 100644 --- a/eclipse/dev/.classpath +++ b/eclipse/dev/.classpath @@ -2,16 +2,16 @@ - - - - - - + + + + + + @@ -30,12 +30,7 @@ - - - - - - - + + diff --git a/eclipse/user/.classpath b/eclipse/user/.classpath index 3a668a55bf7..1fa63d0ffcc 100644 --- a/eclipse/user/.classpath +++ b/eclipse/user/.classpath @@ -6,11 +6,7 @@ - - - - - + @@ -65,9 +61,9 @@ + + - - From ad6dce0e35960ececb97b83d979b9f3c7da600c5 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 4 Dec 2025 13:44:35 +0100 Subject: [PATCH 16/18] issue-10181: added copyright headers to comply with checkstyles rules --- .../client/RecursiveTypeGraphInstantiability.java | 15 +++++++++++++++ ...mplifiedRecursiveTypeGraphInstantiability.java | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java index 703a096dbd4..5038bc0495e 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java +++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java @@ -1,3 +1,18 @@ +/* + * Copyright 2025 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.testcases.client; import com.google.gwt.user.client.rpc.IsSerializable; diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/SimplifiedRecursiveTypeGraphInstantiability.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/SimplifiedRecursiveTypeGraphInstantiability.java index f5ee4f4ec3d..d46816a3942 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/SimplifiedRecursiveTypeGraphInstantiability.java +++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/SimplifiedRecursiveTypeGraphInstantiability.java @@ -1,3 +1,18 @@ +/* + * Copyright 2025 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.testcases.client; import com.google.gwt.user.client.rpc.IsSerializable; From 5d73cb73c92e708f257bc6cff2f5523d84b1d518 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 4 Dec 2025 15:24:46 +0100 Subject: [PATCH 17/18] issue-10181: fixed checkstyle warnings --- .../user/rebind/rpc/SerializableTypeOracleBuilder.java | 4 ++-- .../client/RecursiveTypeGraphInstantiability.java | 4 ++-- .../SimplifiedRecursiveTypeGraphInstantiability.java | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) 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 a0b61179e64..ad239a53f31 100644 --- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java +++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java @@ -1149,8 +1149,8 @@ private boolean checkDeclaredFields(TreeLogger logger, TypeInfoComputed typeInfo checkAllSubtypesOfObject(fieldLogger.branch(TreeLogger.WARN, "Object was reached from a manually serializable type", null), path, problems); } else { - boolean hasInstantiableSubtypes = computeTypeInstantiability(fieldLogger, fieldType, path, problems) - .hasInstantiableSubtypes(); + boolean hasInstantiableSubtypes = computeTypeInstantiability(fieldLogger, fieldType, path, + problems).hasInstantiableSubtypes(); allSucceeded &= hasInstantiableSubtypes; if (!hasInstantiableSubtypes) { fieldLogger.branch(TreeLogger.WARN, "Field type '" diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java index 5038bc0495e..d593d04b703 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java +++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java @@ -19,13 +19,13 @@ public interface RecursiveTypeGraphInstantiability extends IsSerializable { /** - * Not serializable; interface only + * Not serializable; interface only. */ interface A extends IsSerializable { } /** - * Not serializable; interface only, the second interface implemented by {@link B} + * Not serializable; interface only, the second interface implemented by {@link B}. */ interface D extends IsSerializable { } diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/SimplifiedRecursiveTypeGraphInstantiability.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/SimplifiedRecursiveTypeGraphInstantiability.java index d46816a3942..e32539095e9 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/SimplifiedRecursiveTypeGraphInstantiability.java +++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/SimplifiedRecursiveTypeGraphInstantiability.java @@ -19,16 +19,16 @@ public interface SimplifiedRecursiveTypeGraphInstantiability extends IsSerializable { /** - * Not serializable; interface only + * Not serializable; interface only. */ interface A extends IsSerializable { } /** - * Default serializable, but with back-reference to B for which the question of instantiable subtypes - * depends on the serializability of this class. The reference to {@link C} which - * has a back-reference to {@link A} and B being its only subtypes candidate - * helps reproduce issue 10181. + * Default serializable, but with back-reference to B for which the question of instantiable + * subtypes depends on the serializability of this class. The reference to {@link C} which has a + * back-reference to {@link A} and B being its only subtypes candidate helps reproduce issue + * 10181. */ class B implements A { B b; From a0ca1da0afe4dd91610afa7f48e559f1eab25084 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sat, 6 Dec 2025 17:22:21 +0100 Subject: [PATCH 18/18] issue-10181: updated copyright header ("GWT project authors") --- .../rpc/testcases/client/RecursiveTypeGraphInstantiability.java | 2 +- .../client/SimplifiedRecursiveTypeGraphInstantiability.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java index d593d04b703..2af520b093a 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java +++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java @@ -1,5 +1,5 @@ /* - * Copyright 2025 Google Inc. + * Copyright 2025 GWT Project Authors * * 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 diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/SimplifiedRecursiveTypeGraphInstantiability.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/SimplifiedRecursiveTypeGraphInstantiability.java index e32539095e9..57211085fc5 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/SimplifiedRecursiveTypeGraphInstantiability.java +++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/SimplifiedRecursiveTypeGraphInstantiability.java @@ -1,5 +1,5 @@ /* - * Copyright 2025 Google Inc. + * Copyright 2025 GWT Project Authors * * 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