com.dslplatform.json.runtime.ImmutableAnalyzer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dsl-json-java8 Show documentation
Show all versions of dsl-json-java8 Show documentation
DSL Platform compatible Java JSON library (https://dsl-platform.com)
package com.dslplatform.json.runtime;
import com.dslplatform.json.*;
import com.dslplatform.json.processor.Analysis;
import java.io.IOException;
import java.lang.reflect.*;
import java.util.*;
public abstract class ImmutableAnalyzer {
private static final Set objectMethods = new HashSet<>();
private static final ParameterNameExtractor parameterNameExtractor;
static {
for (Method m : Object.class.getMethods()) {
if (m.getParameterTypes().length == 0) {
objectMethods.add(m.getName());
}
}
List extractors = new ArrayList<>();
if (isClassAvailable("java.lang.reflect.Parameter")) {
extractors.add(new Java8ParameterNameExtractor());
}
if (isClassAvailable("com.thoughtworks.paranamer.Paranamer")) {
extractors.add(new ParanamerParameterNameExtractor());
}
parameterNameExtractor = new CompositeParameterNameExtractor(extractors);
}
private static boolean isClassAvailable(String className) {
try {
Class.forName(className);
return true;
} catch (NoClassDefFoundError | ClassNotFoundException ignore) {
return false;
}
}
@Nullable
public static String[] extractNames(Constructor> ctor) {
if (ctor == null) throw new IllegalArgumentException("ctor can't be null");
return parameterNameExtractor.extractNames(ctor);
}
private static class LazyImmutableDescription implements JsonWriter.WriteObject, JsonReader.ReadObject {
private final DslJson json;
private final Type type;
private JsonWriter.WriteObject encoder;
private JsonReader.ReadObject decoder;
volatile ImmutableDescription resolved;
LazyImmutableDescription(DslJson json, Type type) {
this.json = json;
this.type = type;
}
private boolean checkSignatureNotFound() {
int i = 0;
ImmutableDescription local = null;
while (i < 50) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new ConfigurationException(e);
}
local = resolved;
if (local != null) {
encoder = local;
decoder = local;
break;
}
i++;
}
return local == null;
}
@Override
public Object read(final JsonReader reader) throws IOException {
if (decoder == null) {
if (checkSignatureNotFound()) {
final JsonReader.ReadObject tmp = json.tryFindReader(type);
if (tmp == null || tmp == this) {
throw new ConfigurationException("Unable to find reader for " + type);
}
decoder = tmp;
}
}
return decoder.read(reader);
}
@Override
public void write(final JsonWriter writer, @Nullable final Object value) {
if (encoder == null) {
if (checkSignatureNotFound()) {
final JsonWriter.WriteObject tmp = json.tryFindWriter(type);
if (tmp == null || tmp == this) {
throw new ConfigurationException("Unable to find writer for " + type);
}
encoder = tmp;
}
}
encoder.write(writer, value);
}
}
public static final DslJson.ConverterFactory CONVERTER = new DslJson.ConverterFactory() {
@Nullable
@Override
public ImmutableDescription tryCreate(Type manifest, DslJson dslJson) {
if (manifest instanceof Class>) {
return analyze(manifest, (Class>) manifest, dslJson);
}
if (manifest instanceof ParameterizedType) {
final ParameterizedType pt = (ParameterizedType) manifest;
if (pt.getActualTypeArguments().length == 1) {
return analyze(manifest, (Class>) pt.getRawType(), dslJson);
}
}
return null;
}
};
@Nullable
private static ImmutableDescription analyze(final Type manifest, final Class raw, final DslJson> json) {
if (raw.isArray()
|| Collection.class.isAssignableFrom(raw)
|| (raw.getModifiers() & Modifier.ABSTRACT) != 0
|| raw.isInterface()
|| (raw.getDeclaringClass() != null && (raw.getModifiers() & Modifier.STATIC) == 0)
|| (raw.getModifiers() & Modifier.PUBLIC) == 0) {
return null;
}
final ArrayList> ctors = new ArrayList<>();
for (Constructor> ctor : raw.getDeclaredConstructors()) {
if ((ctor.getModifiers() & Modifier.PUBLIC) == 1) {
ctors.add(ctor);
}
}
if (ctors.size() != 1) return null;
final Constructor> ctor = ctors.get(0);
final Type[] paramTypes = ctor.getGenericParameterTypes();
if (paramTypes.length == 0) {
return null;
}
String[] names = extractNames(ctor);
if (names == null) {
final Set types = new HashSet<>();
for(Type p : paramTypes) {
//only allow registration without name when all types are different
//TODO: ideally we could allow some ad hoc heuristics to test which value goes to which parameter.... but meh
if (!types.add(p)) return null;
}
}
final LazyImmutableDescription lazy = new LazyImmutableDescription(json, manifest);
final JsonWriter.WriteObject oldWriter = json.registerWriter(manifest, lazy);
final JsonReader.ReadObject oldReader = json.registerReader(manifest, lazy);
final LinkedHashMap fields = new LinkedHashMap<>();
final LinkedHashMap methods = new LinkedHashMap<>();
final HashMap genericMappings = Generics.analyze(manifest, raw);
final Object[] defArgs = findDefaultArguments(paramTypes, genericMappings, json);
final LinkedHashMap matchingFields = new LinkedHashMap<>();
for (final Field f : raw.getFields()) {
if (isPublicFinalNonStatic(f.getModifiers())) {
matchingFields.put(f.getName(), f);
}
}
final LinkedHashMap matchingMethods = new LinkedHashMap<>();
for (final Method mget : raw.getMethods()) {
if (mget.getParameterTypes().length != 0) continue;
final String name = Analysis.beanOrActualName(mget.getName());
if (isPublicNonStatic(mget.getModifiers()) && !name.contains("$") && !objectMethods.contains(name)) {
matchingMethods.put(name, mget);
}
}
final JsonWriter.WriteObject[] writeProps;
if (names != null) {
if (matchingFields.size() == paramTypes.length) {
for (int i = 0; i < paramTypes.length; i++) {
final Field f = matchingFields.get(names[i]);
if (f == null || !analyzeField(json, paramTypes[i], fields, f, genericMappings)) {
return unregister(manifest, json, oldWriter, oldReader);
}
}
writeProps = fields.values().toArray(new JsonWriter.WriteObject[0]);
} else {
for (int i = 0; i < paramTypes.length; i++) {
final Method m = matchingMethods.get(names[i]);
if (m == null || !analyzeMethod(m, json, paramTypes[i], names[i], methods, genericMappings)) {
return unregister(manifest, json, oldWriter, oldReader);
}
}
writeProps = methods.values().toArray(new JsonWriter.WriteObject[0]);
}
} else {
names = new String[paramTypes.length];
if (matchingFields.size() == paramTypes.length) {
List orderedFields = new ArrayList<>(matchingFields.values());
for (int i = 0; i < paramTypes.length; i++) {
final Field f = orderedFields.get(i);
if (!analyzeField(json, paramTypes[i], fields, f, genericMappings)) {
return unregister(manifest, json, oldWriter, oldReader);
}
names[i] = f.getName();
}
writeProps = fields.values().toArray(new JsonWriter.WriteObject[0]);
} else {
for (Type p : paramTypes) {
for (Map.Entry kv : matchingMethods.entrySet()) {
final Method m = kv.getValue();
if (analyzeMethod(m, json, p, kv.getKey(), methods, genericMappings)) {
matchingMethods.remove(kv.getKey());
break;
}
}
}
if (methods.size() == paramTypes.length) {
writeProps = methods.values().toArray(new JsonWriter.WriteObject[0]);
} else {
return unregister(manifest, json, oldWriter, oldReader);
}
names = (fields.isEmpty() ? methods.keySet() : fields.keySet()).toArray(new String[0]);
}
}
final DecodePropertyInfo[] readProps = new DecodePropertyInfo[paramTypes.length];
for (int i = 0; i < paramTypes.length; i++) {
final Type concreteType = Generics.makeConcrete(paramTypes[i], genericMappings);
readProps[i] = new DecodePropertyInfo<>(names[i], false, false, i, false, new WriteCtor(json, concreteType, ctor));
}
final ImmutableDescription converter = new ImmutableDescription<>(
manifest,
defArgs,
new Settings.Function