diff --git a/android/guava-tests/test/com/google/common/collect/EnumBiMapTest.java b/android/guava-tests/test/com/google/common/collect/EnumBiMapTest.java index 25b057a72d56..fa927e2d8226 100644 --- a/android/guava-tests/test/com/google/common/collect/EnumBiMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/EnumBiMapTest.java @@ -183,11 +183,13 @@ public void testEnumBiMapConstructor() { assertEquals(bimap3, emptyBimap); } + @GwtIncompatible // keyType public void testKeyType() { EnumBiMap bimap = EnumBiMap.create(Currency.class, Country.class); assertEquals(Currency.class, bimap.keyType()); } + @GwtIncompatible // valueType public void testValueType() { EnumBiMap bimap = EnumBiMap.create(Currency.class, Country.class); assertEquals(Country.class, bimap.valueType()); diff --git a/android/guava-tests/test/com/google/common/collect/EnumHashBiMapTest.java b/android/guava-tests/test/com/google/common/collect/EnumHashBiMapTest.java index 2a78f55477da..24f395328803 100644 --- a/android/guava-tests/test/com/google/common/collect/EnumHashBiMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/EnumHashBiMapTest.java @@ -197,6 +197,7 @@ public void testEnumBiMapConstructor() { assertEquals(bimap3, emptyBimap); } + @GwtIncompatible // keyType public void testKeyType() { EnumHashBiMap bimap = EnumHashBiMap.create(Currency.class); assertEquals(Currency.class, bimap.keyType()); diff --git a/android/guava/src/com/google/common/collect/EnumBiMap.java b/android/guava/src/com/google/common/collect/EnumBiMap.java index cb5ce71fd862..c4b540b56465 100644 --- a/android/guava/src/com/google/common/collect/EnumBiMap.java +++ b/android/guava/src/com/google/common/collect/EnumBiMap.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Platform.getDeclaringClassOrObjectForJ2cl; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; @@ -42,8 +43,22 @@ @J2ktIncompatible @ElementTypesAreNonnullByDefault public final class EnumBiMap, V extends Enum> extends AbstractBiMap { - private transient Class keyType; - private transient Class valueType; + /* + * J2CL's EnumMap does not need the Class instance, so we can use Object.class instead. (Or we + * could use null, but that messes with our nullness checking, including under J2KT. We could + * probably work around it by changing how we annotate the J2CL EnumMap, but that's probably more + * trouble than just using Object.class.) + * + * Then we declare the getters for these fields as @GwtIncompatible so that no one can try to use + * them under J2CL—or, as an unfortunate side effect, under GWT. We do still give the fields + * themselves their proper values under GWT, since GWT's EnumMap does need the Class instance. + * + * Note that sometimes these fields *do* have correct values under J2CL: They will if the caller + * calls `create(Foo.class)`, rather than `create(map)`. That's fine; we just shouldn't rely on + * it. + */ + transient Class keyTypeOrObjectUnderJ2cl; + transient Class valueTypeOrObjectUnderJ2cl; /** * Returns a new, empty {@code EnumBiMap} using the specified key and value types. @@ -66,44 +81,48 @@ public static , V extends Enum> EnumBiMap create( * mappings */ public static , V extends Enum> EnumBiMap create(Map map) { - EnumBiMap bimap = create(inferKeyType(map), inferValueType(map)); + EnumBiMap bimap = + create(inferKeyTypeOrObjectUnderJ2cl(map), inferValueTypeOrObjectUnderJ2cl(map)); bimap.putAll(map); return bimap; } - private EnumBiMap(Class keyType, Class valueType) { - super(new EnumMap(keyType), new EnumMap(valueType)); - this.keyType = keyType; - this.valueType = valueType; + private EnumBiMap(Class keyTypeOrObjectUnderJ2cl, Class valueTypeOrObjectUnderJ2cl) { + super( + new EnumMap(keyTypeOrObjectUnderJ2cl), new EnumMap(valueTypeOrObjectUnderJ2cl)); + this.keyTypeOrObjectUnderJ2cl = keyTypeOrObjectUnderJ2cl; + this.valueTypeOrObjectUnderJ2cl = valueTypeOrObjectUnderJ2cl; } - static > Class inferKeyType(Map map) { + static > Class inferKeyTypeOrObjectUnderJ2cl(Map map) { if (map instanceof EnumBiMap) { - return ((EnumBiMap) map).keyType(); + return ((EnumBiMap) map).keyTypeOrObjectUnderJ2cl; } if (map instanceof EnumHashBiMap) { - return ((EnumHashBiMap) map).keyType(); + return ((EnumHashBiMap) map).keyTypeOrObjectUnderJ2cl; } checkArgument(!map.isEmpty()); - return map.keySet().iterator().next().getDeclaringClass(); + return getDeclaringClassOrObjectForJ2cl(map.keySet().iterator().next()); } - private static > Class inferValueType(Map map) { + private static > Class inferValueTypeOrObjectUnderJ2cl(Map map) { if (map instanceof EnumBiMap) { - return ((EnumBiMap) map).valueType; + return ((EnumBiMap) map).valueTypeOrObjectUnderJ2cl; } checkArgument(!map.isEmpty()); - return map.values().iterator().next().getDeclaringClass(); + return getDeclaringClassOrObjectForJ2cl(map.values().iterator().next()); } /** Returns the associated key type. */ + @GwtIncompatible public Class keyType() { - return keyType; + return keyTypeOrObjectUnderJ2cl; } /** Returns the associated value type. */ + @GwtIncompatible public Class valueType() { - return valueType; + return valueTypeOrObjectUnderJ2cl; } @Override @@ -123,8 +142,8 @@ V checkValue(V value) { @GwtIncompatible // java.io.ObjectOutputStream private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); - stream.writeObject(keyType); - stream.writeObject(valueType); + stream.writeObject(keyTypeOrObjectUnderJ2cl); + stream.writeObject(valueTypeOrObjectUnderJ2cl); Serialization.writeMap(this, stream); } @@ -132,9 +151,10 @@ private void writeObject(ObjectOutputStream stream) throws IOException { @GwtIncompatible // java.io.ObjectInputStream private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); - keyType = (Class) stream.readObject(); - valueType = (Class) stream.readObject(); - setDelegates(new EnumMap(keyType), new EnumMap(valueType)); + keyTypeOrObjectUnderJ2cl = (Class) stream.readObject(); + valueTypeOrObjectUnderJ2cl = (Class) stream.readObject(); + setDelegates( + new EnumMap(keyTypeOrObjectUnderJ2cl), new EnumMap(valueTypeOrObjectUnderJ2cl)); Serialization.populateMap(this, stream); } diff --git a/android/guava/src/com/google/common/collect/EnumHashBiMap.java b/android/guava/src/com/google/common/collect/EnumHashBiMap.java index e2bc841a7776..33b45b95cf14 100644 --- a/android/guava/src/com/google/common/collect/EnumHashBiMap.java +++ b/android/guava/src/com/google/common/collect/EnumHashBiMap.java @@ -47,7 +47,7 @@ @ElementTypesAreNonnullByDefault public final class EnumHashBiMap, V extends @Nullable Object> extends AbstractBiMap { - private transient Class keyType; + transient Class keyTypeOrObjectUnderJ2cl; /** * Returns a new, empty {@code EnumHashBiMap} using the specified key type. @@ -71,16 +71,15 @@ public final class EnumHashBiMap, V extends @Nullable Object> */ public static , V extends @Nullable Object> EnumHashBiMap create( Map map) { - EnumHashBiMap bimap = create(EnumBiMap.inferKeyType(map)); + EnumHashBiMap bimap = create(EnumBiMap.inferKeyTypeOrObjectUnderJ2cl(map)); bimap.putAll(map); return bimap; } private EnumHashBiMap(Class keyType) { - super( - new EnumMap(keyType), - Maps.newHashMapWithExpectedSize(keyType.getEnumConstants().length)); - this.keyType = keyType; + super(new EnumMap(keyType), new HashMap()); + // TODO: cpovirk - Pre-size the HashMap based on the number of enum values? + this.keyTypeOrObjectUnderJ2cl = keyType; } // Overriding these 3 methods to show that values may be null (but not keys) @@ -109,8 +108,9 @@ public V forcePut(K key, @ParametricNullness V value) { } /** Returns the associated key type. */ + @GwtIncompatible public Class keyType() { - return keyType; + return keyTypeOrObjectUnderJ2cl; } /** @@ -120,7 +120,7 @@ public Class keyType() { @GwtIncompatible // java.io.ObjectOutputStream private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); - stream.writeObject(keyType); + stream.writeObject(keyTypeOrObjectUnderJ2cl); Serialization.writeMap(this, stream); } @@ -128,9 +128,13 @@ private void writeObject(ObjectOutputStream stream) throws IOException { @GwtIncompatible // java.io.ObjectInputStream private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); - keyType = (Class) stream.readObject(); - setDelegates( - new EnumMap(keyType), new HashMap(keyType.getEnumConstants().length * 3 / 2)); + keyTypeOrObjectUnderJ2cl = (Class) stream.readObject(); + /* + * TODO: cpovirk - Pre-size the HashMap based on the number of enum values? (But *not* based on + * the number of entries in the map, as that makes it easy for hostile inputs to trigger lots of + * allocation—not that any program should be deserializing hostile inputs to begin with!) + */ + setDelegates(new EnumMap(keyTypeOrObjectUnderJ2cl), new HashMap()); Serialization.populateMap(this, stream); } diff --git a/android/guava/src/com/google/common/collect/Platform.java b/android/guava/src/com/google/common/collect/Platform.java index 9654502fcc2f..cd4b5ba40956 100644 --- a/android/guava/src/com/google/common/collect/Platform.java +++ b/android/guava/src/com/google/common/collect/Platform.java @@ -117,6 +117,10 @@ static MapMaker tryWeakKeys(MapMaker mapMaker) { return mapMaker.weakKeys(); } + static > Class getDeclaringClassOrObjectForJ2cl(E e) { + return e.getDeclaringClass(); + } + static int reduceIterationsIfGwt(int iterations) { return iterations; } diff --git a/guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/Platform.java b/guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/Platform.java index 887b96d74f57..3bd3e3aad357 100644 --- a/guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/Platform.java +++ b/guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/Platform.java @@ -21,15 +21,15 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import jsinterop.annotations.JsMethod; import jsinterop.annotations.JsPackage; import jsinterop.annotations.JsProperty; import jsinterop.annotations.JsType; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Minimal GWT emulation of {@code com.google.common.collect.Platform}. * - *

