Skip to content

Commit 7ee5ad6

Browse files
authored
Fix Gson.getDelegateAdapter not working properly for JsonAdapter (#2435)
* Fix `Gson.getDelegateAdapter` not working properly for `JsonAdapter` * Address review feedback and add comments regarding thread-safety * Revert InstanceCreator instance validation * Disallow `null` as `skipPast` * Avoid `equals` usage in `getDelegateAdapter` & minor other changes Previously `getDelegateAdapter` called `factories.contains(skipPast)`, but unlike the other comparisons which check for reference equality, that would have used the `equals` method. This could lead to spurious "GSON cannot serialize ..." exceptions if two factory instances compared equal, but the one provided as `skipPast` had not been registered yet.
1 parent 393db09 commit 7ee5ad6

File tree

9 files changed

+944
-57
lines changed

9 files changed

+944
-57
lines changed

gson/src/main/java/com/google/gson/Gson.java

Lines changed: 56 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.gson;
1818

19+
import com.google.gson.annotations.JsonAdapter;
1920
import com.google.gson.internal.ConstructorConstructor;
2021
import com.google.gson.internal.Excluder;
2122
import com.google.gson.internal.GsonBuildConfig;
@@ -604,42 +605,50 @@ public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
604605
* adapter that does a little bit of work but then delegates further processing to the Gson
605606
* default type adapter. Here is an example:
606607
* <p>Let's say we want to write a type adapter that counts the number of objects being read
607-
* from or written to JSON. We can achieve this by writing a type adapter factory that uses
608-
* the <code>getDelegateAdapter</code> method:
609-
* <pre> {@code
610-
* class StatsTypeAdapterFactory implements TypeAdapterFactory {
611-
* public int numReads = 0;
612-
* public int numWrites = 0;
613-
* public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
614-
* final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
615-
* return new TypeAdapter<T>() {
616-
* public void write(JsonWriter out, T value) throws IOException {
617-
* ++numWrites;
618-
* delegate.write(out, value);
619-
* }
620-
* public T read(JsonReader in) throws IOException {
621-
* ++numReads;
622-
* return delegate.read(in);
623-
* }
624-
* };
625-
* }
626-
* }
627-
* } </pre>
628-
* This factory can now be used like this:
629-
* <pre> {@code
630-
* StatsTypeAdapterFactory stats = new StatsTypeAdapterFactory();
631-
* Gson gson = new GsonBuilder().registerTypeAdapterFactory(stats).create();
632-
* // Call gson.toJson() and fromJson methods on objects
633-
* System.out.println("Num JSON reads" + stats.numReads);
634-
* System.out.println("Num JSON writes" + stats.numWrites);
635-
* }</pre>
636-
* Note that this call will skip all factories registered before {@code skipPast}. In case of
637-
* multiple TypeAdapterFactories registered it is up to the caller of this function to insure
638-
* that the order of registration does not prevent this method from reaching a factory they
639-
* would expect to reply from this call.
640-
* Note that since you can not override type adapter factories for String and Java primitive
641-
* types, our stats factory will not count the number of String or primitives that will be
642-
* read or written.
608+
* from or written to JSON. We can achieve this by writing a type adapter factory that uses
609+
* the <code>getDelegateAdapter</code> method:
610+
* <pre>{@code
611+
* class StatsTypeAdapterFactory implements TypeAdapterFactory {
612+
* public int numReads = 0;
613+
* public int numWrites = 0;
614+
* public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
615+
* final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
616+
* return new TypeAdapter<T>() {
617+
* public void write(JsonWriter out, T value) throws IOException {
618+
* ++numWrites;
619+
* delegate.write(out, value);
620+
* }
621+
* public T read(JsonReader in) throws IOException {
622+
* ++numReads;
623+
* return delegate.read(in);
624+
* }
625+
* };
626+
* }
627+
* }
628+
* }</pre>
629+
* This factory can now be used like this:
630+
* <pre>{@code
631+
* StatsTypeAdapterFactory stats = new StatsTypeAdapterFactory();
632+
* Gson gson = new GsonBuilder().registerTypeAdapterFactory(stats).create();
633+
* // Call gson.toJson() and fromJson methods on objects
634+
* System.out.println("Num JSON reads: " + stats.numReads);
635+
* System.out.println("Num JSON writes: " + stats.numWrites);
636+
* }</pre>
637+
* Note that this call will skip all factories registered before {@code skipPast}. In case of
638+
* multiple TypeAdapterFactories registered it is up to the caller of this function to insure
639+
* that the order of registration does not prevent this method from reaching a factory they
640+
* would expect to reply from this call.
641+
* Note that since you can not override the type adapter factories for some types, see
642+
* {@link GsonBuilder#registerTypeAdapter(Type, Object)}, our stats factory will not count
643+
* the number of instances of those types that will be read or written.
644+
*
645+
* <p>If {@code skipPast} is a factory which has neither been registered on the {@link GsonBuilder}
646+
* nor specified with the {@link JsonAdapter @JsonAdapter} annotation on a class, then this
647+
* method behaves as if {@link #getAdapter(TypeToken)} had been called. This also means that
648+
* for fields with {@code @JsonAdapter} annotation this method behaves normally like {@code getAdapter}
649+
* (except for corner cases where a custom {@link InstanceCreator} is used to create an
650+
* instance of the factory).
651+
*
643652
* @param skipPast The type adapter factory that needs to be skipped while searching for
644653
* a matching type adapter. In most cases, you should just pass <i>this</i> (the type adapter
645654
* factory from where {@code getDelegateAdapter} method is being invoked).
@@ -648,9 +657,10 @@ public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
648657
* @since 2.2
649658
*/
650659
public <T> TypeAdapter<T> getDelegateAdapter(TypeAdapterFactory skipPast, TypeToken<T> type) {
651-
// Hack. If the skipPast factory isn't registered, assume the factory is being requested via
652-
// our @JsonAdapter annotation.
653-
if (!factories.contains(skipPast)) {
660+
Objects.requireNonNull(skipPast, "skipPast must not be null");
661+
Objects.requireNonNull(type, "type must not be null");
662+
663+
if (jsonAdapterFactory.isClassJsonAdapterFactory(type, skipPast)) {
654664
skipPast = jsonAdapterFactory;
655665
}
656666

@@ -668,7 +678,13 @@ public <T> TypeAdapter<T> getDelegateAdapter(TypeAdapterFactory skipPast, TypeTo
668678
return candidate;
669679
}
670680
}
671-
throw new IllegalArgumentException("GSON cannot serialize " + type);
681+
682+
if (skipPastFound) {
683+
throw new IllegalArgumentException("GSON cannot serialize " + type);
684+
} else {
685+
// Probably a factory from @JsonAdapter on a field
686+
return getAdapter(type);
687+
}
672688
}
673689

674690
/**

gson/src/main/java/com/google/gson/InstanceCreator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
* </pre>
6464
*
6565
* <p>Note that it does not matter what the fields of the created instance contain since Gson will
66-
* overwrite them with the deserialized values specified in Json. You should also ensure that a
66+
* overwrite them with the deserialized values specified in JSON. You should also ensure that a
6767
* <i>new</i> object is returned, not a common object since its fields will be overwritten.
6868
* The developer will need to register {@code IdInstanceCreator} with Gson as follows:</p>
6969
*
@@ -81,7 +81,7 @@ public interface InstanceCreator<T> {
8181
/**
8282
* Gson invokes this call-back method during deserialization to create an instance of the
8383
* specified type. The fields of the returned instance are overwritten with the data present
84-
* in the Json. Since the prior contents of the object are destroyed and overwritten, do not
84+
* in the JSON. Since the prior contents of the object are destroyed and overwritten, do not
8585
* return an instance that is useful elsewhere. In particular, do not return a common instance,
8686
* always use {@code new} to create a new instance.
8787
*

gson/src/main/java/com/google/gson/internal/bind/JsonAdapterAnnotationTypeAdapterFactory.java

Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
import com.google.gson.annotations.JsonAdapter;
2525
import com.google.gson.internal.ConstructorConstructor;
2626
import com.google.gson.reflect.TypeToken;
27+
import java.util.Objects;
28+
import java.util.concurrent.ConcurrentHashMap;
29+
import java.util.concurrent.ConcurrentMap;
2730

2831
/**
2932
* Given a type T, looks for the annotation {@link JsonAdapter} and uses an instance of the
@@ -32,35 +35,85 @@
3235
* @since 2.3
3336
*/
3437
public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapterFactory {
38+
private static class DummyTypeAdapterFactory implements TypeAdapterFactory {
39+
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
40+
throw new AssertionError("Factory should not be used");
41+
}
42+
}
43+
44+
/**
45+
* Factory used for {@link TreeTypeAdapter}s created for {@code @JsonAdapter}
46+
* on a class.
47+
*/
48+
private static final TypeAdapterFactory TREE_TYPE_CLASS_DUMMY_FACTORY = new DummyTypeAdapterFactory();
49+
50+
/**
51+
* Factory used for {@link TreeTypeAdapter}s created for {@code @JsonAdapter}
52+
* on a field.
53+
*/
54+
private static final TypeAdapterFactory TREE_TYPE_FIELD_DUMMY_FACTORY = new DummyTypeAdapterFactory();
55+
3556
private final ConstructorConstructor constructorConstructor;
3657

58+
/**
59+
* For a class, if it is annotated with {@code @JsonAdapter} and refers to a {@link TypeAdapterFactory},
60+
* stores the factory instance in case it has been requested already.
61+
* Has to be a {@link ConcurrentMap} because {@link Gson} guarantees to be thread-safe.
62+
*/
63+
// Note: In case these strong reference to TypeAdapterFactory instances are considered
64+
// a memory leak in the future, could consider switching to WeakReference<TypeAdapterFactory>
65+
private final ConcurrentMap<Class<?>, TypeAdapterFactory> adapterFactoryMap;
66+
3767
public JsonAdapterAnnotationTypeAdapterFactory(ConstructorConstructor constructorConstructor) {
3868
this.constructorConstructor = constructorConstructor;
69+
this.adapterFactoryMap = new ConcurrentHashMap<>();
70+
}
71+
72+
// Separate helper method to make sure callers retrieve annotation in a consistent way
73+
private JsonAdapter getAnnotation(Class<?> rawType) {
74+
return rawType.getAnnotation(JsonAdapter.class);
3975
}
4076

4177
@SuppressWarnings("unchecked") // this is not safe; requires that user has specified correct adapter class for @JsonAdapter
4278
@Override
4379
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> targetType) {
4480
Class<? super T> rawType = targetType.getRawType();
45-
JsonAdapter annotation = rawType.getAnnotation(JsonAdapter.class);
81+
JsonAdapter annotation = getAnnotation(rawType);
4682
if (annotation == null) {
4783
return null;
4884
}
49-
return (TypeAdapter<T>) getTypeAdapter(constructorConstructor, gson, targetType, annotation);
85+
return (TypeAdapter<T>) getTypeAdapter(constructorConstructor, gson, targetType, annotation, true);
5086
}
5187

52-
TypeAdapter<?> getTypeAdapter(ConstructorConstructor constructorConstructor, Gson gson,
53-
TypeToken<?> type, JsonAdapter annotation) {
88+
// Separate helper method to make sure callers create adapter in a consistent way
89+
private static Object createAdapter(ConstructorConstructor constructorConstructor, Class<?> adapterClass) {
5490
// TODO: The exception messages created by ConstructorConstructor are currently written in the context of
5591
// deserialization and for example suggest usage of TypeAdapter, which would not work for @JsonAdapter usage
56-
Object instance = constructorConstructor.get(TypeToken.get(annotation.value())).construct();
92+
return constructorConstructor.get(TypeToken.get(adapterClass)).construct();
93+
}
94+
95+
private TypeAdapterFactory putFactoryAndGetCurrent(Class<?> rawType, TypeAdapterFactory factory) {
96+
// Uses putIfAbsent in case multiple threads concurrently create factory
97+
TypeAdapterFactory existingFactory = adapterFactoryMap.putIfAbsent(rawType, factory);
98+
return existingFactory != null ? existingFactory : factory;
99+
}
100+
101+
TypeAdapter<?> getTypeAdapter(ConstructorConstructor constructorConstructor, Gson gson,
102+
TypeToken<?> type, JsonAdapter annotation, boolean isClassAnnotation) {
103+
Object instance = createAdapter(constructorConstructor, annotation.value());
57104

58105
TypeAdapter<?> typeAdapter;
59106
boolean nullSafe = annotation.nullSafe();
60107
if (instance instanceof TypeAdapter) {
61108
typeAdapter = (TypeAdapter<?>) instance;
62109
} else if (instance instanceof TypeAdapterFactory) {
63-
typeAdapter = ((TypeAdapterFactory) instance).create(gson, type);
110+
TypeAdapterFactory factory = (TypeAdapterFactory) instance;
111+
112+
if (isClassAnnotation) {
113+
factory = putFactoryAndGetCurrent(type.getRawType(), factory);
114+
}
115+
116+
typeAdapter = factory.create(gson, type);
64117
} else if (instance instanceof JsonSerializer || instance instanceof JsonDeserializer) {
65118
JsonSerializer<?> serializer = instance instanceof JsonSerializer
66119
? (JsonSerializer<?>) instance
@@ -69,8 +122,16 @@ TypeAdapter<?> getTypeAdapter(ConstructorConstructor constructorConstructor, Gso
69122
? (JsonDeserializer<?>) instance
70123
: null;
71124

125+
// Uses dummy factory instances because TreeTypeAdapter needs a 'skipPast' factory for `Gson.getDelegateAdapter`
126+
// call and has to differentiate there whether TreeTypeAdapter was created for @JsonAdapter on class or field
127+
TypeAdapterFactory skipPast;
128+
if (isClassAnnotation) {
129+
skipPast = TREE_TYPE_CLASS_DUMMY_FACTORY;
130+
} else {
131+
skipPast = TREE_TYPE_FIELD_DUMMY_FACTORY;
132+
}
72133
@SuppressWarnings({ "unchecked", "rawtypes" })
73-
TypeAdapter<?> tempAdapter = new TreeTypeAdapter(serializer, deserializer, gson, type, null, nullSafe);
134+
TypeAdapter<?> tempAdapter = new TreeTypeAdapter(serializer, deserializer, gson, type, skipPast, nullSafe);
74135
typeAdapter = tempAdapter;
75136

76137
nullSafe = false;
@@ -87,4 +148,45 @@ TypeAdapter<?> getTypeAdapter(ConstructorConstructor constructorConstructor, Gso
87148

88149
return typeAdapter;
89150
}
151+
152+
/**
153+
* Returns whether {@code factory} is a type adapter factory created for {@code @JsonAdapter}
154+
* placed on {@code type}.
155+
*/
156+
public boolean isClassJsonAdapterFactory(TypeToken<?> type, TypeAdapterFactory factory) {
157+
Objects.requireNonNull(type);
158+
Objects.requireNonNull(factory);
159+
160+
if (factory == TREE_TYPE_CLASS_DUMMY_FACTORY) {
161+
return true;
162+
}
163+
164+
// Using raw type to match behavior of `create(Gson, TypeToken<T>)` above
165+
Class<?> rawType = type.getRawType();
166+
167+
TypeAdapterFactory existingFactory = adapterFactoryMap.get(rawType);
168+
if (existingFactory != null) {
169+
// Checks for reference equality, like it is done by `Gson.getDelegateAdapter`
170+
return existingFactory == factory;
171+
}
172+
173+
// If no factory has been created for the type yet check manually for a @JsonAdapter annotation
174+
// which specifies a TypeAdapterFactory
175+
// Otherwise behavior would not be consistent, depending on whether or not adapter had been requested
176+
// before call to `isClassJsonAdapterFactory` was made
177+
JsonAdapter annotation = getAnnotation(rawType);
178+
if (annotation == null) {
179+
return false;
180+
}
181+
182+
Class<?> adapterClass = annotation.value();
183+
if (!TypeAdapterFactory.class.isAssignableFrom(adapterClass)) {
184+
return false;
185+
}
186+
187+
Object adapter = createAdapter(constructorConstructor, adapterClass);
188+
TypeAdapterFactory newFactory = (TypeAdapterFactory) adapter;
189+
190+
return putFactoryAndGetCurrent(rawType, newFactory) == factory;
191+
}
90192
}

gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ private BoundField createBoundField(
156156
if (annotation != null) {
157157
// This is not safe; requires that user has specified correct adapter class for @JsonAdapter
158158
mapped = jsonAdapterFactory.getTypeAdapter(
159-
constructorConstructor, context, fieldType, annotation);
159+
constructorConstructor, context, fieldType, annotation, false);
160160
}
161161
final boolean jsonAdapterPresent = mapped != null;
162162
if (mapped == null) mapped = context.getAdapter(fieldType);

gson/src/main/java/com/google/gson/internal/bind/TreeTypeAdapter.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,18 @@ public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter
4343
private final JsonDeserializer<T> deserializer;
4444
final Gson gson;
4545
private final TypeToken<T> typeToken;
46-
private final TypeAdapterFactory skipPast;
46+
/**
47+
* Only intended as {@code skipPast} for {@link Gson#getDelegateAdapter(TypeAdapterFactory, TypeToken)},
48+
* must not be used in any other way.
49+
*/
50+
private final TypeAdapterFactory skipPastForGetDelegateAdapter;
4751
private final GsonContextImpl context = new GsonContextImpl();
4852
private final boolean nullSafe;
4953

50-
/** The delegate is lazily created because it may not be needed, and creating it may fail. */
54+
/**
55+
* The delegate is lazily created because it may not be needed, and creating it may fail.
56+
* Field has to be {@code volatile} because {@link Gson} guarantees to be thread-safe.
57+
*/
5158
private volatile TypeAdapter<T> delegate;
5259

5360
public TreeTypeAdapter(JsonSerializer<T> serializer, JsonDeserializer<T> deserializer,
@@ -56,7 +63,7 @@ public TreeTypeAdapter(JsonSerializer<T> serializer, JsonDeserializer<T> deseria
5663
this.deserializer = deserializer;
5764
this.gson = gson;
5865
this.typeToken = typeToken;
59-
this.skipPast = skipPast;
66+
this.skipPastForGetDelegateAdapter = skipPast;
6067
this.nullSafe = nullSafe;
6168
}
6269

@@ -94,7 +101,7 @@ private TypeAdapter<T> delegate() {
94101
TypeAdapter<T> d = delegate;
95102
return d != null
96103
? d
97-
: (delegate = gson.getDelegateAdapter(skipPast, typeToken));
104+
: (delegate = gson.getDelegateAdapter(skipPastForGetDelegateAdapter, typeToken));
98105
}
99106

100107
/**

0 commit comments

Comments
 (0)