mirror of
https://github.com/fankes/moshi.git
synced 2025-10-19 16:09:21 +08:00
Implement reflective support for Java Records (#1381)
* Standardize around JDK 8 * Update GJF to support newer JDKs * Fix misc java 8 issues in tests * Prepare java 16/records checking at runtime * Implement real RecordJsonAdapter * Spotless * Prepare build for JDK 16+ * Fix property name for kapt * Small cleanup * Make FallbackEnum java-8 happy * Remove animalsniffer * Fix format * Add opens for ExtendsPlatformClassWithProtectedFields * Return null every time in shim for main tests * Use JDK 16 + release 8 to replace animalsniffer * Simplify accessor accessible handling * Remove manifest attrs * Fix typo * Fix KCT tests + upgrade it * Cover another * Try explicit kotlin daemon args for java 17? * Disable 17-ea for now until kotlin 1.5.30 * Add JsonQualifier and Json(name) tests + fix qualifiers * Ensure constructor is accessible * GJF it properly * GJF 1.11 * Unwrap InvocationTargetException * Use MethodHandle for constructor * Use MethodHandle for accessor too * Revert "Remove manifest attrs" This reverts commit 3eb768fd6904bb5c979aa01c3c182e0fb9329d62. * Proper MR jar * *actually* fix GJF, which wasn't getting applied before We can just enable this everywhere now since we require JDK 16 anyway * Make IDE happy about modules access * Fixup records tests to play nice with modules Gotta be public * Add complex smoke test * Remove comment Not a regression test in this case
This commit is contained in:
11
.github/workflows/build.yml
vendored
11
.github/workflows/build.yml
vendored
@@ -10,14 +10,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
java-version:
|
java-version:
|
||||||
- 8
|
- 16
|
||||||
- 9
|
|
||||||
- 10
|
|
||||||
- 11
|
|
||||||
- 12
|
|
||||||
- 13
|
|
||||||
- 14
|
|
||||||
- 15
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -46,7 +39,7 @@ jobs:
|
|||||||
run: ./gradlew build check --stacktrace
|
run: ./gradlew build check --stacktrace
|
||||||
|
|
||||||
- name: Publish (default branch only)
|
- name: Publish (default branch only)
|
||||||
if: github.repository == 'square/moshi' && github.ref == 'refs/heads/master' && matrix.java-version == '8'
|
if: github.repository == 'square/moshi' && github.ref == 'refs/heads/master' && matrix.java-version == '16'
|
||||||
run: ./gradlew uploadArchives
|
run: ./gradlew uploadArchives
|
||||||
env:
|
env:
|
||||||
ORG_GRADLE_PROJECT_SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
|
ORG_GRADLE_PROJECT_SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
|
||||||
|
@@ -14,18 +14,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
id("com.vanniktech.maven.publish")
|
id("com.vanniktech.maven.publish")
|
||||||
id("ru.vyarus.animalsniffer")
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.withType<KotlinCompile>().configureEach {
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = "1.6"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@@ -33,7 +33,6 @@ plugins {
|
|||||||
id("com.vanniktech.maven.publish") version "0.14.2" apply false
|
id("com.vanniktech.maven.publish") version "0.14.2" apply false
|
||||||
id("org.jetbrains.dokka") version "1.4.32" apply false
|
id("org.jetbrains.dokka") version "1.4.32" apply false
|
||||||
id("com.diffplug.spotless") version "5.12.4"
|
id("com.diffplug.spotless") version "5.12.4"
|
||||||
id("ru.vyarus.animalsniffer") version "1.5.3" apply false
|
|
||||||
id("me.champeau.gradle.japicmp") version "0.2.9" apply false
|
id("me.champeau.gradle.japicmp") version "0.2.9" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,8 +43,6 @@ spotless {
|
|||||||
indentWithSpaces(2)
|
indentWithSpaces(2)
|
||||||
endWithNewline()
|
endWithNewline()
|
||||||
}
|
}
|
||||||
// GJF not compatible with JDK 15 yet
|
|
||||||
if (!JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_15)) {
|
|
||||||
val externalJavaFiles = arrayOf(
|
val externalJavaFiles = arrayOf(
|
||||||
"**/ClassFactory.java",
|
"**/ClassFactory.java",
|
||||||
"**/Iso8601Utils.java",
|
"**/Iso8601Utils.java",
|
||||||
@@ -70,7 +67,7 @@ spotless {
|
|||||||
"**/TypesTest.java"
|
"**/TypesTest.java"
|
||||||
)
|
)
|
||||||
val configureCommonJavaFormat: JavaExtension.() -> Unit = {
|
val configureCommonJavaFormat: JavaExtension.() -> Unit = {
|
||||||
googleJavaFormat("1.7")
|
googleJavaFormat("1.11.0")
|
||||||
}
|
}
|
||||||
java {
|
java {
|
||||||
configureCommonJavaFormat()
|
configureCommonJavaFormat()
|
||||||
@@ -88,7 +85,6 @@ spotless {
|
|||||||
configureCommonJavaFormat()
|
configureCommonJavaFormat()
|
||||||
target(*externalJavaFiles)
|
target(*externalJavaFiles)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
kotlin {
|
kotlin {
|
||||||
ktlint(Dependencies.ktlintVersion).userData(mapOf("indent_size" to "2"))
|
ktlint(Dependencies.ktlintVersion).userData(mapOf("indent_size" to "2"))
|
||||||
target("**/*.kt")
|
target("**/*.kt")
|
||||||
@@ -129,15 +125,14 @@ subprojects {
|
|||||||
// Apply with "java" instead of just "java-library" so kotlin projects get it too
|
// Apply with "java" instead of just "java-library" so kotlin projects get it too
|
||||||
pluginManager.withPlugin("java") {
|
pluginManager.withPlugin("java") {
|
||||||
configure<JavaPluginExtension> {
|
configure<JavaPluginExtension> {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_7
|
toolchain {
|
||||||
targetCompatibility = JavaVersion.VERSION_1_7
|
languageVersion.set(JavaLanguageVersion.of(16))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (project.name != "records-tests") {
|
||||||
pluginManager.withPlugin("ru.vyarus.animalsniffer") {
|
tasks.withType<JavaCompile>().configureEach {
|
||||||
dependencies {
|
options.release.set(8)
|
||||||
"compileOnly"(Dependencies.AnimalSniffer.annotations)
|
}
|
||||||
"signature"(Dependencies.AnimalSniffer.java7Signature)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,6 +141,7 @@ subprojects {
|
|||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
@Suppress("SuspiciousCollectionReassignment")
|
@Suppress("SuspiciousCollectionReassignment")
|
||||||
freeCompilerArgs += listOf("-progressive")
|
freeCompilerArgs += listOf("-progressive")
|
||||||
|
jvmTarget = "1.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,11 +21,6 @@ object Dependencies {
|
|||||||
const val ktlintVersion = "0.41.0"
|
const val ktlintVersion = "0.41.0"
|
||||||
const val okio = "com.squareup.okio:okio:2.10.0"
|
const val okio = "com.squareup.okio:okio:2.10.0"
|
||||||
|
|
||||||
object AnimalSniffer {
|
|
||||||
const val annotations = "org.codehaus.mojo:animal-sniffer-annotations:1.16"
|
|
||||||
const val java7Signature = "org.codehaus.mojo.signature:java17:1.0@signature"
|
|
||||||
}
|
|
||||||
|
|
||||||
object AutoService {
|
object AutoService {
|
||||||
private const val version = "1.0"
|
private const val version = "1.0"
|
||||||
const val annotations = "com.google.auto.service:auto-service-annotations:$version"
|
const val annotations = "com.google.auto.service:auto-service-annotations:$version"
|
||||||
@@ -53,7 +48,7 @@ object Dependencies {
|
|||||||
|
|
||||||
object Testing {
|
object Testing {
|
||||||
const val assertj = "org.assertj:assertj-core:3.11.1"
|
const val assertj = "org.assertj:assertj-core:3.11.1"
|
||||||
const val compileTesting = "com.github.tschuchortdev:kotlin-compile-testing:1.4.0"
|
const val compileTesting = "com.github.tschuchortdev:kotlin-compile-testing:1.4.3"
|
||||||
const val junit = "junit:junit:4.13.2"
|
const val junit = "junit:junit:4.13.2"
|
||||||
const val truth = "com.google.truth:truth:1.0.1"
|
const val truth = "com.google.truth:truth:1.0.1"
|
||||||
}
|
}
|
||||||
|
@@ -58,9 +58,9 @@ final class FallbackEnum {
|
|||||||
if (!(annotation instanceof Fallback)) {
|
if (!(annotation instanceof Fallback)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Class<Enum> enumType = (Class<Enum>) rawType;
|
//noinspection rawtypes
|
||||||
Enum<?> fallback = Enum.valueOf(enumType, ((Fallback) annotation).value());
|
return new FallbackEnumJsonAdapter<>(
|
||||||
return new FallbackEnumJsonAdapter<>(enumType, fallback);
|
(Class<? extends Enum>) rawType, ((Fallback) annotation).value());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -70,9 +70,9 @@ final class FallbackEnum {
|
|||||||
final JsonReader.Options options;
|
final JsonReader.Options options;
|
||||||
final T defaultValue;
|
final T defaultValue;
|
||||||
|
|
||||||
FallbackEnumJsonAdapter(Class<T> enumType, T defaultValue) {
|
FallbackEnumJsonAdapter(Class<T> enumType, String fallbackName) {
|
||||||
this.enumType = enumType;
|
this.enumType = enumType;
|
||||||
this.defaultValue = defaultValue;
|
this.defaultValue = Enum.valueOf(enumType, fallbackName);
|
||||||
try {
|
try {
|
||||||
constants = enumType.getEnumConstants();
|
constants = enumType.getEnumConstants();
|
||||||
nameStrings = new String[constants.length];
|
nameStrings = new String[constants.length];
|
||||||
|
@@ -15,9 +15,36 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
# For Dokka https://github.com/Kotlin/dokka/issues/1405
|
# For Dokka https://github.com/Kotlin/dokka/issues/1405
|
||||||
org.gradle.jvmargs=-XX:MaxMetaspaceSize=512m
|
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 \
|
||||||
|
--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \
|
||||||
|
--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \
|
||||||
|
--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED \
|
||||||
|
--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED \
|
||||||
|
--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED \
|
||||||
|
--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED \
|
||||||
|
--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \
|
||||||
|
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
|
||||||
|
--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \
|
||||||
|
--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \
|
||||||
|
--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \
|
||||||
|
--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
|
||||||
|
|
||||||
kapt.includeCompileClasspath=false
|
# TODO move this to DSL in Kotlin 1.5.30 https://youtrack.jetbrains.com/issue/KT-44266
|
||||||
|
kotlin.daemon.jvmargs=-Dfile.encoding=UTF-8 \
|
||||||
|
--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \
|
||||||
|
--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \
|
||||||
|
--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED \
|
||||||
|
--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED \
|
||||||
|
--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED \
|
||||||
|
--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED \
|
||||||
|
--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \
|
||||||
|
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
|
||||||
|
--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \
|
||||||
|
--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \
|
||||||
|
--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \
|
||||||
|
--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
|
||||||
|
|
||||||
|
kapt.include.compile.classpath=false
|
||||||
|
|
||||||
GROUP=com.squareup.moshi
|
GROUP=com.squareup.moshi
|
||||||
VERSION_NAME=1.13.0-SNAPSHOT
|
VERSION_NAME=1.13.0-SNAPSHOT
|
||||||
|
@@ -27,7 +27,6 @@ plugins {
|
|||||||
|
|
||||||
tasks.withType<KotlinCompile>().configureEach {
|
tasks.withType<KotlinCompile>().configureEach {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
|
||||||
@Suppress("SuspiciousCollectionReassignment")
|
@Suppress("SuspiciousCollectionReassignment")
|
||||||
freeCompilerArgs += listOf(
|
freeCompilerArgs += listOf(
|
||||||
"-Xopt-in=com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview"
|
"-Xopt-in=com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview"
|
||||||
@@ -35,10 +34,20 @@ tasks.withType<KotlinCompile>().configureEach {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// To make Gradle happy
|
tasks.withType<Test>().configureEach {
|
||||||
java {
|
// For kapt to work with kotlin-compile-testing
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
jvmArgs(
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
"--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
|
||||||
|
"--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
|
||||||
|
"--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
|
||||||
|
"--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
|
||||||
|
"--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED",
|
||||||
|
"--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
|
||||||
|
"--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED",
|
||||||
|
"--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED",
|
||||||
|
"--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
|
||||||
|
"--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val shade: Configuration = configurations.maybeCreate("compileShaded")
|
val shade: Configuration = configurations.maybeCreate("compileShaded")
|
||||||
|
@@ -21,6 +21,11 @@ plugins {
|
|||||||
kotlin("kapt")
|
kotlin("kapt")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.withType<Test>().configureEach {
|
||||||
|
// ExtendsPlatformClassWithProtectedField tests a case where we set a protected ByteArrayOutputStream.buf field
|
||||||
|
jvmArgs("--add-opens=java.base/java.io=ALL-UNNAMED")
|
||||||
|
}
|
||||||
|
|
||||||
tasks.withType<KotlinCompile>().configureEach {
|
tasks.withType<KotlinCompile>().configureEach {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
@Suppress("SuspiciousCollectionReassignment")
|
@Suppress("SuspiciousCollectionReassignment")
|
||||||
|
@@ -19,14 +19,49 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
id("com.vanniktech.maven.publish")
|
id("com.vanniktech.maven.publish")
|
||||||
id("ru.vyarus.animalsniffer")
|
}
|
||||||
|
|
||||||
|
val mainSourceSet by sourceSets.named("main")
|
||||||
|
val java16 by sourceSets.creating {
|
||||||
|
java {
|
||||||
|
srcDir("src/main/java16")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named<JavaCompile>("compileJava16Java") {
|
||||||
|
javaCompiler.set(
|
||||||
|
javaToolchains.compilerFor {
|
||||||
|
languageVersion.set(JavaLanguageVersion.of(16))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
options.release.set(16)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Package our actual RecordJsonAdapter from java16 sources in and denote it as an MRJAR
|
||||||
|
tasks.named<Jar>("jar") {
|
||||||
|
from(java16.output) {
|
||||||
|
into("META-INF/versions/16")
|
||||||
|
}
|
||||||
|
manifest {
|
||||||
|
attributes("Multi-Release" to "true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
"java16Implementation" {
|
||||||
|
extendsFrom(api.get())
|
||||||
|
extendsFrom(implementation.get())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<Test>().configureEach {
|
||||||
|
// ExtendsPlatformClassWithProtectedField tests a case where we set a protected ByteArrayOutputStream.buf field
|
||||||
|
jvmArgs("--add-opens=java.base/java.io=ALL-UNNAMED")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<KotlinCompile>()
|
tasks.withType<KotlinCompile>()
|
||||||
.configureEach {
|
.configureEach {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.6"
|
|
||||||
|
|
||||||
if (name.contains("test", true)) {
|
if (name.contains("test", true)) {
|
||||||
@Suppress("SuspiciousCollectionReassignment") // It's not suspicious
|
@Suppress("SuspiciousCollectionReassignment") // It's not suspicious
|
||||||
freeCompilerArgs += listOf("-Xopt-in=kotlin.ExperimentalStdlibApi")
|
freeCompilerArgs += listOf("-Xopt-in=kotlin.ExperimentalStdlibApi")
|
||||||
@@ -35,6 +70,8 @@ tasks.withType<KotlinCompile>()
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
// So the j16 source set can "see" main Moshi sources
|
||||||
|
"java16Implementation"(mainSourceSet.output)
|
||||||
compileOnly(Dependencies.jsr305)
|
compileOnly(Dependencies.jsr305)
|
||||||
api(Dependencies.okio)
|
api(Dependencies.okio)
|
||||||
|
|
||||||
|
30
moshi/records-tests/build.gradle.kts
Normal file
30
moshi/records-tests/build.gradle.kts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
`java-library`
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<JavaCompile>().configureEach {
|
||||||
|
options.release.set(16)
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testImplementation(project(":moshi"))
|
||||||
|
testCompileOnly(Dependencies.jsr305)
|
||||||
|
testImplementation(Dependencies.Testing.junit)
|
||||||
|
testImplementation(Dependencies.Testing.truth)
|
||||||
|
}
|
@@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 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.records;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
import com.squareup.moshi.FromJson;
|
||||||
|
import com.squareup.moshi.Json;
|
||||||
|
import com.squareup.moshi.JsonQualifier;
|
||||||
|
import com.squareup.moshi.Moshi;
|
||||||
|
import com.squareup.moshi.ToJson;
|
||||||
|
import com.squareup.moshi.Types;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public final class RecordsTest {
|
||||||
|
|
||||||
|
private final Moshi moshi = new Moshi.Builder().build();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void smokeTest() throws IOException {
|
||||||
|
var stringAdapter = moshi.adapter(String.class);
|
||||||
|
var adapter =
|
||||||
|
moshi
|
||||||
|
.newBuilder()
|
||||||
|
.add(CharSequence.class, stringAdapter)
|
||||||
|
.add(Types.subtypeOf(CharSequence.class), stringAdapter)
|
||||||
|
.add(Types.supertypeOf(CharSequence.class), stringAdapter)
|
||||||
|
.build()
|
||||||
|
.adapter(SmokeTestType.class);
|
||||||
|
var instance =
|
||||||
|
new SmokeTestType(
|
||||||
|
"John",
|
||||||
|
"Smith",
|
||||||
|
25,
|
||||||
|
List.of("American"),
|
||||||
|
70.5f,
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
List.of("super wildcards!"),
|
||||||
|
List.of("extend wildcards!"),
|
||||||
|
List.of("unbounded"),
|
||||||
|
List.of("objectList"),
|
||||||
|
new int[] {1, 2, 3},
|
||||||
|
new String[] {"fav", "arrays"},
|
||||||
|
Map.of("italian", "pasta"),
|
||||||
|
Set.of(List.of(Map.of("someKey", new int[] {1}))),
|
||||||
|
new Map[] {Map.of("Hello", "value")});
|
||||||
|
var json = adapter.toJson(instance);
|
||||||
|
var deserialized = adapter.fromJson(json);
|
||||||
|
assertThat(deserialized).isEqualTo(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record SmokeTestType(
|
||||||
|
@Json(name = "first_name") String firstName,
|
||||||
|
@Json(name = "last_name") String lastName,
|
||||||
|
int age,
|
||||||
|
List<String> nationalities,
|
||||||
|
float weight,
|
||||||
|
Boolean tattoos, // Boxed primitive test
|
||||||
|
boolean hasChildren,
|
||||||
|
List<? super CharSequence> superWildcard,
|
||||||
|
List<? extends CharSequence> extendsWildcard,
|
||||||
|
List<?> unboundedWildcard,
|
||||||
|
List<Object> objectList,
|
||||||
|
int[] favoriteThreeNumbers,
|
||||||
|
String[] favoriteArrayValues,
|
||||||
|
Map<String, String> foodPreferences,
|
||||||
|
Set<List<Map<String, int[]>>> setListMapArrayInt,
|
||||||
|
Map<String, Object>[] nestedArray) {
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
SmokeTestType that = (SmokeTestType) o;
|
||||||
|
return age == that.age
|
||||||
|
&& Float.compare(that.weight, weight) == 0
|
||||||
|
&& hasChildren == that.hasChildren
|
||||||
|
&& firstName.equals(that.firstName)
|
||||||
|
&& lastName.equals(that.lastName)
|
||||||
|
&& nationalities.equals(that.nationalities)
|
||||||
|
&& Objects.equals(tattoos, that.tattoos)
|
||||||
|
&& superWildcard.equals(that.superWildcard)
|
||||||
|
&& extendsWildcard.equals(that.extendsWildcard)
|
||||||
|
&& unboundedWildcard.equals(that.unboundedWildcard)
|
||||||
|
&& objectList.equals(that.objectList)
|
||||||
|
&& Arrays.equals(favoriteThreeNumbers, that.favoriteThreeNumbers)
|
||||||
|
&& Arrays.equals(favoriteArrayValues, that.favoriteArrayValues)
|
||||||
|
&& foodPreferences.equals(that.foodPreferences)
|
||||||
|
// && setListMapArrayInt.equals(that.setListMapArrayInt) // Nested array equality doesn't
|
||||||
|
// carry over
|
||||||
|
&& Arrays.equals(nestedArray, that.nestedArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result =
|
||||||
|
Objects.hash(
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
age,
|
||||||
|
nationalities,
|
||||||
|
weight,
|
||||||
|
tattoos,
|
||||||
|
hasChildren,
|
||||||
|
superWildcard,
|
||||||
|
extendsWildcard,
|
||||||
|
unboundedWildcard,
|
||||||
|
objectList,
|
||||||
|
foodPreferences,
|
||||||
|
setListMapArrayInt);
|
||||||
|
result = 31 * result + Arrays.hashCode(favoriteThreeNumbers);
|
||||||
|
result = 31 * result + Arrays.hashCode(favoriteArrayValues);
|
||||||
|
result = 31 * result + Arrays.hashCode(nestedArray);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void genericRecord() throws IOException {
|
||||||
|
var adapter =
|
||||||
|
moshi.<GenericRecord<String>>adapter(
|
||||||
|
Types.newParameterizedTypeWithOwner(
|
||||||
|
RecordsTest.class, GenericRecord.class, String.class));
|
||||||
|
assertThat(adapter.fromJson("{\"value\":\"Okay!\"}")).isEqualTo(new GenericRecord<>("Okay!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record GenericRecord<T>(T value) {}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void genericBoundedRecord() throws IOException {
|
||||||
|
var adapter =
|
||||||
|
moshi.<GenericBoundedRecord<Integer>>adapter(
|
||||||
|
Types.newParameterizedTypeWithOwner(
|
||||||
|
RecordsTest.class, GenericBoundedRecord.class, Integer.class));
|
||||||
|
assertThat(adapter.fromJson("{\"value\":4}")).isEqualTo(new GenericBoundedRecord<>(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void qualifiedValues() throws IOException {
|
||||||
|
var adapter = moshi.newBuilder().add(new ColorAdapter()).build().adapter(QualifiedValues.class);
|
||||||
|
assertThat(adapter.fromJson("{\"value\":\"#ff0000\"}"))
|
||||||
|
.isEqualTo(new QualifiedValues(16711680));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record QualifiedValues(@HexColor int value) {}
|
||||||
|
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@JsonQualifier
|
||||||
|
@interface HexColor {}
|
||||||
|
|
||||||
|
/** Converts strings like #ff0000 to the corresponding color ints. */
|
||||||
|
public static class ColorAdapter {
|
||||||
|
@ToJson
|
||||||
|
public String toJson(@HexColor int rgb) {
|
||||||
|
return String.format("#%06x", rgb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FromJson
|
||||||
|
@HexColor
|
||||||
|
public int fromJson(String rgb) {
|
||||||
|
return Integer.parseInt(rgb.substring(1), 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record GenericBoundedRecord<T extends Number>(T value) {}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void jsonName() throws IOException {
|
||||||
|
var adapter = moshi.adapter(JsonName.class);
|
||||||
|
assertThat(adapter.fromJson("{\"actualValue\":3}")).isEqualTo(new JsonName(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record JsonName(@Json(name = "actualValue") int value) {}
|
||||||
|
}
|
@@ -51,6 +51,7 @@ public final class Moshi {
|
|||||||
BUILT_IN_FACTORIES.add(CollectionJsonAdapter.FACTORY);
|
BUILT_IN_FACTORIES.add(CollectionJsonAdapter.FACTORY);
|
||||||
BUILT_IN_FACTORIES.add(MapJsonAdapter.FACTORY);
|
BUILT_IN_FACTORIES.add(MapJsonAdapter.FACTORY);
|
||||||
BUILT_IN_FACTORIES.add(ArrayJsonAdapter.FACTORY);
|
BUILT_IN_FACTORIES.add(ArrayJsonAdapter.FACTORY);
|
||||||
|
BUILT_IN_FACTORIES.add(RecordJsonAdapter.FACTORY);
|
||||||
BUILT_IN_FACTORIES.add(ClassJsonAdapter.FACTORY);
|
BUILT_IN_FACTORIES.add(ClassJsonAdapter.FACTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 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.Set;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is just a simple shim for linking in {@link StandardJsonAdapters} and swapped with a real
|
||||||
|
* implementation in Java 16 via MR Jar.
|
||||||
|
*/
|
||||||
|
final class RecordJsonAdapter<T> extends JsonAdapter<T> {
|
||||||
|
|
||||||
|
static final JsonAdapter.Factory FACTORY =
|
||||||
|
new JsonAdapter.Factory() {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public JsonAdapter<?> create(
|
||||||
|
Type type, Set<? extends Annotation> annotations, Moshi moshi) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public T fromJson(JsonReader reader) throws IOException {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void toJson(JsonWriter writer, @Nullable T value) throws IOException {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
218
moshi/src/main/java16/com/squareup/moshi/RecordJsonAdapter.java
Normal file
218
moshi/src/main/java16/com/squareup/moshi/RecordJsonAdapter.java
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 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 static java.lang.invoke.MethodType.methodType;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
import java.lang.reflect.RecordComponent;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.lang.reflect.TypeVariable;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link JsonAdapter} that supports Java {@code record} classes via reflection.
|
||||||
|
*
|
||||||
|
* <p><em>NOTE:</em> Java records require JDK 16 or higher.
|
||||||
|
*/
|
||||||
|
final class RecordJsonAdapter<T> extends JsonAdapter<T> {
|
||||||
|
|
||||||
|
static final JsonAdapter.Factory FACTORY =
|
||||||
|
(type, annotations, moshi) -> {
|
||||||
|
if (!annotations.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(type instanceof Class) && !(type instanceof ParameterizedType)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rawType = Types.getRawType(type);
|
||||||
|
if (!rawType.isRecord()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Type> mappedTypeArgs = null;
|
||||||
|
if (type instanceof ParameterizedType parameterizedType) {
|
||||||
|
Type[] typeArgs = parameterizedType.getActualTypeArguments();
|
||||||
|
var typeVars = rawType.getTypeParameters();
|
||||||
|
mappedTypeArgs = new LinkedHashMap<>(typeArgs.length);
|
||||||
|
for (int i = 0; i < typeArgs.length; ++i) {
|
||||||
|
var typeVarName = typeVars[i].getName();
|
||||||
|
var materialized = typeArgs[i];
|
||||||
|
mappedTypeArgs.put(typeVarName, materialized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var components = rawType.getRecordComponents();
|
||||||
|
var bindings = new LinkedHashMap<String, ComponentBinding<?>>();
|
||||||
|
var constructorParams = new Class<?>[components.length];
|
||||||
|
var lookup = MethodHandles.lookup();
|
||||||
|
for (int i = 0, componentsLength = components.length; i < componentsLength; i++) {
|
||||||
|
RecordComponent component = components[i];
|
||||||
|
constructorParams[i] = component.getType();
|
||||||
|
var name = component.getName();
|
||||||
|
var componentType = component.getGenericType();
|
||||||
|
if (componentType instanceof TypeVariable<?> typeVariable) {
|
||||||
|
var typeVarName = typeVariable.getName();
|
||||||
|
if (mappedTypeArgs == null) {
|
||||||
|
throw new AssertionError(
|
||||||
|
"No mapped type arguments found for type '" + typeVarName + "'");
|
||||||
|
}
|
||||||
|
var mappedType = mappedTypeArgs.get(typeVarName);
|
||||||
|
if (mappedType == null) {
|
||||||
|
throw new AssertionError(
|
||||||
|
"No materialized type argument found for type '" + typeVarName + "'");
|
||||||
|
}
|
||||||
|
componentType = mappedType;
|
||||||
|
}
|
||||||
|
var jsonName = name;
|
||||||
|
Set<Annotation> qualifiers = null;
|
||||||
|
for (var annotation : component.getDeclaredAnnotations()) {
|
||||||
|
if (annotation instanceof Json jsonAnnotation) {
|
||||||
|
jsonName = jsonAnnotation.name();
|
||||||
|
} else {
|
||||||
|
if (annotation.annotationType().isAnnotationPresent(JsonQualifier.class)) {
|
||||||
|
if (qualifiers == null) {
|
||||||
|
qualifiers = new LinkedHashSet<>();
|
||||||
|
}
|
||||||
|
qualifiers.add(annotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (qualifiers == null) {
|
||||||
|
qualifiers = Collections.emptySet();
|
||||||
|
}
|
||||||
|
var adapter = moshi.adapter(componentType, qualifiers);
|
||||||
|
MethodHandle accessor;
|
||||||
|
try {
|
||||||
|
accessor = lookup.unreflect(component.getAccessor());
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
var componentBinding = new ComponentBinding<>(name, jsonName, adapter, accessor);
|
||||||
|
var replaced = bindings.put(jsonName, componentBinding);
|
||||||
|
if (replaced != null) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Conflicting components:\n"
|
||||||
|
+ " "
|
||||||
|
+ replaced.name
|
||||||
|
+ "\n"
|
||||||
|
+ " "
|
||||||
|
+ componentBinding.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodHandle constructor;
|
||||||
|
try {
|
||||||
|
constructor = lookup.findConstructor(rawType, methodType(void.class, constructorParams));
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
return new RecordJsonAdapter<>(constructor, rawType.getSimpleName(), bindings).nullSafe();
|
||||||
|
};
|
||||||
|
|
||||||
|
private static record ComponentBinding<T>(
|
||||||
|
String name, String jsonName, JsonAdapter<T> adapter, MethodHandle accessor) {}
|
||||||
|
|
||||||
|
private final String targetClass;
|
||||||
|
private final MethodHandle constructor;
|
||||||
|
private final ComponentBinding<Object>[] componentBindingsArray;
|
||||||
|
private final JsonReader.Options options;
|
||||||
|
|
||||||
|
@SuppressWarnings("ToArrayCallWithZeroLengthArrayArgument")
|
||||||
|
public RecordJsonAdapter(
|
||||||
|
MethodHandle constructor,
|
||||||
|
String targetClass,
|
||||||
|
Map<String, ComponentBinding<?>> componentBindings) {
|
||||||
|
this.constructor = constructor;
|
||||||
|
this.targetClass = targetClass;
|
||||||
|
//noinspection unchecked
|
||||||
|
this.componentBindingsArray =
|
||||||
|
componentBindings.values().toArray(new ComponentBinding[componentBindings.size()]);
|
||||||
|
this.options =
|
||||||
|
JsonReader.Options.of(
|
||||||
|
componentBindings.keySet().toArray(new String[componentBindings.size()]));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T fromJson(JsonReader reader) throws IOException {
|
||||||
|
var resultsArray = new Object[componentBindingsArray.length];
|
||||||
|
|
||||||
|
reader.beginObject();
|
||||||
|
while (reader.hasNext()) {
|
||||||
|
int index = reader.selectName(options);
|
||||||
|
if (index == -1) {
|
||||||
|
reader.skipName();
|
||||||
|
reader.skipValue();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var result = componentBindingsArray[index].adapter.fromJson(reader);
|
||||||
|
resultsArray[index] = result;
|
||||||
|
}
|
||||||
|
reader.endObject();
|
||||||
|
|
||||||
|
try {
|
||||||
|
//noinspection unchecked
|
||||||
|
return (T) constructor.invokeWithArguments(resultsArray);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (e instanceof InvocationTargetException ite) {
|
||||||
|
Throwable cause = ite.getCause();
|
||||||
|
if (cause instanceof RuntimeException) throw (RuntimeException) cause;
|
||||||
|
if (cause instanceof Error) throw (Error) cause;
|
||||||
|
throw new RuntimeException(cause);
|
||||||
|
} else {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void toJson(JsonWriter writer, T value) throws IOException {
|
||||||
|
writer.beginObject();
|
||||||
|
|
||||||
|
for (var binding : componentBindingsArray) {
|
||||||
|
writer.name(binding.jsonName);
|
||||||
|
try {
|
||||||
|
binding.adapter.toJson(writer, binding.accessor.invoke(value));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (e instanceof InvocationTargetException ite) {
|
||||||
|
Throwable cause = ite.getCause();
|
||||||
|
if (cause instanceof RuntimeException) throw (RuntimeException) cause;
|
||||||
|
if (cause instanceof Error) throw (Error) cause;
|
||||||
|
throw new RuntimeException(cause);
|
||||||
|
} else {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.endObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "JsonAdapter(" + targetClass + ")";
|
||||||
|
}
|
||||||
|
}
|
@@ -126,7 +126,7 @@ public final class JsonAdapterTest {
|
|||||||
fail();
|
fail();
|
||||||
} catch (JsonDataException expected) {
|
} catch (JsonDataException expected) {
|
||||||
assertThat(expected).hasMessageThat().isEqualTo("Unexpected null at $[1]");
|
assertThat(expected).hasMessageThat().isEqualTo("Unexpected null at $[1]");
|
||||||
assertThat(reader.nextNull()).isNull();
|
assertThat(reader.<Object>nextNull()).isNull();
|
||||||
}
|
}
|
||||||
assertThat(toUpperCase.fromJson(reader)).isEqualTo("C");
|
assertThat(toUpperCase.fromJson(reader)).isEqualTo("C");
|
||||||
reader.endArray();
|
reader.endArray();
|
||||||
|
@@ -565,7 +565,7 @@ public final class JsonReaderTest {
|
|||||||
assertThat(reader2.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
|
assertThat(reader2.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
|
||||||
|
|
||||||
JsonReader reader3 = newReader("null");
|
JsonReader reader3 = newReader("null");
|
||||||
assertThat(reader3.nextNull()).isNull();
|
assertThat(reader3.<Object>nextNull()).isNull();
|
||||||
assertThat(reader3.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
|
assertThat(reader3.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
|
||||||
|
|
||||||
JsonReader reader4 = newReader("123");
|
JsonReader reader4 = newReader("123");
|
||||||
|
@@ -59,7 +59,7 @@ public final class JsonValueReaderTest {
|
|||||||
|
|
||||||
assertThat(reader.hasNext()).isTrue();
|
assertThat(reader.hasNext()).isTrue();
|
||||||
assertThat(reader.peek()).isEqualTo(JsonReader.Token.NULL);
|
assertThat(reader.peek()).isEqualTo(JsonReader.Token.NULL);
|
||||||
assertThat(reader.nextNull()).isNull();
|
assertThat(reader.<Object>nextNull()).isNull();
|
||||||
|
|
||||||
assertThat(reader.hasNext()).isFalse();
|
assertThat(reader.hasNext()).isFalse();
|
||||||
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_ARRAY);
|
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_ARRAY);
|
||||||
@@ -103,7 +103,7 @@ public final class JsonValueReaderTest {
|
|||||||
assertThat(reader.peek()).isEqualTo(JsonReader.Token.NAME);
|
assertThat(reader.peek()).isEqualTo(JsonReader.Token.NAME);
|
||||||
assertThat(reader.nextName()).isEqualTo("d");
|
assertThat(reader.nextName()).isEqualTo("d");
|
||||||
assertThat(reader.peek()).isEqualTo(JsonReader.Token.NULL);
|
assertThat(reader.peek()).isEqualTo(JsonReader.Token.NULL);
|
||||||
assertThat(reader.nextNull()).isNull();
|
assertThat(reader.<Object>nextNull()).isNull();
|
||||||
|
|
||||||
assertThat(reader.hasNext()).isFalse();
|
assertThat(reader.hasNext()).isFalse();
|
||||||
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_OBJECT);
|
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_OBJECT);
|
||||||
|
@@ -152,10 +152,10 @@ public final class JsonValueWriterTest {
|
|||||||
public void primitiveIntegerTypesEmitLong() throws Exception {
|
public void primitiveIntegerTypesEmitLong() throws Exception {
|
||||||
JsonValueWriter writer = new JsonValueWriter();
|
JsonValueWriter writer = new JsonValueWriter();
|
||||||
writer.beginArray();
|
writer.beginArray();
|
||||||
writer.value(new Byte(Byte.MIN_VALUE));
|
writer.value(Byte.valueOf(Byte.MIN_VALUE));
|
||||||
writer.value(new Short(Short.MIN_VALUE));
|
writer.value(Short.valueOf(Short.MIN_VALUE));
|
||||||
writer.value(new Integer(Integer.MIN_VALUE));
|
writer.value(Integer.valueOf(Integer.MIN_VALUE));
|
||||||
writer.value(new Long(Long.MIN_VALUE));
|
writer.value(Long.valueOf(Long.MIN_VALUE));
|
||||||
writer.endArray();
|
writer.endArray();
|
||||||
|
|
||||||
List<Number> numbers =
|
List<Number> numbers =
|
||||||
@@ -167,8 +167,8 @@ public final class JsonValueWriterTest {
|
|||||||
public void primitiveFloatingPointTypesEmitDouble() throws Exception {
|
public void primitiveFloatingPointTypesEmitDouble() throws Exception {
|
||||||
JsonValueWriter writer = new JsonValueWriter();
|
JsonValueWriter writer = new JsonValueWriter();
|
||||||
writer.beginArray();
|
writer.beginArray();
|
||||||
writer.value(new Float(0.5f));
|
writer.value(Float.valueOf(0.5f));
|
||||||
writer.value(new Double(0.5d));
|
writer.value(Double.valueOf(0.5d));
|
||||||
writer.endArray();
|
writer.endArray();
|
||||||
|
|
||||||
List<Number> numbers = Arrays.<Number>asList(0.5d, 0.5d);
|
List<Number> numbers = Arrays.<Number>asList(0.5d, 0.5d);
|
||||||
|
@@ -236,17 +236,17 @@ public final class JsonWriterTest {
|
|||||||
JsonWriter writer = factory.newWriter();
|
JsonWriter writer = factory.newWriter();
|
||||||
writer.beginArray();
|
writer.beginArray();
|
||||||
try {
|
try {
|
||||||
writer.value(new Double(Double.NaN));
|
writer.value(Double.valueOf(Double.NaN));
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
writer.value(new Double(Double.NEGATIVE_INFINITY));
|
writer.value(Double.valueOf(Double.NEGATIVE_INFINITY));
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
writer.value(new Double(Double.POSITIVE_INFINITY));
|
writer.value(Double.valueOf(Double.POSITIVE_INFINITY));
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@ pluginManagement {
|
|||||||
rootProject.name = "moshi-root"
|
rootProject.name = "moshi-root"
|
||||||
include(":moshi")
|
include(":moshi")
|
||||||
include(":moshi:japicmp")
|
include(":moshi:japicmp")
|
||||||
|
include(":moshi:records-tests")
|
||||||
include(":adapters")
|
include(":adapters")
|
||||||
include(":adapters:japicmp")
|
include(":adapters:japicmp")
|
||||||
include(":examples")
|
include(":examples")
|
||||||
|
Reference in New Issue
Block a user