2 * Copyright (C) 2011 Google Inc.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
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.
24 package org.openhab.binding.lametrictime.api.common.impl.typeadapters.imported;
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;
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;
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:
54 * abstract class Shape {
58 * class Circle extends Shape {
61 * class Rectangle extends Shape {
65 * class Diamond extends Shape {
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
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
107 * Both the type field name ({@code "type"}) and the type labels ({@code
108 * "Rectangle"}) are configurable.
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");
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.
121 * shapeAdapter.registerSubtype(Rectangle.class, "Rectangle");
122 * shapeAdapter.registerSubtype(Circle.class, "Circle");
123 * shapeAdapter.registerSubtype(Diamond.class, "Diamond");
125 * Finally, register the type adapter factory in your application's GSON builder:
127 * Gson gson = new GsonBuilder()
128 * .registerTypeAdapterFactory(shapeAdapterFactory)
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);
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>();
144 private RuntimeTypeAdapterFactory(Class<?> baseType, String typeFieldName) {
145 if (typeFieldName == null || baseType == null) {
146 throw new NullPointerException();
148 this.baseType = baseType;
149 this.typeFieldName = typeFieldName;
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.
156 public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) {
157 return new RuntimeTypeAdapterFactory<T>(baseType, typeFieldName);
161 * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as
162 * the type field name.
164 public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) {
165 return new RuntimeTypeAdapterFactory<T>(baseType, "type");
169 * Registers {@code type} identified by {@code label}. Labels are case
172 * @throws IllegalArgumentException if either {@code type} or {@code label}
173 * have already been registered on this type adapter.
175 public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
176 if (type == null || label == null) {
177 throw new NullPointerException();
179 if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
180 throw new IllegalArgumentException("types and labels must be unique");
182 labelToSubtype.put(label, type);
183 subtypeToLabel.put(type, label);
188 * Registers {@code type} identified by its {@link Class#getSimpleName simple
189 * name}. Labels are case sensitive.
191 * @throws IllegalArgumentException if either {@code type} or its simple name
192 * have already been registered on this type adapter.
194 public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
195 return registerSubtype(type, type.getSimpleName());
198 public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
199 if (type.getRawType() != baseType) {
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);
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);
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?");
228 return delegate.fromJsonTree(jsonElement);
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?");
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);
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());
250 RuntimeTypeAdapterFactory.write(clone, out);
256 * Takes a reader in any state and returns the next value as a JsonElement.
258 private static JsonElement parse(JsonReader reader) throws JsonParseException {
259 boolean isEmpty = true;
263 return RuntimeTypeAdapterFactory.JSON_ELEMENT.read(reader);
264 } catch (EOFException e) {
266 * For compatibility with JSON 1.5 and earlier, we return a JsonNull for
267 * empty documents instead of throwing.
270 return JsonNull.INSTANCE;
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);
284 * Writes the JSON element to the writer, recursively.
286 private static void write(JsonElement element, JsonWriter writer) throws IOException {
287 RuntimeTypeAdapterFactory.JSON_ELEMENT.write(writer, element);
290 private static final TypeAdapter<JsonElement> JSON_ELEMENT = new TypeAdapter<JsonElement>() {
291 @Override public JsonElement read(JsonReader in) throws IOException {
294 return new JsonPrimitive(in.nextString());
296 String number = in.nextString();
297 return new JsonPrimitive(new LazilyParsedNumber(number));
299 return new JsonPrimitive(in.nextBoolean());
302 return JsonNull.INSTANCE;
304 JsonArray array = new JsonArray();
306 while (in.hasNext()) {
312 JsonObject object = new JsonObject();
314 while (in.hasNext()) {
315 object.add(in.nextName(), read(in));
324 throw new IllegalArgumentException();
328 @Override public void write(JsonWriter out, JsonElement value) throws IOException {
329 if (value == null || value.isJsonNull()) {
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());
338 out.value(primitive.getAsString());
341 } else if (value.isJsonArray()) {
343 for (JsonElement e : value.getAsJsonArray()) {
348 } else if (value.isJsonObject()) {
350 for (Map.Entry<String, JsonElement> e : value.getAsJsonObject().entrySet()) {
351 out.name(e.getKey());
352 write(out, e.getValue());
357 throw new IllegalArgumentException("Couldn't write " + value.getClass());
363 * This class holds a number value that is lazily converted to a specific number type
365 * @author Inderjeet Singh
367 public static final class LazilyParsedNumber extends Number {
368 private final String value;
370 public LazilyParsedNumber(String value) {
375 public int intValue() {
377 return Integer.parseInt(value);
378 } catch (NumberFormatException e) {
380 return (int) Long.parseLong(value);
381 } catch (NumberFormatException nfe) {
382 return new BigDecimal(value).intValue();
388 public long longValue() {
390 return Long.parseLong(value);
391 } catch (NumberFormatException e) {
392 return new BigDecimal(value).longValue();
397 public float floatValue() {
398 return Float.parseFloat(value);
402 public double doubleValue() {
403 return Double.parseDouble(value);
407 public String toString() {
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
416 private Object writeReplace() throws ObjectStreamException {
417 return new BigDecimal(value);