Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use functions in Util where possible #1444

Merged
merged 1 commit into from Dec 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -15,7 +15,8 @@
*/
package com.squareup.moshi.adapters;

import com.squareup.moshi.Json;
import static com.squareup.moshi.internal.Util.jsonName;

import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.JsonDataException;
import com.squareup.moshi.JsonReader;
Expand Down Expand Up @@ -67,12 +68,7 @@ public EnumJsonAdapter<T> withUnknownFallback(@Nullable T fallbackValue) {
nameStrings = new String[constants.length];
for (int i = 0; i < constants.length; i++) {
String constantName = constants[i].name();
Json annotation = enumType.getField(constantName).getAnnotation(Json.class);
String name =
annotation != null && !Json.UNSET_NAME.equals(annotation.name())
? annotation.name()
: constantName;
nameStrings[i] = name;
nameStrings[i] = jsonName(constantName, enumType.getField(constantName));
}
options = JsonReader.Options.of(nameStrings);
} catch (NoSuchFieldException e) {
Expand Down
Expand Up @@ -17,13 +17,13 @@

import static java.lang.annotation.RetentionPolicy.RUNTIME;

import com.squareup.moshi.Json;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.JsonQualifier;
import com.squareup.moshi.JsonReader;
import com.squareup.moshi.JsonWriter;
import com.squareup.moshi.Moshi;
import com.squareup.moshi.Types;
import com.squareup.moshi.internal.Util;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
Expand Down Expand Up @@ -77,13 +77,8 @@ public JsonAdapter<?> create(
constants = enumType.getEnumConstants();
nameStrings = new String[constants.length];
for (int i = 0; i < constants.length; i++) {
T constant = constants[i];
Json annotation = enumType.getField(constant.name()).getAnnotation(Json.class);
String name =
annotation != null && !Json.UNSET_NAME.equals(annotation.name())
? annotation.name()
: constant.name();
nameStrings[i] = name;
String constantName = constants[i].name();
nameStrings[i] = Util.jsonName(constantName, enumType.getField(constantName));
}
options = JsonReader.Options.of(nameStrings);
} catch (NoSuchFieldException e) {
Expand Down
Expand Up @@ -135,7 +135,7 @@ internal class KotlinJsonAdapter<T>(
for (binding in allBindings) {
if (binding == null) continue // Skip constructor parameters that aren't properties.

writer.name(binding.name)
writer.name(binding.jsonName)
binding.adapter.toJson(writer, binding.get(value))
}
writer.endObject()
Expand All @@ -144,8 +144,7 @@ internal class KotlinJsonAdapter<T>(
override fun toString() = "KotlinJsonAdapter(${constructor.returnType})"

data class Binding<K, P>(
val name: String,
val jsonName: String?,
val jsonName: String,
val adapter: JsonAdapter<P>,
val property: KProperty1<K, P>,
val parameter: KParameter?,
Expand Down Expand Up @@ -264,7 +263,7 @@ public class KotlinJsonAdapterFactory : JsonAdapter.Factory {

if (property !is KMutableProperty1 && parameter == null) continue

val name = jsonAnnotation?.name?.takeUnless { it == Json.UNSET_NAME } ?: property.name
val jsonName = jsonAnnotation?.name?.takeUnless { it == Json.UNSET_NAME } ?: property.name
val propertyType = when (val propertyTypeClassifier = property.returnType.classifier) {
is KClass<*> -> {
if (propertyTypeClassifier.isValue) {
Expand Down Expand Up @@ -298,8 +297,7 @@ public class KotlinJsonAdapterFactory : JsonAdapter.Factory {

@Suppress("UNCHECKED_CAST")
bindingsByName[property.name] = KotlinJsonAdapter.Binding(
name,
jsonAnnotation?.name?.takeUnless { it == Json.UNSET_NAME } ?: name,
jsonName,
adapter,
property as KProperty1<Any, Any?>,
parameter,
Expand All @@ -323,7 +321,7 @@ public class KotlinJsonAdapterFactory : JsonAdapter.Factory {
}

val nonIgnoredBindings = bindings.filterNotNull()
val options = JsonReader.Options.of(*nonIgnoredBindings.map { it.name }.toTypedArray())
val options = JsonReader.Options.of(*nonIgnoredBindings.map { it.jsonName }.toTypedArray())
return KotlinJsonAdapter(constructor, bindings, nonIgnoredBindings, options).nullSafe()
}
}
10 changes: 4 additions & 6 deletions moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java
Expand Up @@ -15,6 +15,7 @@
*/
package com.squareup.moshi;

import static com.squareup.moshi.internal.Util.jsonName;
import static com.squareup.moshi.internal.Util.resolve;

import com.squareup.moshi.internal.Util;
Expand Down Expand Up @@ -147,12 +148,9 @@ private void createFieldBindings(
field.setAccessible(true);

// Store it using the field's name. If there was already a field with this name, fail!
String name =
jsonAnnotation != null && !Json.UNSET_NAME.equals(jsonAnnotation.name())
? jsonAnnotation.name()
: fieldName;
FieldBinding<Object> fieldBinding = new FieldBinding<>(name, field, adapter);
FieldBinding<?> replaced = fieldBindings.put(name, fieldBinding);
String jsonName = jsonName(fieldName, jsonAnnotation);
FieldBinding<Object> fieldBinding = new FieldBinding<>(jsonName, field, adapter);
FieldBinding<?> replaced = fieldBindings.put(jsonName, fieldBinding);
if (replaced != null) {
throw new IllegalArgumentException(
"Conflicting fields:\n"
Expand Down
Expand Up @@ -274,12 +274,8 @@ static final class EnumJsonAdapter<T extends Enum<T>> extends JsonAdapter<T> {
nameStrings = new String[constants.length];
for (int i = 0; i < constants.length; i++) {
T constant = constants[i];
Json annotation = enumType.getField(constant.name()).getAnnotation(Json.class);
String name =
annotation != null && !Json.UNSET_NAME.equals(annotation.name())
? annotation.name()
: constant.name();
nameStrings[i] = name;
String constantName = constant.name();
nameStrings[i] = Util.jsonName(constantName, enumType.getField(constantName));
}
options = JsonReader.Options.of(nameStrings);
} catch (NoSuchFieldException e) {
Expand Down
11 changes: 11 additions & 0 deletions moshi/src/main/java/com/squareup/moshi/internal/Util.java
Expand Up @@ -19,6 +19,7 @@
import static com.squareup.moshi.Types.subtypeOf;
import static com.squareup.moshi.Types.supertypeOf;

import com.squareup.moshi.Json;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.JsonClass;
import com.squareup.moshi.JsonDataException;
Expand Down Expand Up @@ -95,6 +96,16 @@ private static String getKotlinMetadataClassName() {

private Util() {}

public static String jsonName(String declaredName, AnnotatedElement element) {
return jsonName(declaredName, element.getAnnotation(Json.class));
}

public static String jsonName(String declaredName, @Nullable Json annotation) {
if (annotation == null) return declaredName;
String annotationName = annotation.name();
return Json.UNSET_NAME.equals(annotationName) ? declaredName : annotationName;
}

public static boolean typesMatch(Type pattern, Type candidate) {
// TODO: permit raw types (like Set.class) to match non-raw candidates (like Set<Long>).
return Types.equals(pattern, candidate);
Expand Down
142 changes: 66 additions & 76 deletions moshi/src/main/java16/com/squareup/moshi/RecordJsonAdapter.java
Expand Up @@ -15,6 +15,7 @@
*/
package com.squareup.moshi;

import static com.squareup.moshi.internal.Util.rethrowCause;
import static java.lang.invoke.MethodType.methodType;

import com.squareup.moshi.internal.Util;
Expand All @@ -25,9 +26,8 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.RecordComponent;
import java.util.Collections;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

Expand All @@ -39,80 +39,81 @@
final class RecordJsonAdapter<T> extends JsonAdapter<T> {

static final JsonAdapter.Factory FACTORY =
(type, annotations, moshi) -> {
if (!annotations.isEmpty()) {
return null;
}
new Factory() {
@Override
public JsonAdapter<?> create(
Type type, Set<? extends Annotation> annotations, Moshi moshi) {
if (!annotations.isEmpty()) {
return null;
}

if (!(type instanceof Class) && !(type instanceof ParameterizedType)) {
return null;
}
if (!(type instanceof Class) && !(type instanceof ParameterizedType)) {
return null;
}

var rawType = Types.getRawType(type);
if (!rawType.isRecord()) {
return null;
}
var rawType = Types.getRawType(type);
if (!rawType.isRecord()) {
return null;
}

var components = rawType.getRecordComponents();
var bindings = new LinkedHashMap<String, ComponentBinding<?>>();
var componentRawTypes = new Class<?>[components.length];
var lookup = MethodHandles.lookup();
for (int i = 0, componentsLength = components.length; i < componentsLength; i++) {
RecordComponent component = components[i];
componentRawTypes[i] = component.getType();
var name = component.getName();
var componentType = Util.resolve(type, rawType, component.getGenericType());
var jsonName = name;
Set<Annotation> qualifiers = null;
for (var annotation : component.getDeclaredAnnotations()) {
if (annotation instanceof Json jsonAnnotation) {
var annotationName = jsonAnnotation.name();
if (!Json.UNSET_NAME.equals(annotationName)) {
jsonName = jsonAnnotation.name();
}
} else {
if (annotation.annotationType().isAnnotationPresent(JsonQualifier.class)) {
if (qualifiers == null) {
qualifiers = new LinkedHashSet<>();
}
qualifiers.add(annotation);
}
var components = rawType.getRecordComponents();
var bindings = new LinkedHashMap<String, ComponentBinding<?>>();
var componentRawTypes = new Class<?>[components.length];
var lookup = MethodHandles.lookup();
for (int i = 0, componentsLength = components.length; i < componentsLength; i++) {
RecordComponent component = components[i];
componentRawTypes[i] = component.getType();
ComponentBinding<Object> componentBinding =
createComponentBinding(type, rawType, moshi, lookup, component);
var replaced = bindings.put(componentBinding.jsonName, componentBinding);
if (replaced != null) {
throw new IllegalArgumentException(
"Conflicting components:\n"
+ " "
+ replaced.componentName
+ "\n"
+ " "
+ componentBinding.componentName);
}
}
if (qualifiers == null) {
qualifiers = Collections.emptySet();

MethodHandle constructor;
try {
constructor =
lookup.findConstructor(rawType, methodType(void.class, componentRawTypes));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new AssertionError(e);
}

return new RecordJsonAdapter<>(constructor, rawType.getSimpleName(), bindings).nullSafe();
}

private static ComponentBinding<Object> createComponentBinding(
Type type,
Class<?> rawType,
Moshi moshi,
MethodHandles.Lookup lookup,
RecordComponent component) {
var componentName = component.getName();
var jsonName = Util.jsonName(componentName, component);

var componentType = Util.resolve(type, rawType, component.getGenericType());
Set<? extends Annotation> qualifiers = Util.jsonAnnotations(component);
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, componentRawTypes));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new AssertionError(e);
return new ComponentBinding<>(componentName, jsonName, adapter, accessor);
}
return new RecordJsonAdapter<>(constructor, rawType.getSimpleName(), bindings).nullSafe();
};

private static record ComponentBinding<T>(
String name, String jsonName, JsonAdapter<T> adapter, MethodHandle accessor) {}
String componentName, String jsonName, JsonAdapter<T> adapter, MethodHandle accessor) {}

private final String targetClass;
private final MethodHandle constructor;
Expand Down Expand Up @@ -146,23 +147,17 @@ public T fromJson(JsonReader reader) throws IOException {
reader.skipValue();
continue;
}
var result = componentBindingsArray[index].adapter.fromJson(reader);
resultsArray[index] = result;
resultsArray[index] = componentBindingsArray[index].adapter.fromJson(reader);
}
reader.endObject();

try {
//noinspection unchecked
return (T) constructor.invokeWithArguments(resultsArray);
} catch (InvocationTargetException e) {
throw rethrowCause(e);
} 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);
}
throw new AssertionError(e);
}
}

Expand All @@ -175,15 +170,10 @@ public void toJson(JsonWriter writer, T value) throws IOException {
Object componentValue;
try {
componentValue = binding.accessor.invoke(value);
} catch (InvocationTargetException e) {
throw Util.rethrowCause(e);
} 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);
}
throw new AssertionError(e);
}
binding.adapter.toJson(writer, componentValue);
}
Expand Down