]> git.basschouten.com Git - openhab-addons.git/blob
e91895378e1bbac146f0a50e7e8103d6cab17d80
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 /*
14  * Copied from
15  * https://raw.githubusercontent.com/google/gson/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java
16  * and repackaged here with additional content from
17  * com.google.gson.internal.{Streams,TypeAdapters,LazilyParsedNumber}
18  * to avoid using the internal package.
19  */
20 package org.openhab.binding.lametrictime.internal.api.common.impl.typeadapters.imported;
21
22 import java.io.EOFException;
23 import java.io.IOException;
24 import java.io.ObjectStreamException;
25 import java.math.BigDecimal;
26 import java.util.LinkedHashMap;
27 import java.util.Map;
28
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31
32 import com.google.gson.Gson;
33 import com.google.gson.JsonArray;
34 import com.google.gson.JsonElement;
35 import com.google.gson.JsonIOException;
36 import com.google.gson.JsonNull;
37 import com.google.gson.JsonObject;
38 import com.google.gson.JsonParseException;
39 import com.google.gson.JsonPrimitive;
40 import com.google.gson.JsonSyntaxException;
41 import com.google.gson.TypeAdapter;
42 import com.google.gson.TypeAdapterFactory;
43 import com.google.gson.reflect.TypeToken;
44 import com.google.gson.stream.JsonReader;
45 import com.google.gson.stream.JsonWriter;
46 import com.google.gson.stream.MalformedJsonException;
47
48 /**
49  * Adapts values whose runtime type may differ from their declaration type. This
50  * is necessary when a field's type is not the same type that GSON should create
51  * when deserializing that field. For example, consider these types:
52  *
53  * <pre>
54  * {
55  *     &#64;code
56  *     abstract class Shape {
57  *         int x;
58  *         int y;
59  *     }
60  *     class Circle extends Shape {
61  *         int radius;
62  *     }
63  *     class Rectangle extends Shape {
64  *         int width;
65  *         int height;
66  *     }
67  *     class Diamond extends Shape {
68  *         int width;
69  *         int height;
70  *     }
71  *     class Drawing {
72  *         Shape bottomShape;
73  *         Shape topShape;
74  *     }
75  * }
76  * </pre>
77  * <p>
78  * Without additional type information, the serialized JSON is ambiguous. Is
79  * the bottom shape in this drawing a rectangle or a diamond?
80  *
81  * <pre>
82  *    {@code
83  *   {
84  *     "bottomShape": {
85  *       "width": 10,
86  *       "height": 5,
87  *       "x": 0,
88  *       "y": 0
89  *     },
90  *     "topShape": {
91  *       "radius": 2,
92  *       "x": 4,
93  *       "y": 1
94  *     }
95  *   }}
96  * </pre>
97  *
98  * This class addresses this problem by adding type information to the
99  * serialized JSON and honoring that type information when the JSON is
100  * deserialized:
101  *
102  * <pre>
103  *    {@code
104  *   {
105  *     "bottomShape": {
106  *       "type": "Diamond",
107  *       "width": 10,
108  *       "height": 5,
109  *       "x": 0,
110  *       "y": 0
111  *     },
112  *     "topShape": {
113  *       "type": "Circle",
114  *       "radius": 2,
115  *       "x": 4,
116  *       "y": 1
117  *     }
118  *   }}
119  * </pre>
120  *
121  * Both the type field name ({@code "type"}) and the type labels ({@code
122  * "Rectangle"}) are configurable.
123  *
124  * <h3>Registering Types</h3>
125  * Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field
126  * name to the {@link #of} factory method. If you don't supply an explicit type
127  * field name, {@code "type"} will be used.
128  *
129  * <pre>
130  * {
131  *     &#64;code
132  *     RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class, "type");
133  * }
134  * </pre>
135  *
136  * Next register all of your subtypes. Every subtype must be explicitly
137  * registered. This protects your application from injection attacks. If you
138  * don't supply an explicit type label, the type's simple name will be used.
139  *
140  * <pre>
141  *    {@code
142  *   shapeAdapter.registerSubtype(Rectangle.class, "Rectangle");
143  *   shapeAdapter.registerSubtype(Circle.class, "Circle");
144  *   shapeAdapter.registerSubtype(Diamond.class, "Diamond");
145  * }
146  * </pre>
147  *
148  * Finally, register the type adapter factory in your application's GSON builder:
149  *
150  * <pre>
151  * {
152  *     &#64;code
153  *     Gson gson = new GsonBuilder().registerTypeAdapterFactory(shapeAdapterFactory).create();
154  * }
155  * </pre>
156  *
157  * Like {@code GsonBuilder}, this API supports chaining:
158  *
159  * <pre>
160  * {
161  *     &#64;code
162  *     RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
163  *             .registerSubtype(Rectangle.class).registerSubtype(Circle.class).registerSubtype(Diamond.class);
164  * }
165  * </pre>
166  *
167  * @author Christophe Bornet - Initial contribution
168  */
169 @NonNullByDefault
170 public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
171     private final Class<?> baseType;
172     private final String typeFieldName;
173     private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<String, Class<?>>();
174     private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<Class<?>, String>();
175
176     private RuntimeTypeAdapterFactory(Class<?> baseType, String typeFieldName) {
177         if (typeFieldName == null || baseType == null) {
178             throw new IllegalArgumentException();
179         }
180         this.baseType = baseType;
181         this.typeFieldName = typeFieldName;
182     }
183
184     /**
185      * Creates a new runtime type adapter using for {@code baseType} using {@code
186      * typeFieldName} as the type field name. Type field names are case sensitive.
187      */
188     public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) {
189         return new RuntimeTypeAdapterFactory<T>(baseType, typeFieldName);
190     }
191
192     /**
193      * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as
194      * the type field name.
195      */
196     public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) {
197         return new RuntimeTypeAdapterFactory<T>(baseType, "type");
198     }
199
200     /**
201      * Registers {@code type} identified by {@code label}. Labels are case
202      * sensitive.
203      *
204      * @throws IllegalArgumentException if either {@code type} or {@code label}
205      *             have already been registered on this type adapter.
206      */
207     public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
208         if (type == null || label == null) {
209             throw new IllegalArgumentException();
210         }
211         if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
212             throw new IllegalArgumentException("types and labels must be unique");
213         }
214         labelToSubtype.put(label, type);
215         subtypeToLabel.put(type, label);
216         return this;
217     }
218
219     /**
220      * Registers {@code type} identified by its {@link Class#getSimpleName simple
221      * name}. Labels are case sensitive.
222      *
223      * @throws IllegalArgumentException if either {@code type} or its simple name
224      *             have already been registered on this type adapter.
225      */
226     public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
227         return registerSubtype(type, type.getSimpleName());
228     }
229
230     @Override
231     public @Nullable <R> TypeAdapter<R> create(@Nullable Gson gson, @Nullable TypeToken<R> type) {
232         if (type.getRawType() != baseType) {
233             return null;
234         }
235
236         final Map<String, TypeAdapter<?>> labelToDelegate = new LinkedHashMap<String, TypeAdapter<?>>();
237         final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate = new LinkedHashMap<Class<?>, TypeAdapter<?>>();
238         for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
239             TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
240             labelToDelegate.put(entry.getKey(), delegate);
241             subtypeToDelegate.put(entry.getValue(), delegate);
242         }
243
244         return new TypeAdapter<R>() {
245             @Override
246             public @Nullable R read(JsonReader in) throws IOException {
247                 JsonElement jsonElement = RuntimeTypeAdapterFactory.parse(in);
248                 JsonElement labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);
249                 if (labelJsonElement == null) {
250                     throw new JsonParseException("cannot deserialize " + baseType
251                             + " because it does not define a field named " + typeFieldName);
252                 }
253                 String label = labelJsonElement.getAsString();
254                 @SuppressWarnings("unchecked") // registration requires that subtype extends T
255                 TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
256                 if (delegate == null) {
257                     throw new JsonParseException("cannot deserialize " + baseType + " subtype named " + label
258                             + "; did you forget to register a subtype?");
259                 }
260                 return delegate.fromJsonTree(jsonElement);
261             }
262
263             @Override
264             public void write(JsonWriter out, @Nullable R value) throws IOException {
265                 Class<?> srcType = value.getClass();
266                 String label = subtypeToLabel.get(srcType);
267                 @SuppressWarnings("unchecked") // registration requires that subtype extends T
268                 TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);
269                 if (delegate == null) {
270                     throw new JsonParseException(
271                             "cannot serialize " + srcType.getName() + "; did you forget to register a subtype?");
272                 }
273                 JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
274                 if (jsonObject.has(typeFieldName)) {
275                     throw new JsonParseException("cannot serialize " + srcType.getName()
276                             + " because it already defines a field named " + typeFieldName);
277                 }
278                 JsonObject clone = new JsonObject();
279                 clone.add(typeFieldName, new JsonPrimitive(label));
280                 for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
281                     clone.add(e.getKey(), e.getValue());
282                 }
283                 RuntimeTypeAdapterFactory.write(clone, out);
284             }
285         }.nullSafe();
286     }
287
288     /**
289      * Takes a reader in any state and returns the next value as a JsonElement.
290      */
291     private static @Nullable JsonElement parse(JsonReader reader) throws JsonParseException {
292         boolean isEmpty = true;
293         try {
294             reader.peek();
295             isEmpty = false;
296             return RuntimeTypeAdapterFactory.JSON_ELEMENT.read(reader);
297         } catch (EOFException e) {
298             /*
299              * For compatibility with JSON 1.5 and earlier, we return a JsonNull for
300              * empty documents instead of throwing.
301              */
302             if (isEmpty) {
303                 return JsonNull.INSTANCE;
304             }
305             // The stream ended prematurely so it is likely a syntax error.
306             throw new JsonSyntaxException(e);
307         } catch (MalformedJsonException e) {
308             throw new JsonSyntaxException(e);
309         } catch (IOException e) {
310             throw new JsonIOException(e);
311         } catch (NumberFormatException e) {
312             throw new JsonSyntaxException(e);
313         }
314     }
315
316     /**
317      * Writes the JSON element to the writer, recursively.
318      */
319     private static void write(JsonElement element, JsonWriter writer) throws IOException {
320         RuntimeTypeAdapterFactory.JSON_ELEMENT.write(writer, element);
321     }
322
323     private static final TypeAdapter<JsonElement> JSON_ELEMENT = new TypeAdapter<JsonElement>() {
324         @Override
325         public @Nullable JsonElement read(@Nullable JsonReader in) throws IOException {
326             switch (in.peek()) {
327                 case STRING:
328                     return new JsonPrimitive(in.nextString());
329                 case NUMBER:
330                     String number = in.nextString();
331                     return new JsonPrimitive(new LazilyParsedNumber(number));
332                 case BOOLEAN:
333                     return new JsonPrimitive(in.nextBoolean());
334                 case NULL:
335                     in.nextNull();
336                     return JsonNull.INSTANCE;
337                 case BEGIN_ARRAY:
338                     JsonArray array = new JsonArray();
339                     in.beginArray();
340                     while (in.hasNext()) {
341                         array.add(read(in));
342                     }
343                     in.endArray();
344                     return array;
345                 case BEGIN_OBJECT:
346                     JsonObject object = new JsonObject();
347                     in.beginObject();
348                     while (in.hasNext()) {
349                         object.add(in.nextName(), read(in));
350                     }
351                     in.endObject();
352                     return object;
353                 case END_DOCUMENT:
354                 case NAME:
355                 case END_OBJECT:
356                 case END_ARRAY:
357                 default:
358                     throw new IllegalArgumentException();
359             }
360         }
361
362         @Override
363         public void write(JsonWriter out, @Nullable JsonElement value) throws IOException {
364             if (value == null || value.isJsonNull()) {
365                 out.nullValue();
366             } else if (value.isJsonPrimitive()) {
367                 JsonPrimitive primitive = value.getAsJsonPrimitive();
368                 if (primitive.isNumber()) {
369                     out.value(primitive.getAsNumber());
370                 } else if (primitive.isBoolean()) {
371                     out.value(primitive.getAsBoolean());
372                 } else {
373                     out.value(primitive.getAsString());
374                 }
375
376             } else if (value.isJsonArray()) {
377                 out.beginArray();
378                 for (JsonElement e : value.getAsJsonArray()) {
379                     write(out, e);
380                 }
381                 out.endArray();
382
383             } else if (value.isJsonObject()) {
384                 out.beginObject();
385                 for (Map.Entry<String, JsonElement> e : value.getAsJsonObject().entrySet()) {
386                     out.name(e.getKey());
387                     write(out, e.getValue());
388                 }
389                 out.endObject();
390
391             } else {
392                 throw new IllegalArgumentException("Couldn't write " + value.getClass());
393             }
394         }
395     };
396
397     /**
398      * This class holds a number value that is lazily converted to a specific number type
399      *
400      * @author Inderjeet Singh
401      */
402     public static final class LazilyParsedNumber extends Number {
403         private final String value;
404
405         public LazilyParsedNumber(String value) {
406             this.value = value;
407         }
408
409         @Override
410         public int intValue() {
411             try {
412                 return Integer.parseInt(value);
413             } catch (NumberFormatException e) {
414                 try {
415                     return (int) Long.parseLong(value);
416                 } catch (NumberFormatException nfe) {
417                     return new BigDecimal(value).intValue();
418                 }
419             }
420         }
421
422         @Override
423         public long longValue() {
424             try {
425                 return Long.parseLong(value);
426             } catch (NumberFormatException e) {
427                 return new BigDecimal(value).longValue();
428             }
429         }
430
431         @Override
432         public float floatValue() {
433             return Float.parseFloat(value);
434         }
435
436         @Override
437         public double doubleValue() {
438             return Double.parseDouble(value);
439         }
440
441         @Override
442         public String toString() {
443             return value;
444         }
445
446         /**
447          * If somebody is unlucky enough to have to serialize one of these, serialize
448          * it as a BigDecimal so that they won't need Gson on the other side to
449          * deserialize it.
450          */
451         private Object writeReplace() throws ObjectStreamException {
452             return new BigDecimal(value);
453         }
454     }
455 }