This .java file should never be consumed by javac. - * * @author Hayward Chan */ final class Platform { @@ -96,6 +96,19 @@ static MapMaker tryWeakKeys(MapMaker mapMaker) { return mapMaker; } + static > Class getDeclaringClassOrObjectForJ2cl(E e) { + Class classOrNull = getDeclaringClassOrNullForJ2cl(e); + @SuppressWarnings("unchecked") + Class result = classOrNull == null ? (Class) (Class) Object.class : classOrNull; + return result; + } + + @JsMethod + private static native > @Nullable Class getDeclaringClassOrNullForJ2cl( + E e) /*-{ + return e.@java.lang.Enum::getDeclaringClass()(); + }-*/; + static int reduceIterationsIfGwt(int iterations) { return iterations / 10; } diff --git a/guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/Platform.native.js b/guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/Platform.native.js new file mode 100644 index 000000000000..0853e2d8f87e --- /dev/null +++ b/guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/Platform.native.js @@ -0,0 +1,3 @@ +Platform.getDeclaringClassOrNullForJ2cl = function(e) { + return null; +} diff --git a/guava-tests/test/com/google/common/collect/EnumBiMapTest.java b/guava-tests/test/com/google/common/collect/EnumBiMapTest.java index 25b057a72d56..fa927e2d8226 100644 --- a/guava-tests/test/com/google/common/collect/EnumBiMapTest.java +++ b/guava-tests/test/com/google/common/collect/EnumBiMapTest.java @@ -183,11 +183,13 @@ public void testEnumBiMapConstructor() { assertEquals(bimap3, emptyBimap); } + @GwtIncompatible // keyType public void testKeyType() { EnumBiMap bimap = EnumBiMap.create(Currency.class, Country.class); assertEquals(Currency.class, bimap.keyType()); } + @GwtIncompatible // valueType public void testValueType() { EnumBiMap bimap = EnumBiMap.create(Currency.class, Country.class); assertEquals(Country.class, bimap.valueType()); diff --git a/guava-tests/test/com/google/common/collect/EnumHashBiMapTest.java b/guava-tests/test/com/google/common/collect/EnumHashBiMapTest.java index 2a78f55477da..24f395328803 100644 --- a/guava-tests/test/com/google/common/collect/EnumHashBiMapTest.java +++ b/guava-tests/test/com/google/common/collect/EnumHashBiMapTest.java @@ -197,6 +197,7 @@ public void testEnumBiMapConstructor() { assertEquals(bimap3, emptyBimap); } + @GwtIncompatible // keyType public void testKeyType() { EnumHashBiMap bimap = EnumHashBiMap.create(Currency.class); assertEquals(Currency.class, bimap.keyType()); diff --git a/guava/src/com/google/common/collect/EnumBiMap.java b/guava/src/com/google/common/collect/EnumBiMap.java index cb5ce71fd862..c4b540b56465 100644 --- a/guava/src/com/google/common/collect/EnumBiMap.java +++ b/guava/src/com/google/common/collect/EnumBiMap.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Platform.getDeclaringClassOrObjectForJ2cl; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; @@ -42,8 +43,22 @@ @J2ktIncompatible @ElementTypesAreNonnullByDefault public final class EnumBiMap, V extends Enum> extends AbstractBiMap { - private transient Class keyType; - private transient Class valueType; + /* + * J2CL's EnumMap does not need the Class instance, so we can use Object.class instead. (Or we + * could use null, but that messes with our nullness checking, including under J2KT. We could + * probably work around it by changing how we annotate the J2CL EnumMap, but that's probably more + * trouble than just using Object.class.) + * + * Then we declare the getters for these fields as @GwtIncompatible so that no one can try to use + * them under J2CL—or, as an unfortunate side effect, under GWT. We do still give the fields + * themselves their proper values under GWT, since GWT's EnumMap does need the Class instance. + * + * Note that sometimes these fields *do* have correct values under J2CL: They will if the caller + * calls `create(Foo.class)`, rather than `create(map)`. That's fine; we just shouldn't rely on + * it. + */ + transient Class keyTypeOrObjectUnderJ2cl; + transient Class valueTypeOrObjectUnderJ2cl; /** * Returns a new, empty {@code EnumBiMap} using the specified key and value types. @@ -66,44 +81,48 @@ public static , V extends Enum> EnumBiMap create( * mappings */ public static , V extends Enum> EnumBiMap create(Map map) { - EnumBiMap bimap = create(inferKeyType(map), inferValueType(map)); + EnumBiMap bimap = + create(inferKeyTypeOrObjectUnderJ2cl(map), inferValueTypeOrObjectUnderJ2cl(map)); bimap.putAll(map); return bimap; } - private EnumBiMap(Class keyType, Class valueType) { - super(new EnumMap(keyType), new EnumMap(valueType)); - this.keyType = keyType; - this.valueType = valueType; + private EnumBiMap(Class keyTypeOrObjectUnderJ2cl, Class valueTypeOrObjectUnderJ2cl) { + super( + new EnumMap(keyTypeOrObjectUnderJ2cl), new EnumMap(valueTypeOrObjectUnderJ2cl)); + this.keyTypeOrObjectUnderJ2cl = keyTypeOrObjectUnderJ2cl; + this.valueTypeOrObjectUnderJ2cl = valueTypeOrObjectUnderJ2cl; } - static > Class inferKeyType(Map map) { + static > Class inferKeyTypeOrObjectUnderJ2cl(Map map) { if (map instanceof EnumBiMap) { - return ((EnumBiMap) map).keyType(); + return ((EnumBiMap) map).keyTypeOrObjectUnderJ2cl; } if (map instanceof EnumHashBiMap) { - return ((EnumHashBiMap) map).keyType(); + return ((EnumHashBiMap) map).keyTypeOrObjectUnderJ2cl; } checkArgument(!map.isEmpty()); - return map.keySet().iterator().next().getDeclaringClass(); + return getDeclaringClassOrObjectForJ2cl(map.keySet().iterator().next()); } - private static > Class inferValueType(Map map) { + private static > Class inferValueTypeOrObjectUnderJ2cl(Map map) { if (map instanceof EnumBiMap) { - return ((EnumBiMap) map).valueType; + return ((EnumBiMap) map).valueTypeOrObjectUnderJ2cl; } checkArgument(!map.isEmpty()); - return map.values().iterator().next().getDeclaringClass(); + return getDeclaringClassOrObjectForJ2cl(map.values().iterator().next()); } /** Returns the associated key type. */ + @GwtIncompatible public Class keyType() { - return keyType; + return keyTypeOrObjectUnderJ2cl; } /** Returns the associated value type. */ + @GwtIncompatible public Class valueType() { - return valueType; + return valueTypeOrObjectUnderJ2cl; } @Override @@ -123,8 +142,8 @@ V checkValue(V value) { @GwtIncompatible // java.io.ObjectOutputStream private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); - stream.writeObject(keyType); - stream.writeObject(valueType); + stream.writeObject(keyTypeOrObjectUnderJ2cl); + stream.writeObject(valueTypeOrObjectUnderJ2cl); Serialization.writeMap(this, stream); } @@ -132,9 +151,10 @@ private void writeObject(ObjectOutputStream stream) throws IOException { @GwtIncompatible // java.io.ObjectInputStream private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); - keyType = (Class) stream.readObject(); - valueType = (Class) stream.readObject(); - setDelegates(new EnumMap(keyType), new EnumMap(valueType)); + keyTypeOrObjectUnderJ2cl = (Class) stream.readObject(); + valueTypeOrObjectUnderJ2cl = (Class) stream.readObject(); + setDelegates( + new EnumMap(keyTypeOrObjectUnderJ2cl), new EnumMap(valueTypeOrObjectUnderJ2cl)); Serialization.populateMap(this, stream); } diff --git a/guava/src/com/google/common/collect/EnumHashBiMap.java b/guava/src/com/google/common/collect/EnumHashBiMap.java index e2bc841a7776..33b45b95cf14 100644 --- a/guava/src/com/google/common/collect/EnumHashBiMap.java +++ b/guava/src/com/google/common/collect/EnumHashBiMap.java @@ -47,7 +47,7 @@ @ElementTypesAreNonnullByDefault public final class EnumHashBiMap, V extends @Nullable Object> extends AbstractBiMap { - private transient Class keyType; + transient Class keyTypeOrObjectUnderJ2cl; /** * Returns a new, empty {@code EnumHashBiMap} using the specified key type. @@ -71,16 +71,15 @@ public final class EnumHashBiMap, V extends @Nullable Object> */ public static , V extends @Nullable Object> EnumHashBiMap create( Map map) { - EnumHashBiMap bimap = create(EnumBiMap.inferKeyType(map)); + EnumHashBiMap bimap = create(EnumBiMap.inferKeyTypeOrObjectUnderJ2cl(map)); bimap.putAll(map); return bimap; } private EnumHashBiMap(Class keyType) { - super( - new EnumMap(keyType), - Maps.newHashMapWithExpectedSize(keyType.getEnumConstants().length)); - this.keyType = keyType; + super(new EnumMap(keyType), new HashMap()); + // TODO: cpovirk - Pre-size the HashMap based on the number of enum values? + this.keyTypeOrObjectUnderJ2cl = keyType; } // Overriding these 3 methods to show that values may be null (but not keys) @@ -109,8 +108,9 @@ public V forcePut(K key, @ParametricNullness V value) { } /** Returns the associated key type. */ + @GwtIncompatible public Class keyType() { - return keyType; + return keyTypeOrObjectUnderJ2cl; } /** @@ -120,7 +120,7 @@ public Class keyType() { @GwtIncompatible // java.io.ObjectOutputStream private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); - stream.writeObject(keyType); + stream.writeObject(keyTypeOrObjectUnderJ2cl); Serialization.writeMap(this, stream); } @@ -128,9 +128,13 @@ private void writeObject(ObjectOutputStream stream) throws IOException { @GwtIncompatible // java.io.ObjectInputStream private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); - keyType = (Class) stream.readObject(); - setDelegates( - new EnumMap(keyType), new HashMap(keyType.getEnumConstants().length * 3 / 2)); + keyTypeOrObjectUnderJ2cl = (Class) stream.readObject(); + /* + * TODO: cpovirk - Pre-size the HashMap based on the number of enum values? (But *not* based on + * the number of entries in the map, as that makes it easy for hostile inputs to trigger lots of + * allocation—not that any program should be deserializing hostile inputs to begin with!) + */ + setDelegates(new EnumMap(keyTypeOrObjectUnderJ2cl), new HashMap()); Serialization.populateMap(this, stream); } diff --git a/guava/src/com/google/common/collect/Platform.java b/guava/src/com/google/common/collect/Platform.java index 718ee465e47a..72928125ec11 100644 --- a/guava/src/com/google/common/collect/Platform.java +++ b/guava/src/com/google/common/collect/Platform.java @@ -126,6 +126,10 @@ static MapMaker tryWeakKeys(MapMaker mapMaker) { return mapMaker.weakKeys(); } + static > Class getDeclaringClassOrObjectForJ2cl(E e) { + return e.getDeclaringClass(); + } + static int reduceIterationsIfGwt(int iterations) { return iterations; }