24
24
import com .google .gson .annotations .JsonAdapter ;
25
25
import com .google .gson .internal .ConstructorConstructor ;
26
26
import com .google .gson .reflect .TypeToken ;
27
+ import java .util .Objects ;
28
+ import java .util .concurrent .ConcurrentHashMap ;
29
+ import java .util .concurrent .ConcurrentMap ;
27
30
28
31
/**
29
32
* Given a type T, looks for the annotation {@link JsonAdapter} and uses an instance of the
32
35
* @since 2.3
33
36
*/
34
37
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
+
35
56
private final ConstructorConstructor constructorConstructor ;
36
57
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
+
37
67
public JsonAdapterAnnotationTypeAdapterFactory (ConstructorConstructor constructorConstructor ) {
38
68
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 );
39
75
}
40
76
41
77
@ SuppressWarnings ("unchecked" ) // this is not safe; requires that user has specified correct adapter class for @JsonAdapter
42
78
@ Override
43
79
public <T > TypeAdapter <T > create (Gson gson , TypeToken <T > targetType ) {
44
80
Class <? super T > rawType = targetType .getRawType ();
45
- JsonAdapter annotation = rawType . getAnnotation (JsonAdapter . class );
81
+ JsonAdapter annotation = getAnnotation (rawType );
46
82
if (annotation == null ) {
47
83
return null ;
48
84
}
49
- return (TypeAdapter <T >) getTypeAdapter (constructorConstructor , gson , targetType , annotation );
85
+ return (TypeAdapter <T >) getTypeAdapter (constructorConstructor , gson , targetType , annotation , true );
50
86
}
51
87
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 ) {
54
90
// TODO: The exception messages created by ConstructorConstructor are currently written in the context of
55
91
// 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 ());
57
104
58
105
TypeAdapter <?> typeAdapter ;
59
106
boolean nullSafe = annotation .nullSafe ();
60
107
if (instance instanceof TypeAdapter ) {
61
108
typeAdapter = (TypeAdapter <?>) instance ;
62
109
} 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 );
64
117
} else if (instance instanceof JsonSerializer || instance instanceof JsonDeserializer ) {
65
118
JsonSerializer <?> serializer = instance instanceof JsonSerializer
66
119
? (JsonSerializer <?>) instance
@@ -69,8 +122,16 @@ TypeAdapter<?> getTypeAdapter(ConstructorConstructor constructorConstructor, Gso
69
122
? (JsonDeserializer <?>) instance
70
123
: null ;
71
124
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
+ }
72
133
@ 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 );
74
135
typeAdapter = tempAdapter ;
75
136
76
137
nullSafe = false ;
@@ -87,4 +148,45 @@ TypeAdapter<?> getTypeAdapter(ConstructorConstructor constructorConstructor, Gso
87
148
88
149
return typeAdapter ;
89
150
}
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
+ }
90
192
}
0 commit comments