mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-29 13:53:54 +01:00
Convert RequiredFieldsCheckOnReadTypeAdapterFactory to kotlin
This commit is contained in:
parent
aa05bce593
commit
a5fb255bb3
4 changed files with 87 additions and 115 deletions
|
|
@ -1,94 +0,0 @@
|
|||
package fr.free.nrw.commons.wikidata.json;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.collection.ArraySet;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
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 fr.free.nrw.commons.wikidata.json.annotations.Required;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* TypeAdapterFactory that provides TypeAdapters that return null values for objects that are
|
||||
* missing fields annotated with @Required.
|
||||
*
|
||||
* BEWARE: This means that a List or other Collection of objects that have @Required fields can
|
||||
* contain null elements after deserialization!
|
||||
*
|
||||
* TODO: Handle null values in lists during deserialization, perhaps with a new @RequiredElements
|
||||
* annotation and another corresponding TypeAdapter(Factory).
|
||||
*/
|
||||
public class RequiredFieldsCheckOnReadTypeAdapterFactory implements TypeAdapterFactory {
|
||||
@Nullable @Override public final <T> TypeAdapter<T> create(@NonNull Gson gson, @NonNull TypeToken<T> typeToken) {
|
||||
Class<?> rawType = typeToken.getRawType();
|
||||
Set<Field> requiredFields = collectRequiredFields(rawType);
|
||||
|
||||
if (requiredFields.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
setFieldsAccessible(requiredFields, true);
|
||||
return new Adapter<>(gson.getDelegateAdapter(this, typeToken), requiredFields);
|
||||
}
|
||||
|
||||
@NonNull private Set<Field> collectRequiredFields(@NonNull Class<?> clazz) {
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
Set<Field> required = new ArraySet<>();
|
||||
for (Field field : fields) {
|
||||
if (field.isAnnotationPresent(Required.class)) {
|
||||
required.add(field);
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableSet(required);
|
||||
}
|
||||
|
||||
private void setFieldsAccessible(Iterable<Field> fields, boolean accessible) {
|
||||
for (Field field : fields) {
|
||||
field.setAccessible(accessible);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Adapter<T> extends TypeAdapter<T> {
|
||||
@NonNull private final TypeAdapter<T> delegate;
|
||||
@NonNull private final Set<Field> requiredFields;
|
||||
|
||||
private Adapter(@NonNull TypeAdapter<T> delegate, @NonNull final Set<Field> requiredFields) {
|
||||
this.delegate = delegate;
|
||||
this.requiredFields = requiredFields;
|
||||
}
|
||||
|
||||
@Override public void write(JsonWriter out, T value) throws IOException {
|
||||
delegate.write(out, value);
|
||||
}
|
||||
|
||||
@Override @Nullable public T read(JsonReader in) throws IOException {
|
||||
T deserialized = delegate.read(in);
|
||||
return allRequiredFieldsPresent(deserialized, requiredFields) ? deserialized : null;
|
||||
}
|
||||
|
||||
private boolean allRequiredFieldsPresent(@NonNull T deserialized,
|
||||
@NonNull Set<Field> required) {
|
||||
for (Field field : required) {
|
||||
try {
|
||||
if (field.get(deserialized) == null) {
|
||||
return false;
|
||||
}
|
||||
} catch (IllegalArgumentException | IllegalAccessException e) {
|
||||
throw new JsonParseException(e);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package fr.free.nrw.commons.wikidata.json
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonParseException
|
||||
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 fr.free.nrw.commons.wikidata.json.annotations.Required
|
||||
import java.io.IOException
|
||||
import java.lang.reflect.Field
|
||||
|
||||
/**
|
||||
* TypeAdapterFactory that provides TypeAdapters that return null values for objects that are
|
||||
* missing fields annotated with @Required.
|
||||
*
|
||||
* BEWARE: This means that a List or other Collection of objects that have @Required fields can
|
||||
* contain null elements after deserialization!
|
||||
*
|
||||
* TODO: Handle null values in lists during deserialization, perhaps with a new @RequiredElements
|
||||
* annotation and another corresponding TypeAdapter(Factory).
|
||||
*/
|
||||
class RequiredFieldsCheckOnReadTypeAdapterFactory : TypeAdapterFactory {
|
||||
override fun <T> create(gson: Gson, typeToken: TypeToken<T>): TypeAdapter<T>? {
|
||||
val rawType: Class<*> = typeToken.rawType
|
||||
val requiredFields = collectRequiredFields(rawType)
|
||||
|
||||
if (requiredFields.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
for (field in requiredFields) {
|
||||
field.isAccessible = true
|
||||
}
|
||||
|
||||
return Adapter(gson.getDelegateAdapter(this, typeToken), requiredFields)
|
||||
}
|
||||
|
||||
private fun collectRequiredFields(clazz: Class<*>): Set<Field> = buildSet {
|
||||
for (field in clazz.declaredFields) {
|
||||
if (field.isAnnotationPresent(Required::class.java)) add(field)
|
||||
}
|
||||
}
|
||||
|
||||
private class Adapter<T>(
|
||||
private val delegate: TypeAdapter<T>,
|
||||
private val requiredFields: Set<Field>
|
||||
) : 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? =
|
||||
if (allRequiredFieldsPresent(delegate.read(reader), requiredFields))
|
||||
delegate.read(reader)
|
||||
else
|
||||
null
|
||||
|
||||
fun allRequiredFieldsPresent(deserialized: T, required: Set<Field>): Boolean {
|
||||
for (field in required) {
|
||||
try {
|
||||
if (field[deserialized] == null) return false
|
||||
} catch (e: IllegalArgumentException) {
|
||||
throw JsonParseException(e)
|
||||
} catch (e: IllegalAccessException) {
|
||||
throw JsonParseException(e)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package fr.free.nrw.commons.wikidata.json.annotations;
|
||||
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
|
||||
/**
|
||||
* Annotate fields in Retrofit POJO classes with this to enforce their presence in order to return
|
||||
* an instantiated object.
|
||||
*
|
||||
* E.g.: @NonNull @Required private String title;
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(FIELD)
|
||||
public @interface Required {
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package fr.free.nrw.commons.wikidata.json.annotations
|
||||
|
||||
|
||||
/**
|
||||
* Annotate fields in Retrofit POJO classes with this to enforce their presence in order to return
|
||||
* an instantiated object.
|
||||
*
|
||||
* E.g.: @NonNull @Required private String title;
|
||||
*/
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.FIELD)
|
||||
annotation class Required
|
||||
Loading…
Add table
Add a link
Reference in a new issue