diff --git a/java8/src/main/java/com/dslplatform/json/processor/ConverterTemplate.java b/java8/src/main/java/com/dslplatform/json/processor/ConverterTemplate.java index 1dfa064b..44eaeb55 100644 --- a/java8/src/main/java/com/dslplatform/json/processor/ConverterTemplate.java +++ b/java8/src/main/java/com/dslplatform/json/processor/ConverterTemplate.java @@ -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(", "); } diff --git a/library/src/main/java/com/dslplatform/json/DslJson.java b/library/src/main/java/com/dslplatform/json/DslJson.java index 4f549b34..56c380a9 100644 --- a/library/src/main/java/com/dslplatform/json/DslJson.java +++ b/library/src/main/java/com/dslplatform/json/DslJson.java @@ -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) { @@ -1112,9 +1117,13 @@ private T lookupFromFactories( final List> factories, final ConcurrentMap 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); } diff --git a/library/src/main/java/com/dslplatform/json/processor/Analysis.java b/library/src/main/java/com/dslplatform/json/processor/Analysis.java index 1cb50620..f12bc033 100644 --- a/library/src/main/java/com/dslplatform/json/processor/Analysis.java +++ b/library/src/main/java/com/dslplatform/json/processor/Analysis.java @@ -182,6 +182,40 @@ public Map analyze() { for (Map.Entry 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 generics = new LinkedHashMap(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; @@ -306,8 +340,9 @@ public Map 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)) { @@ -323,6 +358,12 @@ public Map 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) { @@ -365,7 +406,8 @@ public Map 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()) { @@ -382,6 +424,12 @@ public Map 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) { @@ -914,7 +962,9 @@ private Map findGenericSignatures(TypeMirror type) { List typeArguments = declaredType.getTypeArguments(); List 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)); } diff --git a/library/src/main/java/com/dslplatform/json/processor/AttributeInfo.java b/library/src/main/java/com/dslplatform/json/processor/AttributeInfo.java index 37dbda11..fa86bc8b 100644 --- a/library/src/main/java/com/dslplatform/json/processor/AttributeInfo.java +++ b/library/src/main/java/com/dslplatform/json/processor/AttributeInfo.java @@ -93,6 +93,36 @@ public AttributeInfo( this.containsStructOwnerType = containsStructOwnerType; } + public AttributeInfo asConcreteType(LinkedHashMap 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 structs) { StructInfo struct = typeName == null ? null : structs.get(typeName); return struct != null && struct.type == ObjectType.ENUM; @@ -123,10 +153,12 @@ public List collectionContent(TypeSupport typeSupport, Map typeParametersNames; public final Map genericSignatures; + public final Map argumentMapping = new HashMap(); private ExecutableElement selectedConstructor; private boolean createThroughConstructor; @@ -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) { @@ -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 inheritedAttributes() { - if (inheritsFrom == null) return Collections.emptyList(); + if (inheritsFrom == null) { + return Collections.emptyList(); + } return inheritsFrom.attributes.values(); } diff --git a/processor/src/test/java/com/dslplatform/json/JavaValidationTest.java b/processor/src/test/java/com/dslplatform/json/JavaValidationTest.java index 1fe2e2f4..c1f422b0 100644 --- a/processor/src/test/java/com/dslplatform/json/JavaValidationTest.java +++ b/processor/src/test/java/com/dslplatform/json/JavaValidationTest.java @@ -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( diff --git a/processor/src/test/java/com/dslplatform/json/models/GenericBoundSelfReferences.java b/processor/src/test/java/com/dslplatform/json/models/GenericBoundSelfReferences.java new file mode 100644 index 00000000..3a1c3526 --- /dev/null +++ b/processor/src/test/java/com/dslplatform/json/models/GenericBoundSelfReferences.java @@ -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 { + public GenericBoundSelfReferenceString(String stringValue) { + super(stringValue); + } + } + + @CompiledJson + public static class GenericBoundSelfReference implements Comparable> { + + final T genericField; + + public GenericBoundSelfReference(T genericField) { + this.genericField = genericField; + } + + public final T getGenericField() { + return genericField; + } + + @Override + public int compareTo(GenericBoundSelfReference other) { + return 1; + } + } +} \ No newline at end of file diff --git a/processor/src/test/java/com/dslplatform/json/models/GenericSelfReference.java b/processor/src/test/java/com/dslplatform/json/models/GenericSelfReference.java new file mode 100644 index 00000000..c9298fd5 --- /dev/null +++ b/processor/src/test/java/com/dslplatform/json/models/GenericSelfReference.java @@ -0,0 +1,22 @@ +package com.dslplatform.json.models; + +import com.dslplatform.json.CompiledJson; + +@CompiledJson +public class GenericSelfReference implements Comparable> { + + final T genericField; + + public GenericSelfReference(T genericField) { + this.genericField = genericField; + } + + public final T getGenericField() { + return genericField; + } + + @Override + public int compareTo(GenericSelfReference other) { + return 1; + } +} \ No newline at end of file diff --git a/tests-java8-external-models/pom.xml b/tests-java8-external-models/pom.xml index 13676afe..5badcd04 100644 --- a/tests-java8-external-models/pom.xml +++ b/tests-java8-external-models/pom.xml @@ -32,6 +32,7 @@ maven-compiler-plugin 3.3 + 1.8 1.8 1.8 diff --git a/tests-java8/src/test/java/com/dslplatform/json/GenericTest.java b/tests-java8/src/test/java/com/dslplatform/json/GenericTest.java index 5f89dc84..c64b9147 100644 --- a/tests-java8/src/test/java/com/dslplatform/json/GenericTest.java +++ b/tests-java8/src/test/java/com/dslplatform/json/GenericTest.java @@ -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 implements Comparable> { + + final T genericField; + + public GenericSelfReference(T genericField) { + this.genericField = genericField; + } + + public final T getGenericField() { + return genericField; + } + + @Override + public int compareTo(GenericSelfReference other) { + return 1; + } + } + + @Test + public void canCopeWithSelfReferences() throws IOException { + + byte[] bytes = "{\"genericField\":\"ABC\"}".getBytes("UTF-8"); + + Type type = new TypeDefinition>() {}.type; + GenericSelfReference result = (GenericSelfReference)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 { + + 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")); + } }