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