From bd4ffdf81dbc4a699b35ba764e1bbace86d68249 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 11 Dec 2021 15:27:02 -0500 Subject: [PATCH 01/20] Rename .java to .kt --- .../internal/{NonNullJsonAdapter.java => NonNullJsonAdapter.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename moshi/src/main/java/com/squareup/moshi/internal/{NonNullJsonAdapter.java => NonNullJsonAdapter.kt} (100%) diff --git a/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.java b/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt similarity index 100% rename from moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.java rename to moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt From fbd8485dd5fe8092ad4f9767fe9d82c734221c8f Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 11 Dec 2021 15:27:02 -0500 Subject: [PATCH 02/20] Convert NonNullJsonAdapter --- .../moshi/internal/NonNullJsonAdapter.kt | 47 ++++++------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt b/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt index c0de42afc..8c0337dfb 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt +++ b/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt @@ -13,48 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.squareup.moshi.internal; +package com.squareup.moshi.internal -import com.squareup.moshi.JsonAdapter; -import com.squareup.moshi.JsonDataException; -import com.squareup.moshi.JsonReader; -import com.squareup.moshi.JsonWriter; -import java.io.IOException; -import javax.annotation.Nullable; +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.JsonWriter -public final class NonNullJsonAdapter extends JsonAdapter { - - private final JsonAdapter delegate; - - public NonNullJsonAdapter(JsonAdapter delegate) { - this.delegate = delegate; - } - - public JsonAdapter delegate() { - return delegate; - } - - @Nullable - @Override - public T fromJson(JsonReader reader) throws IOException { - if (reader.peek() == JsonReader.Token.NULL) { - throw new JsonDataException("Unexpected null at " + reader.getPath()); +public class NonNullJsonAdapter(public val delegate: JsonAdapter) : JsonAdapter() { + override fun fromJson(reader: JsonReader): T { + return if (reader.peek() == JsonReader.Token.NULL) { + throw JsonDataException("Unexpected null at " + reader.path) } else { - return delegate.fromJson(reader); + delegate.fromJson(reader) } } - @Override - public void toJson(JsonWriter writer, @Nullable T value) throws IOException { + override fun toJson(writer: JsonWriter, value: T?) { if (value == null) { - throw new JsonDataException("Unexpected null at " + writer.getPath()); + throw JsonDataException("Unexpected null at " + writer.path) } else { - delegate.toJson(writer, value); + delegate.toJson(writer, value) } } - @Override - public String toString() { - return delegate + ".nonNull()"; - } + override fun toString(): String = "$delegate.nonNull()" } From 3a6f672282c567400f7109b10f336c37a0d8237c Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 11 Dec 2021 15:28:20 -0500 Subject: [PATCH 03/20] Rename .java to .kt --- .../internal/{NullSafeJsonAdapter.java => NullSafeJsonAdapter.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename moshi/src/main/java/com/squareup/moshi/internal/{NullSafeJsonAdapter.java => NullSafeJsonAdapter.kt} (100%) diff --git a/moshi/src/main/java/com/squareup/moshi/internal/NullSafeJsonAdapter.java b/moshi/src/main/java/com/squareup/moshi/internal/NullSafeJsonAdapter.kt similarity index 100% rename from moshi/src/main/java/com/squareup/moshi/internal/NullSafeJsonAdapter.java rename to moshi/src/main/java/com/squareup/moshi/internal/NullSafeJsonAdapter.kt From 6c91d24ed68df3f38457a2ee98ef1c41c63e7baf Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 11 Dec 2021 15:28:20 -0500 Subject: [PATCH 04/20] Convert NullSafeJsonAdapter --- .../moshi/internal/NullSafeJsonAdapter.kt | 44 ++++++------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/moshi/src/main/java/com/squareup/moshi/internal/NullSafeJsonAdapter.kt b/moshi/src/main/java/com/squareup/moshi/internal/NullSafeJsonAdapter.kt index 0ed2b85f2..8df2f20cd 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/NullSafeJsonAdapter.kt +++ b/moshi/src/main/java/com/squareup/moshi/internal/NullSafeJsonAdapter.kt @@ -13,46 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.squareup.moshi.internal; +package com.squareup.moshi.internal -import com.squareup.moshi.JsonAdapter; -import com.squareup.moshi.JsonReader; -import com.squareup.moshi.JsonWriter; -import java.io.IOException; -import javax.annotation.Nullable; +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter -public final class NullSafeJsonAdapter extends JsonAdapter { - - private final JsonAdapter delegate; - - public NullSafeJsonAdapter(JsonAdapter delegate) { - this.delegate = delegate; - } - - public JsonAdapter delegate() { - return delegate; - } - - @Override - public @Nullable T fromJson(JsonReader reader) throws IOException { - if (reader.peek() == JsonReader.Token.NULL) { - return reader.nextNull(); +public class NullSafeJsonAdapter(public val delegate: JsonAdapter) : JsonAdapter() { + override fun fromJson(reader: JsonReader): T? { + return if (reader.peek() == JsonReader.Token.NULL) { + reader.nextNull() } else { - return delegate.fromJson(reader); + delegate.fromJson(reader) } } - @Override - public void toJson(JsonWriter writer, @Nullable T value) throws IOException { + override fun toJson(writer: JsonWriter, value: T?) { if (value == null) { - writer.nullValue(); + writer.nullValue() } else { - delegate.toJson(writer, value); + delegate.toJson(writer, value) } } - @Override - public String toString() { - return delegate + ".nullSafe()"; - } + override fun toString(): String = "$delegate.nullSafe()" } From d4a5adb74b415295be0f5fbb773f66178d9d3632 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 11 Dec 2021 16:00:14 -0500 Subject: [PATCH 05/20] Fix missing null-check --- .../main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt b/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt index 8c0337dfb..0c5c8094d 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt +++ b/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt @@ -25,7 +25,7 @@ public class NonNullJsonAdapter(public val delegate: JsonAdapter) : JsonAd return if (reader.peek() == JsonReader.Token.NULL) { throw JsonDataException("Unexpected null at " + reader.path) } else { - delegate.fromJson(reader) + delegate.fromJson(reader)!! } } From 135a51140a42113e9957b379af3f0b670c7f4fb4 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 11 Dec 2021 16:03:42 -0500 Subject: [PATCH 06/20] Rename .java to .kt --- .../main/java/com/squareup/moshi/internal/{Util.java => Util.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename moshi/src/main/java/com/squareup/moshi/internal/{Util.java => Util.kt} (100%) diff --git a/moshi/src/main/java/com/squareup/moshi/internal/Util.java b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt similarity index 100% rename from moshi/src/main/java/com/squareup/moshi/internal/Util.java rename to moshi/src/main/java/com/squareup/moshi/internal/Util.kt From 0460a96326b93c93fe288b78cbadc5f699c1dcfb Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 11 Dec 2021 16:03:42 -0500 Subject: [PATCH 07/20] Convert Util (initial pass) --- .../java/com/squareup/moshi/internal/Util.kt | 1025 ++++++++--------- 1 file changed, 488 insertions(+), 537 deletions(-) diff --git a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt index d8edb504c..7f074e677 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt +++ b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt @@ -13,470 +13,579 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.squareup.moshi.internal; - -import static com.squareup.moshi.Types.arrayOf; -import static com.squareup.moshi.Types.subtypeOf; -import static com.squareup.moshi.Types.supertypeOf; - -import com.squareup.moshi.Json; -import com.squareup.moshi.JsonAdapter; -import com.squareup.moshi.JsonClass; -import com.squareup.moshi.JsonDataException; -import com.squareup.moshi.JsonQualifier; -import com.squareup.moshi.JsonReader; -import com.squareup.moshi.Moshi; -import com.squareup.moshi.Types; -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Constructor; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.GenericDeclaration; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.lang.reflect.WildcardType; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; -import javax.annotation.Nullable; - -public final class Util { - public static final Set NO_ANNOTATIONS = Collections.emptySet(); - public static final Type[] EMPTY_TYPE_ARRAY = new Type[] {}; - @Nullable public static final Class DEFAULT_CONSTRUCTOR_MARKER; - @Nullable private static final Class METADATA; - - /** A map from primitive types to their corresponding wrapper types. */ - private static final Map, Class> PRIMITIVE_TO_WRAPPER_TYPE; - - static { - Class metadata = null; - try { - //noinspection unchecked - metadata = (Class) Class.forName(getKotlinMetadataClassName()); - } catch (ClassNotFoundException ignored) { - } - METADATA = metadata; - - // We look up the constructor marker separately because Metadata might be (justifiably) - // stripped by R8/Proguard but the DefaultConstructorMarker is still present. - Class defaultConstructorMarker = null; - try { - defaultConstructorMarker = Class.forName("kotlin.jvm.internal.DefaultConstructorMarker"); - } catch (ClassNotFoundException ignored) { - } - DEFAULT_CONSTRUCTOR_MARKER = defaultConstructorMarker; - - Map, Class> primToWrap = new LinkedHashMap<>(16); - - primToWrap.put(boolean.class, Boolean.class); - primToWrap.put(byte.class, Byte.class); - primToWrap.put(char.class, Character.class); - primToWrap.put(double.class, Double.class); - primToWrap.put(float.class, Float.class); - primToWrap.put(int.class, Integer.class); - primToWrap.put(long.class, Long.class); - primToWrap.put(short.class, Short.class); - primToWrap.put(void.class, Void.class); - - PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap); +package com.squareup.moshi.internal + +import java.lang.ClassNotFoundException +import java.lang.Void +import java.lang.reflect.AnnotatedElement +import com.squareup.moshi.Json +import com.squareup.moshi.JsonQualifier +import java.lang.reflect.InvocationTargetException +import java.lang.RuntimeException +import java.lang.reflect.ParameterizedType +import java.lang.reflect.GenericArrayType +import com.squareup.moshi.Moshi +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonClass +import java.lang.NoSuchMethodException +import java.lang.IllegalAccessException +import java.lang.InstantiationException +import java.lang.IllegalStateException +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.Types +import com.squareup.moshi.asArrayType +import com.squareup.moshi.rawType +import java.lang.Error +import java.lang.StringBuilder +import java.lang.reflect.Constructor +import java.lang.reflect.Type +import java.lang.reflect.TypeVariable +import java.lang.reflect.WildcardType +import java.util.Collections +import java.util.LinkedHashSet + +@Suppress("unused", "MemberVisibilityCanBePrivate") +public object Util { + @JvmField public val NO_ANNOTATIONS: Set = emptySet() + @JvmField public val EMPTY_TYPE_ARRAY: Array = arrayOf() + + @Suppress("UNCHECKED_CAST") + private val METADATA: Class? = try { + Class.forName(kotlinMetadataClassName) as Class + } catch (ignored: ClassNotFoundException) { + null + } + + // We look up the constructor marker separately because Metadata might be (justifiably) + // stripped by R8/Proguard but the DefaultConstructorMarker is still present. + public val DEFAULT_CONSTRUCTOR_MARKER: Class<*>? = try { + Class.forName("kotlin.jvm.internal.DefaultConstructorMarker") + } catch (ignored: ClassNotFoundException) { + null } - // Extracted as a method with a keep rule to prevent R8 from keeping Kotlin Metada - private static String getKotlinMetadataClassName() { - return "kotlin.Metadata"; + /** A map from primitive types to their corresponding wrapper types. */ + private val PRIMITIVE_TO_WRAPPER_TYPE: Map, Class<*>> = buildMap(16) { + put(Boolean::class.javaPrimitiveType!!, Boolean::class.java) + put(Byte::class.javaPrimitiveType!!, Byte::class.java) + put(Char::class.javaPrimitiveType!!, Char::class.java) + put(Double::class.javaPrimitiveType!!, Double::class.java) + put(Float::class.javaPrimitiveType!!, Float::class.java) + put(Int::class.javaPrimitiveType!!, Int::class.java) + put(Long::class.javaPrimitiveType!!, Long::class.java) + put(Short::class.javaPrimitiveType!!, Short::class.java) + put(Void.TYPE, Void::class.java) } - private Util() {} + // Extracted as a method with a keep rule to prevent R8 from keeping Kotlin Metadata + private val kotlinMetadataClassName: String + get() = "kotlin.Metadata" - public static String jsonName(String declaredName, AnnotatedElement element) { - return jsonName(declaredName, element.getAnnotation(Json.class)); + @JvmStatic + public fun jsonName(declaredName: String, element: AnnotatedElement): String { + return jsonName(declaredName, element.getAnnotation(Json::class.java)) } - public static String jsonName(String declaredName, @Nullable Json annotation) { - if (annotation == null) return declaredName; - String annotationName = annotation.name(); - return Json.UNSET_NAME.equals(annotationName) ? declaredName : annotationName; + @JvmStatic + public fun jsonName(declaredName: String, annotation: Json?): String { + if (annotation == null) return declaredName + val annotationName: String = annotation.name + return if (Json.UNSET_NAME == annotationName) declaredName else annotationName } - public static boolean typesMatch(Type pattern, Type candidate) { + @JvmStatic + public fun typesMatch(pattern: Type, candidate: Type): Boolean { // TODO: permit raw types (like Set.class) to match non-raw candidates (like Set). - return Types.equals(pattern, candidate); + return Types.equals(pattern, candidate) } - public static Set jsonAnnotations(AnnotatedElement annotatedElement) { - return jsonAnnotations(annotatedElement.getAnnotations()); + @JvmStatic + public fun jsonAnnotations(annotatedElement: AnnotatedElement): Set { + return jsonAnnotations(annotatedElement.annotations) } - public static Set jsonAnnotations(Annotation[] annotations) { - Set result = null; - for (Annotation annotation : annotations) { - if (annotation.annotationType().isAnnotationPresent(JsonQualifier.class)) { - if (result == null) result = new LinkedHashSet<>(); - result.add(annotation); + @JvmStatic + public fun jsonAnnotations(annotations: Array): Set { + var result: MutableSet? = null + for (annotation in annotations) { + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") + if ((annotation as java.lang.annotation.Annotation).annotationType().isAnnotationPresent(JsonQualifier::class.java)) { + if (result == null) result = LinkedHashSet() + result.add(annotation) } } - return result != null ? Collections.unmodifiableSet(result) : Util.NO_ANNOTATIONS; + return if (result != null) Collections.unmodifiableSet(result) else NO_ANNOTATIONS } - public static boolean isAnnotationPresent( - Set annotations, Class annotationClass) { - if (annotations.isEmpty()) return false; // Save an iterator in the common case. - for (Annotation annotation : annotations) { - if (annotation.annotationType() == annotationClass) return true; + @JvmStatic + public fun isAnnotationPresent( + annotations: Set, + annotationClass: Class + ): Boolean { + if (annotations.isEmpty()) return false // Save an iterator in the common case. + for (annotation in annotations) { + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") + if ((annotation as java.lang.annotation.Annotation).annotationType() == annotationClass) return true } - return false; + return false } - /** Returns true if {@code annotations} has any annotation whose simple name is Nullable. */ - public static boolean hasNullable(Annotation[] annotations) { - for (Annotation annotation : annotations) { - if (annotation.annotationType().getSimpleName().equals("Nullable")) { - return true; + /** Returns true if `annotations` has any annotation whose simple name is Nullable. */ + @JvmStatic + public fun hasNullable(annotations: Array): Boolean { + for (annotation in annotations) { + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") + if ((annotation as java.lang.annotation.Annotation).annotationType().simpleName == "Nullable") { + return true } } - return false; + return false } /** - * Returns true if {@code rawType} is built in. We don't reflect on private fields of platform + * Returns true if `rawType` is built in. We don't reflect on private fields of platform * types because they're unspecified and likely to be different on Java vs. Android. */ - public static boolean isPlatformType(Class rawType) { - String name = rawType.getName(); - return name.startsWith("android.") - || name.startsWith("androidx.") - || name.startsWith("java.") - || name.startsWith("javax.") - || name.startsWith("kotlin.") - || name.startsWith("kotlinx.") - || name.startsWith("scala."); - } - - /** Throws the cause of {@code e}, wrapping it if it is checked. */ - public static RuntimeException rethrowCause(InvocationTargetException e) { - Throwable cause = e.getTargetException(); - if (cause instanceof RuntimeException) throw (RuntimeException) cause; - if (cause instanceof Error) throw (Error) cause; - throw new RuntimeException(cause); + @JvmStatic + public fun isPlatformType(rawType: Class<*>): Boolean { + val name = rawType.name + return (name.startsWith("android.") + || name.startsWith("androidx.") + || name.startsWith("java.") + || name.startsWith("javax.") + || name.startsWith("kotlin.") + || name.startsWith("kotlinx.") + || name.startsWith("scala.")) + } + + /** Throws the cause of `e`, wrapping it if it is checked. */ + @JvmStatic + public fun rethrowCause(e: InvocationTargetException): RuntimeException { + val cause = e.targetException + if (cause is RuntimeException) throw cause + if (cause is Error) throw cause + throw RuntimeException(cause) } /** - * Returns a type that is functionally equal but not necessarily equal according to {@link - * Object#equals(Object) Object.equals()}. + * Returns a type that is functionally equal but not necessarily equal according to [[Object.equals()]][Object.equals]. */ - public static Type canonicalize(Type type) { - if (type instanceof Class) { - Class c = (Class) type; - return c.isArray() ? new GenericArrayTypeImpl(canonicalize(c.getComponentType())) : c; - - } else if (type instanceof ParameterizedType) { - if (type instanceof ParameterizedTypeImpl) return type; - ParameterizedType p = (ParameterizedType) type; - return new ParameterizedTypeImpl( - p.getOwnerType(), p.getRawType(), p.getActualTypeArguments()); - - } else if (type instanceof GenericArrayType) { - if (type instanceof GenericArrayTypeImpl) return type; - GenericArrayType g = (GenericArrayType) type; - return new GenericArrayTypeImpl(g.getGenericComponentType()); - - } else if (type instanceof WildcardType) { - if (type instanceof WildcardTypeImpl) return type; - WildcardType w = (WildcardType) type; - return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds()); - - } else { - return type; // This type is unsupported! + @JvmStatic + public fun canonicalize(type: Type): Type { + return when (type) { + is Class<*> -> { + if (type.isArray) GenericArrayTypeImpl(canonicalize(type.componentType)) else type + } + is ParameterizedType -> { + if (type is ParameterizedTypeImpl) return type + ParameterizedTypeImpl(type.ownerType, type.rawType, *type.actualTypeArguments) + } + is GenericArrayType -> { + if (type is GenericArrayTypeImpl) return type + GenericArrayTypeImpl(type.genericComponentType) + } + is WildcardType -> { + if (type is WildcardTypeImpl) return type + WildcardTypeImpl(type.upperBounds, type.lowerBounds) + } + else -> { + type // This type is unsupported! + } } } /** If type is a "? extends X" wildcard, returns X; otherwise returns type unchanged. */ - public static Type removeSubtypeWildcard(Type type) { - if (!(type instanceof WildcardType)) return type; - - Type[] lowerBounds = ((WildcardType) type).getLowerBounds(); - if (lowerBounds.length != 0) return type; - - Type[] upperBounds = ((WildcardType) type).getUpperBounds(); - if (upperBounds.length != 1) throw new IllegalArgumentException(); - - return upperBounds[0]; - } - - public static Type resolve(Type context, Class contextRawType, Type toResolve) { - return resolve(context, contextRawType, toResolve, new LinkedHashSet>()); - } - - private static Type resolve( - Type context, - Class contextRawType, - Type toResolve, - Collection> visitedTypeVariables) { + @JvmStatic + public fun removeSubtypeWildcard(type: Type): Type { + if (type !is WildcardType) return type + val lowerBounds = type.lowerBounds + if (lowerBounds.isNotEmpty()) return type + val upperBounds = type.upperBounds + require(upperBounds.size == 1) + return upperBounds[0] + } + + @JvmStatic + public fun resolve(context: Type, contextRawType: Class<*>, toResolve: Type): Type { + return resolve(context, contextRawType, toResolve, LinkedHashSet()) + } + + private fun resolve( + context: Type, + contextRawType: Class<*>, + toResolveInitial: Type, + visitedTypeVariables: MutableCollection> + ): Type { // This implementation is made a little more complicated in an attempt to avoid object-creation. + var toResolve = toResolveInitial while (true) { - if (toResolve instanceof TypeVariable) { - TypeVariable typeVariable = (TypeVariable) toResolve; - if (visitedTypeVariables.contains(typeVariable)) { - // cannot reduce due to infinite recursion - return toResolve; - } else { - visitedTypeVariables.add(typeVariable); + when { + toResolve is TypeVariable<*> -> { + val typeVariable = toResolve + if (typeVariable in visitedTypeVariables) { + // cannot reduce due to infinite recursion + return toResolve + } else { + visitedTypeVariables += typeVariable + } + toResolve = resolveTypeVariable(context, contextRawType, typeVariable) + if (toResolve === typeVariable) return toResolve + } + toResolve is Class<*> && toResolve.isArray -> { + val original = toResolve + val componentType: Type = original.componentType + val newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables) + return if (componentType === newComponentType) original else newComponentType.asArrayType() + } + toResolve is GenericArrayType -> { + val original = toResolve + val componentType = original.genericComponentType + val newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables) + return if (componentType === newComponentType) original else newComponentType.asArrayType() } - toResolve = resolveTypeVariable(context, contextRawType, typeVariable); - if (toResolve == typeVariable) return toResolve; - - } else if (toResolve instanceof Class && ((Class) toResolve).isArray()) { - Class original = (Class) toResolve; - Type componentType = original.getComponentType(); - Type newComponentType = - resolve(context, contextRawType, componentType, visitedTypeVariables); - return componentType == newComponentType ? original : arrayOf(newComponentType); - - } else if (toResolve instanceof GenericArrayType) { - GenericArrayType original = (GenericArrayType) toResolve; - Type componentType = original.getGenericComponentType(); - Type newComponentType = - resolve(context, contextRawType, componentType, visitedTypeVariables); - return componentType == newComponentType ? original : arrayOf(newComponentType); - - } else if (toResolve instanceof ParameterizedType) { - ParameterizedType original = (ParameterizedType) toResolve; - Type ownerType = original.getOwnerType(); - Type newOwnerType = resolve(context, contextRawType, ownerType, visitedTypeVariables); - boolean changed = newOwnerType != ownerType; - - Type[] args = original.getActualTypeArguments(); - for (int t = 0, length = args.length; t < length; t++) { - Type resolvedTypeArgument = - resolve(context, contextRawType, args[t], visitedTypeVariables); - if (resolvedTypeArgument != args[t]) { - if (!changed) { - args = args.clone(); - changed = true; + toResolve is ParameterizedType -> { + val original = toResolve + val ownerType: Type? = original.ownerType + val newOwnerType = ownerType?.let { + resolve(context, contextRawType, ownerType, visitedTypeVariables) + } + var changed = newOwnerType !== ownerType + var args = original.actualTypeArguments + var t = 0 + val length = args.size + while (t < length) { + val resolvedTypeArgument = resolve(context, contextRawType, args[t], visitedTypeVariables) + if (resolvedTypeArgument !== args[t]) { + if (!changed) { + args = args.clone() + changed = true + } + args[t] = resolvedTypeArgument } - args[t] = resolvedTypeArgument; + t++ } + return if (changed) ParameterizedTypeImpl(newOwnerType, original.rawType, *args) else original } - - return changed - ? new ParameterizedTypeImpl(newOwnerType, original.getRawType(), args) - : original; - - } else if (toResolve instanceof WildcardType) { - WildcardType original = (WildcardType) toResolve; - Type[] originalLowerBound = original.getLowerBounds(); - Type[] originalUpperBound = original.getUpperBounds(); - - if (originalLowerBound.length == 1) { - Type lowerBound = - resolve(context, contextRawType, originalLowerBound[0], visitedTypeVariables); - if (lowerBound != originalLowerBound[0]) { - return supertypeOf(lowerBound); - } - } else if (originalUpperBound.length == 1) { - Type upperBound = - resolve(context, contextRawType, originalUpperBound[0], visitedTypeVariables); - if (upperBound != originalUpperBound[0]) { - return subtypeOf(upperBound); + toResolve is WildcardType -> { + val original = toResolve + val originalLowerBound = original.lowerBounds + val originalUpperBound = original.upperBounds + if (originalLowerBound.size == 1) { + val lowerBound = resolve(context, contextRawType, originalLowerBound[0], visitedTypeVariables) + if (lowerBound !== originalLowerBound[0]) { + return Types.supertypeOf(lowerBound) + } + } else if (originalUpperBound.size == 1) { + val upperBound = resolve(context, contextRawType, originalUpperBound[0], visitedTypeVariables) + if (upperBound !== originalUpperBound[0]) { + return Types.subtypeOf(upperBound) + } } + return original } - return original; - - } else { - return toResolve; + else -> return toResolve } } } - static Type resolveTypeVariable(Type context, Class contextRawType, TypeVariable unknown) { - Class declaredByRaw = declaringClassOf(unknown); + @JvmStatic + public fun resolveTypeVariable(context: Type, contextRawType: Class<*>, unknown: TypeVariable<*>): Type { + val declaredByRaw = declaringClassOf(unknown) ?: return unknown // We can't reduce this further. - if (declaredByRaw == null) return unknown; - - Type declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw); - if (declaredBy instanceof ParameterizedType) { - int index = indexOf(declaredByRaw.getTypeParameters(), unknown); - return ((ParameterizedType) declaredBy).getActualTypeArguments()[index]; + val declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw) + if (declaredBy is ParameterizedType) { + val index = declaredByRaw.typeParameters.indexOf(unknown) + return declaredBy.actualTypeArguments[index] } - - return unknown; + return unknown } /** - * Returns the generic supertype for {@code supertype}. For example, given a class {@code - * IntegerSet}, the result for when supertype is {@code Set.class} is {@code Set} and the - * result when the supertype is {@code Collection.class} is {@code Collection}. + * Returns the generic supertype for `supertype`. For example, given a class `IntegerSet`, the result for when supertype is `Set.class` is `Set` and the + * result when the supertype is `Collection.class` is `Collection`. */ - public static Type getGenericSupertype(Type context, Class rawType, Class toResolve) { + @JvmStatic + public fun getGenericSupertype(context: Type, rawTypeInitial: Class<*>, toResolve: Class<*>): Type { + var rawType = rawTypeInitial if (toResolve == rawType) { - return context; + return context } // we skip searching through interfaces if unknown is an interface - if (toResolve.isInterface()) { - Class[] interfaces = rawType.getInterfaces(); - for (int i = 0, length = interfaces.length; i < length; i++) { + if (toResolve.isInterface) { + val interfaces = rawType.interfaces + var i = 0 + val length = interfaces.size + while (i < length) { if (interfaces[i] == toResolve) { - return rawType.getGenericInterfaces()[i]; + return rawType.genericInterfaces[i] } else if (toResolve.isAssignableFrom(interfaces[i])) { - return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], toResolve); + return getGenericSupertype(rawType.genericInterfaces[i], interfaces[i], toResolve) } + i++ } } // check our supertypes - if (!rawType.isInterface()) { - while (rawType != Object.class) { - Class rawSupertype = rawType.getSuperclass(); + if (!rawType.isInterface) { + while (rawType != Any::class.java) { + val rawSupertype = rawType.superclass if (rawSupertype == toResolve) { - return rawType.getGenericSuperclass(); + return rawType.genericSuperclass } else if (toResolve.isAssignableFrom(rawSupertype)) { - return getGenericSupertype(rawType.getGenericSuperclass(), rawSupertype, toResolve); + return getGenericSupertype(rawType.genericSuperclass, rawSupertype, toResolve) } - rawType = rawSupertype; + rawType = rawSupertype } } // we can't resolve this further - return toResolve; + return toResolve } - static int hashCodeOrZero(@Nullable Object o) { - return o != null ? o.hashCode() : 0; + @JvmStatic + public val Any?.hashCodeOrZero: Int + get() { + return this?.hashCode() ?: 0 + } + + @JvmStatic + public fun typeToString(type: Type): String { + return if (type is Class<*>) type.name else type.toString() } - static String typeToString(Type type) { - return type instanceof Class ? ((Class) type).getName() : type.toString(); + /** + * Returns the declaring class of `typeVariable`, or `null` if it was not declared by + * a class. + */ + @JvmStatic + public fun declaringClassOf(typeVariable: TypeVariable<*>): Class<*>? { + val genericDeclaration = typeVariable.genericDeclaration + return if (genericDeclaration is Class<*>) genericDeclaration else null } - static int indexOf(Object[] array, Object toFind) { - for (int i = 0; i < array.length; i++) { - if (toFind.equals(array[i])) return i; + @JvmStatic + public fun checkNotPrimitive(type: Type) { + require(!(type is Class<*> && type.isPrimitive)) { "Unexpected primitive $type. Use the boxed type." } + } + + @JvmStatic + public fun typeAnnotatedWithAnnotations( + type: Type, + annotations: Set + ): String { + return type.toString() + if (annotations.isEmpty()) " (with no annotations)" else " annotated $annotations" + } + + /** + * Loads the generated JsonAdapter for classes annotated [JsonClass]. This works because it + * uses the same naming conventions as `JsonClassCodeGenProcessor`. + */ + @JvmStatic + public fun generatedAdapter( + moshi: Moshi, + type: Type, + rawType: Class<*> + ): JsonAdapter<*>? { + val jsonClass = rawType.getAnnotation(JsonClass::class.java) + if (jsonClass == null || !jsonClass.generateAdapter) { + return null + } + val adapterClassName = Types.generatedJsonAdapterName(rawType.name) + var possiblyFoundAdapter: Class>? = null + return try { + @Suppress("UNCHECKED_CAST") + val adapterClass = Class.forName(adapterClassName, true, rawType.classLoader) as Class> + possiblyFoundAdapter = adapterClass + var constructor: Constructor> + var args: Array + if (type is ParameterizedType) { + val typeArgs = type.actualTypeArguments + try { + // Common case first + constructor = adapterClass.getDeclaredConstructor(Moshi::class.java, Array::class.java) + args = arrayOf(moshi, typeArgs) + } catch (e: NoSuchMethodException) { + constructor = adapterClass.getDeclaredConstructor(Array::class.java) + args = arrayOf(typeArgs) + } + } else { + try { + // Common case first + constructor = adapterClass.getDeclaredConstructor(Moshi::class.java) + args = arrayOf(moshi) + } catch (e: NoSuchMethodException) { + constructor = adapterClass.getDeclaredConstructor() + args = emptyArray() + } + } + constructor.isAccessible = true + constructor.newInstance(*args).nullSafe() + } catch (e: ClassNotFoundException) { + throw RuntimeException("Failed to find the generated JsonAdapter class for $type", e) + } catch (e: NoSuchMethodException) { + if (possiblyFoundAdapter != null && type !is ParameterizedType && possiblyFoundAdapter.typeParameters.isNotEmpty()) { + throw RuntimeException( + "Failed to find the generated JsonAdapter constructor for '" + + type + + "'. Suspiciously, the type was not parameterized but the target class '" + + possiblyFoundAdapter.canonicalName + + "' is generic. Consider using " + + "Types#newParameterizedType() to define these missing type variables.", + e + ) + } else { + throw RuntimeException( + "Failed to find the generated JsonAdapter constructor for $type", e + ) + } + } catch (e: IllegalAccessException) { + throw RuntimeException("Failed to access the generated JsonAdapter for $type", e) + } catch (e: InstantiationException) { + throw RuntimeException("Failed to instantiate the generated JsonAdapter for $type", e) + } catch (e: InvocationTargetException) { + throw rethrowCause(e) } - throw new NoSuchElementException(); + } + + @JvmStatic + public fun isKotlin(targetClass: Class<*>): Boolean { + return METADATA != null && targetClass.isAnnotationPresent(METADATA) } /** - * Returns the declaring class of {@code typeVariable}, or {@code null} if it was not declared by - * a class. + * Reflectively looks up the defaults constructor of a kotlin class. + * + * @param targetClass the target kotlin class to instantiate. + * @param T the type of `targetClass`. + * @return the instantiated `targetClass` instance. */ - static @Nullable Class declaringClassOf(TypeVariable typeVariable) { - GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration(); - return genericDeclaration instanceof Class ? (Class) genericDeclaration : null; + @JvmStatic + public fun lookupDefaultsConstructor(targetClass: Class): Constructor { + checkNotNull(DEFAULT_CONSTRUCTOR_MARKER) { + "DefaultConstructorMarker not on classpath. Make sure the Kotlin stdlib is on the classpath." + } + val defaultConstructor = findConstructor(targetClass) + defaultConstructor.isAccessible = true + return defaultConstructor + } + + private fun findConstructor(targetClass: Class): Constructor { + for (constructor in targetClass.declaredConstructors) { + val paramTypes = constructor.parameterTypes + if (paramTypes.isNotEmpty() && paramTypes[paramTypes.size - 1] == DEFAULT_CONSTRUCTOR_MARKER) { + @Suppress("UNCHECKED_CAST") + return constructor as Constructor + } + } + throw IllegalStateException("No defaults constructor found for $targetClass") } - static void checkNotPrimitive(Type type) { - if ((type instanceof Class) && ((Class) type).isPrimitive()) { - throw new IllegalArgumentException("Unexpected primitive " + type + ". Use the boxed type."); + @JvmStatic + public fun missingProperty( + propertyName: String, jsonName: String, reader: JsonReader + ): JsonDataException { + val path = reader.path + val message = if (jsonName == propertyName) { + "Required value '$propertyName' missing at $path" + } else { + "Required value '$propertyName' (JSON name '$jsonName') missing at $path" } + return JsonDataException(message) } - public static final class ParameterizedTypeImpl implements ParameterizedType { - private final @Nullable Type ownerType; - private final Type rawType; - public final Type[] typeArguments; + @JvmStatic + public fun unexpectedNull( + propertyName: String, jsonName: String, reader: JsonReader + ): JsonDataException { + val path = reader.path + val message: String = if (jsonName == propertyName) { + "Non-null value '$propertyName' was null at $path" + } else { + "Non-null value '$propertyName' (JSON name '$jsonName') was null at $path" + } + return JsonDataException(message) + } - public ParameterizedTypeImpl(@Nullable Type ownerType, Type rawType, Type... typeArguments) { + // Public due to inline access in MoshiKotlinTypesExtensions + @JvmStatic + public fun boxIfPrimitive(type: Class): Class { + // cast is safe: long.class and Long.class are both of type Class + @Suppress("UNCHECKED_CAST") + val wrapped = PRIMITIVE_TO_WRAPPER_TYPE[type] as Class? + return wrapped ?: type + } + + internal class ParameterizedTypeImpl( + ownerType: Type?, + rawType: Type, + vararg typeArguments: Type + ) : ParameterizedType { + private val ownerType: Type? + private val rawType: Type + @JvmField + val typeArguments: Array + + init { // Require an owner type if the raw type needs it. - if (rawType instanceof Class) { - Class enclosingClass = ((Class) rawType).getEnclosingClass(); + if (rawType is Class<*>) { + val enclosingClass = rawType.enclosingClass if (ownerType != null) { - if (enclosingClass == null || Types.getRawType(ownerType) != enclosingClass) { - throw new IllegalArgumentException( - "unexpected owner type for " + rawType + ": " + ownerType); - } - } else if (enclosingClass != null) { - throw new IllegalArgumentException("unexpected owner type for " + rawType + ": null"); - } + require(!(enclosingClass == null || ownerType.rawType != enclosingClass)) { "unexpected owner type for $rawType: $ownerType" } + } else require(enclosingClass == null) { "unexpected owner type for $rawType: null" } } - - this.ownerType = ownerType == null ? null : canonicalize(ownerType); - this.rawType = canonicalize(rawType); - this.typeArguments = typeArguments.clone(); - for (int t = 0; t < this.typeArguments.length; t++) { - if (this.typeArguments[t] == null) throw new NullPointerException(); - checkNotPrimitive(this.typeArguments[t]); - this.typeArguments[t] = canonicalize(this.typeArguments[t]); + this.ownerType = if (ownerType == null) null else canonicalize(ownerType) + this.rawType = canonicalize(rawType) + @Suppress("UNCHECKED_CAST") + this.typeArguments = typeArguments.clone() as Array + for (t in this.typeArguments.indices) { + checkNotPrimitive(this.typeArguments[t]) + this.typeArguments[t] = canonicalize(this.typeArguments[t]) } } - @Override - public Type[] getActualTypeArguments() { - return typeArguments.clone(); - } + override fun getActualTypeArguments() = typeArguments.clone() - @Override - public Type getRawType() { - return rawType; - } + override fun getRawType() = rawType - @Override - public @Nullable Type getOwnerType() { - return ownerType; - } + override fun getOwnerType() = ownerType - @Override - public boolean equals(Object other) { - return other instanceof ParameterizedType && Types.equals(this, (ParameterizedType) other); - } + @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") + override fun equals(other: Any?) = + other is ParameterizedType && Types.equals(this, other as ParameterizedType?) - @Override - public int hashCode() { - return Arrays.hashCode(typeArguments) ^ rawType.hashCode() ^ hashCodeOrZero(ownerType); + override fun hashCode(): Int { + return typeArguments.contentHashCode() xor rawType.hashCode() xor ownerType.hashCodeOrZero } - @Override - public String toString() { - StringBuilder result = new StringBuilder(30 * (typeArguments.length + 1)); - result.append(typeToString(rawType)); - - if (typeArguments.length == 0) { - return result.toString(); + override fun toString(): String { + val result = StringBuilder(30 * (typeArguments.size + 1)) + result.append(typeToString(rawType)) + if (typeArguments.isEmpty()) { + return result.toString() } - - result.append("<").append(typeToString(typeArguments[0])); - for (int i = 1; i < typeArguments.length; i++) { - result.append(", ").append(typeToString(typeArguments[i])); + result.append("<").append(typeToString(typeArguments[0])) + for (i in 1 until typeArguments.size) { + result.append(", ").append(typeToString(typeArguments[i])) } - return result.append(">").toString(); + return result.append(">").toString() } } - public static final class GenericArrayTypeImpl implements GenericArrayType { - private final Type componentType; + internal class GenericArrayTypeImpl(componentType: Type) : GenericArrayType { + private val componentType: Type = canonicalize(componentType) - public GenericArrayTypeImpl(Type componentType) { - this.componentType = canonicalize(componentType); - } + override fun getGenericComponentType() = componentType - @Override - public Type getGenericComponentType() { - return componentType; - } - - @Override - public boolean equals(Object o) { - return o instanceof GenericArrayType && Types.equals(this, (GenericArrayType) o); - } + @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") + override fun equals(other: Any?) = + other is GenericArrayType && Types.equals(this, other as GenericArrayType?) - @Override - public int hashCode() { - return componentType.hashCode(); - } + override fun hashCode() = componentType.hashCode() - @Override - public String toString() { - return typeToString(componentType) + "[]"; - } + override fun toString() = typeToString(componentType) + "[]" } /** @@ -484,201 +593,43 @@ public final class Util { * support what the Java 6 language needs - at most one bound. If a lower bound is set, the upper * bound must be Object.class. */ - public static final class WildcardTypeImpl implements WildcardType { - private final Type upperBound; - private final @Nullable Type lowerBound; - - public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) { - if (lowerBounds.length > 1) throw new IllegalArgumentException(); - if (upperBounds.length != 1) throw new IllegalArgumentException(); - - if (lowerBounds.length == 1) { - if (lowerBounds[0] == null) throw new NullPointerException(); - checkNotPrimitive(lowerBounds[0]); - if (upperBounds[0] != Object.class) throw new IllegalArgumentException(); - this.lowerBound = canonicalize(lowerBounds[0]); - this.upperBound = Object.class; - + internal class WildcardTypeImpl(upperBounds: Array, lowerBounds: Array) : WildcardType { + private val upperBound: Type + private val lowerBound: Type? + + init { + require(lowerBounds.size <= 1) + require(upperBounds.size == 1) + if (lowerBounds.size == 1) { + checkNotPrimitive(lowerBounds[0]) + require(!(upperBounds[0] !== Any::class.java)) + lowerBound = canonicalize(lowerBounds[0]) + upperBound = Any::class.java } else { - if (upperBounds[0] == null) throw new NullPointerException(); - checkNotPrimitive(upperBounds[0]); - this.lowerBound = null; - this.upperBound = canonicalize(upperBounds[0]); + checkNotPrimitive(upperBounds[0]) + lowerBound = null + upperBound = canonicalize(upperBounds[0]) } } - @Override - public Type[] getUpperBounds() { - return new Type[] {upperBound}; - } + override fun getUpperBounds() = arrayOf(upperBound) - @Override - public Type[] getLowerBounds() { - return lowerBound != null ? new Type[] {lowerBound} : EMPTY_TYPE_ARRAY; - } + override fun getLowerBounds() = lowerBound?.let { arrayOf(it) } ?: EMPTY_TYPE_ARRAY - @Override - public boolean equals(Object other) { - return other instanceof WildcardType && Types.equals(this, (WildcardType) other); - } + @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") + override fun equals(other: Any?) = other is WildcardType && Types.equals(this, other as WildcardType?) - @Override - public int hashCode() { + override fun hashCode(): Int { // This equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds()). - return (lowerBound != null ? 31 + lowerBound.hashCode() : 1) ^ (31 + upperBound.hashCode()); + return (if (lowerBound != null) 31 + lowerBound.hashCode() else 1) xor 31 + upperBound.hashCode() } - @Override - public String toString() { - if (lowerBound != null) { - return "? super " + typeToString(lowerBound); - } else if (upperBound == Object.class) { - return "?"; - } else { - return "? extends " + typeToString(upperBound); + override fun toString(): String { + return when { + lowerBound != null -> "? super ${typeToString(lowerBound)}" + upperBound === Any::class.java -> "?" + else -> "? extends ${typeToString(upperBound)}" } } } - - public static String typeAnnotatedWithAnnotations( - Type type, Set annotations) { - return type + (annotations.isEmpty() ? " (with no annotations)" : " annotated " + annotations); - } - - /** - * Loads the generated JsonAdapter for classes annotated {@link JsonClass}. This works because it - * uses the same naming conventions as {@code JsonClassCodeGenProcessor}. - */ - public static @Nullable JsonAdapter generatedAdapter( - Moshi moshi, Type type, Class rawType) { - JsonClass jsonClass = rawType.getAnnotation(JsonClass.class); - if (jsonClass == null || !jsonClass.generateAdapter()) { - return null; - } - String adapterClassName = Types.generatedJsonAdapterName(rawType.getName()); - Class> adapterClass = null; - try { - //noinspection unchecked - We generate types to match. - adapterClass = - (Class>) - Class.forName(adapterClassName, true, rawType.getClassLoader()); - Constructor> constructor; - Object[] args; - if (type instanceof ParameterizedType) { - Type[] typeArgs = ((ParameterizedType) type).getActualTypeArguments(); - try { - // Common case first - constructor = adapterClass.getDeclaredConstructor(Moshi.class, Type[].class); - args = new Object[] {moshi, typeArgs}; - } catch (NoSuchMethodException e) { - constructor = adapterClass.getDeclaredConstructor(Type[].class); - args = new Object[] {typeArgs}; - } - } else { - try { - // Common case first - constructor = adapterClass.getDeclaredConstructor(Moshi.class); - args = new Object[] {moshi}; - } catch (NoSuchMethodException e) { - constructor = adapterClass.getDeclaredConstructor(); - args = new Object[0]; - } - } - constructor.setAccessible(true); - return constructor.newInstance(args).nullSafe(); - } catch (ClassNotFoundException e) { - throw new RuntimeException("Failed to find the generated JsonAdapter class for " + type, e); - } catch (NoSuchMethodException e) { - if (!(type instanceof ParameterizedType) && adapterClass.getTypeParameters().length != 0) { - throw new RuntimeException( - "Failed to find the generated JsonAdapter constructor for '" - + type - + "'. Suspiciously, the type was not parameterized but the target class '" - + adapterClass.getCanonicalName() - + "' is generic. Consider using " - + "Types#newParameterizedType() to define these missing type variables.", - e); - } else { - throw new RuntimeException( - "Failed to find the generated JsonAdapter constructor for " + type, e); - } - } catch (IllegalAccessException e) { - throw new RuntimeException("Failed to access the generated JsonAdapter for " + type, e); - } catch (InstantiationException e) { - throw new RuntimeException("Failed to instantiate the generated JsonAdapter for " + type, e); - } catch (InvocationTargetException e) { - throw rethrowCause(e); - } - } - - public static boolean isKotlin(Class targetClass) { - return METADATA != null && targetClass.isAnnotationPresent(METADATA); - } - - /** - * Reflectively looks up the defaults constructor of a kotlin class. - * - * @param targetClass the target kotlin class to instantiate. - * @param the type of {@code targetClass}. - * @return the instantiated {@code targetClass} instance. - */ - public static Constructor lookupDefaultsConstructor(Class targetClass) { - if (DEFAULT_CONSTRUCTOR_MARKER == null) { - throw new IllegalStateException( - "DefaultConstructorMarker not on classpath. Make sure the " - + "Kotlin stdlib is on the classpath."); - } - Constructor defaultConstructor = findConstructor(targetClass); - defaultConstructor.setAccessible(true); - return defaultConstructor; - } - - private static Constructor findConstructor(Class targetClass) { - for (Constructor constructor : targetClass.getDeclaredConstructors()) { - Class[] paramTypes = constructor.getParameterTypes(); - if (paramTypes.length != 0 - && paramTypes[paramTypes.length - 1].equals(DEFAULT_CONSTRUCTOR_MARKER)) { - //noinspection unchecked - return (Constructor) constructor; - } - } - - throw new IllegalStateException("No defaults constructor found for " + targetClass); - } - - public static JsonDataException missingProperty( - String propertyName, String jsonName, JsonReader reader) { - String path = reader.getPath(); - String message; - if (jsonName.equals(propertyName)) { - message = String.format("Required value '%s' missing at %s", propertyName, path); - } else { - message = - String.format( - "Required value '%s' (JSON name '%s') missing at %s", propertyName, jsonName, path); - } - return new JsonDataException(message); - } - - public static JsonDataException unexpectedNull( - String propertyName, String jsonName, JsonReader reader) { - String path = reader.getPath(); - String message; - if (jsonName.equals(propertyName)) { - message = String.format("Non-null value '%s' was null at %s", propertyName, path); - } else { - message = - String.format( - "Non-null value '%s' (JSON name '%s') was null at %s", propertyName, jsonName, path); - } - return new JsonDataException(message); - } - - // Public due to inline access in MoshiKotlinTypesExtensions - public static Class boxIfPrimitive(Class type) { - // cast is safe: long.class and Long.class are both of type Class - @SuppressWarnings("unchecked") - Class wrapped = (Class) PRIMITIVE_TO_WRAPPER_TYPE.get(type); - return (wrapped == null) ? type : wrapped; - } } From 242b73a23bce33b078a5042861b5b4bd4a6273d5 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 11 Dec 2021 16:06:02 -0500 Subject: [PATCH 08/20] Push to top-level Util --- .../moshi/-MoshiKotlinTypesExtensions.kt | 6 +- .../main/java/com/squareup/moshi/Types.java | 6 +- .../java/com/squareup/moshi/internal/Util.kt | 966 +++++++++--------- 3 files changed, 476 insertions(+), 502 deletions(-) diff --git a/moshi/src/main/java/com/squareup/moshi/-MoshiKotlinTypesExtensions.kt b/moshi/src/main/java/com/squareup/moshi/-MoshiKotlinTypesExtensions.kt index 5be471a37..abeb2ea50 100644 --- a/moshi/src/main/java/com/squareup/moshi/-MoshiKotlinTypesExtensions.kt +++ b/moshi/src/main/java/com/squareup/moshi/-MoshiKotlinTypesExtensions.kt @@ -15,7 +15,7 @@ */ package com.squareup.moshi -import com.squareup.moshi.internal.Util +import com.squareup.moshi.internal.boxIfPrimitive import java.lang.reflect.GenericArrayType import java.lang.reflect.Type import java.lang.reflect.WildcardType @@ -42,7 +42,7 @@ public inline fun Set.nextAnnotations(): Se public inline fun subtypeOf(): WildcardType { var type = typeOf().javaType if (type is Class<*>) { - type = Util.boxIfPrimitive(type) + type = boxIfPrimitive(type) } return Types.subtypeOf(type) } @@ -55,7 +55,7 @@ public inline fun subtypeOf(): WildcardType { public inline fun supertypeOf(): WildcardType { var type = typeOf().javaType if (type is Class<*>) { - type = Util.boxIfPrimitive(type) + type = boxIfPrimitive(type) } return Types.supertypeOf(type) } diff --git a/moshi/src/main/java/com/squareup/moshi/Types.java b/moshi/src/main/java/com/squareup/moshi/Types.java index f03be62d6..716a5e2f4 100644 --- a/moshi/src/main/java/com/squareup/moshi/Types.java +++ b/moshi/src/main/java/com/squareup/moshi/Types.java @@ -19,9 +19,9 @@ import static com.squareup.moshi.internal.Util.getGenericSupertype; import static com.squareup.moshi.internal.Util.resolve; -import com.squareup.moshi.internal.Util.GenericArrayTypeImpl; -import com.squareup.moshi.internal.Util.ParameterizedTypeImpl; -import com.squareup.moshi.internal.Util.WildcardTypeImpl; +import com.squareup.moshi.internal.GenericArrayTypeImpl; +import com.squareup.moshi.internal.ParameterizedTypeImpl; +import com.squareup.moshi.internal.WildcardTypeImpl; import java.lang.annotation.Annotation; import java.lang.reflect.Array; import java.lang.reflect.Field; diff --git a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt index 7f074e677..1b5d58f8f 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt +++ b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:JvmName("Util") +@file:Suppress("unused", "MemberVisibilityCanBePrivate") package com.squareup.moshi.internal import java.lang.ClassNotFoundException @@ -45,591 +47,563 @@ import java.lang.reflect.WildcardType import java.util.Collections import java.util.LinkedHashSet -@Suppress("unused", "MemberVisibilityCanBePrivate") -public object Util { - @JvmField public val NO_ANNOTATIONS: Set = emptySet() - @JvmField public val EMPTY_TYPE_ARRAY: Array = arrayOf() +@JvmField public val NO_ANNOTATIONS: Set = emptySet() +@JvmField public val EMPTY_TYPE_ARRAY: Array = arrayOf() - @Suppress("UNCHECKED_CAST") - private val METADATA: Class? = try { - Class.forName(kotlinMetadataClassName) as Class - } catch (ignored: ClassNotFoundException) { - null - } +@Suppress("UNCHECKED_CAST") +private val METADATA: Class? = try { + Class.forName(kotlinMetadataClassName) as Class +} catch (ignored: ClassNotFoundException) { + null +} - // We look up the constructor marker separately because Metadata might be (justifiably) - // stripped by R8/Proguard but the DefaultConstructorMarker is still present. - public val DEFAULT_CONSTRUCTOR_MARKER: Class<*>? = try { - Class.forName("kotlin.jvm.internal.DefaultConstructorMarker") - } catch (ignored: ClassNotFoundException) { - null - } +// We look up the constructor marker separately because Metadata might be (justifiably) +// stripped by R8/Proguard but the DefaultConstructorMarker is still present. +public val DEFAULT_CONSTRUCTOR_MARKER: Class<*>? = try { + Class.forName("kotlin.jvm.internal.DefaultConstructorMarker") +} catch (ignored: ClassNotFoundException) { + null +} - /** A map from primitive types to their corresponding wrapper types. */ - private val PRIMITIVE_TO_WRAPPER_TYPE: Map, Class<*>> = buildMap(16) { - put(Boolean::class.javaPrimitiveType!!, Boolean::class.java) - put(Byte::class.javaPrimitiveType!!, Byte::class.java) - put(Char::class.javaPrimitiveType!!, Char::class.java) - put(Double::class.javaPrimitiveType!!, Double::class.java) - put(Float::class.javaPrimitiveType!!, Float::class.java) - put(Int::class.javaPrimitiveType!!, Int::class.java) - put(Long::class.javaPrimitiveType!!, Long::class.java) - put(Short::class.javaPrimitiveType!!, Short::class.java) - put(Void.TYPE, Void::class.java) - } +/** A map from primitive types to their corresponding wrapper types. */ +private val PRIMITIVE_TO_WRAPPER_TYPE: Map, Class<*>> = buildMap(16) { + put(Boolean::class.javaPrimitiveType!!, Boolean::class.java) + put(Byte::class.javaPrimitiveType!!, Byte::class.java) + put(Char::class.javaPrimitiveType!!, Char::class.java) + put(Double::class.javaPrimitiveType!!, Double::class.java) + put(Float::class.javaPrimitiveType!!, Float::class.java) + put(Int::class.javaPrimitiveType!!, Int::class.java) + put(Long::class.javaPrimitiveType!!, Long::class.java) + put(Short::class.javaPrimitiveType!!, Short::class.java) + put(Void.TYPE, Void::class.java) +} - // Extracted as a method with a keep rule to prevent R8 from keeping Kotlin Metadata - private val kotlinMetadataClassName: String - get() = "kotlin.Metadata" +// Extracted as a method with a keep rule to prevent R8 from keeping Kotlin Metadata +private val kotlinMetadataClassName: String + get() = "kotlin.Metadata" - @JvmStatic - public fun jsonName(declaredName: String, element: AnnotatedElement): String { - return jsonName(declaredName, element.getAnnotation(Json::class.java)) - } +public fun jsonName(declaredName: String, element: AnnotatedElement): String { + return jsonName(declaredName, element.getAnnotation(Json::class.java)) +} - @JvmStatic - public fun jsonName(declaredName: String, annotation: Json?): String { - if (annotation == null) return declaredName - val annotationName: String = annotation.name - return if (Json.UNSET_NAME == annotationName) declaredName else annotationName - } +public fun jsonName(declaredName: String, annotation: Json?): String { + if (annotation == null) return declaredName + val annotationName: String = annotation.name + return if (Json.UNSET_NAME == annotationName) declaredName else annotationName +} - @JvmStatic - public fun typesMatch(pattern: Type, candidate: Type): Boolean { - // TODO: permit raw types (like Set.class) to match non-raw candidates (like Set). - return Types.equals(pattern, candidate) - } +public fun typesMatch(pattern: Type, candidate: Type): Boolean { + // TODO: permit raw types (like Set.class) to match non-raw candidates (like Set). + return Types.equals(pattern, candidate) +} - @JvmStatic - public fun jsonAnnotations(annotatedElement: AnnotatedElement): Set { - return jsonAnnotations(annotatedElement.annotations) - } +public fun jsonAnnotations(annotatedElement: AnnotatedElement): Set { + return jsonAnnotations(annotatedElement.annotations) +} - @JvmStatic - public fun jsonAnnotations(annotations: Array): Set { - var result: MutableSet? = null - for (annotation in annotations) { - @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") - if ((annotation as java.lang.annotation.Annotation).annotationType().isAnnotationPresent(JsonQualifier::class.java)) { - if (result == null) result = LinkedHashSet() - result.add(annotation) - } +public fun jsonAnnotations(annotations: Array): Set { + var result: MutableSet? = null + for (annotation in annotations) { + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") + if ((annotation as java.lang.annotation.Annotation).annotationType().isAnnotationPresent(JsonQualifier::class.java)) { + if (result == null) result = LinkedHashSet() + result.add(annotation) } - return if (result != null) Collections.unmodifiableSet(result) else NO_ANNOTATIONS } + return if (result != null) Collections.unmodifiableSet(result) else NO_ANNOTATIONS +} - @JvmStatic - public fun isAnnotationPresent( - annotations: Set, - annotationClass: Class - ): Boolean { - if (annotations.isEmpty()) return false // Save an iterator in the common case. - for (annotation in annotations) { - @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") - if ((annotation as java.lang.annotation.Annotation).annotationType() == annotationClass) return true - } - return false +public fun isAnnotationPresent( + annotations: Set, + annotationClass: Class +): Boolean { + if (annotations.isEmpty()) return false // Save an iterator in the common case. + for (annotation in annotations) { + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") + if ((annotation as java.lang.annotation.Annotation).annotationType() == annotationClass) return true } + return false +} - /** Returns true if `annotations` has any annotation whose simple name is Nullable. */ - @JvmStatic - public fun hasNullable(annotations: Array): Boolean { - for (annotation in annotations) { - @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") - if ((annotation as java.lang.annotation.Annotation).annotationType().simpleName == "Nullable") { - return true - } +/** Returns true if `annotations` has any annotation whose simple name is Nullable. */ +public fun hasNullable(annotations: Array): Boolean { + for (annotation in annotations) { + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") + if ((annotation as java.lang.annotation.Annotation).annotationType().simpleName == "Nullable") { + return true } - return false } + return false +} - /** - * Returns true if `rawType` is built in. We don't reflect on private fields of platform - * types because they're unspecified and likely to be different on Java vs. Android. - */ - @JvmStatic - public fun isPlatformType(rawType: Class<*>): Boolean { - val name = rawType.name - return (name.startsWith("android.") - || name.startsWith("androidx.") - || name.startsWith("java.") - || name.startsWith("javax.") - || name.startsWith("kotlin.") - || name.startsWith("kotlinx.") - || name.startsWith("scala.")) - } +/** + * Returns true if `rawType` is built in. We don't reflect on private fields of platform + * types because they're unspecified and likely to be different on Java vs. Android. + */ +public fun isPlatformType(rawType: Class<*>): Boolean { + val name = rawType.name + return (name.startsWith("android.") + || name.startsWith("androidx.") + || name.startsWith("java.") + || name.startsWith("javax.") + || name.startsWith("kotlin.") + || name.startsWith("kotlinx.") + || name.startsWith("scala.")) +} - /** Throws the cause of `e`, wrapping it if it is checked. */ - @JvmStatic - public fun rethrowCause(e: InvocationTargetException): RuntimeException { - val cause = e.targetException - if (cause is RuntimeException) throw cause - if (cause is Error) throw cause - throw RuntimeException(cause) - } +/** Throws the cause of `e`, wrapping it if it is checked. */ +public fun rethrowCause(e: InvocationTargetException): RuntimeException { + val cause = e.targetException + if (cause is RuntimeException) throw cause + if (cause is Error) throw cause + throw RuntimeException(cause) +} - /** - * Returns a type that is functionally equal but not necessarily equal according to [[Object.equals()]][Object.equals]. - */ - @JvmStatic - public fun canonicalize(type: Type): Type { - return when (type) { - is Class<*> -> { - if (type.isArray) GenericArrayTypeImpl(canonicalize(type.componentType)) else type - } - is ParameterizedType -> { - if (type is ParameterizedTypeImpl) return type - ParameterizedTypeImpl(type.ownerType, type.rawType, *type.actualTypeArguments) - } - is GenericArrayType -> { - if (type is GenericArrayTypeImpl) return type - GenericArrayTypeImpl(type.genericComponentType) - } - is WildcardType -> { - if (type is WildcardTypeImpl) return type - WildcardTypeImpl(type.upperBounds, type.lowerBounds) - } - else -> { - type // This type is unsupported! - } +/** + * Returns a type that is functionally equal but not necessarily equal according to [[Object.equals()]][Object.equals]. + */ +public fun canonicalize(type: Type): Type { + return when (type) { + is Class<*> -> { + if (type.isArray) GenericArrayTypeImpl(canonicalize(type.componentType)) else type + } + is ParameterizedType -> { + if (type is ParameterizedTypeImpl) return type + ParameterizedTypeImpl(type.ownerType, type.rawType, *type.actualTypeArguments) + } + is GenericArrayType -> { + if (type is GenericArrayTypeImpl) return type + GenericArrayTypeImpl(type.genericComponentType) + } + is WildcardType -> { + if (type is WildcardTypeImpl) return type + WildcardTypeImpl(type.upperBounds, type.lowerBounds) + } + else -> { + type // This type is unsupported! } } +} - /** If type is a "? extends X" wildcard, returns X; otherwise returns type unchanged. */ - @JvmStatic - public fun removeSubtypeWildcard(type: Type): Type { - if (type !is WildcardType) return type - val lowerBounds = type.lowerBounds - if (lowerBounds.isNotEmpty()) return type - val upperBounds = type.upperBounds - require(upperBounds.size == 1) - return upperBounds[0] - } +/** If type is a "? extends X" wildcard, returns X; otherwise returns type unchanged. */ +public fun removeSubtypeWildcard(type: Type): Type { + if (type !is WildcardType) return type + val lowerBounds = type.lowerBounds + if (lowerBounds.isNotEmpty()) return type + val upperBounds = type.upperBounds + require(upperBounds.size == 1) + return upperBounds[0] +} - @JvmStatic - public fun resolve(context: Type, contextRawType: Class<*>, toResolve: Type): Type { - return resolve(context, contextRawType, toResolve, LinkedHashSet()) - } +public fun resolve(context: Type, contextRawType: Class<*>, toResolve: Type): Type { + return resolve(context, contextRawType, toResolve, LinkedHashSet()) +} - private fun resolve( - context: Type, - contextRawType: Class<*>, - toResolveInitial: Type, - visitedTypeVariables: MutableCollection> - ): Type { - // This implementation is made a little more complicated in an attempt to avoid object-creation. - var toResolve = toResolveInitial - while (true) { - when { - toResolve is TypeVariable<*> -> { - val typeVariable = toResolve - if (typeVariable in visitedTypeVariables) { - // cannot reduce due to infinite recursion - return toResolve - } else { - visitedTypeVariables += typeVariable - } - toResolve = resolveTypeVariable(context, contextRawType, typeVariable) - if (toResolve === typeVariable) return toResolve +private fun resolve( + context: Type, + contextRawType: Class<*>, + toResolveInitial: Type, + visitedTypeVariables: MutableCollection> +): Type { + // This implementation is made a little more complicated in an attempt to avoid object-creation. + var toResolve = toResolveInitial + while (true) { + when { + toResolve is TypeVariable<*> -> { + val typeVariable = toResolve + if (typeVariable in visitedTypeVariables) { + // cannot reduce due to infinite recursion + return toResolve + } else { + visitedTypeVariables += typeVariable } - toResolve is Class<*> && toResolve.isArray -> { - val original = toResolve - val componentType: Type = original.componentType - val newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables) - return if (componentType === newComponentType) original else newComponentType.asArrayType() - } - toResolve is GenericArrayType -> { - val original = toResolve - val componentType = original.genericComponentType - val newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables) - return if (componentType === newComponentType) original else newComponentType.asArrayType() + toResolve = resolveTypeVariable(context, contextRawType, typeVariable) + if (toResolve === typeVariable) return toResolve + } + toResolve is Class<*> && toResolve.isArray -> { + val original = toResolve + val componentType: Type = original.componentType + val newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables) + return if (componentType === newComponentType) original else newComponentType.asArrayType() + } + toResolve is GenericArrayType -> { + val original = toResolve + val componentType = original.genericComponentType + val newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables) + return if (componentType === newComponentType) original else newComponentType.asArrayType() + } + toResolve is ParameterizedType -> { + val original = toResolve + val ownerType: Type? = original.ownerType + val newOwnerType = ownerType?.let { + resolve(context, contextRawType, ownerType, visitedTypeVariables) } - toResolve is ParameterizedType -> { - val original = toResolve - val ownerType: Type? = original.ownerType - val newOwnerType = ownerType?.let { - resolve(context, contextRawType, ownerType, visitedTypeVariables) - } - var changed = newOwnerType !== ownerType - var args = original.actualTypeArguments - var t = 0 - val length = args.size - while (t < length) { - val resolvedTypeArgument = resolve(context, contextRawType, args[t], visitedTypeVariables) - if (resolvedTypeArgument !== args[t]) { - if (!changed) { - args = args.clone() - changed = true - } - args[t] = resolvedTypeArgument + var changed = newOwnerType !== ownerType + var args = original.actualTypeArguments + var t = 0 + val length = args.size + while (t < length) { + val resolvedTypeArgument = resolve(context, contextRawType, args[t], visitedTypeVariables) + if (resolvedTypeArgument !== args[t]) { + if (!changed) { + args = args.clone() + changed = true } - t++ + args[t] = resolvedTypeArgument } - return if (changed) ParameterizedTypeImpl(newOwnerType, original.rawType, *args) else original + t++ } - toResolve is WildcardType -> { - val original = toResolve - val originalLowerBound = original.lowerBounds - val originalUpperBound = original.upperBounds - if (originalLowerBound.size == 1) { - val lowerBound = resolve(context, contextRawType, originalLowerBound[0], visitedTypeVariables) - if (lowerBound !== originalLowerBound[0]) { - return Types.supertypeOf(lowerBound) - } - } else if (originalUpperBound.size == 1) { - val upperBound = resolve(context, contextRawType, originalUpperBound[0], visitedTypeVariables) - if (upperBound !== originalUpperBound[0]) { - return Types.subtypeOf(upperBound) - } + return if (changed) ParameterizedTypeImpl(newOwnerType, original.rawType, *args) else original + } + toResolve is WildcardType -> { + val original = toResolve + val originalLowerBound = original.lowerBounds + val originalUpperBound = original.upperBounds + if (originalLowerBound.size == 1) { + val lowerBound = resolve(context, contextRawType, originalLowerBound[0], visitedTypeVariables) + if (lowerBound !== originalLowerBound[0]) { + return Types.supertypeOf(lowerBound) + } + } else if (originalUpperBound.size == 1) { + val upperBound = resolve(context, contextRawType, originalUpperBound[0], visitedTypeVariables) + if (upperBound !== originalUpperBound[0]) { + return Types.subtypeOf(upperBound) } - return original } - else -> return toResolve + return original } + else -> return toResolve } } +} - @JvmStatic - public fun resolveTypeVariable(context: Type, contextRawType: Class<*>, unknown: TypeVariable<*>): Type { - val declaredByRaw = declaringClassOf(unknown) ?: return unknown +public fun resolveTypeVariable(context: Type, contextRawType: Class<*>, unknown: TypeVariable<*>): Type { + val declaredByRaw = declaringClassOf(unknown) ?: return unknown - // We can't reduce this further. - val declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw) - if (declaredBy is ParameterizedType) { - val index = declaredByRaw.typeParameters.indexOf(unknown) - return declaredBy.actualTypeArguments[index] - } - return unknown + // We can't reduce this further. + val declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw) + if (declaredBy is ParameterizedType) { + val index = declaredByRaw.typeParameters.indexOf(unknown) + return declaredBy.actualTypeArguments[index] } + return unknown +} - /** - * Returns the generic supertype for `supertype`. For example, given a class `IntegerSet`, the result for when supertype is `Set.class` is `Set` and the - * result when the supertype is `Collection.class` is `Collection`. - */ - @JvmStatic - public fun getGenericSupertype(context: Type, rawTypeInitial: Class<*>, toResolve: Class<*>): Type { - var rawType = rawTypeInitial - if (toResolve == rawType) { - return context - } - - // we skip searching through interfaces if unknown is an interface - if (toResolve.isInterface) { - val interfaces = rawType.interfaces - var i = 0 - val length = interfaces.size - while (i < length) { - if (interfaces[i] == toResolve) { - return rawType.genericInterfaces[i] - } else if (toResolve.isAssignableFrom(interfaces[i])) { - return getGenericSupertype(rawType.genericInterfaces[i], interfaces[i], toResolve) - } - i++ +/** + * Returns the generic supertype for `supertype`. For example, given a class `IntegerSet`, the result for when supertype is `Set.class` is `Set` and the + * result when the supertype is `Collection.class` is `Collection`. + */ +public fun getGenericSupertype(context: Type, rawTypeInitial: Class<*>, toResolve: Class<*>): Type { + var rawType = rawTypeInitial + if (toResolve == rawType) { + return context + } + + // we skip searching through interfaces if unknown is an interface + if (toResolve.isInterface) { + val interfaces = rawType.interfaces + var i = 0 + val length = interfaces.size + while (i < length) { + if (interfaces[i] == toResolve) { + return rawType.genericInterfaces[i] + } else if (toResolve.isAssignableFrom(interfaces[i])) { + return getGenericSupertype(rawType.genericInterfaces[i], interfaces[i], toResolve) } + i++ } + } - // check our supertypes - if (!rawType.isInterface) { - while (rawType != Any::class.java) { - val rawSupertype = rawType.superclass - if (rawSupertype == toResolve) { - return rawType.genericSuperclass - } else if (toResolve.isAssignableFrom(rawSupertype)) { - return getGenericSupertype(rawType.genericSuperclass, rawSupertype, toResolve) - } - rawType = rawSupertype + // check our supertypes + if (!rawType.isInterface) { + while (rawType != Any::class.java) { + val rawSupertype = rawType.superclass + if (rawSupertype == toResolve) { + return rawType.genericSuperclass + } else if (toResolve.isAssignableFrom(rawSupertype)) { + return getGenericSupertype(rawType.genericSuperclass, rawSupertype, toResolve) } + rawType = rawSupertype } - - // we can't resolve this further - return toResolve } - @JvmStatic - public val Any?.hashCodeOrZero: Int - get() { - return this?.hashCode() ?: 0 - } + // we can't resolve this further + return toResolve +} - @JvmStatic - public fun typeToString(type: Type): String { - return if (type is Class<*>) type.name else type.toString() +public val Any?.hashCodeOrZero: Int + get() { + return this?.hashCode() ?: 0 } - /** - * Returns the declaring class of `typeVariable`, or `null` if it was not declared by - * a class. - */ - @JvmStatic - public fun declaringClassOf(typeVariable: TypeVariable<*>): Class<*>? { - val genericDeclaration = typeVariable.genericDeclaration - return if (genericDeclaration is Class<*>) genericDeclaration else null - } +public fun typeToString(type: Type): String { + return if (type is Class<*>) type.name else type.toString() +} - @JvmStatic - public fun checkNotPrimitive(type: Type) { - require(!(type is Class<*> && type.isPrimitive)) { "Unexpected primitive $type. Use the boxed type." } - } +/** + * Returns the declaring class of `typeVariable`, or `null` if it was not declared by + * a class. + */ +public fun declaringClassOf(typeVariable: TypeVariable<*>): Class<*>? { + val genericDeclaration = typeVariable.genericDeclaration + return if (genericDeclaration is Class<*>) genericDeclaration else null +} - @JvmStatic - public fun typeAnnotatedWithAnnotations( - type: Type, - annotations: Set - ): String { - return type.toString() + if (annotations.isEmpty()) " (with no annotations)" else " annotated $annotations" - } +public fun checkNotPrimitive(type: Type) { + require(!(type is Class<*> && type.isPrimitive)) { "Unexpected primitive $type. Use the boxed type." } +} - /** - * Loads the generated JsonAdapter for classes annotated [JsonClass]. This works because it - * uses the same naming conventions as `JsonClassCodeGenProcessor`. - */ - @JvmStatic - public fun generatedAdapter( - moshi: Moshi, - type: Type, - rawType: Class<*> - ): JsonAdapter<*>? { - val jsonClass = rawType.getAnnotation(JsonClass::class.java) - if (jsonClass == null || !jsonClass.generateAdapter) { - return null - } - val adapterClassName = Types.generatedJsonAdapterName(rawType.name) - var possiblyFoundAdapter: Class>? = null - return try { - @Suppress("UNCHECKED_CAST") - val adapterClass = Class.forName(adapterClassName, true, rawType.classLoader) as Class> - possiblyFoundAdapter = adapterClass - var constructor: Constructor> - var args: Array - if (type is ParameterizedType) { - val typeArgs = type.actualTypeArguments - try { - // Common case first - constructor = adapterClass.getDeclaredConstructor(Moshi::class.java, Array::class.java) - args = arrayOf(moshi, typeArgs) - } catch (e: NoSuchMethodException) { - constructor = adapterClass.getDeclaredConstructor(Array::class.java) - args = arrayOf(typeArgs) - } - } else { - try { - // Common case first - constructor = adapterClass.getDeclaredConstructor(Moshi::class.java) - args = arrayOf(moshi) - } catch (e: NoSuchMethodException) { - constructor = adapterClass.getDeclaredConstructor() - args = emptyArray() - } +public fun typeAnnotatedWithAnnotations( + type: Type, + annotations: Set +): String { + return type.toString() + if (annotations.isEmpty()) " (with no annotations)" else " annotated $annotations" +} + +/** + * Loads the generated JsonAdapter for classes annotated [JsonClass]. This works because it + * uses the same naming conventions as `JsonClassCodeGenProcessor`. + */ +public fun generatedAdapter( + moshi: Moshi, + type: Type, + rawType: Class<*> +): JsonAdapter<*>? { + val jsonClass = rawType.getAnnotation(JsonClass::class.java) + if (jsonClass == null || !jsonClass.generateAdapter) { + return null + } + val adapterClassName = Types.generatedJsonAdapterName(rawType.name) + var possiblyFoundAdapter: Class>? = null + return try { + @Suppress("UNCHECKED_CAST") + val adapterClass = Class.forName(adapterClassName, true, rawType.classLoader) as Class> + possiblyFoundAdapter = adapterClass + var constructor: Constructor> + var args: Array + if (type is ParameterizedType) { + val typeArgs = type.actualTypeArguments + try { + // Common case first + constructor = adapterClass.getDeclaredConstructor(Moshi::class.java, Array::class.java) + args = arrayOf(moshi, typeArgs) + } catch (e: NoSuchMethodException) { + constructor = adapterClass.getDeclaredConstructor(Array::class.java) + args = arrayOf(typeArgs) } - constructor.isAccessible = true - constructor.newInstance(*args).nullSafe() - } catch (e: ClassNotFoundException) { - throw RuntimeException("Failed to find the generated JsonAdapter class for $type", e) - } catch (e: NoSuchMethodException) { - if (possiblyFoundAdapter != null && type !is ParameterizedType && possiblyFoundAdapter.typeParameters.isNotEmpty()) { - throw RuntimeException( - "Failed to find the generated JsonAdapter constructor for '" - + type - + "'. Suspiciously, the type was not parameterized but the target class '" - + possiblyFoundAdapter.canonicalName - + "' is generic. Consider using " - + "Types#newParameterizedType() to define these missing type variables.", - e - ) - } else { - throw RuntimeException( - "Failed to find the generated JsonAdapter constructor for $type", e - ) + } else { + try { + // Common case first + constructor = adapterClass.getDeclaredConstructor(Moshi::class.java) + args = arrayOf(moshi) + } catch (e: NoSuchMethodException) { + constructor = adapterClass.getDeclaredConstructor() + args = emptyArray() } - } catch (e: IllegalAccessException) { - throw RuntimeException("Failed to access the generated JsonAdapter for $type", e) - } catch (e: InstantiationException) { - throw RuntimeException("Failed to instantiate the generated JsonAdapter for $type", e) - } catch (e: InvocationTargetException) { - throw rethrowCause(e) } + constructor.isAccessible = true + constructor.newInstance(*args).nullSafe() + } catch (e: ClassNotFoundException) { + throw RuntimeException("Failed to find the generated JsonAdapter class for $type", e) + } catch (e: NoSuchMethodException) { + if (possiblyFoundAdapter != null && type !is ParameterizedType && possiblyFoundAdapter.typeParameters.isNotEmpty()) { + throw RuntimeException( + "Failed to find the generated JsonAdapter constructor for '" + + type + + "'. Suspiciously, the type was not parameterized but the target class '" + + possiblyFoundAdapter.canonicalName + + "' is generic. Consider using " + + "Types#newParameterizedType() to define these missing type variables.", + e + ) + } else { + throw RuntimeException( + "Failed to find the generated JsonAdapter constructor for $type", e + ) + } + } catch (e: IllegalAccessException) { + throw RuntimeException("Failed to access the generated JsonAdapter for $type", e) + } catch (e: InstantiationException) { + throw RuntimeException("Failed to instantiate the generated JsonAdapter for $type", e) + } catch (e: InvocationTargetException) { + throw rethrowCause(e) } +} - @JvmStatic - public fun isKotlin(targetClass: Class<*>): Boolean { - return METADATA != null && targetClass.isAnnotationPresent(METADATA) - } +public fun isKotlin(targetClass: Class<*>): Boolean { + return METADATA != null && targetClass.isAnnotationPresent(METADATA) +} - /** - * Reflectively looks up the defaults constructor of a kotlin class. - * - * @param targetClass the target kotlin class to instantiate. - * @param T the type of `targetClass`. - * @return the instantiated `targetClass` instance. - */ - @JvmStatic - public fun lookupDefaultsConstructor(targetClass: Class): Constructor { - checkNotNull(DEFAULT_CONSTRUCTOR_MARKER) { - "DefaultConstructorMarker not on classpath. Make sure the Kotlin stdlib is on the classpath." - } - val defaultConstructor = findConstructor(targetClass) - defaultConstructor.isAccessible = true - return defaultConstructor +/** + * Reflectively looks up the defaults constructor of a kotlin class. + * + * @param targetClass the target kotlin class to instantiate. + * @param T the type of `targetClass`. + * @return the instantiated `targetClass` instance. + */ +public fun lookupDefaultsConstructor(targetClass: Class): Constructor { + checkNotNull(DEFAULT_CONSTRUCTOR_MARKER) { + "DefaultConstructorMarker not on classpath. Make sure the Kotlin stdlib is on the classpath." } + val defaultConstructor = findConstructor(targetClass) + defaultConstructor.isAccessible = true + return defaultConstructor +} - private fun findConstructor(targetClass: Class): Constructor { - for (constructor in targetClass.declaredConstructors) { - val paramTypes = constructor.parameterTypes - if (paramTypes.isNotEmpty() && paramTypes[paramTypes.size - 1] == DEFAULT_CONSTRUCTOR_MARKER) { - @Suppress("UNCHECKED_CAST") - return constructor as Constructor - } +private fun findConstructor(targetClass: Class): Constructor { + for (constructor in targetClass.declaredConstructors) { + val paramTypes = constructor.parameterTypes + if (paramTypes.isNotEmpty() && paramTypes[paramTypes.size - 1] == DEFAULT_CONSTRUCTOR_MARKER) { + @Suppress("UNCHECKED_CAST") + return constructor as Constructor } - throw IllegalStateException("No defaults constructor found for $targetClass") } + throw IllegalStateException("No defaults constructor found for $targetClass") +} - @JvmStatic - public fun missingProperty( - propertyName: String, jsonName: String, reader: JsonReader - ): JsonDataException { - val path = reader.path - val message = if (jsonName == propertyName) { - "Required value '$propertyName' missing at $path" - } else { - "Required value '$propertyName' (JSON name '$jsonName') missing at $path" - } - return JsonDataException(message) +public fun missingProperty( + propertyName: String, jsonName: String, reader: JsonReader +): JsonDataException { + val path = reader.path + val message = if (jsonName == propertyName) { + "Required value '$propertyName' missing at $path" + } else { + "Required value '$propertyName' (JSON name '$jsonName') missing at $path" } + return JsonDataException(message) +} - @JvmStatic - public fun unexpectedNull( - propertyName: String, jsonName: String, reader: JsonReader - ): JsonDataException { - val path = reader.path - val message: String = if (jsonName == propertyName) { - "Non-null value '$propertyName' was null at $path" - } else { - "Non-null value '$propertyName' (JSON name '$jsonName') was null at $path" - } - return JsonDataException(message) +public fun unexpectedNull( + propertyName: String, jsonName: String, reader: JsonReader +): JsonDataException { + val path = reader.path + val message: String = if (jsonName == propertyName) { + "Non-null value '$propertyName' was null at $path" + } else { + "Non-null value '$propertyName' (JSON name '$jsonName') was null at $path" } + return JsonDataException(message) +} + +// Public due to inline access in MoshiKotlinTypesExtensions +public fun boxIfPrimitive(type: Class): Class { + // cast is safe: long.class and Long.class are both of type Class + @Suppress("UNCHECKED_CAST") + val wrapped = PRIMITIVE_TO_WRAPPER_TYPE[type] as Class? + return wrapped ?: type +} - // Public due to inline access in MoshiKotlinTypesExtensions - @JvmStatic - public fun boxIfPrimitive(type: Class): Class { - // cast is safe: long.class and Long.class are both of type Class +internal class ParameterizedTypeImpl( + ownerType: Type?, + rawType: Type, + vararg typeArguments: Type +) : ParameterizedType { + private val ownerType: Type? + private val rawType: Type + @JvmField + val typeArguments: Array + + init { + // Require an owner type if the raw type needs it. + if (rawType is Class<*>) { + val enclosingClass = rawType.enclosingClass + if (ownerType != null) { + require(!(enclosingClass == null || ownerType.rawType != enclosingClass)) { "unexpected owner type for $rawType: $ownerType" } + } else require(enclosingClass == null) { "unexpected owner type for $rawType: null" } + } + this.ownerType = if (ownerType == null) null else canonicalize(ownerType) + this.rawType = canonicalize(rawType) @Suppress("UNCHECKED_CAST") - val wrapped = PRIMITIVE_TO_WRAPPER_TYPE[type] as Class? - return wrapped ?: type + this.typeArguments = typeArguments.clone() as Array + for (t in this.typeArguments.indices) { + checkNotPrimitive(this.typeArguments[t]) + this.typeArguments[t] = canonicalize(this.typeArguments[t]) + } } - internal class ParameterizedTypeImpl( - ownerType: Type?, - rawType: Type, - vararg typeArguments: Type - ) : ParameterizedType { - private val ownerType: Type? - private val rawType: Type - @JvmField - val typeArguments: Array - - init { - // Require an owner type if the raw type needs it. - if (rawType is Class<*>) { - val enclosingClass = rawType.enclosingClass - if (ownerType != null) { - require(!(enclosingClass == null || ownerType.rawType != enclosingClass)) { "unexpected owner type for $rawType: $ownerType" } - } else require(enclosingClass == null) { "unexpected owner type for $rawType: null" } - } - this.ownerType = if (ownerType == null) null else canonicalize(ownerType) - this.rawType = canonicalize(rawType) - @Suppress("UNCHECKED_CAST") - this.typeArguments = typeArguments.clone() as Array - for (t in this.typeArguments.indices) { - checkNotPrimitive(this.typeArguments[t]) - this.typeArguments[t] = canonicalize(this.typeArguments[t]) - } - } + override fun getActualTypeArguments() = typeArguments.clone() - override fun getActualTypeArguments() = typeArguments.clone() + override fun getRawType() = rawType - override fun getRawType() = rawType + override fun getOwnerType() = ownerType - override fun getOwnerType() = ownerType + @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") + override fun equals(other: Any?) = + other is ParameterizedType && Types.equals(this, other as ParameterizedType?) - @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") - override fun equals(other: Any?) = - other is ParameterizedType && Types.equals(this, other as ParameterizedType?) + override fun hashCode(): Int { + return typeArguments.contentHashCode() xor rawType.hashCode() xor ownerType.hashCodeOrZero + } - override fun hashCode(): Int { - return typeArguments.contentHashCode() xor rawType.hashCode() xor ownerType.hashCodeOrZero + override fun toString(): String { + val result = StringBuilder(30 * (typeArguments.size + 1)) + result.append(typeToString(rawType)) + if (typeArguments.isEmpty()) { + return result.toString() } - - override fun toString(): String { - val result = StringBuilder(30 * (typeArguments.size + 1)) - result.append(typeToString(rawType)) - if (typeArguments.isEmpty()) { - return result.toString() - } - result.append("<").append(typeToString(typeArguments[0])) - for (i in 1 until typeArguments.size) { - result.append(", ").append(typeToString(typeArguments[i])) - } - return result.append(">").toString() + result.append("<").append(typeToString(typeArguments[0])) + for (i in 1 until typeArguments.size) { + result.append(", ").append(typeToString(typeArguments[i])) } + return result.append(">").toString() } +} - internal class GenericArrayTypeImpl(componentType: Type) : GenericArrayType { - private val componentType: Type = canonicalize(componentType) +internal class GenericArrayTypeImpl(componentType: Type) : GenericArrayType { + private val componentType: Type = canonicalize(componentType) - override fun getGenericComponentType() = componentType + override fun getGenericComponentType() = componentType - @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") - override fun equals(other: Any?) = - other is GenericArrayType && Types.equals(this, other as GenericArrayType?) + @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") + override fun equals(other: Any?) = + other is GenericArrayType && Types.equals(this, other as GenericArrayType?) - override fun hashCode() = componentType.hashCode() + override fun hashCode() = componentType.hashCode() - override fun toString() = typeToString(componentType) + "[]" - } + override fun toString() = typeToString(componentType) + "[]" +} - /** - * The WildcardType interface supports multiple upper bounds and multiple lower bounds. We only - * support what the Java 6 language needs - at most one bound. If a lower bound is set, the upper - * bound must be Object.class. - */ - internal class WildcardTypeImpl(upperBounds: Array, lowerBounds: Array) : WildcardType { - private val upperBound: Type - private val lowerBound: Type? - - init { - require(lowerBounds.size <= 1) - require(upperBounds.size == 1) - if (lowerBounds.size == 1) { - checkNotPrimitive(lowerBounds[0]) - require(!(upperBounds[0] !== Any::class.java)) - lowerBound = canonicalize(lowerBounds[0]) - upperBound = Any::class.java - } else { - checkNotPrimitive(upperBounds[0]) - lowerBound = null - upperBound = canonicalize(upperBounds[0]) - } +/** + * The WildcardType interface supports multiple upper bounds and multiple lower bounds. We only + * support what the Java 6 language needs - at most one bound. If a lower bound is set, the upper + * bound must be Object.class. + */ +internal class WildcardTypeImpl(upperBounds: Array, lowerBounds: Array) : WildcardType { + private val upperBound: Type + private val lowerBound: Type? + + init { + require(lowerBounds.size <= 1) + require(upperBounds.size == 1) + if (lowerBounds.size == 1) { + checkNotPrimitive(lowerBounds[0]) + require(!(upperBounds[0] !== Any::class.java)) + lowerBound = canonicalize(lowerBounds[0]) + upperBound = Any::class.java + } else { + checkNotPrimitive(upperBounds[0]) + lowerBound = null + upperBound = canonicalize(upperBounds[0]) } + } - override fun getUpperBounds() = arrayOf(upperBound) + override fun getUpperBounds() = arrayOf(upperBound) - override fun getLowerBounds() = lowerBound?.let { arrayOf(it) } ?: EMPTY_TYPE_ARRAY + override fun getLowerBounds() = lowerBound?.let { arrayOf(it) } ?: EMPTY_TYPE_ARRAY - @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") - override fun equals(other: Any?) = other is WildcardType && Types.equals(this, other as WildcardType?) + @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") + override fun equals(other: Any?) = other is WildcardType && Types.equals(this, other as WildcardType?) - override fun hashCode(): Int { - // This equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds()). - return (if (lowerBound != null) 31 + lowerBound.hashCode() else 1) xor 31 + upperBound.hashCode() - } + override fun hashCode(): Int { + // This equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds()). + return (if (lowerBound != null) 31 + lowerBound.hashCode() else 1) xor 31 + upperBound.hashCode() + } - override fun toString(): String { - return when { - lowerBound != null -> "? super ${typeToString(lowerBound)}" - upperBound === Any::class.java -> "?" - else -> "? extends ${typeToString(upperBound)}" - } + override fun toString(): String { + return when { + lowerBound != null -> "? super ${typeToString(lowerBound)}" + upperBound === Any::class.java -> "?" + else -> "? extends ${typeToString(upperBound)}" } } } From 8bebd3a3b5a57affb101834324ade9a54f43bf84 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 11 Dec 2021 16:16:04 -0500 Subject: [PATCH 09/20] Use extensions and properties where possible --- .../squareup/moshi/recipes/FallbackEnum.java | 2 +- .../moshi/adapters/EnumJsonAdapter.java | 2 +- .../moshi/-MoshiKotlinTypesExtensions.kt | 4 +- .../squareup/moshi/AdapterMethodsFactory.java | 18 +- .../com/squareup/moshi/ClassJsonAdapter.java | 4 +- .../squareup/moshi/StandardJsonAdapters.java | 2 +- .../java/com/squareup/moshi/internal/Util.kt | 200 +++++++++--------- .../com/squareup/moshi/RecordJsonAdapter.java | 4 +- .../java/com/squareup/moshi/MoshiTest.java | 6 +- 9 files changed, 121 insertions(+), 121 deletions(-) diff --git a/examples/src/main/java/com/squareup/moshi/recipes/FallbackEnum.java b/examples/src/main/java/com/squareup/moshi/recipes/FallbackEnum.java index 287e32c1b..72f8c857a 100644 --- a/examples/src/main/java/com/squareup/moshi/recipes/FallbackEnum.java +++ b/examples/src/main/java/com/squareup/moshi/recipes/FallbackEnum.java @@ -78,7 +78,7 @@ public JsonAdapter create( nameStrings = new String[constants.length]; for (int i = 0; i < constants.length; i++) { String constantName = constants[i].name(); - nameStrings[i] = Util.jsonName(constantName, enumType.getField(constantName)); + nameStrings[i] = Util.jsonName(enumType.getField(constantName), constantName); } options = JsonReader.Options.of(nameStrings); } catch (NoSuchFieldException e) { diff --git a/moshi-adapters/src/main/java/com/squareup/moshi/adapters/EnumJsonAdapter.java b/moshi-adapters/src/main/java/com/squareup/moshi/adapters/EnumJsonAdapter.java index 058231f90..6f9e03823 100644 --- a/moshi-adapters/src/main/java/com/squareup/moshi/adapters/EnumJsonAdapter.java +++ b/moshi-adapters/src/main/java/com/squareup/moshi/adapters/EnumJsonAdapter.java @@ -68,7 +68,7 @@ public EnumJsonAdapter withUnknownFallback(@Nullable T fallbackValue) { nameStrings = new String[constants.length]; for (int i = 0; i < constants.length; i++) { String constantName = constants[i].name(); - nameStrings[i] = jsonName(constantName, enumType.getField(constantName)); + nameStrings[i] = jsonName(enumType.getField(constantName), constantName); } options = JsonReader.Options.of(nameStrings); } catch (NoSuchFieldException e) { diff --git a/moshi/src/main/java/com/squareup/moshi/-MoshiKotlinTypesExtensions.kt b/moshi/src/main/java/com/squareup/moshi/-MoshiKotlinTypesExtensions.kt index abeb2ea50..96f387dc2 100644 --- a/moshi/src/main/java/com/squareup/moshi/-MoshiKotlinTypesExtensions.kt +++ b/moshi/src/main/java/com/squareup/moshi/-MoshiKotlinTypesExtensions.kt @@ -42,7 +42,7 @@ public inline fun Set.nextAnnotations(): Se public inline fun subtypeOf(): WildcardType { var type = typeOf().javaType if (type is Class<*>) { - type = boxIfPrimitive(type) + type = type.boxIfPrimitive() } return Types.subtypeOf(type) } @@ -55,7 +55,7 @@ public inline fun subtypeOf(): WildcardType { public inline fun supertypeOf(): WildcardType { var type = typeOf().javaType if (type is Class<*>) { - type = boxIfPrimitive(type) + type = type.boxIfPrimitive() } return Types.supertypeOf(type) } diff --git a/moshi/src/main/java/com/squareup/moshi/AdapterMethodsFactory.java b/moshi/src/main/java/com/squareup/moshi/AdapterMethodsFactory.java index 84c8df700..942a50d6e 100644 --- a/moshi/src/main/java/com/squareup/moshi/AdapterMethodsFactory.java +++ b/moshi/src/main/java/com/squareup/moshi/AdapterMethodsFactory.java @@ -16,7 +16,7 @@ package com.squareup.moshi; import static com.squareup.moshi.internal.Util.canonicalize; -import static com.squareup.moshi.internal.Util.jsonAnnotations; +import static com.squareup.moshi.internal.Util.getJsonAnnotations; import static com.squareup.moshi.internal.Util.typeAnnotatedWithAnnotations; import com.squareup.moshi.internal.Util; @@ -172,7 +172,7 @@ static AdapterMethod toAdapter(Object adapter, Method method) { && parametersAreJsonAdapters(2, parameterTypes)) { // void pointToJson(JsonWriter jsonWriter, Point point) { // void pointToJson(JsonWriter jsonWriter, Point point, JsonAdapter adapter, ...) { - Set qualifierAnnotations = jsonAnnotations(parameterAnnotations[1]); + Set qualifierAnnotations = getJsonAnnotations(parameterAnnotations[1]); return new AdapterMethod( parameterTypes[1], qualifierAnnotations, @@ -190,10 +190,10 @@ public void toJson(Moshi moshi, JsonWriter writer, @Nullable Object value) } else if (parameterTypes.length == 1 && returnType != void.class) { // List pointToJson(Point point) { - final Set returnTypeAnnotations = jsonAnnotations(method); + final Set returnTypeAnnotations = Util.getJsonAnnotations(method); final Set qualifierAnnotations = - jsonAnnotations(parameterAnnotations[0]); - boolean nullable = Util.hasNullable(parameterAnnotations[0]); + getJsonAnnotations(parameterAnnotations[0]); + boolean nullable = Util.getHasNullable(parameterAnnotations[0]); return new AdapterMethod( parameterTypes[0], qualifierAnnotations, @@ -251,7 +251,7 @@ private static boolean parametersAreJsonAdapters(int offset, Type[] parameterTyp static AdapterMethod fromAdapter(Object adapter, Method method) { method.setAccessible(true); final Type returnType = method.getGenericReturnType(); - final Set returnTypeAnnotations = jsonAnnotations(method); + final Set returnTypeAnnotations = Util.getJsonAnnotations(method); final Type[] parameterTypes = method.getGenericParameterTypes(); Annotation[][] parameterAnnotations = method.getParameterAnnotations(); @@ -273,8 +273,8 @@ public Object fromJson(Moshi moshi, JsonReader reader) } else if (parameterTypes.length == 1 && returnType != void.class) { // Point pointFromJson(List o) { final Set qualifierAnnotations = - jsonAnnotations(parameterAnnotations[0]); - boolean nullable = Util.hasNullable(parameterAnnotations[0]); + getJsonAnnotations(parameterAnnotations[0]); + boolean nullable = Util.getHasNullable(parameterAnnotations[0]); return new AdapterMethod( returnType, returnTypeAnnotations, adapter, method, parameterTypes.length, 1, nullable) { JsonAdapter delegate; @@ -354,7 +354,7 @@ public void bind(Moshi moshi, JsonAdapter.Factory factory) { Annotation[][] parameterAnnotations = method.getParameterAnnotations(); for (int i = adaptersOffset, size = parameterTypes.length; i < size; i++) { Type type = ((ParameterizedType) parameterTypes[i]).getActualTypeArguments()[0]; - Set jsonAnnotations = jsonAnnotations(parameterAnnotations[i]); + Set jsonAnnotations = getJsonAnnotations(parameterAnnotations[i]); jsonAdapters[i - adaptersOffset] = Types.equals(this.type, type) && annotations.equals(jsonAnnotations) ? moshi.nextAdapter(factory, type, jsonAnnotations) diff --git a/moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java b/moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java index 73b65e350..9eebb2ea1 100644 --- a/moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java +++ b/moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java @@ -140,7 +140,7 @@ private void createFieldBindings( // Look up a type adapter for this type. Type fieldType = resolve(type, rawType, field.getGenericType()); - Set annotations = Util.jsonAnnotations(field); + Set annotations = Util.getJsonAnnotations(field); String fieldName = field.getName(); JsonAdapter adapter = moshi.adapter(fieldType, annotations, fieldName); @@ -148,7 +148,7 @@ private void createFieldBindings( field.setAccessible(true); // Store it using the field's name. If there was already a field with this name, fail! - String jsonName = jsonName(fieldName, jsonAnnotation); + String jsonName = jsonName(jsonAnnotation, fieldName); FieldBinding fieldBinding = new FieldBinding<>(jsonName, field, adapter); FieldBinding replaced = fieldBindings.put(jsonName, fieldBinding); if (replaced != null) { diff --git a/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java b/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java index 000c85859..05dc09651 100644 --- a/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java +++ b/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java @@ -275,7 +275,7 @@ static final class EnumJsonAdapter> extends JsonAdapter { for (int i = 0; i < constants.length; i++) { T constant = constants[i]; String constantName = constant.name(); - nameStrings[i] = Util.jsonName(constantName, enumType.getField(constantName)); + nameStrings[i] = Util.jsonName(enumType.getField(constantName), constantName); } options = JsonReader.Options.of(nameStrings); } catch (NoSuchFieldException e) { diff --git a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt index 1b5d58f8f..3b568f50e 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt +++ b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt @@ -82,13 +82,13 @@ private val PRIMITIVE_TO_WRAPPER_TYPE: Map, Class<*>> = buildMap(16) { private val kotlinMetadataClassName: String get() = "kotlin.Metadata" -public fun jsonName(declaredName: String, element: AnnotatedElement): String { - return jsonName(declaredName, element.getAnnotation(Json::class.java)) +public fun AnnotatedElement.jsonName(declaredName: String): String { + return getAnnotation(Json::class.java).jsonName(declaredName) } -public fun jsonName(declaredName: String, annotation: Json?): String { - if (annotation == null) return declaredName - val annotationName: String = annotation.name +public fun Json?.jsonName(declaredName: String): String { + if (this == null) return declaredName + val annotationName: String = name return if (Json.UNSET_NAME == annotationName) declaredName else annotationName } @@ -97,28 +97,29 @@ public fun typesMatch(pattern: Type, candidate: Type): Boolean { return Types.equals(pattern, candidate) } -public fun jsonAnnotations(annotatedElement: AnnotatedElement): Set { - return jsonAnnotations(annotatedElement.annotations) -} +public val AnnotatedElement.jsonAnnotations: Set + get() = annotations.jsonAnnotations -public fun jsonAnnotations(annotations: Array): Set { - var result: MutableSet? = null - for (annotation in annotations) { - @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") - if ((annotation as java.lang.annotation.Annotation).annotationType().isAnnotationPresent(JsonQualifier::class.java)) { - if (result == null) result = LinkedHashSet() - result.add(annotation) +public val Array.jsonAnnotations: Set + get() { + var result: MutableSet? = null + for (annotation in this) { + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") + if ((annotation as java.lang.annotation.Annotation).annotationType() + .isAnnotationPresent(JsonQualifier::class.java) + ) { + if (result == null) result = LinkedHashSet() + result.add(annotation) + } } + return if (result != null) Collections.unmodifiableSet(result) else NO_ANNOTATIONS } - return if (result != null) Collections.unmodifiableSet(result) else NO_ANNOTATIONS -} -public fun isAnnotationPresent( - annotations: Set, +public fun Set.isAnnotationPresent( annotationClass: Class ): Boolean { - if (annotations.isEmpty()) return false // Save an iterator in the common case. - for (annotation in annotations) { + if (isEmpty()) return false // Save an iterator in the common case. + for (annotation in this) { @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") if ((annotation as java.lang.annotation.Annotation).annotationType() == annotationClass) return true } @@ -126,34 +127,36 @@ public fun isAnnotationPresent( } /** Returns true if `annotations` has any annotation whose simple name is Nullable. */ -public fun hasNullable(annotations: Array): Boolean { - for (annotation in annotations) { - @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") - if ((annotation as java.lang.annotation.Annotation).annotationType().simpleName == "Nullable") { - return true +public val Array.hasNullable: Boolean + get() { + for (annotation in this) { + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") + if ((annotation as java.lang.annotation.Annotation).annotationType().simpleName == "Nullable") { + return true + } } + return false } - return false -} /** * Returns true if `rawType` is built in. We don't reflect on private fields of platform * types because they're unspecified and likely to be different on Java vs. Android. */ -public fun isPlatformType(rawType: Class<*>): Boolean { - val name = rawType.name - return (name.startsWith("android.") - || name.startsWith("androidx.") - || name.startsWith("java.") - || name.startsWith("javax.") - || name.startsWith("kotlin.") - || name.startsWith("kotlinx.") - || name.startsWith("scala.")) -} +public val Class<*>.isPlatformType: Boolean + get() { + val name = name + return (name.startsWith("android.") + || name.startsWith("androidx.") + || name.startsWith("java.") + || name.startsWith("javax.") + || name.startsWith("kotlin.") + || name.startsWith("kotlinx.") + || name.startsWith("scala.")) + } /** Throws the cause of `e`, wrapping it if it is checked. */ -public fun rethrowCause(e: InvocationTargetException): RuntimeException { - val cause = e.targetException +public fun InvocationTargetException.rethrowCause(): RuntimeException { + val cause = targetException if (cause is RuntimeException) throw cause if (cause is Error) throw cause throw RuntimeException(cause) @@ -162,35 +165,33 @@ public fun rethrowCause(e: InvocationTargetException): RuntimeException { /** * Returns a type that is functionally equal but not necessarily equal according to [[Object.equals()]][Object.equals]. */ -public fun canonicalize(type: Type): Type { - return when (type) { +public fun Type.canonicalize(): Type { + return when (this) { is Class<*> -> { - if (type.isArray) GenericArrayTypeImpl(canonicalize(type.componentType)) else type + if (isArray) GenericArrayTypeImpl(this@canonicalize.componentType.canonicalize()) else this } is ParameterizedType -> { - if (type is ParameterizedTypeImpl) return type - ParameterizedTypeImpl(type.ownerType, type.rawType, *type.actualTypeArguments) + if (this is ParameterizedTypeImpl) return this + ParameterizedTypeImpl(ownerType, rawType, *actualTypeArguments) } is GenericArrayType -> { - if (type is GenericArrayTypeImpl) return type - GenericArrayTypeImpl(type.genericComponentType) + if (this is GenericArrayTypeImpl) return this + GenericArrayTypeImpl(genericComponentType) } is WildcardType -> { - if (type is WildcardTypeImpl) return type - WildcardTypeImpl(type.upperBounds, type.lowerBounds) - } - else -> { - type // This type is unsupported! + if (this is WildcardTypeImpl) return this + WildcardTypeImpl(upperBounds, lowerBounds) } + else -> this // This type is unsupported! } } /** If type is a "? extends X" wildcard, returns X; otherwise returns type unchanged. */ -public fun removeSubtypeWildcard(type: Type): Type { - if (type !is WildcardType) return type - val lowerBounds = type.lowerBounds - if (lowerBounds.isNotEmpty()) return type - val upperBounds = type.upperBounds +public fun Type.removeSubtypeWildcard(): Type { + if (this !is WildcardType) return this + val lowerBounds = lowerBounds + if (lowerBounds.isNotEmpty()) return this + val upperBounds = upperBounds require(upperBounds.size == 1) return upperBounds[0] } @@ -336,8 +337,8 @@ public val Any?.hashCodeOrZero: Int return this?.hashCode() ?: 0 } -public fun typeToString(type: Type): String { - return if (type is Class<*>) type.name else type.toString() +public fun Type.typeToString(): String { + return if (this is Class<*>) name else toString() } /** @@ -349,23 +350,19 @@ public fun declaringClassOf(typeVariable: TypeVariable<*>): Class<*>? { return if (genericDeclaration is Class<*>) genericDeclaration else null } -public fun checkNotPrimitive(type: Type) { - require(!(type is Class<*> && type.isPrimitive)) { "Unexpected primitive $type. Use the boxed type." } +public fun Type.checkNotPrimitive() { + require(!(this is Class<*> && isPrimitive)) { "Unexpected primitive ${this}. Use the boxed type." } } -public fun typeAnnotatedWithAnnotations( - type: Type, - annotations: Set -): String { - return type.toString() + if (annotations.isEmpty()) " (with no annotations)" else " annotated $annotations" +public fun Type.typeAnnotatedWithAnnotations(annotations: Set): String { + return toString() + if (annotations.isEmpty()) " (with no annotations)" else " annotated $annotations" } /** * Loads the generated JsonAdapter for classes annotated [JsonClass]. This works because it * uses the same naming conventions as `JsonClassCodeGenProcessor`. */ -public fun generatedAdapter( - moshi: Moshi, +public fun Moshi.generatedAdapter( type: Type, rawType: Class<*> ): JsonAdapter<*>? { @@ -386,7 +383,7 @@ public fun generatedAdapter( try { // Common case first constructor = adapterClass.getDeclaredConstructor(Moshi::class.java, Array::class.java) - args = arrayOf(moshi, typeArgs) + args = arrayOf(this, typeArgs) } catch (e: NoSuchMethodException) { constructor = adapterClass.getDeclaredConstructor(Array::class.java) args = arrayOf(typeArgs) @@ -395,7 +392,7 @@ public fun generatedAdapter( try { // Common case first constructor = adapterClass.getDeclaredConstructor(Moshi::class.java) - args = arrayOf(moshi) + args = arrayOf(this) } catch (e: NoSuchMethodException) { constructor = adapterClass.getDeclaredConstructor() args = emptyArray() @@ -426,43 +423,44 @@ public fun generatedAdapter( } catch (e: InstantiationException) { throw RuntimeException("Failed to instantiate the generated JsonAdapter for $type", e) } catch (e: InvocationTargetException) { - throw rethrowCause(e) + throw e.rethrowCause() } } -public fun isKotlin(targetClass: Class<*>): Boolean { - return METADATA != null && targetClass.isAnnotationPresent(METADATA) -} +public val Class<*>.isKotlin: Boolean + get() = METADATA != null && isAnnotationPresent(METADATA) /** * Reflectively looks up the defaults constructor of a kotlin class. * - * @param targetClass the target kotlin class to instantiate. + * @param this@lookupDefaultsConstructor the target kotlin class to instantiate. * @param T the type of `targetClass`. * @return the instantiated `targetClass` instance. */ -public fun lookupDefaultsConstructor(targetClass: Class): Constructor { +public fun Class.lookupDefaultsConstructor(): Constructor { checkNotNull(DEFAULT_CONSTRUCTOR_MARKER) { "DefaultConstructorMarker not on classpath. Make sure the Kotlin stdlib is on the classpath." } - val defaultConstructor = findConstructor(targetClass) + val defaultConstructor = findConstructor() defaultConstructor.isAccessible = true return defaultConstructor } -private fun findConstructor(targetClass: Class): Constructor { - for (constructor in targetClass.declaredConstructors) { +private fun Class.findConstructor(): Constructor { + for (constructor in declaredConstructors) { val paramTypes = constructor.parameterTypes if (paramTypes.isNotEmpty() && paramTypes[paramTypes.size - 1] == DEFAULT_CONSTRUCTOR_MARKER) { @Suppress("UNCHECKED_CAST") return constructor as Constructor } } - throw IllegalStateException("No defaults constructor found for $targetClass") + throw IllegalStateException("No defaults constructor found for ${this}") } public fun missingProperty( - propertyName: String, jsonName: String, reader: JsonReader + propertyName: String, + jsonName: String, + reader: JsonReader ): JsonDataException { val path = reader.path val message = if (jsonName == propertyName) { @@ -474,7 +472,9 @@ public fun missingProperty( } public fun unexpectedNull( - propertyName: String, jsonName: String, reader: JsonReader + propertyName: String, + jsonName: String, + reader: JsonReader ): JsonDataException { val path = reader.path val message: String = if (jsonName == propertyName) { @@ -486,11 +486,11 @@ public fun unexpectedNull( } // Public due to inline access in MoshiKotlinTypesExtensions -public fun boxIfPrimitive(type: Class): Class { +public fun Class.boxIfPrimitive(): Class { // cast is safe: long.class and Long.class are both of type Class @Suppress("UNCHECKED_CAST") - val wrapped = PRIMITIVE_TO_WRAPPER_TYPE[type] as Class? - return wrapped ?: type + val wrapped = PRIMITIVE_TO_WRAPPER_TYPE[this] as Class? + return wrapped ?: this } internal class ParameterizedTypeImpl( @@ -511,13 +511,13 @@ internal class ParameterizedTypeImpl( require(!(enclosingClass == null || ownerType.rawType != enclosingClass)) { "unexpected owner type for $rawType: $ownerType" } } else require(enclosingClass == null) { "unexpected owner type for $rawType: null" } } - this.ownerType = if (ownerType == null) null else canonicalize(ownerType) - this.rawType = canonicalize(rawType) + this.ownerType = ownerType?.canonicalize() + this.rawType = rawType.canonicalize() @Suppress("UNCHECKED_CAST") this.typeArguments = typeArguments.clone() as Array for (t in this.typeArguments.indices) { - checkNotPrimitive(this.typeArguments[t]) - this.typeArguments[t] = canonicalize(this.typeArguments[t]) + this.typeArguments[t].checkNotPrimitive() + this.typeArguments[t] = this.typeArguments[t].canonicalize() } } @@ -537,20 +537,20 @@ internal class ParameterizedTypeImpl( override fun toString(): String { val result = StringBuilder(30 * (typeArguments.size + 1)) - result.append(typeToString(rawType)) + result.append(rawType.typeToString()) if (typeArguments.isEmpty()) { return result.toString() } - result.append("<").append(typeToString(typeArguments[0])) + result.append("<").append(typeArguments[0].typeToString()) for (i in 1 until typeArguments.size) { - result.append(", ").append(typeToString(typeArguments[i])) + result.append(", ").append(typeArguments[i].typeToString()) } return result.append(">").toString() } } internal class GenericArrayTypeImpl(componentType: Type) : GenericArrayType { - private val componentType: Type = canonicalize(componentType) + private val componentType: Type = componentType.canonicalize() override fun getGenericComponentType() = componentType @@ -560,7 +560,7 @@ internal class GenericArrayTypeImpl(componentType: Type) : GenericArrayType { override fun hashCode() = componentType.hashCode() - override fun toString() = typeToString(componentType) + "[]" + override fun toString() = componentType.typeToString() + "[]" } /** @@ -576,14 +576,14 @@ internal class WildcardTypeImpl(upperBounds: Array, lowerBounds: Array, lowerBounds: Array "? super ${typeToString(lowerBound)}" + lowerBound != null -> "? super ${lowerBound.typeToString()}" upperBound === Any::class.java -> "?" - else -> "? extends ${typeToString(upperBound)}" + else -> "? extends ${upperBound.typeToString()}" } } } diff --git a/moshi/src/main/java16/com/squareup/moshi/RecordJsonAdapter.java b/moshi/src/main/java16/com/squareup/moshi/RecordJsonAdapter.java index 973ad8185..3419f941f 100644 --- a/moshi/src/main/java16/com/squareup/moshi/RecordJsonAdapter.java +++ b/moshi/src/main/java16/com/squareup/moshi/RecordJsonAdapter.java @@ -95,10 +95,10 @@ private static ComponentBinding createComponentBinding( MethodHandles.Lookup lookup, RecordComponent component) { var componentName = component.getName(); - var jsonName = Util.jsonName(componentName, component); + var jsonName = Util.jsonName(component, componentName); var componentType = Util.resolve(type, rawType, component.getGenericType()); - Set qualifiers = Util.jsonAnnotations(component); + Set qualifiers = Util.getJsonAnnotations(component); var adapter = moshi.adapter(componentType, qualifiers); MethodHandle accessor; diff --git a/moshi/src/test/java/com/squareup/moshi/MoshiTest.java b/moshi/src/test/java/com/squareup/moshi/MoshiTest.java index f00d52dde..ca7016dd0 100644 --- a/moshi/src/test/java/com/squareup/moshi/MoshiTest.java +++ b/moshi/src/test/java/com/squareup/moshi/MoshiTest.java @@ -819,7 +819,7 @@ public void delegatingJsonAdapterFactory() throws Exception { Moshi moshi = new Moshi.Builder().add(new UppercaseAdapterFactory()).build(); Field uppercaseString = MoshiTest.class.getDeclaredField("uppercaseString"); - Set annotations = Util.jsonAnnotations(uppercaseString); + Set annotations = Util.getJsonAnnotations(uppercaseString); JsonAdapter adapter = moshi.adapter(String.class, annotations).lenient(); assertThat(adapter.toJson("a")).isEqualTo("\"A\""); assertThat(adapter.fromJson("\"b\"")).isEqualTo("B"); @@ -869,7 +869,7 @@ public void collectionsDoNotKeepAnnotations() throws Exception { Field uppercaseStringsField = MoshiTest.class.getDeclaredField("uppercaseStrings"); try { moshi.adapter( - uppercaseStringsField.getGenericType(), Util.jsonAnnotations(uppercaseStringsField)); + uppercaseStringsField.getGenericType(), Util.getJsonAnnotations(uppercaseStringsField)); fail(); } catch (IllegalArgumentException expected) { assertThat(expected) @@ -886,7 +886,7 @@ public void noTypeAdapterForQualifiedPlatformType() throws Exception { Field uppercaseStringField = MoshiTest.class.getDeclaredField("uppercaseString"); try { moshi.adapter( - uppercaseStringField.getGenericType(), Util.jsonAnnotations(uppercaseStringField)); + uppercaseStringField.getGenericType(), Util.getJsonAnnotations(uppercaseStringField)); fail(); } catch (IllegalArgumentException expected) { assertThat(expected) From 65663f87748ff633b6b759a851d6df551be03f3e Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 11 Dec 2021 16:20:20 -0500 Subject: [PATCH 10/20] Use knownNotNull --- moshi/build.gradle.kts | 6 ++++-- .../squareup/moshi/internal/NonNullJsonAdapter.kt | 4 +++- .../main/java/com/squareup/moshi/internal/Util.kt | 12 ++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/moshi/build.gradle.kts b/moshi/build.gradle.kts index f82d45dd0..58142d625 100644 --- a/moshi/build.gradle.kts +++ b/moshi/build.gradle.kts @@ -52,10 +52,12 @@ tasks.withType().configureEach { tasks.withType() .configureEach { kotlinOptions { + val toAdd = mutableListOf("-Xopt-in=kotlin.RequiresOptIn") if (name.contains("test", true)) { - @Suppress("SuspiciousCollectionReassignment") // It's not suspicious - freeCompilerArgs += listOf("-Xopt-in=kotlin.ExperimentalStdlibApi") + toAdd += "-Xopt-in=kotlin.ExperimentalStdlibApi" } + @Suppress("SuspiciousCollectionReassignment") // It's not suspicious + freeCompilerArgs += toAdd } } diff --git a/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt b/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt index 0c5c8094d..e6884a395 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt +++ b/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt @@ -25,7 +25,9 @@ public class NonNullJsonAdapter(public val delegate: JsonAdapter) : JsonAd return if (reader.peek() == JsonReader.Token.NULL) { throw JsonDataException("Unexpected null at " + reader.path) } else { - delegate.fromJson(reader)!! + val result = delegate.fromJson(reader) + knownNotNull(result) + result } } diff --git a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt index 3b568f50e..e14bd7843 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt +++ b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt @@ -46,6 +46,8 @@ import java.lang.reflect.TypeVariable import java.lang.reflect.WildcardType import java.util.Collections import java.util.LinkedHashSet +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract @JvmField public val NO_ANNOTATIONS: Set = emptySet() @JvmField public val EMPTY_TYPE_ARRAY: Array = arrayOf() @@ -485,6 +487,16 @@ public fun unexpectedNull( return JsonDataException(message) } +// A sneaky way to mark value as known to be not null, allowing smart casts and skipping the null-check intrinsic +// Safe to use here because it's already compiled and not being used externally +@Suppress("NOTHING_TO_INLINE") +@OptIn(ExperimentalContracts::class) +public inline fun knownNotNull(value: T?) { + contract { + returns() implies (value != null) + } +} + // Public due to inline access in MoshiKotlinTypesExtensions public fun Class.boxIfPrimitive(): Class { // cast is safe: long.class and Long.class are both of type Class From 42dd474c579f6f4e28ed7a5cf7e98d0046cb7fa0 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 11 Dec 2021 16:22:52 -0500 Subject: [PATCH 11/20] Spotless --- .../moshi/internal/NonNullJsonAdapter.kt | 2 +- .../java/com/squareup/moshi/internal/Util.kt | 62 ++++++++++--------- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt b/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt index e6884a395..42a3a46d7 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt +++ b/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt @@ -16,8 +16,8 @@ package com.squareup.moshi.internal import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.JsonReader import com.squareup.moshi.JsonDataException +import com.squareup.moshi.JsonReader import com.squareup.moshi.JsonWriter public class NonNullJsonAdapter(public val delegate: JsonAdapter) : JsonAdapter() { diff --git a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt index e14bd7843..2b2009569 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt +++ b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt @@ -17,30 +17,30 @@ @file:Suppress("unused", "MemberVisibilityCanBePrivate") package com.squareup.moshi.internal -import java.lang.ClassNotFoundException -import java.lang.Void -import java.lang.reflect.AnnotatedElement import com.squareup.moshi.Json -import com.squareup.moshi.JsonQualifier -import java.lang.reflect.InvocationTargetException -import java.lang.RuntimeException -import java.lang.reflect.ParameterizedType -import java.lang.reflect.GenericArrayType -import com.squareup.moshi.Moshi import com.squareup.moshi.JsonAdapter import com.squareup.moshi.JsonClass -import java.lang.NoSuchMethodException -import java.lang.IllegalAccessException -import java.lang.InstantiationException -import java.lang.IllegalStateException -import com.squareup.moshi.JsonReader import com.squareup.moshi.JsonDataException +import com.squareup.moshi.JsonQualifier +import com.squareup.moshi.JsonReader +import com.squareup.moshi.Moshi import com.squareup.moshi.Types import com.squareup.moshi.asArrayType import com.squareup.moshi.rawType +import java.lang.ClassNotFoundException import java.lang.Error +import java.lang.IllegalAccessException +import java.lang.IllegalStateException +import java.lang.InstantiationException +import java.lang.NoSuchMethodException +import java.lang.RuntimeException import java.lang.StringBuilder +import java.lang.Void +import java.lang.reflect.AnnotatedElement import java.lang.reflect.Constructor +import java.lang.reflect.GenericArrayType +import java.lang.reflect.InvocationTargetException +import java.lang.reflect.ParameterizedType import java.lang.reflect.Type import java.lang.reflect.TypeVariable import java.lang.reflect.WildcardType @@ -108,7 +108,7 @@ public val Array.jsonAnnotations: Set for (annotation in this) { @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") if ((annotation as java.lang.annotation.Annotation).annotationType() - .isAnnotationPresent(JsonQualifier::class.java) + .isAnnotationPresent(JsonQualifier::class.java) ) { if (result == null) result = LinkedHashSet() result.add(annotation) @@ -147,13 +147,15 @@ public val Array.hasNullable: Boolean public val Class<*>.isPlatformType: Boolean get() { val name = name - return (name.startsWith("android.") - || name.startsWith("androidx.") - || name.startsWith("java.") - || name.startsWith("javax.") - || name.startsWith("kotlin.") - || name.startsWith("kotlinx.") - || name.startsWith("scala.")) + return ( + name.startsWith("android.") || + name.startsWith("androidx.") || + name.startsWith("java.") || + name.startsWith("javax.") || + name.startsWith("kotlin.") || + name.startsWith("kotlinx.") || + name.startsWith("scala.") + ) } /** Throws the cause of `e`, wrapping it if it is checked. */ @@ -353,7 +355,7 @@ public fun declaringClassOf(typeVariable: TypeVariable<*>): Class<*>? { } public fun Type.checkNotPrimitive() { - require(!(this is Class<*> && isPrimitive)) { "Unexpected primitive ${this}. Use the boxed type." } + require(!(this is Class<*> && isPrimitive)) { "Unexpected primitive $this. Use the boxed type." } } public fun Type.typeAnnotatedWithAnnotations(annotations: Set): String { @@ -407,12 +409,12 @@ public fun Moshi.generatedAdapter( } catch (e: NoSuchMethodException) { if (possiblyFoundAdapter != null && type !is ParameterizedType && possiblyFoundAdapter.typeParameters.isNotEmpty()) { throw RuntimeException( - "Failed to find the generated JsonAdapter constructor for '" - + type - + "'. Suspiciously, the type was not parameterized but the target class '" - + possiblyFoundAdapter.canonicalName - + "' is generic. Consider using " - + "Types#newParameterizedType() to define these missing type variables.", + "Failed to find the generated JsonAdapter constructor for '" + + type + + "'. Suspiciously, the type was not parameterized but the target class '" + + possiblyFoundAdapter.canonicalName + + "' is generic. Consider using " + + "Types#newParameterizedType() to define these missing type variables.", e ) } else { @@ -456,7 +458,7 @@ private fun Class.findConstructor(): Constructor { return constructor as Constructor } } - throw IllegalStateException("No defaults constructor found for ${this}") + throw IllegalStateException("No defaults constructor found for $this") } public fun missingProperty( From 0c1c3909c8f64e6358c718167314604422cd7f1c Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 11 Dec 2021 16:30:42 -0500 Subject: [PATCH 12/20] Clean up impl type classes a bit --- .../main/java/com/squareup/moshi/Types.java | 10 +- .../java/com/squareup/moshi/internal/Util.kt | 115 +++++++++++------- 2 files changed, 73 insertions(+), 52 deletions(-) diff --git a/moshi/src/main/java/com/squareup/moshi/Types.java b/moshi/src/main/java/com/squareup/moshi/Types.java index 716a5e2f4..41bf55737 100644 --- a/moshi/src/main/java/com/squareup/moshi/Types.java +++ b/moshi/src/main/java/com/squareup/moshi/Types.java @@ -110,7 +110,7 @@ public static ParameterizedType newParameterizedType(Type rawType, Type... typeA if (typeArguments.length == 0) { throw new IllegalArgumentException("Missing type arguments for " + rawType); } - return new ParameterizedTypeImpl(null, rawType, typeArguments); + return ParameterizedTypeImpl.create(null, rawType, typeArguments); } /** @@ -122,12 +122,12 @@ public static ParameterizedType newParameterizedTypeWithOwner( if (typeArguments.length == 0) { throw new IllegalArgumentException("Missing type arguments for " + rawType); } - return new ParameterizedTypeImpl(ownerType, rawType, typeArguments); + return ParameterizedTypeImpl.create(ownerType, rawType, typeArguments); } /** Returns an array type whose elements are all instances of {@code componentType}. */ public static GenericArrayType arrayOf(Type componentType) { - return new GenericArrayTypeImpl(componentType); + return GenericArrayTypeImpl.create(componentType); } /** @@ -143,7 +143,7 @@ public static WildcardType subtypeOf(Type bound) { } else { upperBounds = new Type[] {bound}; } - return new WildcardTypeImpl(upperBounds, EMPTY_TYPE_ARRAY); + return WildcardTypeImpl.create(upperBounds, EMPTY_TYPE_ARRAY); } /** @@ -157,7 +157,7 @@ public static WildcardType supertypeOf(Type bound) { } else { lowerBounds = new Type[] {bound}; } - return new WildcardTypeImpl(new Type[] {Object.class}, lowerBounds); + return WildcardTypeImpl.create(new Type[] {Object.class}, lowerBounds); } public static Class getRawType(Type type) { diff --git a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt index 2b2009569..6f58c8075 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt +++ b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt @@ -437,7 +437,7 @@ public val Class<*>.isKotlin: Boolean /** * Reflectively looks up the defaults constructor of a kotlin class. * - * @param this@lookupDefaultsConstructor the target kotlin class to instantiate. + * @receiver the target kotlin class to instantiate. * @param T the type of `targetClass`. * @return the instantiated `targetClass` instance. */ @@ -507,34 +507,12 @@ public fun Class.boxIfPrimitive(): Class { return wrapped ?: this } -internal class ParameterizedTypeImpl( - ownerType: Type?, - rawType: Type, - vararg typeArguments: Type -) : ParameterizedType { - private val ownerType: Type? - private val rawType: Type +internal class ParameterizedTypeImpl private constructor( + private val ownerType: Type?, + private val rawType: Type, @JvmField val typeArguments: Array - - init { - // Require an owner type if the raw type needs it. - if (rawType is Class<*>) { - val enclosingClass = rawType.enclosingClass - if (ownerType != null) { - require(!(enclosingClass == null || ownerType.rawType != enclosingClass)) { "unexpected owner type for $rawType: $ownerType" } - } else require(enclosingClass == null) { "unexpected owner type for $rawType: null" } - } - this.ownerType = ownerType?.canonicalize() - this.rawType = rawType.canonicalize() - @Suppress("UNCHECKED_CAST") - this.typeArguments = typeArguments.clone() as Array - for (t in this.typeArguments.indices) { - this.typeArguments[t].checkNotPrimitive() - this.typeArguments[t] = this.typeArguments[t].canonicalize() - } - } - +) : ParameterizedType { override fun getActualTypeArguments() = typeArguments.clone() override fun getRawType() = rawType @@ -561,11 +539,34 @@ internal class ParameterizedTypeImpl( } return result.append(">").toString() } -} -internal class GenericArrayTypeImpl(componentType: Type) : GenericArrayType { - private val componentType: Type = componentType.canonicalize() + companion object { + @JvmName("create") + @JvmStatic + operator fun invoke( + ownerType: Type?, + rawType: Type, + vararg typeArguments: Type + ): ParameterizedTypeImpl { + // Require an owner type if the raw type needs it. + if (rawType is Class<*>) { + val enclosingClass = rawType.enclosingClass + if (ownerType != null) { + require(!(enclosingClass == null || ownerType.rawType != enclosingClass)) { "unexpected owner type for $rawType: $ownerType" } + } else require(enclosingClass == null) { "unexpected owner type for $rawType: null" } + } + @Suppress("UNCHECKED_CAST") + val finalTypeArgs = typeArguments.clone() as Array + for (t in finalTypeArgs.indices) { + finalTypeArgs[t].checkNotPrimitive() + finalTypeArgs[t] = finalTypeArgs[t].canonicalize() + } + return ParameterizedTypeImpl(ownerType?.canonicalize(), rawType.canonicalize(), finalTypeArgs) + } + } +} +internal class GenericArrayTypeImpl private constructor(private val componentType: Type) : GenericArrayType { override fun getGenericComponentType() = componentType @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") @@ -575,6 +576,14 @@ internal class GenericArrayTypeImpl(componentType: Type) : GenericArrayType { override fun hashCode() = componentType.hashCode() override fun toString() = componentType.typeToString() + "[]" + + companion object { + @JvmName("create") + @JvmStatic + operator fun invoke(componentType: Type): GenericArrayTypeImpl { + return GenericArrayTypeImpl(componentType.canonicalize()) + } + } } /** @@ -582,24 +591,10 @@ internal class GenericArrayTypeImpl(componentType: Type) : GenericArrayType { * support what the Java 6 language needs - at most one bound. If a lower bound is set, the upper * bound must be Object.class. */ -internal class WildcardTypeImpl(upperBounds: Array, lowerBounds: Array) : WildcardType { - private val upperBound: Type +internal class WildcardTypeImpl private constructor( + private val upperBound: Type, private val lowerBound: Type? - - init { - require(lowerBounds.size <= 1) - require(upperBounds.size == 1) - if (lowerBounds.size == 1) { - lowerBounds[0].checkNotPrimitive() - require(!(upperBounds[0] !== Any::class.java)) - lowerBound = lowerBounds[0].canonicalize() - upperBound = Any::class.java - } else { - upperBounds[0].checkNotPrimitive() - lowerBound = null - upperBound = upperBounds[0].canonicalize() - } - } +) : WildcardType { override fun getUpperBounds() = arrayOf(upperBound) @@ -620,4 +615,30 @@ internal class WildcardTypeImpl(upperBounds: Array, lowerBounds: Array "? extends ${upperBound.typeToString()}" } } + + companion object { + @JvmStatic + @JvmName("create") + operator fun invoke( + upperBounds: Array, + lowerBounds: Array + ): WildcardTypeImpl { + require(lowerBounds.size <= 1) + require(upperBounds.size == 1) + return if (lowerBounds.size == 1) { + lowerBounds[0].checkNotPrimitive() + require(!(upperBounds[0] !== Any::class.java)) + WildcardTypeImpl( + lowerBound = lowerBounds[0].canonicalize(), + upperBound = Any::class.java + ) + } else { + upperBounds[0].checkNotPrimitive() + WildcardTypeImpl( + lowerBound = null, + upperBound = upperBounds[0].canonicalize() + ) + } + } + } } From baccb6cfe0539105b93c36c4cb0f5239f7aa7588 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 11 Dec 2021 16:40:59 -0500 Subject: [PATCH 13/20] Update code gen --- .../moshi/kotlin/codegen/api/AdapterGenerator.kt | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt b/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt index 9878ce672..d8c421ee1 100644 --- a/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt +++ b/moshi-kotlin-codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt @@ -39,7 +39,6 @@ import com.squareup.moshi.JsonAdapter import com.squareup.moshi.JsonReader import com.squareup.moshi.JsonWriter import com.squareup.moshi.Moshi -import com.squareup.moshi.internal.Util import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.ParameterOnly import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.ParameterProperty import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.PropertyOnly @@ -47,7 +46,7 @@ import java.lang.reflect.Constructor import java.lang.reflect.Type import org.objectweb.asm.Type as AsmType -private val MOSHI_UTIL = Util::class.asClassName() +private const val MOSHI_UTIL_PACKAGE = "com.squareup.moshi.internal" private const val TO_STRING_PREFIX = "GeneratedJsonAdapter(" private const val TO_STRING_SIZE_BASE = TO_STRING_PREFIX.length + 1 // 1 is the closing paren @@ -61,8 +60,8 @@ public class AdapterGenerator( private companion object { private val INT_TYPE_BLOCK = CodeBlock.of("%T::class.javaPrimitiveType", INT) private val DEFAULT_CONSTRUCTOR_MARKER_TYPE_BLOCK = CodeBlock.of( - "%T.DEFAULT_CONSTRUCTOR_MARKER", - Util::class + "%M", + MemberName(MOSHI_UTIL_PACKAGE, "DEFAULT_CONSTRUCTOR_MARKER") ) private val CN_MOSHI = Moshi::class.asClassName() private val CN_TYPE = Type::class.asClassName() @@ -656,8 +655,8 @@ public class AdapterGenerator( private fun unexpectedNull(property: PropertyGenerator, reader: ParameterSpec): CodeBlock { return CodeBlock.of( - "%T.unexpectedNull(%S, %S, %N)", - MOSHI_UTIL, + "%M(%S, %S, %N)", + MemberName(MOSHI_UTIL_PACKAGE, "unexpectedNull"), property.localName, property.jsonName, reader @@ -699,8 +698,8 @@ public class AdapterGenerator( private fun FunSpec.Builder.addMissingPropertyCheck(property: PropertyGenerator, readerParam: ParameterSpec) { val missingPropertyBlock = CodeBlock.of( - "%T.missingProperty(%S, %S, %N)", - MOSHI_UTIL, + "%M(%S, %S, %N)", + MemberName(MOSHI_UTIL_PACKAGE, "missingProperty"), property.localName, property.jsonName, readerParam From 187393a3fff01996b152b69c9ee40400564b0ddd Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sun, 12 Dec 2021 12:16:19 -0500 Subject: [PATCH 14/20] Use extension for resolving --- .../kotlin/codegen/GeneratedAdaptersTest.kt | 2 +- .../moshi/kotlin/reflect/KotlinJsonAdapter.kt | 21 +++++++++------- .../com/squareup/moshi/ClassJsonAdapter.java | 2 +- .../main/java/com/squareup/moshi/Types.java | 4 +-- .../java/com/squareup/moshi/internal/Util.kt | 25 +++++++++---------- .../com/squareup/moshi/RecordJsonAdapter.java | 2 +- .../moshi/RecursiveTypesResolveTest.java | 3 +-- 7 files changed, 30 insertions(+), 29 deletions(-) diff --git a/moshi-kotlin-tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt b/moshi-kotlin-tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt index e447b2bc5..f42dff60b 100644 --- a/moshi-kotlin-tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt +++ b/moshi-kotlin-tests/codegen-only/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest.kt @@ -1270,7 +1270,7 @@ class GeneratedAdaptersTest { @Test fun customGenerator_withClassPresent() { val moshi = Moshi.Builder().build() val adapter = moshi.adapter() - val unwrapped = (adapter as NullSafeJsonAdapter).delegate() + val unwrapped = (adapter as NullSafeJsonAdapter).delegate assertThat(unwrapped).isInstanceOf( GeneratedAdaptersTest_CustomGeneratedClassJsonAdapter::class.java ) diff --git a/moshi-kotlin/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt b/moshi-kotlin/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt index 0da0d91b5..11ff644ab 100644 --- a/moshi-kotlin/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt +++ b/moshi-kotlin/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt @@ -22,9 +22,12 @@ import com.squareup.moshi.JsonReader import com.squareup.moshi.JsonWriter import com.squareup.moshi.Moshi import com.squareup.moshi.Types -import com.squareup.moshi.internal.Util -import com.squareup.moshi.internal.Util.generatedAdapter -import com.squareup.moshi.internal.Util.resolve +import com.squareup.moshi.internal.generatedAdapter +import com.squareup.moshi.internal.isPlatformType +import com.squareup.moshi.internal.jsonAnnotations +import com.squareup.moshi.internal.missingProperty +import com.squareup.moshi.internal.resolve +import com.squareup.moshi.internal.unexpectedNull import com.squareup.moshi.rawType import java.lang.reflect.Modifier import java.lang.reflect.Type @@ -86,7 +89,7 @@ internal class KotlinJsonAdapter( values[propertyIndex] = binding.adapter.fromJson(reader) if (values[propertyIndex] == null && !binding.property.returnType.isMarkedNullable) { - throw Util.unexpectedNull( + throw unexpectedNull( binding.property.name, binding.jsonName, reader @@ -102,7 +105,7 @@ internal class KotlinJsonAdapter( when { constructor.parameters[i].isOptional -> isFullInitialized = false constructor.parameters[i].type.isMarkedNullable -> values[i] = null // Replace absent with null. - else -> throw Util.missingProperty( + else -> throw missingProperty( constructor.parameters[i].name, allBindings[i]?.jsonName, reader @@ -195,9 +198,9 @@ public class KotlinJsonAdapterFactory : JsonAdapter.Factory { if (rawType.isInterface) return null if (rawType.isEnum) return null if (!rawType.isAnnotationPresent(KOTLIN_METADATA)) return null - if (Util.isPlatformType(rawType)) return null + if (rawType.isPlatformType) return null try { - val generatedAdapter = generatedAdapter(moshi, type, rawType) + val generatedAdapter = moshi.generatedAdapter(type, rawType) if (generatedAdapter != null) { return generatedAdapter } @@ -288,10 +291,10 @@ public class KotlinJsonAdapterFactory : JsonAdapter.Factory { } else -> error("Not possible!") } - val resolvedPropertyType = resolve(type, rawType, propertyType) + val resolvedPropertyType = propertyType.resolve(type, rawType) val adapter = moshi.adapter( resolvedPropertyType, - Util.jsonAnnotations(allAnnotations.toTypedArray()), + allAnnotations.toTypedArray().jsonAnnotations, property.name ) diff --git a/moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java b/moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java index 9eebb2ea1..9437c4d78 100644 --- a/moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java +++ b/moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java @@ -139,7 +139,7 @@ private void createFieldBindings( if (jsonAnnotation != null && jsonAnnotation.ignore()) continue; // Look up a type adapter for this type. - Type fieldType = resolve(type, rawType, field.getGenericType()); + Type fieldType = resolve(field.getGenericType(), type, rawType); Set annotations = Util.getJsonAnnotations(field); String fieldName = field.getName(); JsonAdapter adapter = moshi.adapter(fieldType, annotations, fieldName); diff --git a/moshi/src/main/java/com/squareup/moshi/Types.java b/moshi/src/main/java/com/squareup/moshi/Types.java index 41bf55737..461890f79 100644 --- a/moshi/src/main/java/com/squareup/moshi/Types.java +++ b/moshi/src/main/java/com/squareup/moshi/Types.java @@ -362,12 +362,12 @@ static Type[] mapKeyAndValueTypes(Type context, Class contextRawType) { static Type getSupertype(Type context, Class contextRawType, Class supertype) { if (!supertype.isAssignableFrom(contextRawType)) throw new IllegalArgumentException(); return resolve( - context, contextRawType, getGenericSupertype(context, contextRawType, supertype)); + getGenericSupertype(context, contextRawType, supertype), context, contextRawType); } static Type getGenericSuperclass(Type type) { Class rawType = Types.getRawType(type); - return resolve(type, rawType, rawType.getGenericSuperclass()); + return resolve(rawType.getGenericSuperclass(), type, rawType); } /** diff --git a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt index 6f58c8075..084488184 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt +++ b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt @@ -200,18 +200,17 @@ public fun Type.removeSubtypeWildcard(): Type { return upperBounds[0] } -public fun resolve(context: Type, contextRawType: Class<*>, toResolve: Type): Type { - return resolve(context, contextRawType, toResolve, LinkedHashSet()) +public fun Type.resolve(context: Type, contextRawType: Class<*>): Type { + return this.resolve(context, contextRawType, LinkedHashSet()) } -private fun resolve( +private fun Type.resolve( context: Type, contextRawType: Class<*>, - toResolveInitial: Type, visitedTypeVariables: MutableCollection> ): Type { // This implementation is made a little more complicated in an attempt to avoid object-creation. - var toResolve = toResolveInitial + var toResolve = this while (true) { when { toResolve is TypeVariable<*> -> { @@ -228,27 +227,27 @@ private fun resolve( toResolve is Class<*> && toResolve.isArray -> { val original = toResolve val componentType: Type = original.componentType - val newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables) + val newComponentType = componentType.resolve(context, contextRawType, visitedTypeVariables) return if (componentType === newComponentType) original else newComponentType.asArrayType() } toResolve is GenericArrayType -> { val original = toResolve val componentType = original.genericComponentType - val newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables) + val newComponentType = componentType.resolve(context, contextRawType, visitedTypeVariables) return if (componentType === newComponentType) original else newComponentType.asArrayType() } toResolve is ParameterizedType -> { val original = toResolve val ownerType: Type? = original.ownerType val newOwnerType = ownerType?.let { - resolve(context, contextRawType, ownerType, visitedTypeVariables) + ownerType.resolve(context, contextRawType, visitedTypeVariables) } var changed = newOwnerType !== ownerType var args = original.actualTypeArguments var t = 0 val length = args.size while (t < length) { - val resolvedTypeArgument = resolve(context, contextRawType, args[t], visitedTypeVariables) + val resolvedTypeArgument = args[t].resolve(context, contextRawType, visitedTypeVariables) if (resolvedTypeArgument !== args[t]) { if (!changed) { args = args.clone() @@ -265,12 +264,12 @@ private fun resolve( val originalLowerBound = original.lowerBounds val originalUpperBound = original.upperBounds if (originalLowerBound.size == 1) { - val lowerBound = resolve(context, contextRawType, originalLowerBound[0], visitedTypeVariables) + val lowerBound = originalLowerBound[0].resolve(context, contextRawType, visitedTypeVariables) if (lowerBound !== originalLowerBound[0]) { return Types.supertypeOf(lowerBound) } } else if (originalUpperBound.size == 1) { - val upperBound = resolve(context, contextRawType, originalUpperBound[0], visitedTypeVariables) + val upperBound = originalUpperBound[0].resolve(context, contextRawType, visitedTypeVariables) if (upperBound !== originalUpperBound[0]) { return Types.subtypeOf(upperBound) } @@ -462,8 +461,8 @@ private fun Class.findConstructor(): Constructor { } public fun missingProperty( - propertyName: String, - jsonName: String, + propertyName: String?, + jsonName: String?, reader: JsonReader ): JsonDataException { val path = reader.path diff --git a/moshi/src/main/java16/com/squareup/moshi/RecordJsonAdapter.java b/moshi/src/main/java16/com/squareup/moshi/RecordJsonAdapter.java index 3419f941f..2e77e39b9 100644 --- a/moshi/src/main/java16/com/squareup/moshi/RecordJsonAdapter.java +++ b/moshi/src/main/java16/com/squareup/moshi/RecordJsonAdapter.java @@ -97,7 +97,7 @@ private static ComponentBinding createComponentBinding( var componentName = component.getName(); var jsonName = Util.jsonName(component, componentName); - var componentType = Util.resolve(type, rawType, component.getGenericType()); + var componentType = Util.resolve(component.getGenericType(), type, rawType); Set qualifiers = Util.getJsonAnnotations(component); var adapter = moshi.adapter(componentType, qualifiers); diff --git a/moshi/src/test/java/com/squareup/moshi/RecursiveTypesResolveTest.java b/moshi/src/test/java/com/squareup/moshi/RecursiveTypesResolveTest.java index a390606f5..0cef1a9a6 100644 --- a/moshi/src/test/java/com/squareup/moshi/RecursiveTypesResolveTest.java +++ b/moshi/src/test/java/com/squareup/moshi/RecursiveTypesResolveTest.java @@ -23,8 +23,7 @@ import org.junit.Test; /** - * Test fixes for infinite recursion on {@link Util#resolve(java.lang.reflect.Type, Class, - * java.lang.reflect.Type)}, described at Issue + * Test fixes for infinite recursion on {@link Util#resolve(java.lang.reflect.Type, java.lang.reflect.Type, Class)}, described at Issue * #440 and similar issues. * *

These tests originally caused {@link StackOverflowError} because of infinite recursion on From 111eda57c423784bf996fe285ba3adecf90c664b Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sun, 12 Dec 2021 12:37:06 -0500 Subject: [PATCH 15/20] Spotless --- moshi/src/main/java/com/squareup/moshi/Types.java | 2 +- .../java/com/squareup/moshi/RecursiveTypesResolveTest.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/moshi/src/main/java/com/squareup/moshi/Types.java b/moshi/src/main/java/com/squareup/moshi/Types.java index 461890f79..19332e7d7 100644 --- a/moshi/src/main/java/com/squareup/moshi/Types.java +++ b/moshi/src/main/java/com/squareup/moshi/Types.java @@ -362,7 +362,7 @@ static Type[] mapKeyAndValueTypes(Type context, Class contextRawType) { static Type getSupertype(Type context, Class contextRawType, Class supertype) { if (!supertype.isAssignableFrom(contextRawType)) throw new IllegalArgumentException(); return resolve( - getGenericSupertype(context, contextRawType, supertype), context, contextRawType); + getGenericSupertype(context, contextRawType, supertype), context, contextRawType); } static Type getGenericSuperclass(Type type) { diff --git a/moshi/src/test/java/com/squareup/moshi/RecursiveTypesResolveTest.java b/moshi/src/test/java/com/squareup/moshi/RecursiveTypesResolveTest.java index 0cef1a9a6..b1a020392 100644 --- a/moshi/src/test/java/com/squareup/moshi/RecursiveTypesResolveTest.java +++ b/moshi/src/test/java/com/squareup/moshi/RecursiveTypesResolveTest.java @@ -23,8 +23,9 @@ import org.junit.Test; /** - * Test fixes for infinite recursion on {@link Util#resolve(java.lang.reflect.Type, java.lang.reflect.Type, Class)}, described at Issue - * #440 and similar issues. + * Test fixes for infinite recursion on {@link Util#resolve(java.lang.reflect.Type, + * java.lang.reflect.Type, Class)}, described at Issue #440 and similar issues. * *

These tests originally caused {@link StackOverflowError} because of infinite recursion on * attempts to resolve generics on types, with an intermediate types like 'Foo2<? extends ? super From 624a2a9b4cc2b317a3856e66c3b54e8898f607bf Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sun, 26 Dec 2021 16:00:52 -0600 Subject: [PATCH 16/20] Fix master conflicts --- .../src/main/java/com/squareup/moshi/Moshi.kt | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/moshi/src/main/java/com/squareup/moshi/Moshi.kt b/moshi/src/main/java/com/squareup/moshi/Moshi.kt index 72b414efd..06c85a10b 100644 --- a/moshi/src/main/java/com/squareup/moshi/Moshi.kt +++ b/moshi/src/main/java/com/squareup/moshi/Moshi.kt @@ -16,12 +16,12 @@ package com.squareup.moshi import com.squareup.moshi.Types.createJsonQualifierImplementation -import com.squareup.moshi.internal.Util -import com.squareup.moshi.internal.Util.canonicalize -import com.squareup.moshi.internal.Util.isAnnotationPresent -import com.squareup.moshi.internal.Util.removeSubtypeWildcard -import com.squareup.moshi.internal.Util.typeAnnotatedWithAnnotations -import com.squareup.moshi.internal.Util.typesMatch +import com.squareup.moshi.internal.NO_ANNOTATIONS +import com.squareup.moshi.internal.canonicalize +import com.squareup.moshi.internal.isAnnotationPresent +import com.squareup.moshi.internal.removeSubtypeWildcard +import com.squareup.moshi.internal.typeAnnotatedWithAnnotations +import com.squareup.moshi.internal.typesMatch import java.lang.reflect.Type import javax.annotation.CheckReturnValue @@ -42,10 +42,10 @@ public class Moshi internal constructor(builder: Builder) { /** Returns a JSON adapter for `type`, creating it if necessary. */ @CheckReturnValue - public fun adapter(type: Type): JsonAdapter = adapter(type, Util.NO_ANNOTATIONS) + public fun adapter(type: Type): JsonAdapter = adapter(type, NO_ANNOTATIONS) @CheckReturnValue - public fun adapter(type: Class): JsonAdapter = adapter(type, Util.NO_ANNOTATIONS) + public fun adapter(type: Class): JsonAdapter = adapter(type, NO_ANNOTATIONS) @CheckReturnValue public fun adapter(type: Type, annotationType: Class): JsonAdapter = @@ -78,7 +78,7 @@ public class Moshi internal constructor(builder: Builder) { annotations: Set, fieldName: String? ): JsonAdapter { - val cleanedType = removeSubtypeWildcard(canonicalize(type)) + val cleanedType = type.canonicalize().removeSubtypeWildcard() // If there's an equivalent adapter in the cache, we're done! val cacheKey = cacheKey(cleanedType, annotations) @@ -107,7 +107,7 @@ public class Moshi internal constructor(builder: Builder) { success = true return result } - throw IllegalArgumentException("No JsonAdapter for ${typeAnnotatedWithAnnotations(type, annotations)}") + throw IllegalArgumentException("No JsonAdapter for ${type.typeAnnotatedWithAnnotations(annotations)}") } catch (e: IllegalArgumentException) { throw lookupChain.exceptionWithLookupStack(e) } finally { @@ -121,7 +121,7 @@ public class Moshi internal constructor(builder: Builder) { type: Type, annotations: Set ): JsonAdapter { - val cleanedType = removeSubtypeWildcard(canonicalize(type)) + val cleanedType = type.canonicalize().removeSubtypeWildcard() val skipPastIndex = factories.indexOf(skipPast) require(skipPastIndex != -1) { "Unable to skip past unknown factory $skipPast" } for (i in (skipPastIndex + 1) until factories.size) { @@ -129,7 +129,7 @@ public class Moshi internal constructor(builder: Builder) { val result = factories[i].create(cleanedType, annotations, this) as JsonAdapter? if (result != null) return result } - throw IllegalArgumentException("No next JsonAdapter for ${typeAnnotatedWithAnnotations(cleanedType, annotations)}") + throw IllegalArgumentException("No next JsonAdapter for ${cleanedType.typeAnnotatedWithAnnotations(annotations)}") } /** Returns a new builder containing all custom factories used by the current instance. */ @@ -354,7 +354,7 @@ public class Moshi internal constructor(builder: Builder) { require(annotation.isAnnotationPresent(JsonQualifier::class.java)) { "$annotation does not have @JsonQualifier" } require(annotation.declaredMethods.isEmpty()) { "Use JsonAdapter.Factory for annotations with elements" } return JsonAdapter.Factory { targetType, annotations, _ -> - if (typesMatch(type, targetType) && annotations.size == 1 && isAnnotationPresent(annotations, annotation)) { + if (typesMatch(type, targetType) && annotations.size == 1 && annotations.isAnnotationPresent(annotation)) { jsonAdapter } else { null From 4c605e48f24efb39de2c17b99a115fff73887f7d Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sun, 26 Dec 2021 16:01:15 -0600 Subject: [PATCH 17/20] Rename to toStringWithAnnotations() --- .../main/java/com/squareup/moshi/AdapterMethodsFactory.java | 4 ++-- moshi/src/main/java/com/squareup/moshi/Moshi.kt | 6 +++--- moshi/src/main/java/com/squareup/moshi/internal/Util.kt | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/moshi/src/main/java/com/squareup/moshi/AdapterMethodsFactory.java b/moshi/src/main/java/com/squareup/moshi/AdapterMethodsFactory.java index 942a50d6e..90ef68252 100644 --- a/moshi/src/main/java/com/squareup/moshi/AdapterMethodsFactory.java +++ b/moshi/src/main/java/com/squareup/moshi/AdapterMethodsFactory.java @@ -17,7 +17,7 @@ import static com.squareup.moshi.internal.Util.canonicalize; import static com.squareup.moshi.internal.Util.getJsonAnnotations; -import static com.squareup.moshi.internal.Util.typeAnnotatedWithAnnotations; +import static com.squareup.moshi.internal.Util.toStringWithAnnotations; import com.squareup.moshi.internal.Util; import java.io.IOException; @@ -57,7 +57,7 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory { "No " + missingAnnotation + " adapter for " - + typeAnnotatedWithAnnotations(type, annotations), + + toStringWithAnnotations(type, annotations), e); } } else { diff --git a/moshi/src/main/java/com/squareup/moshi/Moshi.kt b/moshi/src/main/java/com/squareup/moshi/Moshi.kt index 06c85a10b..9d2ce40fa 100644 --- a/moshi/src/main/java/com/squareup/moshi/Moshi.kt +++ b/moshi/src/main/java/com/squareup/moshi/Moshi.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.internal.NO_ANNOTATIONS import com.squareup.moshi.internal.canonicalize import com.squareup.moshi.internal.isAnnotationPresent import com.squareup.moshi.internal.removeSubtypeWildcard -import com.squareup.moshi.internal.typeAnnotatedWithAnnotations +import com.squareup.moshi.internal.toStringWithAnnotations import com.squareup.moshi.internal.typesMatch import java.lang.reflect.Type import javax.annotation.CheckReturnValue @@ -107,7 +107,7 @@ public class Moshi internal constructor(builder: Builder) { success = true return result } - throw IllegalArgumentException("No JsonAdapter for ${type.typeAnnotatedWithAnnotations(annotations)}") + throw IllegalArgumentException("No JsonAdapter for ${type.toStringWithAnnotations(annotations)}") } catch (e: IllegalArgumentException) { throw lookupChain.exceptionWithLookupStack(e) } finally { @@ -129,7 +129,7 @@ public class Moshi internal constructor(builder: Builder) { val result = factories[i].create(cleanedType, annotations, this) as JsonAdapter? if (result != null) return result } - throw IllegalArgumentException("No next JsonAdapter for ${cleanedType.typeAnnotatedWithAnnotations(annotations)}") + throw IllegalArgumentException("No next JsonAdapter for ${cleanedType.toStringWithAnnotations(annotations)}") } /** Returns a new builder containing all custom factories used by the current instance. */ diff --git a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt index 084488184..68b02bc0e 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt +++ b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt @@ -357,7 +357,7 @@ public fun Type.checkNotPrimitive() { require(!(this is Class<*> && isPrimitive)) { "Unexpected primitive $this. Use the boxed type." } } -public fun Type.typeAnnotatedWithAnnotations(annotations: Set): String { +public fun Type.toStringWithAnnotations(annotations: Set): String { return toString() + if (annotations.isEmpty()) " (with no annotations)" else " annotated $annotations" } From aa8831444be23101f166e756a12d522f8ea6942e Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Thu, 6 Jan 2022 14:48:41 -0500 Subject: [PATCH 18/20] for (t in args.indices) { --- moshi/src/main/java/com/squareup/moshi/internal/Util.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt index 68b02bc0e..4d8ccffac 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt +++ b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt @@ -244,9 +244,7 @@ private fun Type.resolve( } var changed = newOwnerType !== ownerType var args = original.actualTypeArguments - var t = 0 - val length = args.size - while (t < length) { + for (t in args.indices) { val resolvedTypeArgument = args[t].resolve(context, contextRawType, visitedTypeVariables) if (resolvedTypeArgument !== args[t]) { if (!changed) { @@ -255,7 +253,6 @@ private fun Type.resolve( } args[t] = resolvedTypeArgument } - t++ } return if (changed) ParameterizedTypeImpl(newOwnerType, original.rawType, *args) else original } From 71ca7075996e4c0657d5caf73d9bf5e7df01c088 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Thu, 6 Jan 2022 14:49:15 -0500 Subject: [PATCH 19/20] for (i in interfaces.indices) { --- moshi/src/main/java/com/squareup/moshi/internal/Util.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt index 4d8ccffac..1c641c8b1 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt +++ b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt @@ -303,15 +303,12 @@ public fun getGenericSupertype(context: Type, rawTypeInitial: Class<*>, toResolv // we skip searching through interfaces if unknown is an interface if (toResolve.isInterface) { val interfaces = rawType.interfaces - var i = 0 - val length = interfaces.size - while (i < length) { + for (i in interfaces.indices) { if (interfaces[i] == toResolve) { return rawType.genericInterfaces[i] } else if (toResolve.isAssignableFrom(interfaces[i])) { return getGenericSupertype(rawType.genericInterfaces[i], interfaces[i], toResolve) } - i++ } } From d44dc9bbbecf43c9fd614b46178b266d5599fdfe Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Thu, 6 Jan 2022 14:49:42 -0500 Subject: [PATCH 20/20] Template --- moshi/src/main/java/com/squareup/moshi/internal/Util.kt | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt index 1c641c8b1..77f629c31 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt +++ b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt @@ -402,12 +402,7 @@ public fun Moshi.generatedAdapter( } catch (e: NoSuchMethodException) { if (possiblyFoundAdapter != null && type !is ParameterizedType && possiblyFoundAdapter.typeParameters.isNotEmpty()) { throw RuntimeException( - "Failed to find the generated JsonAdapter constructor for '" + - type + - "'. Suspiciously, the type was not parameterized but the target class '" + - possiblyFoundAdapter.canonicalName + - "' is generic. Consider using " + - "Types#newParameterizedType() to define these missing type variables.", + "Failed to find the generated JsonAdapter constructor for '$type'. Suspiciously, the type was not parameterized but the target class '${possiblyFoundAdapter.canonicalName}' is generic. Consider using Types#newParameterizedType() to define these missing type variables.", e ) } else {