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