mirror of
				https://github.com/commons-app/apps-android-commons.git
				synced 2025-10-31 14:53:59 +01:00 
			
		
		
		
	Converted type adapters
This commit is contained in:
		
							parent
							
								
									a5fb255bb3
								
							
						
					
					
						commit
						85dda964bc
					
				
					 8 changed files with 353 additions and 365 deletions
				
			
		|  | @ -1,29 +0,0 @@ | |||
| package fr.free.nrw.commons.wikidata.json; | ||||
| 
 | ||||
| import com.google.gson.TypeAdapter; | ||||
| import com.google.gson.stream.JsonReader; | ||||
| import com.google.gson.stream.JsonToken; | ||||
| import com.google.gson.stream.JsonWriter; | ||||
| 
 | ||||
| import fr.free.nrw.commons.wikidata.model.page.Namespace; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| public class NamespaceTypeAdapter extends TypeAdapter<Namespace> { | ||||
| 
 | ||||
|     @Override | ||||
|     public void write(JsonWriter out, Namespace namespace) throws IOException { | ||||
|         out.value(namespace.code()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Namespace read(JsonReader in) throws IOException { | ||||
|         if (in.peek() == JsonToken.STRING) { | ||||
|             // Prior to 3210ce44, we marshaled Namespace as the name string of the enum, instead of | ||||
|             // the code number. This introduces a backwards-compatible check for the string value. | ||||
|             // TODO: remove after April 2017, when all older namespaces have been deserialized. | ||||
|             return Namespace.valueOf(in.nextString()); | ||||
|         } | ||||
|         return Namespace.of(in.nextInt()); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,26 @@ | |||
| package fr.free.nrw.commons.wikidata.json | ||||
| 
 | ||||
| import com.google.gson.TypeAdapter | ||||
| import com.google.gson.stream.JsonReader | ||||
| import com.google.gson.stream.JsonToken | ||||
| import com.google.gson.stream.JsonWriter | ||||
| import fr.free.nrw.commons.wikidata.model.page.Namespace | ||||
| import java.io.IOException | ||||
| 
 | ||||
| class NamespaceTypeAdapter : TypeAdapter<Namespace>() { | ||||
|     @Throws(IOException::class) | ||||
|     override fun write(out: JsonWriter, namespace: Namespace) { | ||||
|         out.value(namespace.code().toLong()) | ||||
|     } | ||||
| 
 | ||||
|     @Throws(IOException::class) | ||||
|     override fun read(reader: JsonReader): Namespace { | ||||
|         if (reader.peek() == JsonToken.STRING) { | ||||
|             // Prior to 3210ce44, we marshaled Namespace as the name string of the enum, instead of | ||||
|             // the code number. This introduces a backwards-compatible check for the string value. | ||||
|             // TODO: remove after April 2017, when all older namespaces have been deserialized. | ||||
|             return Namespace.valueOf(reader.nextString()) | ||||
|         } | ||||
|         return Namespace.of(reader.nextInt()) | ||||
|     } | ||||
| } | ||||
|  | @ -1,34 +0,0 @@ | |||
| package fr.free.nrw.commons.wikidata.json; | ||||
| 
 | ||||
| import com.google.gson.Gson; | ||||
| import com.google.gson.TypeAdapter; | ||||
| import com.google.gson.TypeAdapterFactory; | ||||
| import com.google.gson.reflect.TypeToken; | ||||
| import com.google.gson.stream.JsonReader; | ||||
| import com.google.gson.stream.JsonWriter; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| public class PostProcessingTypeAdapter implements TypeAdapterFactory { | ||||
|     public interface PostProcessable { | ||||
|         void postProcess(); | ||||
|     } | ||||
| 
 | ||||
|     public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { | ||||
|         final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type); | ||||
| 
 | ||||
|         return new TypeAdapter<T>() { | ||||
|             public void write(JsonWriter out, T value) throws IOException { | ||||
|                 delegate.write(out, value); | ||||
|             } | ||||
| 
 | ||||
|             public T read(JsonReader in) throws IOException { | ||||
|                 T obj = delegate.read(in); | ||||
|                 if (obj instanceof PostProcessable) { | ||||
|                     ((PostProcessable)obj).postProcess(); | ||||
|                 } | ||||
|                 return obj; | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,35 @@ | |||
| package fr.free.nrw.commons.wikidata.json | ||||
| 
 | ||||
| import com.google.gson.Gson | ||||
| import com.google.gson.TypeAdapter | ||||
| import com.google.gson.TypeAdapterFactory | ||||
| import com.google.gson.reflect.TypeToken | ||||
| import com.google.gson.stream.JsonReader | ||||
| import com.google.gson.stream.JsonWriter | ||||
| import java.io.IOException | ||||
| 
 | ||||
| class PostProcessingTypeAdapter : TypeAdapterFactory { | ||||
|     interface PostProcessable { | ||||
|         fun postProcess() | ||||
|     } | ||||
| 
 | ||||
|     override fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T> { | ||||
|         val delegate = gson.getDelegateAdapter(this, type) | ||||
| 
 | ||||
|         return object : TypeAdapter<T>() { | ||||
|             @Throws(IOException::class) | ||||
|             override fun write(out: JsonWriter, value: T) { | ||||
|                 delegate.write(out, value) | ||||
|             } | ||||
| 
 | ||||
|             @Throws(IOException::class) | ||||
|             override fun read(reader: JsonReader): T { | ||||
|                 val obj = delegate.read(reader) | ||||
|                 if (obj is PostProcessable) { | ||||
|                     (obj as PostProcessable).postProcess() | ||||
|                 } | ||||
|                 return obj | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -1,280 +0,0 @@ | |||
| package fr.free.nrw.commons.wikidata.json; | ||||
| 
 | ||||
| /* | ||||
|  * Copyright (C) 2011 Google Inc. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| import android.util.Log; | ||||
| import java.io.IOException; | ||||
| import java.util.LinkedHashMap; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import com.google.gson.Gson; | ||||
| import com.google.gson.JsonElement; | ||||
| import com.google.gson.JsonObject; | ||||
| import com.google.gson.JsonParseException; | ||||
| import com.google.gson.JsonPrimitive; | ||||
| import com.google.gson.TypeAdapter; | ||||
| import com.google.gson.TypeAdapterFactory; | ||||
| import com.google.gson.internal.Streams; | ||||
| import com.google.gson.reflect.TypeToken; | ||||
| import com.google.gson.stream.JsonReader; | ||||
| import com.google.gson.stream.JsonWriter; | ||||
| 
 | ||||
| /** | ||||
|  * Adapts values whose runtime type may differ from their declaration type. This | ||||
|  * is necessary when a field's type is not the same type that GSON should create | ||||
|  * when deserializing that field. For example, consider these types: | ||||
|  * <pre>   {@code | ||||
|  *   abstract class Shape { | ||||
|  *     int x; | ||||
|  *     int y; | ||||
|  *   } | ||||
|  *   class Circle extends Shape { | ||||
|  *     int radius; | ||||
|  *   } | ||||
|  *   class Rectangle extends Shape { | ||||
|  *     int width; | ||||
|  *     int height; | ||||
|  *   } | ||||
|  *   class Diamond extends Shape { | ||||
|  *     int width; | ||||
|  *     int height; | ||||
|  *   } | ||||
|  *   class Drawing { | ||||
|  *     Shape bottomShape; | ||||
|  *     Shape topShape; | ||||
|  *   } | ||||
|  * }</pre> | ||||
|  * <p>Without additional type information, the serialized JSON is ambiguous. Is | ||||
|  * the bottom shape in this drawing a rectangle or a diamond? <pre>   {@code | ||||
|  *   { | ||||
|  *     "bottomShape": { | ||||
|  *       "width": 10, | ||||
|  *       "height": 5, | ||||
|  *       "x": 0, | ||||
|  *       "y": 0 | ||||
|  *     }, | ||||
|  *     "topShape": { | ||||
|  *       "radius": 2, | ||||
|  *       "x": 4, | ||||
|  *       "y": 1 | ||||
|  *     } | ||||
|  *   }}</pre> | ||||
|  * This class addresses this problem by adding type information to the | ||||
|  * serialized JSON and honoring that type information when the JSON is | ||||
|  * deserialized: <pre>   {@code | ||||
|  *   { | ||||
|  *     "bottomShape": { | ||||
|  *       "type": "Diamond", | ||||
|  *       "width": 10, | ||||
|  *       "height": 5, | ||||
|  *       "x": 0, | ||||
|  *       "y": 0 | ||||
|  *     }, | ||||
|  *     "topShape": { | ||||
|  *       "type": "Circle", | ||||
|  *       "radius": 2, | ||||
|  *       "x": 4, | ||||
|  *       "y": 1 | ||||
|  *     } | ||||
|  *   }}</pre> | ||||
|  * Both the type field name ({@code "type"}) and the type labels ({@code | ||||
|  * "Rectangle"}) are configurable. | ||||
|  * | ||||
|  * <h3>Registering Types</h3> | ||||
|  * Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field | ||||
|  * name to the {@link #of} factory method. If you don't supply an explicit type | ||||
|  * field name, {@code "type"} will be used. <pre>   {@code | ||||
|  *   RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory | ||||
|  *       = RuntimeTypeAdapterFactory.of(Shape.class, "type"); | ||||
|  * }</pre> | ||||
|  * Next register all of your subtypes. Every subtype must be explicitly | ||||
|  * registered. This protects your application from injection attacks. If you | ||||
|  * don't supply an explicit type label, the type's simple name will be used. | ||||
|  * <pre>   {@code | ||||
|  *   shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle"); | ||||
|  *   shapeAdapterFactory.registerSubtype(Circle.class, "Circle"); | ||||
|  *   shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond"); | ||||
|  * }</pre> | ||||
|  * Finally, register the type adapter factory in your application's GSON builder: | ||||
|  * <pre>   {@code | ||||
|  *   Gson gson = new GsonBuilder() | ||||
|  *       .registerTypeAdapterFactory(shapeAdapterFactory) | ||||
|  *       .create(); | ||||
|  * }</pre> | ||||
|  * Like {@code GsonBuilder}, this API supports chaining: <pre>   {@code | ||||
|  *   RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class) | ||||
|  *       .registerSubtype(Rectangle.class) | ||||
|  *       .registerSubtype(Circle.class) | ||||
|  *       .registerSubtype(Diamond.class); | ||||
|  * }</pre> | ||||
|  * | ||||
|  * <h3>Serialization and deserialization</h3> | ||||
|  * In order to serialize and deserialize a polymorphic object, | ||||
|  * you must specify the base type explicitly. | ||||
|  * <pre>   {@code | ||||
|  *   Diamond diamond = new Diamond(); | ||||
|  *   String json = gson.toJson(diamond, Shape.class); | ||||
|  * }</pre> | ||||
|  * And then: | ||||
|  * <pre>   {@code | ||||
|  *   Shape shape = gson.fromJson(json, Shape.class); | ||||
|  * }</pre> | ||||
|  */ | ||||
| public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory { | ||||
|   private final Class<?> baseType; | ||||
|   private final String typeFieldName; | ||||
|   private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<String, Class<?>>(); | ||||
|   private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<Class<?>, String>(); | ||||
|   private final boolean maintainType; | ||||
| 
 | ||||
|   private RuntimeTypeAdapterFactory(Class<?> baseType, String typeFieldName, boolean maintainType) { | ||||
|     if (typeFieldName == null || baseType == null) { | ||||
|       throw new NullPointerException(); | ||||
|     } | ||||
|     this.baseType = baseType; | ||||
|     this.typeFieldName = typeFieldName; | ||||
|     this.maintainType = maintainType; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Creates a new runtime type adapter using for {@code baseType} using {@code | ||||
|    * typeFieldName} as the type field name. Type field names are case sensitive. | ||||
|    * {@code maintainType} flag decide if the type will be stored in pojo or not. | ||||
|    */ | ||||
|   public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName, boolean maintainType) { | ||||
|     return new RuntimeTypeAdapterFactory<T>(baseType, typeFieldName, maintainType); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Creates a new runtime type adapter using for {@code baseType} using {@code | ||||
|    * typeFieldName} as the type field name. Type field names are case sensitive. | ||||
|    */ | ||||
|   public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) { | ||||
|     return new RuntimeTypeAdapterFactory<T>(baseType, typeFieldName, false); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as | ||||
|    * the type field name. | ||||
|    */ | ||||
|   public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) { | ||||
|     return new RuntimeTypeAdapterFactory<T>(baseType, "type", false); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Registers {@code type} identified by {@code label}. Labels are case | ||||
|    * sensitive. | ||||
|    * | ||||
|    * @throws IllegalArgumentException if either {@code type} or {@code label} | ||||
|    *     have already been registered on this type adapter. | ||||
|    */ | ||||
|   public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) { | ||||
|     if (type == null || label == null) { | ||||
|       throw new NullPointerException(); | ||||
|     } | ||||
|     if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) { | ||||
|       throw new IllegalArgumentException("types and labels must be unique"); | ||||
|     } | ||||
|     labelToSubtype.put(label, type); | ||||
|     subtypeToLabel.put(type, label); | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Registers {@code type} identified by its {@link Class#getSimpleName simple | ||||
|    * name}. Labels are case sensitive. | ||||
|    * | ||||
|    * @throws IllegalArgumentException if either {@code type} or its simple name | ||||
|    *     have already been registered on this type adapter. | ||||
|    */ | ||||
|   public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) { | ||||
|     return registerSubtype(type, type.getSimpleName()); | ||||
|   } | ||||
| 
 | ||||
|   public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) { | ||||
|     if (type.getRawType() != baseType) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     final Map<String, TypeAdapter<?>> labelToDelegate | ||||
|         = new LinkedHashMap<String, TypeAdapter<?>>(); | ||||
|     final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate | ||||
|         = new LinkedHashMap<Class<?>, TypeAdapter<?>>(); | ||||
|     for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) { | ||||
|       TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); | ||||
|       labelToDelegate.put(entry.getKey(), delegate); | ||||
|       subtypeToDelegate.put(entry.getValue(), delegate); | ||||
|     } | ||||
| 
 | ||||
|     return new TypeAdapter<R>() { | ||||
|       @Override public R read(JsonReader in) throws IOException { | ||||
|         JsonElement jsonElement = Streams.parse(in); | ||||
|         JsonElement labelJsonElement; | ||||
|         if (maintainType) { | ||||
|           labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName); | ||||
|         } else { | ||||
|           labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName); | ||||
|         } | ||||
| 
 | ||||
|         if (labelJsonElement == null) { | ||||
|           throw new JsonParseException("cannot deserialize " + baseType | ||||
|               + " because it does not define a field named " + typeFieldName); | ||||
|         } | ||||
|         String label = labelJsonElement.getAsString(); | ||||
|         @SuppressWarnings("unchecked") // registration requires that subtype extends T | ||||
|             TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label); | ||||
|         if (delegate == null) { | ||||
| 
 | ||||
|           Log.e("RuntimeTypeAdapter", "cannot deserialize " + baseType + " subtype named " | ||||
|               + label + "; did you forget to register a subtype? " +jsonElement); | ||||
|           return null; | ||||
|         } | ||||
|         return delegate.fromJsonTree(jsonElement); | ||||
|       } | ||||
| 
 | ||||
|       @Override public void write(JsonWriter out, R value) throws IOException { | ||||
|         Class<?> srcType = value.getClass(); | ||||
|         String label = subtypeToLabel.get(srcType); | ||||
|         @SuppressWarnings("unchecked") // registration requires that subtype extends T | ||||
|             TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType); | ||||
|         if (delegate == null) { | ||||
|           throw new JsonParseException("cannot serialize " + srcType.getName() | ||||
|               + "; did you forget to register a subtype?"); | ||||
|         } | ||||
|         JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject(); | ||||
| 
 | ||||
|         if (maintainType) { | ||||
|           Streams.write(jsonObject, out); | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         JsonObject clone = new JsonObject(); | ||||
| 
 | ||||
|         if (jsonObject.has(typeFieldName)) { | ||||
|           throw new JsonParseException("cannot serialize " + srcType.getName() | ||||
|               + " because it already defines a field named " + typeFieldName); | ||||
|         } | ||||
|         clone.add(typeFieldName, new JsonPrimitive(label)); | ||||
| 
 | ||||
|         for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) { | ||||
|           clone.add(e.getKey(), e.getValue()); | ||||
|         } | ||||
|         Streams.write(clone, out); | ||||
|       } | ||||
|     }.nullSafe(); | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,273 @@ | |||
| package fr.free.nrw.commons.wikidata.json | ||||
| 
 | ||||
| import com.google.gson.Gson | ||||
| import com.google.gson.JsonObject | ||||
| import com.google.gson.JsonParseException | ||||
| import com.google.gson.JsonPrimitive | ||||
| import com.google.gson.TypeAdapter | ||||
| import com.google.gson.TypeAdapterFactory | ||||
| import com.google.gson.internal.Streams | ||||
| import com.google.gson.reflect.TypeToken | ||||
| import com.google.gson.stream.JsonReader | ||||
| import com.google.gson.stream.JsonWriter | ||||
| import timber.log.Timber | ||||
| import java.io.IOException | ||||
| 
 | ||||
| /* | ||||
| * Copyright (C) 2011 Google Inc. | ||||
| * | ||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| * you may not use this file except in compliance with the License. | ||||
| * You may obtain a copy of the License at | ||||
| * | ||||
| *      http://www.apache.org/licenses/LICENSE-2.0 | ||||
| * | ||||
| * Unless required by applicable law or agreed to in writing, software | ||||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| * See the License for the specific language governing permissions and | ||||
| * limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| /** | ||||
|  * Adapts values whose runtime type may differ from their declaration type. This | ||||
|  * is necessary when a field's type is not the same type that GSON should create | ||||
|  * when deserializing that field. For example, consider these types: | ||||
|  * <pre>   `abstract class Shape { | ||||
|  * int x; | ||||
|  * int y; | ||||
|  * } | ||||
|  * class Circle extends Shape { | ||||
|  * int radius; | ||||
|  * } | ||||
|  * class Rectangle extends Shape { | ||||
|  * int width; | ||||
|  * int height; | ||||
|  * } | ||||
|  * class Diamond extends Shape { | ||||
|  * int width; | ||||
|  * int height; | ||||
|  * } | ||||
|  * class Drawing { | ||||
|  * Shape bottomShape; | ||||
|  * Shape topShape; | ||||
|  * } | ||||
| `</pre> * | ||||
|  * | ||||
|  * Without additional type information, the serialized JSON is ambiguous. Is | ||||
|  * the bottom shape in this drawing a rectangle or a diamond? <pre>   `{ | ||||
|  * "bottomShape": { | ||||
|  * "width": 10, | ||||
|  * "height": 5, | ||||
|  * "x": 0, | ||||
|  * "y": 0 | ||||
|  * }, | ||||
|  * "topShape": { | ||||
|  * "radius": 2, | ||||
|  * "x": 4, | ||||
|  * "y": 1 | ||||
|  * } | ||||
|  * }`</pre> | ||||
|  * This class addresses this problem by adding type information to the | ||||
|  * serialized JSON and honoring that type information when the JSON is | ||||
|  * deserialized: <pre>   `{ | ||||
|  * "bottomShape": { | ||||
|  * "type": "Diamond", | ||||
|  * "width": 10, | ||||
|  * "height": 5, | ||||
|  * "x": 0, | ||||
|  * "y": 0 | ||||
|  * }, | ||||
|  * "topShape": { | ||||
|  * "type": "Circle", | ||||
|  * "radius": 2, | ||||
|  * "x": 4, | ||||
|  * "y": 1 | ||||
|  * } | ||||
|  * }`</pre> | ||||
|  * Both the type field name (`"type"`) and the type labels (`"Rectangle"`) are configurable. | ||||
|  * | ||||
|  * <h3>Registering Types</h3> | ||||
|  * Create a `RuntimeTypeAdapterFactory` by passing the base type and type field | ||||
|  * name to the [.of] factory method. If you don't supply an explicit type | ||||
|  * field name, `"type"` will be used. <pre>   `RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory | ||||
|  * = RuntimeTypeAdapterFactory.of(Shape.class, "type"); | ||||
| `</pre> * | ||||
|  * Next register all of your subtypes. Every subtype must be explicitly | ||||
|  * registered. This protects your application from injection attacks. If you | ||||
|  * don't supply an explicit type label, the type's simple name will be used. | ||||
|  * <pre>   `shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle"); | ||||
|  * shapeAdapterFactory.registerSubtype(Circle.class, "Circle"); | ||||
|  * shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond"); | ||||
| `</pre> * | ||||
|  * Finally, register the type adapter factory in your application's GSON builder: | ||||
|  * <pre>   `Gson gson = new GsonBuilder() | ||||
|  * .registerTypeAdapterFactory(shapeAdapterFactory) | ||||
|  * .create(); | ||||
| `</pre> * | ||||
|  * Like `GsonBuilder`, this API supports chaining: <pre>   `RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class) | ||||
|  * .registerSubtype(Rectangle.class) | ||||
|  * .registerSubtype(Circle.class) | ||||
|  * .registerSubtype(Diamond.class); | ||||
| `</pre> * | ||||
|  * | ||||
|  * <h3>Serialization and deserialization</h3> | ||||
|  * In order to serialize and deserialize a polymorphic object, | ||||
|  * you must specify the base type explicitly. | ||||
|  * <pre>   `Diamond diamond = new Diamond(); | ||||
|  * String json = gson.toJson(diamond, Shape.class); | ||||
| `</pre> * | ||||
|  * And then: | ||||
|  * <pre>   `Shape shape = gson.fromJson(json, Shape.class); | ||||
| `</pre> * | ||||
|  */ | ||||
| class RuntimeTypeAdapterFactory<T>( | ||||
|     baseType: Class<*>?, | ||||
|     typeFieldName: String?, | ||||
|     maintainType: Boolean | ||||
| ) : TypeAdapterFactory { | ||||
| 
 | ||||
|     private val baseType: Class<*> | ||||
|     private val typeFieldName: String | ||||
|     private val labelToSubtype = mutableMapOf<String, Class<*>>() | ||||
|     private val subtypeToLabel = mutableMapOf<Class<*>, String>() | ||||
|     private val maintainType: Boolean | ||||
| 
 | ||||
|     init { | ||||
|         if (typeFieldName == null || baseType == null) { | ||||
|             throw NullPointerException() | ||||
|         } | ||||
|         this.baseType = baseType | ||||
|         this.typeFieldName = typeFieldName | ||||
|         this.maintainType = maintainType | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Registers `type` identified by `label`. Labels are case | ||||
|      * sensitive. | ||||
|      * | ||||
|      * @throws IllegalArgumentException if either `type` or `label` | ||||
|      * have already been registered on this type adapter. | ||||
|      */ | ||||
|     fun registerSubtype(type: Class<out T>?, label: String?): RuntimeTypeAdapterFactory<T> { | ||||
|         if (type == null || label == null) { | ||||
|             throw NullPointerException() | ||||
|         } | ||||
|         require(!(subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label))) { | ||||
|             "types and labels must be unique" | ||||
|         } | ||||
| 
 | ||||
|         labelToSubtype[label] = type | ||||
|         subtypeToLabel[type] = label | ||||
|         return this | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Registers `type` identified by its [simple][Class.getSimpleName]. Labels are case sensitive. | ||||
|      * | ||||
|      * @throws IllegalArgumentException if either `type` or its simple name | ||||
|      * have already been registered on this type adapter. | ||||
|      */ | ||||
|     fun registerSubtype(type: Class<out T>): RuntimeTypeAdapterFactory<T> { | ||||
|         return registerSubtype(type, type.simpleName) | ||||
|     } | ||||
| 
 | ||||
|     override fun <R : Any> create(gson: Gson, type: TypeToken<R>): TypeAdapter<R>? { | ||||
|         if (type.rawType != baseType) { | ||||
|             return null | ||||
|         } | ||||
| 
 | ||||
|         val labelToDelegate = mutableMapOf<String, TypeAdapter<*>>() | ||||
|         val subtypeToDelegate = mutableMapOf<Class<*>, TypeAdapter<*>>() | ||||
|         for ((key, value) in labelToSubtype) { | ||||
|             val delegate = gson.getDelegateAdapter( | ||||
|                 this, TypeToken.get( | ||||
|                     value | ||||
|                 ) | ||||
|             ) | ||||
|             labelToDelegate[key] = delegate | ||||
|             subtypeToDelegate[value] = delegate | ||||
|         } | ||||
| 
 | ||||
|         return object : TypeAdapter<R>() { | ||||
|             @Throws(IOException::class) | ||||
|             override fun read(reader: JsonReader): R? { | ||||
|                 val jsonElement = Streams.parse(reader) | ||||
|                 val labelJsonElement = if (maintainType) { | ||||
|                     jsonElement.asJsonObject[typeFieldName] | ||||
|                 } else { | ||||
|                     jsonElement.asJsonObject.remove(typeFieldName) | ||||
|                 } | ||||
| 
 | ||||
|                 if (labelJsonElement == null) { | ||||
|                     throw JsonParseException( | ||||
|                         "cannot deserialize $baseType because it does not define a field named $typeFieldName" | ||||
|                     ) | ||||
|                 } | ||||
|                 val label = labelJsonElement.asString | ||||
|                 val delegate = labelToDelegate[label] as TypeAdapter<R>? | ||||
|                 if (delegate == null) { | ||||
|                     Timber.tag("RuntimeTypeAdapter").e( | ||||
|                         "cannot deserialize $baseType subtype named $label; did you forget to register a subtype? $jsonElement" | ||||
|                     ) | ||||
|                     return null | ||||
|                 } | ||||
|                 return delegate.fromJsonTree(jsonElement) | ||||
|             } | ||||
| 
 | ||||
|             @Throws(IOException::class) | ||||
|             override fun write(out: JsonWriter, value: R) { | ||||
|                 val srcType: Class<*> = value::class.java.javaClass | ||||
|                 val delegate = | ||||
|                     subtypeToDelegate[srcType] as TypeAdapter<R?>? ?: throw JsonParseException( | ||||
|                         "cannot serialize ${srcType.name}; did you forget to register a subtype?" | ||||
|                     ) | ||||
| 
 | ||||
|                 val jsonObject = delegate.toJsonTree(value).asJsonObject | ||||
|                 if (maintainType) { | ||||
|                     Streams.write(jsonObject, out) | ||||
|                     return | ||||
|                 } | ||||
| 
 | ||||
|                 if (jsonObject.has(typeFieldName)) { | ||||
|                     throw JsonParseException( | ||||
|                         "cannot serialize ${srcType.name} because it already defines a field named $typeFieldName" | ||||
|                     ) | ||||
|                 } | ||||
|                 val clone = JsonObject() | ||||
|                 val label = subtypeToLabel[srcType] | ||||
|                 clone.add(typeFieldName, JsonPrimitive(label)) | ||||
|                 for ((key, value1) in jsonObject.entrySet()) { | ||||
|                     clone.add(key, value1) | ||||
|                 } | ||||
|                 Streams.write(clone, out) | ||||
|             } | ||||
|         }.nullSafe() | ||||
|     } | ||||
| 
 | ||||
|     companion object { | ||||
|         /** | ||||
|          * Creates a new runtime type adapter using for `baseType` using `typeFieldName` as the type field name. Type field names are case sensitive. | ||||
|          * `maintainType` flag decide if the type will be stored in pojo or not. | ||||
|          */ | ||||
|         fun <T> of( | ||||
|             baseType: Class<T>, | ||||
|             typeFieldName: String, | ||||
|             maintainType: Boolean | ||||
|         ): RuntimeTypeAdapterFactory<T> = | ||||
|             RuntimeTypeAdapterFactory(baseType, typeFieldName, maintainType) | ||||
| 
 | ||||
|         /** | ||||
|          * Creates a new runtime type adapter using for `baseType` using `typeFieldName` as the type field name. Type field names are case sensitive. | ||||
|          */ | ||||
|         fun <T> of(baseType: Class<T>, typeFieldName: String): RuntimeTypeAdapterFactory<T> = | ||||
|             RuntimeTypeAdapterFactory(baseType, typeFieldName, false) | ||||
| 
 | ||||
|         /** | ||||
|          * Creates a new runtime type adapter for `baseType` using `"type"` as | ||||
|          * the type field name. | ||||
|          */ | ||||
|         fun <T> of(baseType: Class<T>): RuntimeTypeAdapterFactory<T> = | ||||
|             RuntimeTypeAdapterFactory(baseType, "type", false) | ||||
|     } | ||||
| } | ||||
|  | @ -1,22 +0,0 @@ | |||
| package fr.free.nrw.commons.wikidata.json; | ||||
| 
 | ||||
| import android.net.Uri; | ||||
| 
 | ||||
| import com.google.gson.TypeAdapter; | ||||
| import com.google.gson.stream.JsonReader; | ||||
| import com.google.gson.stream.JsonWriter; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| public class UriTypeAdapter extends TypeAdapter<Uri> { | ||||
|     @Override | ||||
|     public void write(JsonWriter out, Uri value) throws IOException { | ||||
|         out.value(value.toString()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Uri read(JsonReader in) throws IOException { | ||||
|         String url = in.nextString(); | ||||
|         return Uri.parse(url); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,19 @@ | |||
| package fr.free.nrw.commons.wikidata.json | ||||
| 
 | ||||
| import android.net.Uri | ||||
| import com.google.gson.TypeAdapter | ||||
| import com.google.gson.stream.JsonReader | ||||
| import com.google.gson.stream.JsonWriter | ||||
| import java.io.IOException | ||||
| 
 | ||||
| class UriTypeAdapter : TypeAdapter<Uri>() { | ||||
|     @Throws(IOException::class) | ||||
|     override fun write(out: JsonWriter, value: Uri) { | ||||
|         out.value(value.toString()) | ||||
|     } | ||||
| 
 | ||||
|     @Throws(IOException::class) | ||||
|     override fun read(reader: JsonReader): Uri { | ||||
|         return Uri.parse(reader.nextString()) | ||||
|     } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Paul Hawke
						Paul Hawke