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)
The newest version!
package com.dslplatform.json.runtime;
import com.dslplatform.json.*;
import com.dslplatform.json.processor.Analysis;
import java.io.IOException;
import java.lang.annotation.Annotation;
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(Method factory) {
if (factory == null) throw new IllegalArgumentException("factory can't be null");
return parameterNameExtractor.extractNames(factory);
}
@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 Constructor> ctor = findBestCtor(raw, json);
final Method factory = findBestFactory(raw, json);
if (ctor == null && factory == null) return null;
final Type[] paramTypes = factory != null ? factory.getGenericParameterTypes() : ctor.getGenericParameterTypes();
if (paramTypes.length == 0
|| paramTypes.length == 1 && ObjectAnalyzer.matchesContext(paramTypes[0], json)) {
return null;
}
String[] names = factory != null ? extractNames(factory) : extractNames(ctor);
final Map typeIndex;
if (names == null) {
typeIndex = new HashMap<>();
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 (typeIndex.containsKey(p)) return null;
typeIndex.put(p, typeIndex.size());
}
} else {
typeIndex = 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 GenericsMapper genericMappings = GenericsMapper.create(manifest, raw);
final Object[] defArgs = findDefaultArguments(paramTypes, raw, genericMappings, json);
final int contextCount = json.context != null && Arrays.asList(defArgs).contains(json.context) ? 1 : 0;
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 boolean isBoolean = boolean.class.equals(mget.getReturnType());
final String name = Analysis.beanOrActualName(mget.getName(), isBoolean);
if (isPublicNonStatic(mget.getModifiers()) && !name.contains("$") && !objectMethods.contains(name)) {
matchingMethods.put(name, mget);
if (isBoolean && !name.equals(mget.getName())) {
matchingMethods.put(mget.getName(), mget);
}
}
}
final JsonWriter.WriteObject[] writeProps;
final int attributesCount = paramTypes.length - contextCount;
if (contextCount == 1 && paramTypes.length == 1) {
return unregister(manifest, json, oldWriter, oldReader);
}
if (names != null) {
if (matchingFields.size() == attributesCount) {
for (int i = 0; i < paramTypes.length; i++) {
final Field f = matchingFields.get(names[i]);
if (f == null && json.context != null && json.context == defArgs[i]) continue;
if (f == null || !analyzeField(json, paramTypes[i], fields, f, raw, 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 && json.context != null && json.context == defArgs[i]) continue;
if (m == null || !analyzeMethod(m, json, paramTypes[i], names[i], methods, raw, genericMappings)) {
return unregister(manifest, json, oldWriter, oldReader);
}
}
writeProps = methods.values().toArray(new JsonWriter.WriteObject[0]);
}
} else {
names = new String[typeIndex.size()];
if (matchingFields.size() == attributesCount) {
for (Type p : paramTypes) {
for (Map.Entry kv : matchingFields.entrySet()) {
final Field f = kv.getValue();
if (analyzeField(json, p, fields, f, raw, genericMappings)) {
matchingFields.remove(kv.getKey());
names[typeIndex.get(p)] = kv.getKey();
break;
}
}
}
if (fields.size() != attributesCount) {
return unregister(manifest, json, oldWriter, oldReader);
}
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, raw, genericMappings)) {
matchingMethods.remove(kv.getKey());
names[typeIndex.get(p)] = kv.getKey();
break;
}
}
}
if (methods.size() != attributesCount) {
return unregister(manifest, json, oldWriter, oldReader);
}
writeProps = methods.values().toArray(new JsonWriter.WriteObject[0]);
}
}
final DecodePropertyInfo[] readProps = new DecodePropertyInfo[attributesCount];
int idx = 0;
for (int i = 0; i < paramTypes.length; i++) {
final Type concreteType = genericMappings.makeConcrete(paramTypes[i], raw);
if (json.context != null && defArgs[i] == json.context) continue;
readProps[idx++] = new DecodePropertyInfo<>(names[i], false, false, i, false, new WriteMember(json, concreteType, factory != null ? factory : ctor));
}
final Settings.Function