mirror of
https://github.com/fankes/moshi.git
synced 2025-10-18 23:49:21 +08:00
Convert Moshi.java to Kotlin (#1462)
* Rename .java to .kt * Migrate Moshi.java to Moshi.kt * Idiomatic cleanups * Move comments down * Apply suggestions from code review Co-authored-by: Egor Andreevich <egor@squareup.com> * Small little cleanups Co-authored-by: Egor Andreevich <egor@squareup.com>
This commit is contained in:
@@ -289,7 +289,7 @@ public class KotlinJsonAdapterFactory : JsonAdapter.Factory {
|
|||||||
else -> error("Not possible!")
|
else -> error("Not possible!")
|
||||||
}
|
}
|
||||||
val resolvedPropertyType = resolve(type, rawType, propertyType)
|
val resolvedPropertyType = resolve(type, rawType, propertyType)
|
||||||
val adapter = moshi.adapter<Any>(
|
val adapter = moshi.adapter<Any?>(
|
||||||
resolvedPropertyType,
|
resolvedPropertyType,
|
||||||
Util.jsonAnnotations(allAnnotations.toTypedArray()),
|
Util.jsonAnnotations(allAnnotations.toTypedArray()),
|
||||||
property.name
|
property.name
|
||||||
|
@@ -1,423 +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 static com.squareup.moshi.internal.Util.canonicalize;
|
|
||||||
import static com.squareup.moshi.internal.Util.removeSubtypeWildcard;
|
|
||||||
import static com.squareup.moshi.internal.Util.typeAnnotatedWithAnnotations;
|
|
||||||
|
|
||||||
import com.squareup.moshi.internal.Util;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import javax.annotation.CheckReturnValue;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Coordinates binding between JSON values and Java objects.
|
|
||||||
*
|
|
||||||
* <p>Moshi instances are thread-safe, meaning multiple threads can safely use a single instance
|
|
||||||
* concurrently.
|
|
||||||
*/
|
|
||||||
public final class Moshi {
|
|
||||||
static final List<JsonAdapter.Factory> BUILT_IN_FACTORIES = new ArrayList<>(5);
|
|
||||||
|
|
||||||
static {
|
|
||||||
BUILT_IN_FACTORIES.add(StandardJsonAdapters.FACTORY);
|
|
||||||
BUILT_IN_FACTORIES.add(CollectionJsonAdapter.FACTORY);
|
|
||||||
BUILT_IN_FACTORIES.add(MapJsonAdapter.FACTORY);
|
|
||||||
BUILT_IN_FACTORIES.add(ArrayJsonAdapter.FACTORY);
|
|
||||||
BUILT_IN_FACTORIES.add(RecordJsonAdapter.FACTORY);
|
|
||||||
BUILT_IN_FACTORIES.add(ClassJsonAdapter.FACTORY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final List<JsonAdapter.Factory> factories;
|
|
||||||
private final int lastOffset;
|
|
||||||
private final ThreadLocal<LookupChain> lookupChainThreadLocal = new ThreadLocal<>();
|
|
||||||
private final Map<Object, JsonAdapter<?>> adapterCache = new LinkedHashMap<>();
|
|
||||||
|
|
||||||
Moshi(Builder builder) {
|
|
||||||
List<JsonAdapter.Factory> factories =
|
|
||||||
new ArrayList<>(builder.factories.size() + BUILT_IN_FACTORIES.size());
|
|
||||||
factories.addAll(builder.factories);
|
|
||||||
factories.addAll(BUILT_IN_FACTORIES);
|
|
||||||
this.factories = Collections.unmodifiableList(factories);
|
|
||||||
this.lastOffset = builder.lastOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns a JSON adapter for {@code type}, creating it if necessary. */
|
|
||||||
@CheckReturnValue
|
|
||||||
public <T> JsonAdapter<T> adapter(Type type) {
|
|
||||||
return adapter(type, Util.NO_ANNOTATIONS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@CheckReturnValue
|
|
||||||
public <T> JsonAdapter<T> adapter(Class<T> type) {
|
|
||||||
return adapter(type, Util.NO_ANNOTATIONS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@CheckReturnValue
|
|
||||||
public <T> JsonAdapter<T> adapter(Type type, Class<? extends Annotation> annotationType) {
|
|
||||||
if (annotationType == null) {
|
|
||||||
throw new NullPointerException("annotationType == null");
|
|
||||||
}
|
|
||||||
return adapter(
|
|
||||||
type, Collections.singleton(Types.createJsonQualifierImplementation(annotationType)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@CheckReturnValue
|
|
||||||
public <T> JsonAdapter<T> adapter(Type type, Class<? extends Annotation>... annotationTypes) {
|
|
||||||
if (annotationTypes.length == 1) {
|
|
||||||
return adapter(type, annotationTypes[0]);
|
|
||||||
}
|
|
||||||
Set<Annotation> annotations = new LinkedHashSet<>(annotationTypes.length);
|
|
||||||
for (Class<? extends Annotation> annotationType : annotationTypes) {
|
|
||||||
annotations.add(Types.createJsonQualifierImplementation(annotationType));
|
|
||||||
}
|
|
||||||
return adapter(type, Collections.unmodifiableSet(annotations));
|
|
||||||
}
|
|
||||||
|
|
||||||
@CheckReturnValue
|
|
||||||
public <T> JsonAdapter<T> adapter(Type type, Set<? extends Annotation> annotations) {
|
|
||||||
return adapter(type, annotations, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param fieldName An optional field name associated with this type. The field name is used as a
|
|
||||||
* hint for better adapter lookup error messages for nested structures.
|
|
||||||
*/
|
|
||||||
@CheckReturnValue
|
|
||||||
@SuppressWarnings("unchecked") // Factories are required to return only matching JsonAdapters.
|
|
||||||
public <T> JsonAdapter<T> adapter(
|
|
||||||
Type type, Set<? extends Annotation> annotations, @Nullable String fieldName) {
|
|
||||||
if (type == null) {
|
|
||||||
throw new NullPointerException("type == null");
|
|
||||||
}
|
|
||||||
if (annotations == null) {
|
|
||||||
throw new NullPointerException("annotations == null");
|
|
||||||
}
|
|
||||||
|
|
||||||
type = removeSubtypeWildcard(canonicalize(type));
|
|
||||||
|
|
||||||
// If there's an equivalent adapter in the cache, we're done!
|
|
||||||
Object cacheKey = cacheKey(type, annotations);
|
|
||||||
synchronized (adapterCache) {
|
|
||||||
JsonAdapter<?> result = adapterCache.get(cacheKey);
|
|
||||||
if (result != null) return (JsonAdapter<T>) result;
|
|
||||||
}
|
|
||||||
|
|
||||||
LookupChain lookupChain = lookupChainThreadLocal.get();
|
|
||||||
if (lookupChain == null) {
|
|
||||||
lookupChain = new LookupChain();
|
|
||||||
lookupChainThreadLocal.set(lookupChain);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean success = false;
|
|
||||||
JsonAdapter<T> adapterFromCall = lookupChain.push(type, fieldName, cacheKey);
|
|
||||||
try {
|
|
||||||
if (adapterFromCall != null) return adapterFromCall;
|
|
||||||
|
|
||||||
// Ask each factory to create the JSON adapter.
|
|
||||||
for (int i = 0, size = factories.size(); i < size; i++) {
|
|
||||||
JsonAdapter<T> result = (JsonAdapter<T>) factories.get(i).create(type, annotations, this);
|
|
||||||
if (result == null) continue;
|
|
||||||
|
|
||||||
// Success! Notify the LookupChain so it is cached and can be used by re-entrant calls.
|
|
||||||
lookupChain.adapterFound(result);
|
|
||||||
success = true;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"No JsonAdapter for " + typeAnnotatedWithAnnotations(type, annotations));
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw lookupChain.exceptionWithLookupStack(e);
|
|
||||||
} finally {
|
|
||||||
lookupChain.pop(success);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@CheckReturnValue
|
|
||||||
@SuppressWarnings("unchecked") // Factories are required to return only matching JsonAdapters.
|
|
||||||
public <T> JsonAdapter<T> nextAdapter(
|
|
||||||
JsonAdapter.Factory skipPast, Type type, Set<? extends Annotation> annotations) {
|
|
||||||
if (annotations == null) throw new NullPointerException("annotations == null");
|
|
||||||
|
|
||||||
type = removeSubtypeWildcard(canonicalize(type));
|
|
||||||
|
|
||||||
int skipPastIndex = factories.indexOf(skipPast);
|
|
||||||
if (skipPastIndex == -1) {
|
|
||||||
throw new IllegalArgumentException("Unable to skip past unknown factory " + skipPast);
|
|
||||||
}
|
|
||||||
for (int i = skipPastIndex + 1, size = factories.size(); i < size; i++) {
|
|
||||||
JsonAdapter<T> result = (JsonAdapter<T>) factories.get(i).create(type, annotations, this);
|
|
||||||
if (result != null) return result;
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"No next JsonAdapter for " + typeAnnotatedWithAnnotations(type, annotations));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns a new builder containing all custom factories used by the current instance. */
|
|
||||||
@CheckReturnValue
|
|
||||||
public Moshi.Builder newBuilder() {
|
|
||||||
Builder result = new Builder();
|
|
||||||
for (int i = 0, limit = lastOffset; i < limit; i++) {
|
|
||||||
result.add(factories.get(i));
|
|
||||||
}
|
|
||||||
for (int i = lastOffset, limit = factories.size() - BUILT_IN_FACTORIES.size(); i < limit; i++) {
|
|
||||||
result.addLast(factories.get(i));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns an opaque object that's equal if the type and annotations are equal. */
|
|
||||||
private Object cacheKey(Type type, Set<? extends Annotation> annotations) {
|
|
||||||
if (annotations.isEmpty()) return type;
|
|
||||||
return Arrays.asList(type, annotations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
final List<JsonAdapter.Factory> factories = new ArrayList<>();
|
|
||||||
int lastOffset = 0;
|
|
||||||
|
|
||||||
public <T> Builder add(Type type, JsonAdapter<T> jsonAdapter) {
|
|
||||||
return add(newAdapterFactory(type, jsonAdapter));
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> Builder add(
|
|
||||||
Type type, Class<? extends Annotation> annotation, JsonAdapter<T> jsonAdapter) {
|
|
||||||
return add(newAdapterFactory(type, annotation, jsonAdapter));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder add(JsonAdapter.Factory factory) {
|
|
||||||
if (factory == null) throw new IllegalArgumentException("factory == null");
|
|
||||||
factories.add(lastOffset++, factory);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder add(Object adapter) {
|
|
||||||
if (adapter == null) throw new IllegalArgumentException("adapter == null");
|
|
||||||
return add(AdapterMethodsFactory.get(adapter));
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> Builder addLast(Type type, JsonAdapter<T> jsonAdapter) {
|
|
||||||
return addLast(newAdapterFactory(type, jsonAdapter));
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> Builder addLast(
|
|
||||||
Type type, Class<? extends Annotation> annotation, JsonAdapter<T> jsonAdapter) {
|
|
||||||
return addLast(newAdapterFactory(type, annotation, jsonAdapter));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addLast(JsonAdapter.Factory factory) {
|
|
||||||
if (factory == null) throw new IllegalArgumentException("factory == null");
|
|
||||||
factories.add(factory);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addLast(Object adapter) {
|
|
||||||
if (adapter == null) throw new IllegalArgumentException("adapter == null");
|
|
||||||
return addLast(AdapterMethodsFactory.get(adapter));
|
|
||||||
}
|
|
||||||
|
|
||||||
@CheckReturnValue
|
|
||||||
public Moshi build() {
|
|
||||||
return new Moshi(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static <T> JsonAdapter.Factory newAdapterFactory(
|
|
||||||
final Type type, final JsonAdapter<T> jsonAdapter) {
|
|
||||||
if (type == null) throw new IllegalArgumentException("type == null");
|
|
||||||
if (jsonAdapter == null) throw new IllegalArgumentException("jsonAdapter == null");
|
|
||||||
|
|
||||||
return new JsonAdapter.Factory() {
|
|
||||||
@Override
|
|
||||||
public @Nullable JsonAdapter<?> create(
|
|
||||||
Type targetType, Set<? extends Annotation> annotations, Moshi moshi) {
|
|
||||||
return annotations.isEmpty() && Util.typesMatch(type, targetType) ? jsonAdapter : null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static <T> JsonAdapter.Factory newAdapterFactory(
|
|
||||||
final Type type,
|
|
||||||
final Class<? extends Annotation> annotation,
|
|
||||||
final JsonAdapter<T> jsonAdapter) {
|
|
||||||
if (type == null) throw new IllegalArgumentException("type == null");
|
|
||||||
if (annotation == null) throw new IllegalArgumentException("annotation == null");
|
|
||||||
if (jsonAdapter == null) throw new IllegalArgumentException("jsonAdapter == null");
|
|
||||||
if (!annotation.isAnnotationPresent(JsonQualifier.class)) {
|
|
||||||
throw new IllegalArgumentException(annotation + " does not have @JsonQualifier");
|
|
||||||
}
|
|
||||||
if (annotation.getDeclaredMethods().length > 0) {
|
|
||||||
throw new IllegalArgumentException("Use JsonAdapter.Factory for annotations with elements");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JsonAdapter.Factory() {
|
|
||||||
@Override
|
|
||||||
public @Nullable JsonAdapter<?> create(
|
|
||||||
Type targetType, Set<? extends Annotation> annotations, Moshi moshi) {
|
|
||||||
if (Util.typesMatch(type, targetType)
|
|
||||||
&& annotations.size() == 1
|
|
||||||
&& Util.isAnnotationPresent(annotations, annotation)) {
|
|
||||||
return jsonAdapter;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A possibly-reentrant chain of lookups for JSON adapters.
|
|
||||||
*
|
|
||||||
* <p>We keep track of the current stack of lookups: we may start by looking up the JSON adapter
|
|
||||||
* for Employee, re-enter looking for the JSON adapter of HomeAddress, and re-enter again looking
|
|
||||||
* up the JSON adapter of PostalCode. If any of these lookups fail we can provide a stack trace
|
|
||||||
* with all of the lookups.
|
|
||||||
*
|
|
||||||
* <p>Sometimes a JSON adapter factory depends on its own product; either directly or indirectly.
|
|
||||||
* To make this work, we offer a JSON adapter stub while the final adapter is being computed. When
|
|
||||||
* it is ready, we wire the stub to that finished adapter. This is necessary in self-referential
|
|
||||||
* object models, such as an {@code Employee} class that has a {@code List<Employee>} field for an
|
|
||||||
* organization's management hierarchy.
|
|
||||||
*
|
|
||||||
* <p>This class defers putting any JSON adapters in the cache until the topmost JSON adapter has
|
|
||||||
* successfully been computed. That way we don't pollute the cache with incomplete stubs, or
|
|
||||||
* adapters that may transitively depend on incomplete stubs.
|
|
||||||
*/
|
|
||||||
final class LookupChain {
|
|
||||||
final List<Lookup<?>> callLookups = new ArrayList<>();
|
|
||||||
final Deque<Lookup<?>> stack = new ArrayDeque<>();
|
|
||||||
boolean exceptionAnnotated;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a JSON adapter that was already created for this call, or null if this is the first
|
|
||||||
* time in this call that the cache key has been requested in this call. This may return a
|
|
||||||
* lookup that isn't yet ready if this lookup is reentrant.
|
|
||||||
*/
|
|
||||||
<T> JsonAdapter<T> push(Type type, @Nullable String fieldName, Object cacheKey) {
|
|
||||||
// Try to find a lookup with the same key for the same call.
|
|
||||||
for (int i = 0, size = callLookups.size(); i < size; i++) {
|
|
||||||
Lookup<?> lookup = callLookups.get(i);
|
|
||||||
if (lookup.cacheKey.equals(cacheKey)) {
|
|
||||||
Lookup<T> hit = (Lookup<T>) lookup;
|
|
||||||
stack.add(hit);
|
|
||||||
return hit.adapter != null ? hit.adapter : hit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We might need to know about this cache key later in this call. Prepare for that.
|
|
||||||
Lookup<Object> lookup = new Lookup<>(type, fieldName, cacheKey);
|
|
||||||
callLookups.add(lookup);
|
|
||||||
stack.add(lookup);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sets the adapter result of the current lookup. */
|
|
||||||
<T> void adapterFound(JsonAdapter<T> result) {
|
|
||||||
Lookup<T> currentLookup = (Lookup<T>) stack.getLast();
|
|
||||||
currentLookup.adapter = result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Completes the current lookup by removing a stack frame.
|
|
||||||
*
|
|
||||||
* @param success true if the adapter cache should be populated if this is the topmost lookup.
|
|
||||||
*/
|
|
||||||
void pop(boolean success) {
|
|
||||||
stack.removeLast();
|
|
||||||
if (!stack.isEmpty()) return;
|
|
||||||
|
|
||||||
lookupChainThreadLocal.remove();
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
synchronized (adapterCache) {
|
|
||||||
for (int i = 0, size = callLookups.size(); i < size; i++) {
|
|
||||||
Lookup<?> lookup = callLookups.get(i);
|
|
||||||
JsonAdapter<?> replaced = adapterCache.put(lookup.cacheKey, lookup.adapter);
|
|
||||||
if (replaced != null) {
|
|
||||||
((Lookup<Object>) lookup).adapter = (JsonAdapter<Object>) replaced;
|
|
||||||
adapterCache.put(lookup.cacheKey, replaced);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IllegalArgumentException exceptionWithLookupStack(IllegalArgumentException e) {
|
|
||||||
// Don't add the lookup stack to more than one exception; the deepest is sufficient.
|
|
||||||
if (exceptionAnnotated) return e;
|
|
||||||
exceptionAnnotated = true;
|
|
||||||
|
|
||||||
int size = stack.size();
|
|
||||||
if (size == 1 && stack.getFirst().fieldName == null) return e;
|
|
||||||
|
|
||||||
StringBuilder errorMessageBuilder = new StringBuilder(e.getMessage());
|
|
||||||
for (Iterator<Lookup<?>> i = stack.descendingIterator(); i.hasNext(); ) {
|
|
||||||
Lookup<?> lookup = i.next();
|
|
||||||
errorMessageBuilder.append("\nfor ").append(lookup.type);
|
|
||||||
if (lookup.fieldName != null) {
|
|
||||||
errorMessageBuilder.append(' ').append(lookup.fieldName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new IllegalArgumentException(errorMessageBuilder.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** This class implements {@code JsonAdapter} so it can be used as a stub for re-entrant calls. */
|
|
||||||
static final class Lookup<T> extends JsonAdapter<T> {
|
|
||||||
final Type type;
|
|
||||||
final @Nullable String fieldName;
|
|
||||||
final Object cacheKey;
|
|
||||||
@Nullable JsonAdapter<T> adapter;
|
|
||||||
|
|
||||||
Lookup(Type type, @Nullable String fieldName, Object cacheKey) {
|
|
||||||
this.type = type;
|
|
||||||
this.fieldName = fieldName;
|
|
||||||
this.cacheKey = cacheKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T fromJson(JsonReader reader) throws IOException {
|
|
||||||
if (adapter == null) throw new IllegalStateException("JsonAdapter isn't ready");
|
|
||||||
return adapter.fromJson(reader);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void toJson(JsonWriter writer, T value) throws IOException {
|
|
||||||
if (adapter == null) throw new IllegalStateException("JsonAdapter isn't ready");
|
|
||||||
adapter.toJson(writer, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return adapter != null ? adapter.toString() : super.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
365
moshi/src/main/java/com/squareup/moshi/Moshi.kt
Normal file
365
moshi/src/main/java/com/squareup/moshi/Moshi.kt
Normal file
@@ -0,0 +1,365 @@
|
|||||||
|
/*
|
||||||
|
* 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.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 java.lang.reflect.Type
|
||||||
|
import javax.annotation.CheckReturnValue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coordinates binding between JSON values and Java objects.
|
||||||
|
*
|
||||||
|
* Moshi instances are thread-safe, meaning multiple threads can safely use a single instance
|
||||||
|
* concurrently.
|
||||||
|
*/
|
||||||
|
public class Moshi internal constructor(builder: Builder) {
|
||||||
|
private val factories = buildList {
|
||||||
|
addAll(builder.factories)
|
||||||
|
addAll(BUILT_IN_FACTORIES)
|
||||||
|
}
|
||||||
|
private val lastOffset = builder.lastOffset
|
||||||
|
private val lookupChainThreadLocal = ThreadLocal<LookupChain>()
|
||||||
|
private val adapterCache = LinkedHashMap<Any?, JsonAdapter<*>?>()
|
||||||
|
|
||||||
|
/** Returns a JSON adapter for `type`, creating it if necessary. */
|
||||||
|
@CheckReturnValue
|
||||||
|
public fun <T> adapter(type: Type): JsonAdapter<T> = adapter(type, Util.NO_ANNOTATIONS)
|
||||||
|
|
||||||
|
@CheckReturnValue
|
||||||
|
public fun <T> adapter(type: Class<T>): JsonAdapter<T> = adapter(type, Util.NO_ANNOTATIONS)
|
||||||
|
|
||||||
|
@CheckReturnValue
|
||||||
|
public fun <T> adapter(type: Type, annotationType: Class<out Annotation>): JsonAdapter<T> =
|
||||||
|
adapter(type, setOf(createJsonQualifierImplementation(annotationType)))
|
||||||
|
|
||||||
|
@CheckReturnValue
|
||||||
|
public fun <T> adapter(type: Type, vararg annotationTypes: Class<out Annotation>): JsonAdapter<T> {
|
||||||
|
if (annotationTypes.size == 1) {
|
||||||
|
return adapter(type, annotationTypes[0])
|
||||||
|
}
|
||||||
|
val annotations = buildSet(annotationTypes.size) {
|
||||||
|
for (annotationType in annotationTypes) {
|
||||||
|
add(createJsonQualifierImplementation(annotationType)!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return adapter(type, annotations)
|
||||||
|
}
|
||||||
|
|
||||||
|
@CheckReturnValue
|
||||||
|
public fun <T> adapter(type: Type, annotations: Set<Annotation>): JsonAdapter<T> =
|
||||||
|
adapter(type, annotations, fieldName = null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param fieldName An optional field name associated with this type. The field name is used as a
|
||||||
|
* hint for better adapter lookup error messages for nested structures.
|
||||||
|
*/
|
||||||
|
@CheckReturnValue
|
||||||
|
public fun <T> adapter(
|
||||||
|
type: Type,
|
||||||
|
annotations: Set<Annotation>,
|
||||||
|
fieldName: String?
|
||||||
|
): JsonAdapter<T> {
|
||||||
|
val cleanedType = removeSubtypeWildcard(canonicalize(type))
|
||||||
|
|
||||||
|
// If there's an equivalent adapter in the cache, we're done!
|
||||||
|
val cacheKey = cacheKey(cleanedType, annotations)
|
||||||
|
synchronized(adapterCache) {
|
||||||
|
val result = adapterCache[cacheKey]
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
if (result != null) return result as JsonAdapter<T>
|
||||||
|
}
|
||||||
|
var lookupChain = lookupChainThreadLocal.get()
|
||||||
|
if (lookupChain == null) {
|
||||||
|
lookupChain = LookupChain()
|
||||||
|
lookupChainThreadLocal.set(lookupChain)
|
||||||
|
}
|
||||||
|
var success = false
|
||||||
|
val adapterFromCall = lookupChain.push<T>(cleanedType, fieldName, cacheKey)
|
||||||
|
try {
|
||||||
|
if (adapterFromCall != null) return adapterFromCall
|
||||||
|
|
||||||
|
// Ask each factory to create the JSON adapter.
|
||||||
|
for (i in factories.indices) {
|
||||||
|
@Suppress("UNCHECKED_CAST") // Factories are required to return only matching JsonAdapters.
|
||||||
|
val result = factories[i].create(cleanedType, annotations, this) as JsonAdapter<T>? ?: continue
|
||||||
|
|
||||||
|
// Success! Notify the LookupChain so it is cached and can be used by re-entrant calls.
|
||||||
|
lookupChain.adapterFound(result)
|
||||||
|
success = true
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException("No JsonAdapter for ${typeAnnotatedWithAnnotations(type, annotations)}")
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
throw lookupChain.exceptionWithLookupStack(e)
|
||||||
|
} finally {
|
||||||
|
lookupChain.pop(success)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CheckReturnValue
|
||||||
|
public fun <T> nextAdapter(
|
||||||
|
skipPast: JsonAdapter.Factory,
|
||||||
|
type: Type,
|
||||||
|
annotations: Set<Annotation>
|
||||||
|
): JsonAdapter<T> {
|
||||||
|
val cleanedType = removeSubtypeWildcard(canonicalize(type))
|
||||||
|
val skipPastIndex = factories.indexOf(skipPast)
|
||||||
|
require(skipPastIndex != -1) { "Unable to skip past unknown factory $skipPast" }
|
||||||
|
for (i in (skipPastIndex + 1) until factories.size) {
|
||||||
|
@Suppress("UNCHECKED_CAST") // Factories are required to return only matching JsonAdapters.
|
||||||
|
val result = factories[i].create(cleanedType, annotations, this) as JsonAdapter<T>?
|
||||||
|
if (result != null) return result
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException("No next JsonAdapter for ${typeAnnotatedWithAnnotations(cleanedType, annotations)}")
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a new builder containing all custom factories used by the current instance. */
|
||||||
|
@CheckReturnValue
|
||||||
|
public fun newBuilder(): Builder {
|
||||||
|
val result = Builder()
|
||||||
|
// Runs to reuse var names
|
||||||
|
run {
|
||||||
|
val limit = lastOffset
|
||||||
|
for (i in 0 until limit) {
|
||||||
|
result.add(factories[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val limit = factories.size - BUILT_IN_FACTORIES.size
|
||||||
|
for (i in lastOffset until limit) {
|
||||||
|
result.addLast(factories[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns an opaque object that's equal if the type and annotations are equal. */
|
||||||
|
private fun cacheKey(type: Type, annotations: Set<Annotation>): Any {
|
||||||
|
return if (annotations.isEmpty()) type else listOf(type, annotations)
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Builder {
|
||||||
|
internal val factories = mutableListOf<JsonAdapter.Factory>()
|
||||||
|
internal var lastOffset = 0
|
||||||
|
|
||||||
|
public fun <T> add(type: Type, jsonAdapter: JsonAdapter<T>): Builder = apply {
|
||||||
|
add(newAdapterFactory(type, jsonAdapter))
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <T> add(
|
||||||
|
type: Type,
|
||||||
|
annotation: Class<out Annotation>,
|
||||||
|
jsonAdapter: JsonAdapter<T>
|
||||||
|
): Builder = apply {
|
||||||
|
add(newAdapterFactory(type, annotation, jsonAdapter))
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun add(factory: JsonAdapter.Factory): Builder = apply {
|
||||||
|
factories.add(lastOffset++, factory)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun add(adapter: Any): Builder = apply {
|
||||||
|
add(AdapterMethodsFactory.get(adapter))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
public fun <T> addLast(type: Type, jsonAdapter: JsonAdapter<T>): Builder = apply {
|
||||||
|
addLast(newAdapterFactory(type, jsonAdapter))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
public fun <T> addLast(
|
||||||
|
type: Type,
|
||||||
|
annotation: Class<out Annotation>,
|
||||||
|
jsonAdapter: JsonAdapter<T>
|
||||||
|
): Builder = apply {
|
||||||
|
addLast(newAdapterFactory(type, annotation, jsonAdapter))
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun addLast(factory: JsonAdapter.Factory): Builder = apply {
|
||||||
|
factories.add(factory)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
public fun addLast(adapter: Any): Builder = apply {
|
||||||
|
addLast(AdapterMethodsFactory.get(adapter))
|
||||||
|
}
|
||||||
|
|
||||||
|
@CheckReturnValue
|
||||||
|
public fun build(): Moshi = Moshi(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A possibly-reentrant chain of lookups for JSON adapters.
|
||||||
|
*
|
||||||
|
* We keep track of the current stack of lookups: we may start by looking up the JSON adapter
|
||||||
|
* for Employee, re-enter looking for the JSON adapter of HomeAddress, and re-enter again looking
|
||||||
|
* up the JSON adapter of PostalCode. If any of these lookups fail we can provide a stack trace
|
||||||
|
* with all of the lookups.
|
||||||
|
*
|
||||||
|
* Sometimes a JSON adapter factory depends on its own product; either directly or indirectly.
|
||||||
|
* To make this work, we offer a JSON adapter stub while the final adapter is being computed. When
|
||||||
|
* it is ready, we wire the stub to that finished adapter. This is necessary in self-referential
|
||||||
|
* object models, such as an `Employee` class that has a `List<Employee>` field for an
|
||||||
|
* organization's management hierarchy.
|
||||||
|
*
|
||||||
|
* This class defers putting any JSON adapters in the cache until the topmost JSON adapter has
|
||||||
|
* successfully been computed. That way we don't pollute the cache with incomplete stubs, or
|
||||||
|
* adapters that may transitively depend on incomplete stubs.
|
||||||
|
*/
|
||||||
|
internal inner class LookupChain {
|
||||||
|
private val callLookups = mutableListOf<Lookup<*>>()
|
||||||
|
private val stack = ArrayDeque<Lookup<*>>()
|
||||||
|
private var exceptionAnnotated = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a JSON adapter that was already created for this call, or null if this is the first
|
||||||
|
* time in this call that the cache key has been requested in this call. This may return a
|
||||||
|
* lookup that isn't yet ready if this lookup is reentrant.
|
||||||
|
*/
|
||||||
|
fun <T> push(type: Type, fieldName: String?, cacheKey: Any): JsonAdapter<T>? {
|
||||||
|
// Try to find a lookup with the same key for the same call.
|
||||||
|
var i = 0
|
||||||
|
val size = callLookups.size
|
||||||
|
while (i < size) {
|
||||||
|
val lookup = callLookups[i]
|
||||||
|
if (lookup.cacheKey == cacheKey) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val hit = lookup as Lookup<T>
|
||||||
|
stack += hit
|
||||||
|
return if (hit.adapter != null) hit.adapter else hit
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
// We might need to know about this cache key later in this call. Prepare for that.
|
||||||
|
val lookup = Lookup<Any>(type, fieldName, cacheKey)
|
||||||
|
callLookups += lookup
|
||||||
|
stack += lookup
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the adapter result of the current lookup. */
|
||||||
|
fun <T> adapterFound(result: JsonAdapter<T>) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val currentLookup = stack.last() as Lookup<T>
|
||||||
|
currentLookup.adapter = result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completes the current lookup by removing a stack frame.
|
||||||
|
*
|
||||||
|
* @param success true if the adapter cache should be populated if this is the topmost lookup.
|
||||||
|
*/
|
||||||
|
fun pop(success: Boolean) {
|
||||||
|
stack.removeLast()
|
||||||
|
if (!stack.isEmpty()) return
|
||||||
|
lookupChainThreadLocal.remove()
|
||||||
|
if (success) {
|
||||||
|
synchronized(adapterCache) {
|
||||||
|
var i = 0
|
||||||
|
val size = callLookups.size
|
||||||
|
while (i < size) {
|
||||||
|
val lookup = callLookups[i]
|
||||||
|
val replaced = adapterCache.put(lookup.cacheKey, lookup.adapter)
|
||||||
|
if (replaced != null) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
(lookup as Lookup<Any>).adapter = replaced as JsonAdapter<Any>
|
||||||
|
adapterCache[lookup.cacheKey] = replaced
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exceptionWithLookupStack(e: IllegalArgumentException): IllegalArgumentException {
|
||||||
|
// Don't add the lookup stack to more than one exception; the deepest is sufficient.
|
||||||
|
if (exceptionAnnotated) return e
|
||||||
|
exceptionAnnotated = true
|
||||||
|
val size = stack.size
|
||||||
|
if (size == 1 && stack.first().fieldName == null) return e
|
||||||
|
val errorMessage = buildString {
|
||||||
|
append(e.message)
|
||||||
|
for (lookup in stack.asReversed()) {
|
||||||
|
append("\nfor ").append(lookup.type)
|
||||||
|
if (lookup.fieldName != null) {
|
||||||
|
append(' ').append(lookup.fieldName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return IllegalArgumentException(errorMessage, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This class implements `JsonAdapter` so it can be used as a stub for re-entrant calls. */
|
||||||
|
internal class Lookup<T>(val type: Type, val fieldName: String?, val cacheKey: Any) : JsonAdapter<T>() {
|
||||||
|
var adapter: JsonAdapter<T>? = null
|
||||||
|
|
||||||
|
override fun fromJson(reader: JsonReader) = withAdapter { fromJson(reader) }
|
||||||
|
|
||||||
|
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") // TODO remove after JsonAdapter is migrated
|
||||||
|
override fun toJson(writer: JsonWriter, value: T?) = withAdapter { toJson(writer, value) }
|
||||||
|
|
||||||
|
private inline fun <R> withAdapter(body: JsonAdapter<T>.() -> R): R =
|
||||||
|
checkNotNull(adapter) { "JsonAdapter isn't ready" }.body()
|
||||||
|
|
||||||
|
override fun toString() = adapter?.toString() ?: super.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal companion object {
|
||||||
|
@JvmField
|
||||||
|
val BUILT_IN_FACTORIES: List<JsonAdapter.Factory> = buildList(6) {
|
||||||
|
add(StandardJsonAdapters.FACTORY)
|
||||||
|
add(CollectionJsonAdapter.FACTORY)
|
||||||
|
add(MapJsonAdapter.FACTORY)
|
||||||
|
add(ArrayJsonAdapter.FACTORY)
|
||||||
|
add(RecordJsonAdapter.FACTORY)
|
||||||
|
add(ClassJsonAdapter.FACTORY)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> newAdapterFactory(
|
||||||
|
type: Type,
|
||||||
|
jsonAdapter: JsonAdapter<T>
|
||||||
|
): JsonAdapter.Factory {
|
||||||
|
return JsonAdapter.Factory { targetType, annotations, _ ->
|
||||||
|
if (annotations.isEmpty() && typesMatch(type, targetType)) jsonAdapter else null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> newAdapterFactory(
|
||||||
|
type: Type,
|
||||||
|
annotation: Class<out Annotation>,
|
||||||
|
jsonAdapter: JsonAdapter<T>
|
||||||
|
): JsonAdapter.Factory {
|
||||||
|
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)) {
|
||||||
|
jsonAdapter
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -603,44 +603,44 @@ public final class MoshiTest {
|
|||||||
try {
|
try {
|
||||||
builder.add((null));
|
builder.add((null));
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (NullPointerException expected) {
|
||||||
assertThat(expected).hasMessageThat().isEqualTo("factory == null");
|
assertThat(expected).hasMessageThat().contains("Parameter specified as non-null is null");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
builder.add((Object) null);
|
builder.add((Object) null);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (NullPointerException expected) {
|
||||||
assertThat(expected).hasMessageThat().isEqualTo("adapter == null");
|
assertThat(expected).hasMessageThat().contains("Parameter specified as non-null is null");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
builder.add(null, null);
|
builder.add(null, null);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (NullPointerException expected) {
|
||||||
assertThat(expected).hasMessageThat().isEqualTo("type == null");
|
assertThat(expected).hasMessageThat().contains("Parameter specified as non-null is null");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
builder.add(type, null);
|
builder.add(type, null);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (NullPointerException expected) {
|
||||||
assertThat(expected).hasMessageThat().isEqualTo("jsonAdapter == null");
|
assertThat(expected).hasMessageThat().contains("Parameter specified as non-null is null");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
builder.add(null, null, null);
|
builder.add(null, null, null);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (NullPointerException expected) {
|
||||||
assertThat(expected).hasMessageThat().isEqualTo("type == null");
|
assertThat(expected).hasMessageThat().contains("Parameter specified as non-null is null");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
builder.add(type, null, null);
|
builder.add(type, null, null);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (NullPointerException expected) {
|
||||||
assertThat(expected).hasMessageThat().isEqualTo("annotation == null");
|
assertThat(expected).hasMessageThat().contains("Parameter specified as non-null is null");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
builder.add(type, annotation, null);
|
builder.add(type, annotation, null);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (NullPointerException expected) {
|
||||||
assertThat(expected).hasMessageThat().isEqualTo("jsonAdapter == null");
|
assertThat(expected).hasMessageThat().contains("Parameter specified as non-null is null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -771,7 +771,7 @@ public final class MoshiTest {
|
|||||||
moshi.adapter(null, Collections.<Annotation>emptySet());
|
moshi.adapter(null, Collections.<Annotation>emptySet());
|
||||||
fail();
|
fail();
|
||||||
} catch (NullPointerException expected) {
|
} catch (NullPointerException expected) {
|
||||||
assertThat(expected).hasMessageThat().isEqualTo("type == null");
|
assertThat(expected).hasMessageThat().contains("Parameter specified as non-null is null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -782,13 +782,13 @@ public final class MoshiTest {
|
|||||||
moshi.adapter(String.class, (Class<? extends Annotation>) null);
|
moshi.adapter(String.class, (Class<? extends Annotation>) null);
|
||||||
fail();
|
fail();
|
||||||
} catch (NullPointerException expected) {
|
} catch (NullPointerException expected) {
|
||||||
assertThat(expected).hasMessageThat().isEqualTo("annotationType == null");
|
assertThat(expected).hasMessageThat().contains("Parameter specified as non-null is null");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
moshi.adapter(String.class, (Set<? extends Annotation>) null);
|
moshi.adapter(String.class, (Set<? extends Annotation>) null);
|
||||||
fail();
|
fail();
|
||||||
} catch (NullPointerException expected) {
|
} catch (NullPointerException expected) {
|
||||||
assertThat(expected).hasMessageThat().isEqualTo("annotations == null");
|
assertThat(expected).hasMessageThat().contains("Parameter specified as non-null is null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -808,7 +808,7 @@ public final class MoshiTest {
|
|||||||
moshi.adapter(Object.class);
|
moshi.adapter(Object.class);
|
||||||
fail();
|
fail();
|
||||||
} catch (NullPointerException expected) {
|
} catch (NullPointerException expected) {
|
||||||
assertThat(expected).hasMessageThat().isEqualTo("annotations == null");
|
assertThat(expected).hasMessageThat().contains("Parameter specified as non-null is null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1235,7 +1235,8 @@ public final class MoshiTest {
|
|||||||
Moshi moshi = new Moshi.Builder().add(Pizza.class, new PizzaAdapter()).build();
|
Moshi moshi = new Moshi.Builder().add(Pizza.class, new PizzaAdapter()).build();
|
||||||
Moshi.Builder newBuilder = moshi.newBuilder();
|
Moshi.Builder newBuilder = moshi.newBuilder();
|
||||||
for (JsonAdapter.Factory factory : Moshi.BUILT_IN_FACTORIES) {
|
for (JsonAdapter.Factory factory : Moshi.BUILT_IN_FACTORIES) {
|
||||||
assertThat(factory).isNotIn(newBuilder.factories);
|
// Awkward but java sources don't know about the internal-ness of this
|
||||||
|
assertThat(factory).isNotIn(newBuilder.getFactories$moshi());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user