Convert RuntimeJsonAdapterFactory to Kotlin
This commit is contained in:
parent
73270476d2
commit
7514edb399
|
@ -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<>();
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the subtype that can be created based on the label. When an unknown type is found
|
||||||
|
* during encoding an [IllegalArgumentException] will be thrown. When an unknown label
|
||||||
|
* is found during decoding a [JsonDataException] will be thrown.
|
||||||
|
*/
|
||||||
|
fun registerSubtype(subtype: Class<out T>?, label: String?): RuntimeJsonAdapterFactory<T> {
|
||||||
|
if (subtype == null) throw NullPointerException("subtype == null")
|
||||||
|
if (label == null) throw NullPointerException("label == null")
|
||||||
|
require(!(labelToType.containsKey(label) || labelToType.containsValue(subtype))) { "Subtypes and labels must be unique." }
|
||||||
|
labelToType[label] = subtype
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create(type: Type, annotations: Set<Annotation?>, moshi: Moshi): JsonAdapter<*>? {
|
||||||
|
if (Types.getRawType(type) != baseType || !annotations.isEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val size = labelToType.size
|
||||||
|
val labelToAdapter: MutableMap<String, JsonAdapter<Any>> = LinkedHashMap(size)
|
||||||
|
val typeToLabel: MutableMap<Type, String> = LinkedHashMap(size)
|
||||||
|
for ((label, typeValue) in labelToType) {
|
||||||
|
typeToLabel[typeValue] = label
|
||||||
|
labelToAdapter[label] = moshi.adapter(typeValue)
|
||||||
|
}
|
||||||
|
val fallbackAdapter = moshi.adapter<Any>(fallbackType)
|
||||||
|
val objectJsonAdapter = moshi.adapter(Any::class.java)
|
||||||
|
return RuntimeJsonAdapter(labelKey, labelToAdapter, typeToLabel,
|
||||||
|
objectJsonAdapter, fallbackAdapter).nullSafe()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class RuntimeJsonAdapter(val labelKey: String, val labelToAdapter: Map<String, JsonAdapter<Any>>,
|
||||||
|
val typeToLabel: Map<Type, String>, val objectJsonAdapter: JsonAdapter<Any>,
|
||||||
|
val fallbackAdapter: JsonAdapter<Any>) : JsonAdapter<Any?>() {
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun fromJson(reader: JsonReader): Any? {
|
||||||
|
val peekedToken = reader.peek()
|
||||||
|
if (peekedToken != JsonReader.Token.BEGIN_OBJECT) {
|
||||||
|
throw JsonDataException("Expected BEGIN_OBJECT but was " + peekedToken
|
||||||
|
+ " at path " + reader.path)
|
||||||
|
}
|
||||||
|
val jsonValue = reader.readJsonValue()
|
||||||
|
val jsonObject = jsonValue as Map<String, Any>?
|
||||||
|
val label = jsonObject!![labelKey] as? String ?: return null
|
||||||
|
val adapter = labelToAdapter[label] ?: return fallbackAdapter.fromJsonValue(jsonValue)
|
||||||
|
return adapter.fromJsonValue(jsonValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun toJson(writer: JsonWriter, value: Any?) {
|
||||||
|
val type: Class<*> = value!!.javaClass
|
||||||
|
val label = typeToLabel[type]
|
||||||
|
?: throw IllegalArgumentException("Expected one of "
|
||||||
|
+ typeToLabel.keys
|
||||||
|
+ " but found "
|
||||||
|
+ value
|
||||||
|
+ ", a "
|
||||||
|
+ value.javaClass
|
||||||
|
+ ". Register this subtype.")
|
||||||
|
val adapter = labelToAdapter[label]!!
|
||||||
|
val jsonValue = adapter.toJsonValue(value) as Map<String, Any>?
|
||||||
|
val valueWithLabel: MutableMap<String, Any> = LinkedHashMap(1 + jsonValue!!.size)
|
||||||
|
valueWithLabel[labelKey] = label
|
||||||
|
valueWithLabel.putAll(jsonValue)
|
||||||
|
objectJsonAdapter.toJson(writer, valueWithLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "RuntimeJsonAdapter($labelKey)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
/**
|
/**
|
||||||
* @param baseType The base type for which this factory will create adapters. Cannot be 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
|
* @param labelKey The key in the JSON object whose value determines the type to which to map the
|
||||||
* JSON object.
|
* JSON object.
|
||||||
*/
|
*/
|
||||||
@CheckReturnValue
|
@CheckReturnValue
|
||||||
public static <T> RuntimeJsonAdapterFactory<T> of(Class<T> baseType, String labelKey, Class<? extends T> fallbackType) {
|
fun <T> of(baseType: Class<T>?, labelKey: String?, fallbackType: Class<out T>?): RuntimeJsonAdapterFactory<T> {
|
||||||
if (baseType == null) throw new NullPointerException("baseType == null");
|
if (baseType == null) throw NullPointerException("baseType == null")
|
||||||
if (labelKey == null) throw new NullPointerException("labelKey == null");
|
if (labelKey == null) throw NullPointerException("labelKey == null")
|
||||||
if (baseType == Object.class) {
|
require(baseType != Any::class.java) { "The base type must not be Object. Consider using a marker interface." }
|
||||||
throw new IllegalArgumentException(
|
return RuntimeJsonAdapterFactory(baseType, labelKey, fallbackType)
|
||||||
"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
|
|
||||||
* during encoding an {@linkplain IllegalArgumentException} will be thrown. When an unknown label
|
|
||||||
* is found during decoding a {@linkplain JsonDataException} will be thrown.
|
|
||||||
*/
|
|
||||||
public RuntimeJsonAdapterFactory<T> registerSubtype(Class<? extends T> subtype, String label) {
|
|
||||||
if (subtype == null) throw new NullPointerException("subtype == null");
|
|
||||||
if (label == null) throw new NullPointerException("label == null");
|
|
||||||
if (labelToType.containsKey(label) || labelToType.containsValue(subtype)) {
|
|
||||||
throw new IllegalArgumentException("Subtypes and labels must be unique.");
|
|
||||||
}
|
|
||||||
labelToType.put(label, subtype);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public JsonAdapter<?> create(Type type, Set<? extends Annotation> annotations, Moshi moshi) {
|
|
||||||
if (Types.getRawType(type) != baseType || !annotations.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int size = labelToType.size();
|
|
||||||
Map<String, JsonAdapter<Object>> labelToAdapter = new LinkedHashMap<>(size);
|
|
||||||
Map<Type, String> typeToLabel = new LinkedHashMap<>(size);
|
|
||||||
for (Map.Entry<String, Type> entry : labelToType.entrySet()) {
|
|
||||||
String label = entry.getKey();
|
|
||||||
Type typeValue = entry.getValue();
|
|
||||||
typeToLabel.put(typeValue, label);
|
|
||||||
labelToAdapter.put(label, moshi.adapter(typeValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
final JsonAdapter<Object> fallbackAdapter = moshi.adapter(fallbackType);
|
|
||||||
JsonAdapter<Object> objectJsonAdapter = moshi.adapter(Object.class);
|
|
||||||
|
|
||||||
return new RuntimeJsonAdapter(labelKey, labelToAdapter, typeToLabel,
|
|
||||||
objectJsonAdapter, fallbackAdapter).nullSafe();
|
|
||||||
}
|
|
||||||
|
|
||||||
static final class RuntimeJsonAdapter extends JsonAdapter<Object> {
|
|
||||||
final String labelKey;
|
|
||||||
final Map<String, JsonAdapter<Object>> labelToAdapter;
|
|
||||||
final Map<Type, String> typeToLabel;
|
|
||||||
final JsonAdapter<Object> objectJsonAdapter;
|
|
||||||
final JsonAdapter<Object> fallbackAdapter;
|
|
||||||
|
|
||||||
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) {
|
|
||||||
throw new JsonDataException("Expected BEGIN_OBJECT but was " + peekedToken
|
|
||||||
+ " at path " + reader.getPath());
|
|
||||||
}
|
|
||||||
Object jsonValue = reader.readJsonValue();
|
|
||||||
Map<String, Object> jsonObject = (Map<String, Object>) jsonValue;
|
|
||||||
Object label = jsonObject.get(labelKey);
|
|
||||||
if (!(label instanceof String)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonAdapter<Object> adapter = labelToAdapter.get(label);
|
|
||||||
if (adapter == null) {
|
|
||||||
return fallbackAdapter.fromJsonValue(jsonValue);
|
|
||||||
}
|
|
||||||
return adapter.fromJsonValue(jsonValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void toJson(JsonWriter writer, Object value) throws IOException {
|
|
||||||
Class<?> type = value.getClass();
|
|
||||||
String label = typeToLabel.get(type);
|
|
||||||
if (label == null) {
|
|
||||||
throw new IllegalArgumentException("Expected one of "
|
|
||||||
+ typeToLabel.keySet()
|
|
||||||
+ " but found "
|
|
||||||
+ value
|
|
||||||
+ ", a "
|
|
||||||
+ value.getClass()
|
|
||||||
+ ". Register this subtype.");
|
|
||||||
}
|
|
||||||
JsonAdapter<Object> adapter = labelToAdapter.get(label);
|
|
||||||
Map<String, Object> jsonValue = (Map<String, Object>) adapter.toJsonValue(value);
|
|
||||||
|
|
||||||
Map<String, Object> valueWithLabel = new LinkedHashMap<>(1 + jsonValue.size());
|
|
||||||
valueWithLabel.put(labelKey, label);
|
|
||||||
valueWithLabel.putAll(jsonValue);
|
|
||||||
objectJsonAdapter.toJson(writer, valueWithLabel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "RuntimeJsonAdapter(" + labelKey + ")";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue