Convert RuntimeJsonAdapterFactory to Kotlin

This commit is contained in:
Benoit Marty 2022-04-12 15:16:48 +02:00 committed by Benoit Marty
parent 73270476d2
commit 7514edb399
1 changed files with 86 additions and 128 deletions

View File

@ -13,157 +13,115 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.matrix.android.sdk.internal.network.parsing
package org.matrix.android.sdk.internal.network.parsing; import com.squareup.moshi.JsonAdapter
import org.matrix.android.sdk.internal.network.parsing.RuntimeJsonAdapterFactory
import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.Moshi
import com.squareup.moshi.JsonDataException; import org.matrix.android.sdk.internal.network.parsing.RuntimeJsonAdapterFactory.RuntimeJsonAdapter
import com.squareup.moshi.JsonReader; import kotlin.Throws
import com.squareup.moshi.JsonWriter; import com.squareup.moshi.JsonDataException
import com.squareup.moshi.Moshi; import com.squareup.moshi.JsonReader
import com.squareup.moshi.Types; import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Types
import java.io.IOException; import java.io.IOException
import java.lang.annotation.Annotation; import java.lang.IllegalArgumentException
import java.lang.reflect.Type; import java.lang.NullPointerException
import java.util.LinkedHashMap; import java.lang.reflect.Type
import java.util.Map; import java.util.LinkedHashMap
import java.util.Set; import javax.annotation.CheckReturnValue
import javax.annotation.CheckReturnValue;
/** /**
* A JsonAdapter factory for polymorphic types. This is useful when the type is not known before * A JsonAdapter factory for polymorphic types. This is useful when the type is not known before
* decoding the JSON. This factory's adapters expect JSON in the format of a JSON object with a * decoding the JSON. This factory's adapters expect JSON in the format of a JSON object with a
* key whose value is a label that determines the type to which to map the JSON object. * key whose value is a label that determines the type to which to map the JSON object.
*/ */
public final class RuntimeJsonAdapterFactory<T> implements JsonAdapter.Factory { class RuntimeJsonAdapterFactory<T> internal constructor(val baseType: Class<T>, val labelKey: String, val fallbackType: Class<*>?) : JsonAdapter.Factory {
final Class<T> baseType; val labelToType: MutableMap<String, Type> = LinkedHashMap()
final String labelKey;
final Class fallbackType;
final Map<String, Type> labelToType = new LinkedHashMap<>();
/**
* @param baseType The base type for which this factory will create adapters. Cannot be Object.
* @param labelKey The key in the JSON object whose value determines the type to which to map the
* JSON object.
*/
@CheckReturnValue
public static <T> RuntimeJsonAdapterFactory<T> of(Class<T> baseType, String labelKey, Class<? extends T> fallbackType) {
if (baseType == null) throw new NullPointerException("baseType == null");
if (labelKey == null) throw new NullPointerException("labelKey == null");
if (baseType == Object.class) {
throw new IllegalArgumentException(
"The base type must not be Object. Consider using a marker interface.");
}
return new RuntimeJsonAdapterFactory<>(baseType, labelKey, fallbackType);
}
RuntimeJsonAdapterFactory(Class<T> baseType, String labelKey, Class fallbackType) {
this.baseType = baseType;
this.labelKey = labelKey;
this.fallbackType = fallbackType;
}
/** /**
* Register the subtype that can be created based on the label. When an unknown type is found * Register the subtype that can be created based on the label. When an unknown type is found
* during encoding an {@linkplain IllegalArgumentException} will be thrown. When an unknown label * during encoding an [IllegalArgumentException] will be thrown. When an unknown label
* is found during decoding a {@linkplain JsonDataException} will be thrown. * is found during decoding a [JsonDataException] will be thrown.
*/ */
public RuntimeJsonAdapterFactory<T> registerSubtype(Class<? extends T> subtype, String label) { fun registerSubtype(subtype: Class<out T>?, label: String?): RuntimeJsonAdapterFactory<T> {
if (subtype == null) throw new NullPointerException("subtype == null"); if (subtype == null) throw NullPointerException("subtype == null")
if (label == null) throw new NullPointerException("label == null"); if (label == null) throw NullPointerException("label == null")
if (labelToType.containsKey(label) || labelToType.containsValue(subtype)) { require(!(labelToType.containsKey(label) || labelToType.containsValue(subtype))) { "Subtypes and labels must be unique." }
throw new IllegalArgumentException("Subtypes and labels must be unique."); labelToType[label] = subtype
} return this
labelToType.put(label, subtype);
return this;
} }
@Override override fun create(type: Type, annotations: Set<Annotation?>, moshi: Moshi): JsonAdapter<*>? {
public JsonAdapter<?> create(Type type, Set<? extends Annotation> annotations, Moshi moshi) {
if (Types.getRawType(type) != baseType || !annotations.isEmpty()) { if (Types.getRawType(type) != baseType || !annotations.isEmpty()) {
return null; return null
} }
int size = labelToType.size(); val size = labelToType.size
Map<String, JsonAdapter<Object>> labelToAdapter = new LinkedHashMap<>(size); val labelToAdapter: MutableMap<String, JsonAdapter<Any>> = LinkedHashMap(size)
Map<Type, String> typeToLabel = new LinkedHashMap<>(size); val typeToLabel: MutableMap<Type, String> = LinkedHashMap(size)
for (Map.Entry<String, Type> entry : labelToType.entrySet()) { for ((label, typeValue) in labelToType) {
String label = entry.getKey(); typeToLabel[typeValue] = label
Type typeValue = entry.getValue(); labelToAdapter[label] = moshi.adapter(typeValue)
typeToLabel.put(typeValue, label);
labelToAdapter.put(label, moshi.adapter(typeValue));
} }
val fallbackAdapter = moshi.adapter<Any>(fallbackType)
final JsonAdapter<Object> fallbackAdapter = moshi.adapter(fallbackType); val objectJsonAdapter = moshi.adapter(Any::class.java)
JsonAdapter<Object> objectJsonAdapter = moshi.adapter(Object.class); return RuntimeJsonAdapter(labelKey, labelToAdapter, typeToLabel,
objectJsonAdapter, fallbackAdapter).nullSafe()
return new RuntimeJsonAdapter(labelKey, labelToAdapter, typeToLabel,
objectJsonAdapter, fallbackAdapter).nullSafe();
} }
static final class RuntimeJsonAdapter extends JsonAdapter<Object> { internal class RuntimeJsonAdapter(val labelKey: String, val labelToAdapter: Map<String, JsonAdapter<Any>>,
final String labelKey; val typeToLabel: Map<Type, String>, val objectJsonAdapter: JsonAdapter<Any>,
final Map<String, JsonAdapter<Object>> labelToAdapter; val fallbackAdapter: JsonAdapter<Any>) : JsonAdapter<Any?>() {
final Map<Type, String> typeToLabel; @Throws(IOException::class)
final JsonAdapter<Object> objectJsonAdapter; override fun fromJson(reader: JsonReader): Any? {
final JsonAdapter<Object> fallbackAdapter; val peekedToken = reader.peek()
RuntimeJsonAdapter(String labelKey, Map<String, JsonAdapter<Object>> labelToAdapter,
Map<Type, String> typeToLabel, JsonAdapter<Object> objectJsonAdapter,
JsonAdapter<Object> fallbackAdapter) {
this.labelKey = labelKey;
this.labelToAdapter = labelToAdapter;
this.typeToLabel = typeToLabel;
this.objectJsonAdapter = objectJsonAdapter;
this.fallbackAdapter = fallbackAdapter;
}
@Override
public Object fromJson(JsonReader reader) throws IOException {
JsonReader.Token peekedToken = reader.peek();
if (peekedToken != JsonReader.Token.BEGIN_OBJECT) { if (peekedToken != JsonReader.Token.BEGIN_OBJECT) {
throw new JsonDataException("Expected BEGIN_OBJECT but was " + peekedToken throw JsonDataException("Expected BEGIN_OBJECT but was " + peekedToken
+ " at path " + reader.getPath()); + " at path " + reader.path)
} }
Object jsonValue = reader.readJsonValue(); val jsonValue = reader.readJsonValue()
Map<String, Object> jsonObject = (Map<String, Object>) jsonValue; val jsonObject = jsonValue as Map<String, Any>?
Object label = jsonObject.get(labelKey); val label = jsonObject!![labelKey] as? String ?: return null
if (!(label instanceof String)) { val adapter = labelToAdapter[label] ?: return fallbackAdapter.fromJsonValue(jsonValue)
return null; return adapter.fromJsonValue(jsonValue)
}
JsonAdapter<Object> adapter = labelToAdapter.get(label);
if (adapter == null) {
return fallbackAdapter.fromJsonValue(jsonValue);
}
return adapter.fromJsonValue(jsonValue);
} }
@Override @Throws(IOException::class)
public void toJson(JsonWriter writer, Object value) throws IOException { override fun toJson(writer: JsonWriter, value: Any?) {
Class<?> type = value.getClass(); val type: Class<*> = value!!.javaClass
String label = typeToLabel.get(type); val label = typeToLabel[type]
if (label == null) { ?: throw IllegalArgumentException("Expected one of "
throw new IllegalArgumentException("Expected one of " + typeToLabel.keys
+ typeToLabel.keySet() + " but found "
+ " but found " + value
+ value + ", a "
+ ", a " + value.javaClass
+ value.getClass() + ". Register this subtype.")
+ ". Register this subtype."); val adapter = labelToAdapter[label]!!
} val jsonValue = adapter.toJsonValue(value) as Map<String, Any>?
JsonAdapter<Object> adapter = labelToAdapter.get(label); val valueWithLabel: MutableMap<String, Any> = LinkedHashMap(1 + jsonValue!!.size)
Map<String, Object> jsonValue = (Map<String, Object>) adapter.toJsonValue(value); valueWithLabel[labelKey] = label
valueWithLabel.putAll(jsonValue)
Map<String, Object> valueWithLabel = new LinkedHashMap<>(1 + jsonValue.size()); objectJsonAdapter.toJson(writer, valueWithLabel)
valueWithLabel.put(labelKey, label);
valueWithLabel.putAll(jsonValue);
objectJsonAdapter.toJson(writer, valueWithLabel);
} }
@Override override fun toString(): String {
public String toString() { return "RuntimeJsonAdapter($labelKey)"
return "RuntimeJsonAdapter(" + labelKey + ")";
} }
} }
}
companion object {
/**
* @param baseType The base type for which this factory will create adapters. Cannot be Object.
* @param labelKey The key in the JSON object whose value determines the type to which to map the
* JSON object.
*/
@CheckReturnValue
fun <T> of(baseType: Class<T>?, labelKey: String?, fallbackType: Class<out T>?): RuntimeJsonAdapterFactory<T> {
if (baseType == null) throw NullPointerException("baseType == null")
if (labelKey == null) throw NullPointerException("labelKey == null")
require(baseType != Any::class.java) { "The base type must not be Object. Consider using a marker interface." }
return RuntimeJsonAdapterFactory(baseType, labelKey, fallbackType)
}
}
}