|
| 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 thirdparties |
| 18 | + |
| 19 | +import java.io.IOException |
| 20 | +import java.util.LinkedHashMap |
| 21 | + |
| 22 | +import com.google.gson.Gson |
| 23 | +import com.google.gson.JsonElement |
| 24 | +import com.google.gson.JsonObject |
| 25 | +import com.google.gson.JsonParseException |
| 26 | +import com.google.gson.JsonPrimitive |
| 27 | +import com.google.gson.TypeAdapter |
| 28 | +import com.google.gson.TypeAdapterFactory |
| 29 | +import com.google.gson.internal.Streams |
| 30 | +import com.google.gson.reflect.TypeToken |
| 31 | +import com.google.gson.stream.JsonReader |
| 32 | +import com.google.gson.stream.JsonWriter |
| 33 | + |
| 34 | +/** |
| 35 | + * Adapts values whose runtime type may differ from their declaration type. This |
| 36 | + * is necessary when a field's type is not the same type that GSON should create |
| 37 | + * when deserializing that field. For example, consider these types: |
| 38 | + * <pre> `abstract class Shape { |
| 39 | + * int x; |
| 40 | + * int y; |
| 41 | + * } |
| 42 | + * class Circle extends Shape { |
| 43 | + * int radius; |
| 44 | + * } |
| 45 | + * class Rectangle extends Shape { |
| 46 | + * int width; |
| 47 | + * int height; |
| 48 | + * } |
| 49 | + * class Diamond extends Shape { |
| 50 | + * int width; |
| 51 | + * int height; |
| 52 | + * } |
| 53 | + * class Drawing { |
| 54 | + * Shape bottomShape; |
| 55 | + * Shape topShape; |
| 56 | + * } |
| 57 | +`</pre> * |
| 58 | + * |
| 59 | + * Without additional type information, the serialized JSON is ambiguous. Is |
| 60 | + * the bottom shape in this drawing a rectangle or a diamond? <pre> `{ |
| 61 | + * "bottomShape": { |
| 62 | + * "width": 10, |
| 63 | + * "height": 5, |
| 64 | + * "x": 0, |
| 65 | + * "y": 0 |
| 66 | + * }, |
| 67 | + * "topShape": { |
| 68 | + * "radius": 2, |
| 69 | + * "x": 4, |
| 70 | + * "y": 1 |
| 71 | + * } |
| 72 | + * }`</pre> |
| 73 | + * This class addresses this problem by adding type information to the |
| 74 | + * serialized JSON and honoring that type information when the JSON is |
| 75 | + * deserialized: <pre> `{ |
| 76 | + * "bottomShape": { |
| 77 | + * "type": "Diamond", |
| 78 | + * "width": 10, |
| 79 | + * "height": 5, |
| 80 | + * "x": 0, |
| 81 | + * "y": 0 |
| 82 | + * }, |
| 83 | + * "topShape": { |
| 84 | + * "type": "Circle", |
| 85 | + * "radius": 2, |
| 86 | + * "x": 4, |
| 87 | + * "y": 1 |
| 88 | + * } |
| 89 | + * }`</pre> |
| 90 | + * Both the type field name (`"type"`) and the type labels (`"Rectangle"`) are configurable. |
| 91 | + * |
| 92 | + * <h3>Registering Types</h3> |
| 93 | + * Create a `RuntimeTypeAdapterFactory` by passing the base type and type field |
| 94 | + * name to the [.of] factory method. If you don't supply an explicit type |
| 95 | + * field name, `"type"` will be used. <pre> `RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory |
| 96 | + * = RuntimeTypeAdapterFactory.of(Shape.class, "type"); |
| 97 | +`</pre> * |
| 98 | + * Next register all of your subtypes. Every subtype must be explicitly |
| 99 | + * registered. This protects your application from injection attacks. If you |
| 100 | + * don't supply an explicit type label, the type's simple name will be used. |
| 101 | + * <pre> `shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle"); |
| 102 | + * shapeAdapterFactory.registerSubtype(Circle.class, "Circle"); |
| 103 | + * shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond"); |
| 104 | +`</pre> * |
| 105 | + * Finally, register the type adapter factory in your application's GSON builder: |
| 106 | + * <pre> `Gson gson = new GsonBuilder() |
| 107 | + * .registerTypeAdapterFactory(shapeAdapterFactory) |
| 108 | + * .create(); |
| 109 | +`</pre> * |
| 110 | + * Like `GsonBuilder`, this API supports chaining: <pre> `RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class) |
| 111 | + * .registerSubtype(Rectangle.class) |
| 112 | + * .registerSubtype(Circle.class) |
| 113 | + * .registerSubtype(Diamond.class); |
| 114 | +`</pre> * |
| 115 | + */ |
| 116 | +class RuntimeTypeAdapterFactory<T> private constructor(private val baseType: Class<*>?, private val typeFieldName: String?, private val maintainType: Boolean) : TypeAdapterFactory { |
| 117 | + private val labelToSubtype = LinkedHashMap<String, Class<*>>() |
| 118 | + private val subtypeToLabel = LinkedHashMap<Class<*>, String>() |
| 119 | + |
| 120 | + init { |
| 121 | + if (typeFieldName == null || baseType == null) { |
| 122 | + throw NullPointerException() |
| 123 | + } |
| 124 | + } |
| 125 | + |
| 126 | + /** |
| 127 | + * Registers `type` identified by `label`. Labels are case |
| 128 | + * sensitive. |
| 129 | + * |
| 130 | + * @throws IllegalArgumentException if either `type` or `label` |
| 131 | + * have already been registered on this type adapter. |
| 132 | + */ |
| 133 | + @JvmOverloads |
| 134 | + fun registerSubtype(type: Class<out T>?, label: String? = type?.getSimpleName()): RuntimeTypeAdapterFactory<T> { |
| 135 | + if (type == null || label == null) { |
| 136 | + throw NullPointerException() |
| 137 | + } |
| 138 | + if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) { |
| 139 | + throw IllegalArgumentException("types and labels must be unique") |
| 140 | + } |
| 141 | + labelToSubtype[label] = type |
| 142 | + subtypeToLabel[type] = label |
| 143 | + return this |
| 144 | + } |
| 145 | + |
| 146 | + override fun <R : Any> create(gson: Gson, type: TypeToken<R>): TypeAdapter<R>? { |
| 147 | + if (type.rawType != baseType) { |
| 148 | + return null |
| 149 | + } |
| 150 | + |
| 151 | + val labelToDelegate = LinkedHashMap<String, TypeAdapter<*>>() |
| 152 | + val subtypeToDelegate = LinkedHashMap<Class<*>, TypeAdapter<*>>() |
| 153 | + for ((key, value) in labelToSubtype) { |
| 154 | + val delegate = gson.getDelegateAdapter(this, TypeToken.get(value)) |
| 155 | + labelToDelegate[key] = delegate |
| 156 | + subtypeToDelegate[value] = delegate |
| 157 | + } |
| 158 | + |
| 159 | + return object : TypeAdapter<R>() { |
| 160 | + @Throws(IOException::class) |
| 161 | + override fun read(`in`: JsonReader): R { |
| 162 | + val jsonElement = Streams.parse(`in`) |
| 163 | + val labelJsonElement: JsonElement? |
| 164 | + if (maintainType) { |
| 165 | + labelJsonElement = jsonElement.asJsonObject.get(typeFieldName) |
| 166 | + } else { |
| 167 | + labelJsonElement = jsonElement.asJsonObject.remove(typeFieldName) |
| 168 | + } |
| 169 | + |
| 170 | + if (labelJsonElement == null) { |
| 171 | + throw JsonParseException("cannot deserialize " + baseType |
| 172 | + + " because it does not define a field named " + typeFieldName) |
| 173 | + } |
| 174 | + val label = labelJsonElement.asString |
| 175 | + val delegate = labelToDelegate[label] as? TypeAdapter<R> |
| 176 | + ?: throw JsonParseException("cannot deserialize " + baseType + " subtype named " |
| 177 | + + label + "; did you forget to register a subtype?")// registration requires that subtype extends T |
| 178 | + return delegate.fromJsonTree(jsonElement) |
| 179 | + } |
| 180 | + |
| 181 | + @Throws(IOException::class) |
| 182 | + override fun write(out: JsonWriter, value: R) { |
| 183 | + val srcType = value.javaClass |
| 184 | + val label = subtypeToLabel[srcType] |
| 185 | + val delegate = subtypeToDelegate[srcType] as? TypeAdapter<R> |
| 186 | + ?: throw JsonParseException("cannot serialize " + srcType.getName() |
| 187 | + + "; did you forget to register a subtype?")// registration requires that subtype extends T |
| 188 | + val jsonObject = delegate.toJsonTree(value).asJsonObject |
| 189 | + |
| 190 | + if (maintainType) { |
| 191 | + Streams.write(jsonObject, out) |
| 192 | + return |
| 193 | + } |
| 194 | + |
| 195 | + val clone = JsonObject() |
| 196 | + |
| 197 | + if (jsonObject.has(typeFieldName)) { |
| 198 | + throw JsonParseException("cannot serialize " + srcType.getName() |
| 199 | + + " because it already defines a field named " + typeFieldName) |
| 200 | + } |
| 201 | + clone.add(typeFieldName, JsonPrimitive(label)) |
| 202 | + |
| 203 | + for ((key, value1) in jsonObject.entrySet()) { |
| 204 | + clone.add(key, value1) |
| 205 | + } |
| 206 | + Streams.write(clone, out) |
| 207 | + } |
| 208 | + }.nullSafe() |
| 209 | + } |
| 210 | + |
| 211 | + companion object { |
| 212 | + |
| 213 | + /** |
| 214 | + * Creates a new runtime type adapter using for `baseType` using `typeFieldName` as the type field name. Type field names are case sensitive. |
| 215 | + * `maintainType` flag decide if the type will be stored in pojo or not. |
| 216 | + */ |
| 217 | + fun <T> of(baseType: Class<T>, typeFieldName: String, maintainType: Boolean): RuntimeTypeAdapterFactory<T> { |
| 218 | + return RuntimeTypeAdapterFactory(baseType, typeFieldName, maintainType) |
| 219 | + } |
| 220 | + |
| 221 | + /** |
| 222 | + * Creates a new runtime type adapter using for `baseType` using `typeFieldName` as the type field name. Type field names are case sensitive. |
| 223 | + */ |
| 224 | + fun <T> of(baseType: Class<T>, typeFieldName: String): RuntimeTypeAdapterFactory<T> { |
| 225 | + return RuntimeTypeAdapterFactory(baseType, typeFieldName, false) |
| 226 | + } |
| 227 | + |
| 228 | + /** |
| 229 | + * Creates a new runtime type adapter for `baseType` using `"type"` as |
| 230 | + * the type field name. |
| 231 | + */ |
| 232 | + fun <T> of(baseType: Class<T>): RuntimeTypeAdapterFactory<T> { |
| 233 | + return RuntimeTypeAdapterFactory(baseType, "type", false) |
| 234 | + } |
| 235 | + } |
| 236 | +} |
| 237 | +/** |
| 238 | + * Registers `type` identified by its [simple][Class.getSimpleName]. Labels are case sensitive. |
| 239 | + * |
| 240 | + * @throws IllegalArgumentException if either `type` or its simple name |
| 241 | + * have already been registered on this type adapter. |
| 242 | + */ |
0 commit comments