1+ /*
2+ * Copyright (C) 2011 Google Inc.
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+
17+ package org .springframework .ide .vscode .commons ;
18+
19+ import com .google .errorprone .annotations .CanIgnoreReturnValue ;
20+ import com .google .gson .Gson ;
21+ import com .google .gson .JsonElement ;
22+ import com .google .gson .JsonObject ;
23+ import com .google .gson .JsonParseException ;
24+ import com .google .gson .JsonPrimitive ;
25+ import com .google .gson .TypeAdapter ;
26+ import com .google .gson .TypeAdapterFactory ;
27+ import com .google .gson .reflect .TypeToken ;
28+ import com .google .gson .stream .JsonReader ;
29+ import com .google .gson .stream .JsonWriter ;
30+ import java .io .IOException ;
31+ import java .util .LinkedHashMap ;
32+ import java .util .Map ;
33+
34+ /**
35+ * Adapts values whose runtime type may differ from their declaration type. This is necessary when a
36+ * field's type is not the same type that GSON should create when deserializing that field. For
37+ * example, consider these types:
38+ *
39+ * <pre>{@code
40+ * abstract class Shape {
41+ * int x;
42+ * int y;
43+ * }
44+ * class Circle extends Shape {
45+ * int radius;
46+ * }
47+ * class Rectangle extends Shape {
48+ * int width;
49+ * int height;
50+ * }
51+ * class Diamond extends Shape {
52+ * int width;
53+ * int height;
54+ * }
55+ * class Drawing {
56+ * Shape bottomShape;
57+ * Shape topShape;
58+ * }
59+ * }</pre>
60+ *
61+ * <p>Without additional type information, the serialized JSON is ambiguous. Is the bottom shape in
62+ * this drawing a rectangle or a diamond?
63+ *
64+ * <pre>{@code
65+ * {
66+ * "bottomShape": {
67+ * "width": 10,
68+ * "height": 5,
69+ * "x": 0,
70+ * "y": 0
71+ * },
72+ * "topShape": {
73+ * "radius": 2,
74+ * "x": 4,
75+ * "y": 1
76+ * }
77+ * }
78+ * }</pre>
79+ *
80+ * This class addresses this problem by adding type information to the serialized JSON and honoring
81+ * that type information when the JSON is deserialized:
82+ *
83+ * <pre>{@code
84+ * {
85+ * "bottomShape": {
86+ * "type": "Diamond",
87+ * "width": 10,
88+ * "height": 5,
89+ * "x": 0,
90+ * "y": 0
91+ * },
92+ * "topShape": {
93+ * "type": "Circle",
94+ * "radius": 2,
95+ * "x": 4,
96+ * "y": 1
97+ * }
98+ * }
99+ * }</pre>
100+ *
101+ * Both the type field name ({@code "type"}) and the type labels ({@code "Rectangle"}) are
102+ * configurable.
103+ *
104+ * <h2>Registering Types</h2>
105+ *
106+ * Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field name to the
107+ * {@link #of} factory method. If you don't supply an explicit type field name, {@code "type"} will
108+ * be used.
109+ *
110+ * <pre>{@code
111+ * RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory
112+ * = RuntimeTypeAdapterFactory.of(Shape.class, "type");
113+ * }</pre>
114+ *
115+ * Next register all of your subtypes. Every subtype must be explicitly registered. This protects
116+ * your application from injection attacks. If you don't supply an explicit type label, the type's
117+ * simple name will be used.
118+ *
119+ * <pre>{@code
120+ * shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle");
121+ * shapeAdapterFactory.registerSubtype(Circle.class, "Circle");
122+ * shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond");
123+ * }</pre>
124+ *
125+ * Finally, register the type adapter factory in your application's GSON builder:
126+ *
127+ * <pre>{@code
128+ * Gson gson = new GsonBuilder()
129+ * .registerTypeAdapterFactory(shapeAdapterFactory)
130+ * .create();
131+ * }</pre>
132+ *
133+ * Like {@code GsonBuilder}, this API supports chaining:
134+ *
135+ * <pre>{@code
136+ * RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
137+ * .registerSubtype(Rectangle.class)
138+ * .registerSubtype(Circle.class)
139+ * .registerSubtype(Diamond.class);
140+ * }</pre>
141+ *
142+ * <h2>Serialization and deserialization</h2>
143+ *
144+ * In order to serialize and deserialize a polymorphic object, you must specify the base type
145+ * explicitly.
146+ *
147+ * <pre>{@code
148+ * Diamond diamond = new Diamond();
149+ * String json = gson.toJson(diamond, Shape.class);
150+ * }</pre>
151+ *
152+ * And then:
153+ *
154+ * <pre>{@code
155+ * Shape shape = gson.fromJson(json, Shape.class);
156+ * }</pre>
157+ */
158+ public final class RuntimeTypeAdapterFactory <T > implements TypeAdapterFactory {
159+ private final Class <?> baseType ;
160+ private final String typeFieldName ;
161+ private final Map <String , Class <?>> labelToSubtype = new LinkedHashMap <>();
162+ private final Map <Class <?>, String > subtypeToLabel = new LinkedHashMap <>();
163+ private final boolean maintainType ;
164+ private boolean recognizeSubtypes ;
165+
166+ private RuntimeTypeAdapterFactory (Class <?> baseType , String typeFieldName , boolean maintainType ) {
167+ if (typeFieldName == null || baseType == null ) {
168+ throw new NullPointerException ();
169+ }
170+ this .baseType = baseType ;
171+ this .typeFieldName = typeFieldName ;
172+ this .maintainType = maintainType ;
173+ }
174+
175+ /**
176+ * Creates a new runtime type adapter for {@code baseType} using {@code typeFieldName} as the type
177+ * field name. Type field names are case sensitive.
178+ *
179+ * @param maintainType true if the type field should be included in deserialized objects
180+ */
181+ public static <T > RuntimeTypeAdapterFactory <T > of (
182+ Class <T > baseType , String typeFieldName , boolean maintainType ) {
183+ return new RuntimeTypeAdapterFactory <>(baseType , typeFieldName , maintainType );
184+ }
185+
186+ /**
187+ * Creates a new runtime type adapter for {@code baseType} using {@code typeFieldName} as the type
188+ * field name. Type field names are case sensitive.
189+ */
190+ public static <T > RuntimeTypeAdapterFactory <T > of (Class <T > baseType , String typeFieldName ) {
191+ return new RuntimeTypeAdapterFactory <>(baseType , typeFieldName , false );
192+ }
193+
194+ /**
195+ * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as the type field
196+ * name.
197+ */
198+ public static <T > RuntimeTypeAdapterFactory <T > of (Class <T > baseType ) {
199+ return new RuntimeTypeAdapterFactory <>(baseType , "type" , false );
200+ }
201+
202+ /**
203+ * Ensures that this factory will handle not just the given {@code baseType}, but any subtype of
204+ * that type.
205+ */
206+ @ CanIgnoreReturnValue
207+ public RuntimeTypeAdapterFactory <T > recognizeSubtypes () {
208+ this .recognizeSubtypes = true ;
209+ return this ;
210+ }
211+
212+ /**
213+ * Registers {@code type} identified by {@code label}. Labels are case sensitive.
214+ *
215+ * @throws IllegalArgumentException if either {@code type} or {@code label} have already been
216+ * registered on this type adapter.
217+ */
218+ @ CanIgnoreReturnValue
219+ public RuntimeTypeAdapterFactory <T > registerSubtype (Class <? extends T > type , String label ) {
220+ if (type == null || label == null ) {
221+ throw new NullPointerException ();
222+ }
223+ if (subtypeToLabel .containsKey (type ) || labelToSubtype .containsKey (label )) {
224+ throw new IllegalArgumentException ("types and labels must be unique" );
225+ }
226+ labelToSubtype .put (label , type );
227+ subtypeToLabel .put (type , label );
228+ return this ;
229+ }
230+
231+ /**
232+ * Registers {@code type} identified by its {@link Class#getSimpleName simple name}. Labels are
233+ * case sensitive.
234+ *
235+ * @throws IllegalArgumentException if either {@code type} or its simple name have already been
236+ * registered on this type adapter.
237+ */
238+ @ CanIgnoreReturnValue
239+ public RuntimeTypeAdapterFactory <T > registerSubtype (Class <? extends T > type ) {
240+ return registerSubtype (type , type .getSimpleName ());
241+ }
242+
243+ @ Override
244+ public <R > TypeAdapter <R > create (Gson gson , TypeToken <R > type ) {
245+ if (type == null ) {
246+ return null ;
247+ }
248+ Class <?> rawType = type .getRawType ();
249+ boolean handle =
250+ recognizeSubtypes ? baseType .isAssignableFrom (rawType ) : baseType .equals (rawType );
251+ if (!handle ) {
252+ return null ;
253+ }
254+
255+ TypeAdapter <JsonElement > jsonElementAdapter = gson .getAdapter (JsonElement .class );
256+ Map <String , TypeAdapter <?>> labelToDelegate = new LinkedHashMap <>();
257+ Map <Class <?>, TypeAdapter <?>> subtypeToDelegate = new LinkedHashMap <>();
258+ for (Map .Entry <String , Class <?>> entry : labelToSubtype .entrySet ()) {
259+ TypeAdapter <?> delegate = gson .getDelegateAdapter (this , TypeToken .get (entry .getValue ()));
260+ labelToDelegate .put (entry .getKey (), delegate );
261+ subtypeToDelegate .put (entry .getValue (), delegate );
262+ }
263+
264+ return new TypeAdapter <R >() {
265+ @ Override
266+ public R read (JsonReader in ) throws IOException {
267+ JsonElement jsonElement = jsonElementAdapter .read (in );
268+ JsonElement labelJsonElement ;
269+ if (maintainType ) {
270+ labelJsonElement = jsonElement .getAsJsonObject ().get (typeFieldName );
271+ } else {
272+ labelJsonElement = jsonElement .getAsJsonObject ().remove (typeFieldName );
273+ }
274+
275+ if (labelJsonElement == null ) {
276+ throw new JsonParseException (
277+ "cannot deserialize "
278+ + baseType
279+ + " because it does not define a field named "
280+ + typeFieldName );
281+ }
282+ String label = labelJsonElement .getAsString ();
283+ @ SuppressWarnings ("unchecked" ) // registration requires that subtype extends T
284+ TypeAdapter <R > delegate = (TypeAdapter <R >) labelToDelegate .get (label );
285+ if (delegate == null ) {
286+ throw new JsonParseException (
287+ "cannot deserialize "
288+ + baseType
289+ + " subtype named "
290+ + label
291+ + "; did you forget to register a subtype?" );
292+ }
293+ return delegate .fromJsonTree (jsonElement );
294+ }
295+
296+ @ Override
297+ public void write (JsonWriter out , R value ) throws IOException {
298+ Class <?> srcType = value .getClass ();
299+ String label = subtypeToLabel .get (srcType );
300+ @ SuppressWarnings ("unchecked" ) // registration requires that subtype extends T
301+ TypeAdapter <R > delegate = (TypeAdapter <R >) subtypeToDelegate .get (srcType );
302+ if (delegate == null ) {
303+ throw new JsonParseException (
304+ "cannot serialize " + srcType .getName () + "; did you forget to register a subtype?" );
305+ }
306+ JsonObject jsonObject = delegate .toJsonTree (value ).getAsJsonObject ();
307+
308+ if (maintainType ) {
309+ jsonElementAdapter .write (out , jsonObject );
310+ return ;
311+ }
312+
313+ JsonObject clone = new JsonObject ();
314+
315+ if (jsonObject .has (typeFieldName )) {
316+ throw new JsonParseException (
317+ "cannot serialize "
318+ + srcType .getName ()
319+ + " because it already defines a field named "
320+ + typeFieldName );
321+ }
322+ clone .add (typeFieldName , new JsonPrimitive (label ));
323+
324+ for (Map .Entry <String , JsonElement > e : jsonObject .entrySet ()) {
325+ clone .add (e .getKey (), e .getValue ());
326+ }
327+ jsonElementAdapter .write (out , clone );
328+ }
329+ }.nullSafe ();
330+ }
331+ }
0 commit comments