diff --git a/moshi/japicmp/build.gradle.kts b/moshi/japicmp/build.gradle.kts index b944a0c..a7c7433 100644 --- a/moshi/japicmp/build.gradle.kts +++ b/moshi/japicmp/build.gradle.kts @@ -30,6 +30,9 @@ val japicmp = tasks.register("japicmp") { "com.squareup.moshi.internal.NullSafeJsonAdapter", // Internal. "com.squareup.moshi.internal.Util" // Internal. ) + fieldExcludes = listOf( + "com.squareup.moshi.CollectionJsonAdapter#FACTORY" // False-positive, class is not public anyway + ) } tasks.named("check").configure { diff --git a/moshi/src/main/java/com/squareup/moshi/CollectionJsonAdapter.java b/moshi/src/main/java/com/squareup/moshi/CollectionJsonAdapter.java deleted file mode 100644 index 01bdf33..0000000 --- a/moshi/src/main/java/com/squareup/moshi/CollectionJsonAdapter.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2014 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.squareup.moshi; - -import java.io.IOException; -import java.lang.annotation.Annotation; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import javax.annotation.Nullable; - -/** Converts collection types to JSON arrays containing their converted contents. */ -abstract class CollectionJsonAdapter, T> extends JsonAdapter { - public static final JsonAdapter.Factory FACTORY = - new JsonAdapter.Factory() { - @Override - public @Nullable JsonAdapter create( - Type type, Set annotations, Moshi moshi) { - Class rawType = Types.getRawType(type); - if (!annotations.isEmpty()) return null; - if (rawType == List.class || rawType == Collection.class) { - return newArrayListAdapter(type, moshi).nullSafe(); - } else if (rawType == Set.class) { - return newLinkedHashSetAdapter(type, moshi).nullSafe(); - } - return null; - } - }; - - private final JsonAdapter elementAdapter; - - private CollectionJsonAdapter(JsonAdapter elementAdapter) { - this.elementAdapter = elementAdapter; - } - - static JsonAdapter> newArrayListAdapter(Type type, Moshi moshi) { - Type elementType = Types.collectionElementType(type, Collection.class); - JsonAdapter elementAdapter = moshi.adapter(elementType); - return new CollectionJsonAdapter, T>(elementAdapter) { - @Override - Collection newCollection() { - return new ArrayList<>(); - } - }; - } - - static JsonAdapter> newLinkedHashSetAdapter(Type type, Moshi moshi) { - Type elementType = Types.collectionElementType(type, Collection.class); - JsonAdapter elementAdapter = moshi.adapter(elementType); - return new CollectionJsonAdapter, T>(elementAdapter) { - @Override - Set newCollection() { - return new LinkedHashSet<>(); - } - }; - } - - abstract C newCollection(); - - @Override - public C fromJson(JsonReader reader) throws IOException { - C result = newCollection(); - reader.beginArray(); - while (reader.hasNext()) { - result.add(elementAdapter.fromJson(reader)); - } - reader.endArray(); - return result; - } - - @Override - public void toJson(JsonWriter writer, C value) throws IOException { - writer.beginArray(); - for (T element : value) { - elementAdapter.toJson(writer, element); - } - writer.endArray(); - } - - @Override - public String toString() { - return elementAdapter + ".collection()"; - } -} diff --git a/moshi/src/main/java/com/squareup/moshi/CollectionJsonAdapter.kt b/moshi/src/main/java/com/squareup/moshi/CollectionJsonAdapter.kt new file mode 100644 index 0000000..62f956f --- /dev/null +++ b/moshi/src/main/java/com/squareup/moshi/CollectionJsonAdapter.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2014 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.squareup.moshi + +import com.squareup.moshi.internal.markNotNull +import java.lang.reflect.Type + +/** Converts collection types to JSON arrays containing their converted contents. */ +internal abstract class CollectionJsonAdapter, T> private constructor( + private val elementAdapter: JsonAdapter +) : JsonAdapter() { + + abstract fun newCollection(): C + + override fun fromJson(reader: JsonReader): C { + val result = newCollection() + reader.beginArray() + while (reader.hasNext()) { + result.add(elementAdapter.fromJson(reader)) + } + reader.endArray() + return result + } + + override fun toJson(writer: JsonWriter, value: C?) { + markNotNull(value) // Always wrapped in nullSafe() + writer.beginArray() + for (element in value) { + elementAdapter.toJson(writer, element) + } + writer.endArray() + } + + override fun toString() = "$elementAdapter.collection()" + + companion object Factory : JsonAdapter.Factory { + override fun create(type: Type, annotations: Set, moshi: Moshi): JsonAdapter<*>? { + if (annotations.isNotEmpty()) return null + return when (type.rawType) { + List::class.java, Collection::class.java -> { + newArrayListAdapter(type, moshi).nullSafe() + } + Set::class.java -> { + newLinkedHashSetAdapter(type, moshi).nullSafe() + } + else -> null + } + } + + private fun newArrayListAdapter(type: Type, moshi: Moshi): JsonAdapter> { + val elementType = Types.collectionElementType(type, Collection::class.java) + val elementAdapter = moshi.adapter(elementType) + return object : CollectionJsonAdapter, T>(elementAdapter) { + override fun newCollection(): MutableCollection = ArrayList() + } + } + + private fun newLinkedHashSetAdapter(type: Type, moshi: Moshi): JsonAdapter> { + val elementType = Types.collectionElementType(type, Collection::class.java) + val elementAdapter = moshi.adapter(elementType) + return object : CollectionJsonAdapter, T>(elementAdapter) { + override fun newCollection(): MutableSet = LinkedHashSet() + } + } + } +} diff --git a/moshi/src/main/java/com/squareup/moshi/JsonUtf8Reader.kt b/moshi/src/main/java/com/squareup/moshi/JsonUtf8Reader.kt index b6fead1..5931924 100644 --- a/moshi/src/main/java/com/squareup/moshi/JsonUtf8Reader.kt +++ b/moshi/src/main/java/com/squareup/moshi/JsonUtf8Reader.kt @@ -15,6 +15,7 @@ */ package com.squareup.moshi +import com.squareup.moshi.internal.knownNotNull import okio.Buffer import okio.BufferedSource import okio.ByteString @@ -23,7 +24,6 @@ import okio.EOFException import okio.IOException import okio.buffer import java.math.BigDecimal -import kotlin.contracts.contract internal class JsonUtf8Reader : JsonReader { /** The input JSON. */ @@ -1098,19 +1098,3 @@ internal class JsonUtf8Reader : JsonReader { @Suppress("NOTHING_TO_INLINE") private inline fun Byte.asChar(): Char = toInt().toChar() - -// Sneaky backdoor way of marking a value as non-null to the compiler and skip the null-check intrinsic. -// Safe to use (unstable) contracts since they're gone in the final bytecode -// TODO move this to Util.kt after it's migrated to kotlin -@Suppress("NOTHING_TO_INLINE") -private inline fun markNotNull(value: T?) { - contract { - returns() implies (value != null) - } -} - -@Suppress("NOTHING_TO_INLINE") -private inline fun knownNotNull(value: T?): T { - markNotNull(value) - return value -} diff --git a/moshi/src/main/java/com/squareup/moshi/Moshi.kt b/moshi/src/main/java/com/squareup/moshi/Moshi.kt index 9d2ce40..c2044f8 100644 --- a/moshi/src/main/java/com/squareup/moshi/Moshi.kt +++ b/moshi/src/main/java/com/squareup/moshi/Moshi.kt @@ -330,7 +330,7 @@ public class Moshi internal constructor(builder: Builder) { @JvmField val BUILT_IN_FACTORIES: List = buildList(6) { add(StandardJsonAdapters.FACTORY) - add(CollectionJsonAdapter.FACTORY) + add(CollectionJsonAdapter.Factory) add(MapJsonAdapter.FACTORY) add(ArrayJsonAdapter.FACTORY) add(RecordJsonAdapter.FACTORY) 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 42a3a46..0b47133 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt +++ b/moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.kt @@ -25,9 +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 { - val result = delegate.fromJson(reader) - knownNotNull(result) - result + knownNotNull(delegate.fromJson(reader)) } } 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 77f629c..a48be09 100644 --- a/moshi/src/main/java/com/squareup/moshi/internal/Util.kt +++ b/moshi/src/main/java/com/squareup/moshi/internal/Util.kt @@ -46,7 +46,6 @@ 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() @@ -477,16 +476,21 @@ 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 +// Sneaky backdoor way of marking a value as non-null to the compiler and skip the null-check intrinsic. +// Safe to use (unstable) contracts since they're gone in the final bytecode @Suppress("NOTHING_TO_INLINE") -@OptIn(ExperimentalContracts::class) -public inline fun knownNotNull(value: T?) { +internal inline fun markNotNull(value: T?) { contract { returns() implies (value != null) } } +@Suppress("NOTHING_TO_INLINE") +internal inline fun knownNotNull(value: T?): T { + markNotNull(value) + return value +} + // 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