Skip to content

Commit

Permalink
Improved support for generic types
Browse files Browse the repository at this point in the history
If generic type is used as type declaration it should be used from available generic types.
This should be used for actual type mapping for concrete types. Therefore it can't be used for generic type mapping, as it should remain as-is.

Improved writer caching. Cache raw types for generics so they can be looked up without searching for factories.
Simplistic support for different ctor mapping. For now only recognize type variables. Ideally same index could/should be used for resolution.
For now this only works when default name is used. Support for aliases still needs to be added.

Recognize generic type during raw type analysis. If nothing is found, try to check extended lookup, otherwise converter is not invoked.

While this does cover basic cases, additional support for more complicated cases is required. Along with testing of such cases, eg generics across several levels of hierarchy.
  • Loading branch information
zapov committed Dec 28, 2022
1 parent a7a4a33 commit 4ca21bc
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 8 deletions.
Expand Up @@ -866,7 +866,8 @@ private void returnInstance(final String alignment, StructInfo info, final Strin
}
int i = params.size();
for (VariableElement p : params) {
code.append("_").append(p.getSimpleName()).append("_");
VariableElement n = info.argumentMapping.get(p);
code.append("_").append((n != null ? n : p).getSimpleName()).append("_");
i--;
if (i > 0) code.append(", ");
}
Expand Down
13 changes: 11 additions & 2 deletions library/src/main/java/com/dslplatform/json/DslJson.java
Expand Up @@ -1055,7 +1055,12 @@ public JsonWriter.WriteObject<?> tryFindWriter(final Type manifest) {
}
}
writer = lookupFromFactories(manifest, actualType, writerFactories, writers);
if (writer != null) return writer;
if (writer != null) {
if (manifest instanceof Class<?> && actualType == manifest) {
writers.putIfAbsent(manifest, writer);
}
return writer;
}
if (!(actualType instanceof Class<?>)) return null;
Class<?> found = writerMap.get(actualType);
if (found != null) {
Expand Down Expand Up @@ -1112,9 +1117,13 @@ private <T> T lookupFromFactories(
final List<ConverterFactory<T>> factories,
final ConcurrentMap<Type, T> cache) {
if (manifest instanceof Class<?>) {
externalConverterAnalyzer.tryFindConverter((Class<?>) manifest, this);
Class<?> raw = (Class<?>) manifest;
externalConverterAnalyzer.tryFindConverter(raw, this);
T found = cache.get(manifest);
if (found != null) return found;
if (raw.getTypeParameters().length > 0) {
checkExternal(manifest, cache);
}
} else if (manifest instanceof ParameterizedType) {
checkExternal(manifest, cache);
}
Expand Down
56 changes: 53 additions & 3 deletions library/src/main/java/com/dslplatform/json/processor/Analysis.java
Expand Up @@ -182,6 +182,40 @@ public Map<String, StructInfo> analyze() {
for (Map.Entry<String, StructInfo> it : structs.entrySet()) {
final StructInfo info = it.getValue();
final String className = it.getKey();
if (info.type == ObjectType.CLASS) {
TypeMirror parentType = info.element.getSuperclass();
String parentName = parentType.toString();
int genIndex = parentName.indexOf('<');
if (genIndex != -1) {
LinkedHashMap<String, TypeMirror> generics = new LinkedHashMap<String, TypeMirror>(info.genericSignatures);
String rawName = parentName.substring(0, genIndex);
StructInfo parentInfo = structs.get(rawName);
info.supertype(parentInfo);
while (parentInfo != null) {
for (String key : parentInfo.attributes.keySet()) {
if (info.attributes.containsKey(key)) continue;
AttributeInfo attr = parentInfo.attributes.get(key);
if (attr.isGeneric) {
AttributeInfo newAttr = attr.asConcreteType(generics);
if (newAttr != null) {
info.attributes.put(key, newAttr);
} else {
hasError = true;
messager.printMessage(
Diagnostic.Kind.ERROR,
"Unable to convert generic type to concrete type.",
attr.element,
attr.annotation);
}
} else {
info.attributes.put(key, attr);
}
}
parentType = parentInfo.element.getSuperclass();
parentInfo = structs.get(parentType.toString());
}
}
}
if (info.type == ObjectType.CLASS && info.selectedConstructor() == null && info.annotatedFactory == null
&& info.builder == null && !info.hasKnownConversion() && info.matchingConstructors != null) {
hasError = true;
Expand Down Expand Up @@ -306,8 +340,9 @@ public Map<String, StructInfo> analyze() {
info.annotatedFactory,
info.annotation);
} else {
for (VariableElement p : info.annotatedFactory.getParameters()) {
for (int i = 0; i < info.annotatedFactory.getParameters().size(); i++) {
boolean found = false;
VariableElement p = info.annotatedFactory.getParameters().get(i);
String argName = p.getSimpleName().toString();
for (AttributeInfo attr : info.attributes.values()) {
if (attr.name.equals(argName)) {
Expand All @@ -323,6 +358,12 @@ public Map<String, StructInfo> analyze() {
}
}
}
ExecutableElement parentFactory = info.getParent() != null ? info.getParent().annotatedFactory : null;
if (!found && parentFactory != null && parentFactory.getParameters().size() == info.annotatedFactory.getParameters().size()) {
VariableElement pe = parentFactory.getParameters().get(i);
found = pe.asType().getKind() == TypeKind.TYPEVAR;
info.argumentMapping.put(p, pe);
}
if (!found) {
hasError = true;
if (info.objectFormatPolicy == CompiledJson.ObjectFormatPolicy.EXPLICIT) {
Expand Down Expand Up @@ -365,7 +406,8 @@ public Map<String, StructInfo> analyze() {
}
}
if (info.type == ObjectType.CLASS && !info.hasKnownConversion() && info.usesCtorWithArguments()) {
for (VariableElement p : info.selectedConstructor().getParameters()) {
for (int i = 0; i < info.selectedConstructor().getParameters().size(); i++) {
VariableElement p = info.selectedConstructor().getParameters().get(i);
boolean found = false;
String argName = p.getSimpleName().toString();
for (AttributeInfo attr : info.attributes.values()) {
Expand All @@ -382,6 +424,12 @@ public Map<String, StructInfo> analyze() {
}
}
}
ExecutableElement parentCtor = info.getParent() != null ? info.getParent().selectedConstructor() : null;
if (!found && parentCtor != null && parentCtor.getParameters().size() == info.selectedConstructor().getParameters().size()) {
VariableElement pe = parentCtor.getParameters().get(i);
found = pe.asType().getKind() == TypeKind.TYPEVAR;
info.argumentMapping.put(p, pe);
}
if (!found) {
hasError = true;
if (info.objectFormatPolicy == CompiledJson.ObjectFormatPolicy.EXPLICIT) {
Expand Down Expand Up @@ -914,7 +962,9 @@ private Map<String, TypeMirror> findGenericSignatures(TypeMirror type) {
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
List<? extends TypeParameterElement> typeParameters = ((TypeElement) element).getTypeParameters();
for (int i = 0; i < typeParameters.size(); i++) {
genericAttributes.put(typeParameters.get(i).toString(), typeArguments.get(i));
String genName = typeParameters.get(i).toString();
if (genericAttributes.containsKey(genName)) continue;
genericAttributes.put(genName, typeArguments.get(i));
}
queue.addAll(types.directSupertypes(mirror));
}
Expand Down
Expand Up @@ -93,6 +93,36 @@ public AttributeInfo(
this.containsStructOwnerType = containsStructOwnerType;
}

public AttributeInfo asConcreteType(LinkedHashMap<String, TypeMirror> genericSignatures) {
TypeMirror concreteType = genericSignatures.get(this.type.toString());
if (concreteType == null) return null;
return new AttributeInfo(
this.name,
this.readMethod,
this.writeMethod,
this.field,
this.argument,
concreteType,
this.isList,
this.isSet,
this.isMap,
this.annotation,
this.notNull,
this.mandatory,
this.index,
this.alias,
this.fullMatch,
this.typeSignature,
this.includeToMinimal,
this.converter,
this.isJsonObject,
this.usedTypes,
this.typeVariablesIndex,
genericSignatures,
this.containsStructOwnerType
);
}

public boolean isEnum(Map<String, StructInfo> structs) {
StructInfo struct = typeName == null ? null : structs.get(typeName);
return struct != null && struct.type == ObjectType.ENUM;
Expand Down Expand Up @@ -123,10 +153,12 @@ public List<String> collectionContent(TypeSupport typeSupport, Map<String, Struc
return canResolveCollection(content, typeSupport, structs) ? Collections.singletonList(content) : null;
} else if (isList || isSet) {
int ind = typeName.indexOf('<');
if (ind == -1) return null;
String content = typeName.substring(ind + 1, typeName.length() - 1);
return canResolveCollection(content, typeSupport, structs) ? Collections.singletonList(content) : null;
} else if (isMap) {
int indGen = typeName.indexOf('<');
if (indGen == -1) return null;
int indComma = typeName.indexOf(',', indGen + 1);
String content1 = typeName.substring(indGen + 1, indComma);
String content2 = typeName.substring(indComma + 1, typeName.length() - 1);
Expand Down
Expand Up @@ -43,6 +43,7 @@ public class StructInfo {
public final boolean isParameterized;
public final List<String> typeParametersNames;
public final Map<String, TypeMirror> genericSignatures;
public final Map<VariableElement, VariableElement> argumentMapping = new HashMap<VariableElement, VariableElement>();

private ExecutableElement selectedConstructor;
private boolean createThroughConstructor;
Expand Down Expand Up @@ -110,6 +111,9 @@ else if (matchingConstructors.size() == 1 && type == ObjectType.CLASS) {
this.typeParametersNames = extractParametersNames(element.getTypeParameters());
this.isParameterized = !typeParametersNames.isEmpty();
this.genericSignatures = genericSignatures;
for (String gt : typeParametersNames) {
genericSignatures.remove(gt);
}
}

public StructInfo(ConverterInfo converter, DeclaredType discoveredBy, TypeElement target, String name, String binaryName) {
Expand Down Expand Up @@ -156,13 +160,20 @@ public ExecutableElement selectedConstructor() {

public void supertype(@Nullable StructInfo parent) {
if (parent == null) return;
if (type == ObjectType.CLASS && parent.implementations.contains(this)) {
if (type == ObjectType.CLASS) {
inheritsFrom = parent;
}
}

@Nullable
public StructInfo getParent() {
return inheritsFrom;
}

public Collection<AttributeInfo> inheritedAttributes() {
if (inheritsFrom == null) return Collections.emptyList();
if (inheritsFrom == null) {
return Collections.emptyList();
}
return inheritsFrom.attributes.values();
}

Expand Down
Expand Up @@ -755,6 +755,16 @@ public void genericsWithInheritence() {
checkValidCompilation(GenericsWithInheritance.FirstChild.class);
}

@Test
public void genericsWithSelfReference() {
checkValidCompilation(GenericSelfReference.class);
}

@Test
public void genericsBoundWithSelfReference() {
checkValidCompilation(GenericBoundSelfReferences.GenericBoundSelfReferenceString.class);
}

@Test
public void conflictingDiscriminator() {
assertCompilationReturned(
Expand Down
@@ -0,0 +1,31 @@
package com.dslplatform.json.models;

import com.dslplatform.json.CompiledJson;

public abstract class GenericBoundSelfReferences {
@CompiledJson
public static class GenericBoundSelfReferenceString extends GenericBoundSelfReference<String> {
public GenericBoundSelfReferenceString(String stringValue) {
super(stringValue);
}
}

@CompiledJson
public static class GenericBoundSelfReference<T> implements Comparable<GenericBoundSelfReference<T>> {

final T genericField;

public GenericBoundSelfReference(T genericField) {
this.genericField = genericField;
}

public final T getGenericField() {
return genericField;
}

@Override
public int compareTo(GenericBoundSelfReference<T> other) {
return 1;
}
}
}
@@ -0,0 +1,22 @@
package com.dslplatform.json.models;

import com.dslplatform.json.CompiledJson;

@CompiledJson
public class GenericSelfReference<T> implements Comparable<GenericSelfReference<T>> {

final T genericField;

public GenericSelfReference(T genericField) {
this.genericField = genericField;
}

public final T getGenericField() {
return genericField;
}

@Override
public int compareTo(GenericSelfReference<T> other) {
return 1;
}
}
1 change: 1 addition & 0 deletions tests-java8-external-models/pom.xml
Expand Up @@ -32,6 +32,7 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<compilerVersion>1.8</compilerVersion>
<source>1.8</source>
<target>1.8</target>
</configuration>
Expand Down
67 changes: 67 additions & 0 deletions tests-java8/src/test/java/com/dslplatform/json/GenericTest.java
Expand Up @@ -344,4 +344,71 @@ public void emptyGenericArraysWithCtor() throws IOException {
Assert.assertEquals(0, z.i4.length);
Assert.assertEquals(0, z.i5.length);
}

@CompiledJson
public static class GenericSelfReference<T> implements Comparable<GenericSelfReference<T>> {

final T genericField;

public GenericSelfReference(T genericField) {
this.genericField = genericField;
}

public final T getGenericField() {
return genericField;
}

@Override
public int compareTo(GenericSelfReference<T> other) {
return 1;
}
}

@Test
public void canCopeWithSelfReferences() throws IOException {

byte[] bytes = "{\"genericField\":\"ABC\"}".getBytes("UTF-8");

Type type = new TypeDefinition<GenericSelfReference<String>>() {}.type;
GenericSelfReference<String> result = (GenericSelfReference<String>)dslJson.deserialize(type, bytes, bytes.length);

Assert.assertEquals("ABC", result.getGenericField());

ByteArrayOutputStream os = new ByteArrayOutputStream();
dslJsonRuntime.serialize(new GenericSelfReference<>("XYZ"), os);

Assert.assertEquals("{\"genericField\":\"XYZ\"}", os.toString("UTF-8"));

os.reset();
JsonWriter.WriteObject wo = dslJson.tryFindWriter(type);
JsonWriter w = dslJson.newWriter();
w.reset(os);
wo.write(w, new GenericSelfReference<>("XYZ"));
w.flush();

Assert.assertEquals("{\"genericField\":\"XYZ\"}", os.toString("UTF-8"));
}

@CompiledJson
public static class GenericSelfReferenceString extends GenericSelfReference<String> {

public GenericSelfReferenceString(String stringField) {
super(stringField);
}
}

@Test
public void canCopeWithBoundSelfReferences() throws IOException {

byte[] bytes = "{\"genericField\":\"ABC\"}".getBytes("UTF-8");

GenericSelfReferenceString result = dslJson.deserialize(GenericSelfReferenceString.class, bytes, bytes.length);

Assert.assertEquals("ABC", result.getGenericField());

ByteArrayOutputStream os = new ByteArrayOutputStream();
dslJson.serialize(new GenericSelfReferenceString("XYZ"), os);

Assert.assertEquals("{\"genericField\":\"XYZ\"}", os.toString("UTF-8"));
}
}

0 comments on commit 4ca21bc

Please sign in to comment.