From 4234c31e29ccd4847985411c5748d1856c368427 Mon Sep 17 00:00:00 2001 From: Teodor Naydenov Date: Mon, 19 Nov 2018 16:32:34 +0200 Subject: [PATCH 1/4] Add support for Mapper/Reader/Writer of a Collection --- .../AbstractBeanMapperGenerator.java | 96 +------------- .../AbstractCollectionMapperGenerator.java | 81 ++++++++++++ .../AbstractJsonMapperGenerator.java | 4 +- .../processor/AbstractMapperGenerator.java | 119 ++++++++++++++++++ .../processor/CollectionMapperGenerator.java | 35 ++++++ .../processor/CollectionReaderGenerator.java | 31 +++++ .../processor/CollectionWriterGenerator.java | 31 +++++ .../jacksonapt/processor/MapperGenerator.java | 9 ++ .../processor/MapperGeneratorFactory.java | 35 ++++++ .../processor/ObjectMapperProcessor.java | 6 +- .../dominokit/jacksonapt/processor/Type.java | 1 - .../FieldDeserializersChainBuilder.java | 4 + .../JSONRegistrationProcessor.java | 81 +++++++++++- .../FieldSerializerChainBuilder.java | 4 + .../processor/CollectionMapperTest.java | 52 ++++++++ .../JSONRegistrationProcessorTest.java | 11 +- .../processor/SimpleBeanObject.java | 27 ++++ .../jacksonapt/registration/JsonRegistry.java | 6 +- .../jacksonapt/registration/Type.java | 118 +++++++++++++++++ 19 files changed, 639 insertions(+), 112 deletions(-) create mode 100644 jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractCollectionMapperGenerator.java create mode 100644 jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractMapperGenerator.java create mode 100644 jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionMapperGenerator.java create mode 100644 jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionReaderGenerator.java create mode 100644 jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionWriterGenerator.java create mode 100644 jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/MapperGenerator.java create mode 100644 jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/MapperGeneratorFactory.java create mode 100644 jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/CollectionMapperTest.java create mode 100644 jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/SimpleBeanObject.java create mode 100644 jackson-apt/src/main/java/org/dominokit/jacksonapt/registration/Type.java diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractBeanMapperGenerator.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractBeanMapperGenerator.java index f744b371..2561ff8d 100644 --- a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractBeanMapperGenerator.java +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractBeanMapperGenerator.java @@ -1,14 +1,10 @@ package org.dominokit.jacksonapt.processor; -import com.google.auto.common.MoreTypes; import com.squareup.javapoet.*; import org.dominokit.jacksonapt.*; import javax.lang.model.element.*; -import javax.lang.model.type.TypeMirror; -import java.io.IOException; -import static java.util.Objects.isNull; import static org.dominokit.jacksonapt.processor.ObjectMapperProcessor.*; /** @@ -17,75 +13,14 @@ * @author vegegoku * @version $Id: $Id */ -public abstract class AbstractBeanMapperGenerator { - - void generate(Element element) throws IOException { - String className = enclosingName(element, "_") + (useInterface(element)?element.getSimpleName():"Mapper") + "Impl"; - String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString(); - TypeMirror beanType = getBeanType(element); - Name beanName = typeUtils.asElement(beanType).getSimpleName(); - - generateJsonMappers(beanType, packageName, beanName); - - TypeSpec.Builder builder = TypeSpec.classBuilder(className) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .superclass(abstractObjectMapper(element)) - .addMethod(makeConstructor(beanName)) - .addMethods(getMapperMethods(element, beanName)); - if(useInterface(element)) - builder.addSuperinterface(TypeName.get(element.asType())); - - TypeSpec classSpec = builder - .build(); - - JavaFile.builder(packageName, classSpec).build().writeTo(filer); - } - - private String enclosingName(Element element, String postfix) { - if (useInterface(element)) { - return element.getEnclosingElement().getSimpleName().toString() + postfix; - - } - return element.getSimpleName().toString() + postfix; - - } - - private boolean notEnclosed(Element element) { - return ElementKind.PACKAGE.equals(element.getEnclosingElement()) || isNull(element.getEnclosingElement()); - } - - private TypeMirror getBeanType(Element element) { - if(useInterface(element)){ - TypeMirror objectReader = ((TypeElement) typeUtils.asElement(element.asType())).getInterfaces().get(0); - return MoreTypes.asDeclared(objectReader).getTypeArguments().get(0); - }else{ - return element.asType(); - } - - } - - private boolean useInterface(Element element) { - return Type.isAssignableFrom(element.asType(), ObjectMapper.class) || Type.isAssignableFrom(element.asType(), ObjectReader.class) || Type.isAssignableFrom(element.asType(), ObjectWriter.class); - } - - private TypeName abstractObjectMapper(Element element) { - TypeMirror beanType = getBeanType(element); - return ParameterizedTypeName.get(ClassName.get(getSuperClass()), - ClassName.get(beanType)); - } - - private MethodSpec makeConstructor(Name beanName) { - return MethodSpec.constructorBuilder() - .addModifiers(Modifier.PUBLIC) - .addStatement("super(\"" + beanName + "\")").build(); - } - +public abstract class AbstractBeanMapperGenerator extends AbstractMapperGenerator { + MethodSpec makeNewDeserializerMethod(Element element, Name beanName) { return MethodSpec.methodBuilder("newDeserializer") .addModifiers(Modifier.PROTECTED) .addAnnotation(Override.class) .returns(ParameterizedTypeName.get(ClassName.get(JsonDeserializer.class), - ClassName.get(getBeanType(element)))) + ClassName.get(getElementType(element)))) .addStatement("return new " + beanName + "BeanJsonDeserializerImpl()") .build(); } @@ -98,29 +33,4 @@ MethodSpec makeNewSerializerMethod(Name beanName) { .addStatement("return new " + beanName + "BeanJsonSerializerImpl()") .build(); } - - /** - *

getSuperClass.

- * - * @return a {@link java.lang.Class} object. - */ - protected abstract Class getSuperClass(); - - /** - *

getMapperMethods.

- * - * @param element a {@link javax.lang.model.element.Element} object. - * @param beanName a {@link javax.lang.model.element.Name} object. - * @return a {@link java.lang.Iterable} object. - */ - protected abstract Iterable getMapperMethods(Element element, Name beanName); - - /** - *

generateJsonMappers.

- * - * @param beanType a {@link javax.lang.model.type.TypeMirror} object. - * @param packageName a {@link java.lang.String} object. - * @param beanName a {@link javax.lang.model.element.Name} object. - */ - protected abstract void generateJsonMappers(TypeMirror beanType, String packageName, Name beanName); } diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractCollectionMapperGenerator.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractCollectionMapperGenerator.java new file mode 100644 index 00000000..af8366fd --- /dev/null +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractCollectionMapperGenerator.java @@ -0,0 +1,81 @@ +package org.dominokit.jacksonapt.processor; + +import static org.dominokit.jacksonapt.processor.AbstractMapperProcessor.elementUtils; +import static org.dominokit.jacksonapt.processor.AbstractMapperProcessor.typeUtils; +import static org.dominokit.jacksonapt.processor.ObjectMapperProcessor.DEFAULT_WILDCARD; + +import java.util.List; + +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; + +import org.dominokit.jacksonapt.JsonDeserializer; +import org.dominokit.jacksonapt.JsonSerializer; +import org.dominokit.jacksonapt.processor.deserialization.FieldDeserializersChainBuilder; +import org.dominokit.jacksonapt.processor.serialization.FieldSerializerChainBuilder; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; + + +public abstract class AbstractCollectionMapperGenerator extends AbstractMapperGenerator { + protected MethodSpec makeNewDeserializerMethod(Element element, Name beanName) { + return MethodSpec.methodBuilder("newDeserializer") + .addModifiers(Modifier.PROTECTED) + .addAnnotation(Override.class) + .returns(ParameterizedTypeName.get(ClassName.get(JsonDeserializer.class), + ClassName.get(getElementType(element)))) + .addCode( + CodeBlock.builder() + .add("return ") + .add(new FieldDeserializersChainBuilder(getElementType(element)).getInstance(getElementType(element))) + .add(";") + .build()) + .build(); + } + + protected MethodSpec makeNewSerializerMethod(Element element, Name beanName) { + return MethodSpec.methodBuilder("newSerializer") + .addModifiers(Modifier.PROTECTED) + .addAnnotation(Override.class) + .returns(ParameterizedTypeName.get(ClassName.get(JsonSerializer.class), DEFAULT_WILDCARD)) + .addCode( + CodeBlock.builder() + .add("return ") + .add(new FieldSerializerChainBuilder(getElementType(element)).getInstance(getElementType(element))) + .add(";") + .build()) + .build(); + } + + + @Override + protected void generateJsonMappers(TypeMirror beanType, String packageName, Name beanName) { + List typeArguments = ((DeclaredType)beanType).getTypeArguments(); + for (TypeMirror typeParamType:typeArguments){ + if(Type.isCollection(typeParamType) || Type.isMap(typeParamType)) + generateJsonMappers(typeParamType, packageName, typeUtils.asElement(typeParamType).getSimpleName()); + else if (Type.isBasicType(typeParamType)) + //In case of basic type, no need to generate serializer + return; + else { + Element typeParamElement = typeUtils.asElement(typeParamType); + String typeParamPackageName = elementUtils.getPackageOf(typeParamElement).getQualifiedName().toString(); + + generateDeserializer(typeParamType, typeParamPackageName, typeParamElement.getSimpleName()); + generateSerializer(typeParamType, typeParamPackageName, typeParamElement.getSimpleName()); + } + } + } + + protected void generateSerializer(TypeMirror beanType, String packageName, Name beanName) { + } + + protected void generateDeserializer(TypeMirror beanType, String packageName, Name beanName) { + } +} diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractJsonMapperGenerator.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractJsonMapperGenerator.java index 2bfe4bee..0c5da743 100644 --- a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractJsonMapperGenerator.java +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractJsonMapperGenerator.java @@ -22,12 +22,10 @@ import javax.lang.model.element.*; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import javax.tools.Diagnostic; import java.io.IOException; import java.util.*; import java.util.stream.Collectors; -import static org.dominokit.jacksonapt.processor.AbstractMapperProcessor.messager; import static org.dominokit.jacksonapt.processor.ObjectMapperProcessor.typeUtils; /** @@ -81,7 +79,7 @@ private MethodSpec targetTypeMethod() { .addModifiers(Modifier.PUBLIC) .addAnnotation(Override.class) .returns(ClassName.get(Class.class)) - .addStatement("return $T.class", TypeName.get(beanType)) + .addStatement("return $T.class", TypeName.get(ObjectMapperProcessor.typeUtils.erasure(beanType))) .build(); } diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractMapperGenerator.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractMapperGenerator.java new file mode 100644 index 00000000..c19227fb --- /dev/null +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractMapperGenerator.java @@ -0,0 +1,119 @@ +package org.dominokit.jacksonapt.processor; + +import static org.dominokit.jacksonapt.processor.AbstractMapperProcessor.elementUtils; +import static org.dominokit.jacksonapt.processor.AbstractMapperProcessor.filer; +import static org.dominokit.jacksonapt.processor.AbstractMapperProcessor.typeUtils; + +import java.io.IOException; + +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +import org.dominokit.jacksonapt.ObjectMapper; +import org.dominokit.jacksonapt.ObjectReader; +import org.dominokit.jacksonapt.ObjectWriter; + +import com.google.auto.common.MoreTypes; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; + +public abstract class AbstractMapperGenerator implements MapperGenerator { + + @Override + public void generate(Element element) throws IOException { + String className = getBeanClassName(element); + String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString(); + TypeMirror beanType = getElementType(element); + Name beanName = typeUtils.asElement(beanType).getSimpleName(); + + generateJsonMappers(beanType, packageName, beanName); + + TypeSpec.Builder builder = TypeSpec.classBuilder(className) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .superclass(abstractObjectMapper(element)) + .addMethod(makeConstructor(beanName)) + .addMethods(getMapperMethods(element, beanName)); + if(useInterface(element)) + builder.addSuperinterface(TypeName.get(element.asType())); + + TypeSpec classSpec = builder + .build(); + + JavaFile.builder(packageName, classSpec).build().writeTo(filer); + } + + protected String getBeanClassName(Element element) { + return enclosingName(element, "_") + (useInterface(element)?element.getSimpleName():"Mapper") + "Impl"; + } + + protected static TypeMirror getElementType(Element element) { + if(useInterface(element)){ + TypeMirror objectReader = ((TypeElement) typeUtils.asElement(element.asType())).getInterfaces().get(0); + return MoreTypes.asDeclared(objectReader).getTypeArguments().get(0); + }else{ + return element.asType(); + } + + } + + protected static boolean useInterface(Element element) { + return + Type.isAssignableFrom(element.asType(), ObjectMapper.class) + || Type.isAssignableFrom(element.asType(), ObjectReader.class) + || Type.isAssignableFrom(element.asType(), ObjectWriter.class); + } + + protected String enclosingName(Element element, String postfix) { + if (useInterface(element)) { + return element.getEnclosingElement().getSimpleName().toString() + postfix; + + } + return element.getSimpleName().toString() + postfix; + + } + + protected TypeName abstractObjectMapper(Element element) { + TypeMirror beanType = getElementType(element); + return ParameterizedTypeName.get(ClassName.get(getSuperClass()), + ClassName.get(beanType)); + } + + protected MethodSpec makeConstructor(Name beanName) { + return MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC) + .addStatement("super(\"" + beanName + "\")").build(); + } + + /** + *

getSuperClass.

+ * + * @return a {@link java.lang.Class} object. + */ + protected abstract Class getSuperClass(); + + /** + *

getMapperMethods.

+ * + * @param element a {@link javax.lang.model.element.Element} object. + * @param beanName a {@link javax.lang.model.element.Name} object. + * @return a {@link java.lang.Iterable} object. + */ + protected abstract Iterable getMapperMethods(Element element, Name beanName); + + /** + *

generateJsonMappers.

+ * + * @param beanType a {@link javax.lang.model.type.TypeMirror} object. + * @param packageName a {@link java.lang.String} object. + * @param beanName a {@link javax.lang.model.element.Name} object. + */ + protected abstract void generateJsonMappers(TypeMirror beanType, String packageName, Name beanName); + +} diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionMapperGenerator.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionMapperGenerator.java new file mode 100644 index 00000000..4ad8670e --- /dev/null +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionMapperGenerator.java @@ -0,0 +1,35 @@ +package org.dominokit.jacksonapt.processor; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.lang.model.element.Element; +import javax.lang.model.element.Name; +import javax.lang.model.type.TypeMirror; + +import org.dominokit.jacksonapt.AbstractObjectMapper; + +import com.squareup.javapoet.MethodSpec; + +public class CollectionMapperGenerator extends AbstractCollectionMapperGenerator { + @Override + protected Class getSuperClass() { + return AbstractObjectMapper.class; + } + + @Override + protected Iterable getMapperMethods(Element element, Name beanName) { + return Stream.of(makeNewDeserializerMethod(element, beanName), makeNewSerializerMethod(element, beanName)) + .collect(Collectors.toSet()); + } + + @Override + protected void generateSerializer(TypeMirror beanType, String packageName, Name beanName) { + new SerializerGenerator().generate(beanType, packageName, beanName); + } + + @Override + protected void generateDeserializer(TypeMirror beanType, String packageName, Name beanName) { + new DeserializerGenerator().generate(beanType, packageName, beanName); + } +} diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionReaderGenerator.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionReaderGenerator.java new file mode 100644 index 00000000..79da7db6 --- /dev/null +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionReaderGenerator.java @@ -0,0 +1,31 @@ +package org.dominokit.jacksonapt.processor; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.lang.model.element.Element; +import javax.lang.model.element.Name; +import javax.lang.model.type.TypeMirror; + +import org.dominokit.jacksonapt.AbstractObjectReader; + +import com.squareup.javapoet.MethodSpec; + +public class CollectionReaderGenerator extends AbstractCollectionMapperGenerator { + + @Override + protected Class getSuperClass() { + return AbstractObjectReader.class; + } + + @Override + protected Iterable getMapperMethods(Element element, Name beanName) { + return Stream.of(makeNewDeserializerMethod(element, beanName)) + .collect(Collectors.toSet()); + } + + @Override + protected void generateDeserializer(TypeMirror beanType, String packageName, Name beanName) { + new DeserializerGenerator().generate(beanType, packageName, beanName); + } +} diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionWriterGenerator.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionWriterGenerator.java new file mode 100644 index 00000000..feac64b1 --- /dev/null +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionWriterGenerator.java @@ -0,0 +1,31 @@ +package org.dominokit.jacksonapt.processor; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.lang.model.element.Element; +import javax.lang.model.element.Name; +import javax.lang.model.type.TypeMirror; + +import org.dominokit.jacksonapt.AbstractObjectWriter; + +import com.squareup.javapoet.MethodSpec; + +public class CollectionWriterGenerator extends AbstractCollectionMapperGenerator { + + @Override + protected Class getSuperClass() { + return AbstractObjectWriter.class; + } + + @Override + protected Iterable getMapperMethods(Element element, Name beanName) { + return Stream.of(makeNewSerializerMethod(element, beanName)) + .collect(Collectors.toSet()); + } + + @Override + protected void generateSerializer(TypeMirror beanType, String packageName, Name beanName) { + new SerializerGenerator().generate(beanType, packageName, beanName); + } +} diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/MapperGenerator.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/MapperGenerator.java new file mode 100644 index 00000000..d5eb38ed --- /dev/null +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/MapperGenerator.java @@ -0,0 +1,9 @@ +package org.dominokit.jacksonapt.processor; + +import java.io.IOException; + +import javax.lang.model.element.Element; + +public interface MapperGenerator { + void generate(Element element) throws IOException; +} diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/MapperGeneratorFactory.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/MapperGeneratorFactory.java new file mode 100644 index 00000000..919d7d92 --- /dev/null +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/MapperGeneratorFactory.java @@ -0,0 +1,35 @@ +package org.dominokit.jacksonapt.processor; + + +import java.io.IOException; + +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; + + +public class MapperGeneratorFactory implements MapperGenerator{ + @Override + public void generate(Element element) throws IOException { + TypeMirror beanType = AbstractMapperGenerator.getElementType(element); + if (Type.isCollection(beanType) || Type.isMap(beanType)) { + new CollectionMapperGenerator().generate(element);; + } else + new BeanMapperGenerator().generate(element); + } + + public void generateReader(Element element) throws IOException { + TypeMirror beanType = AbstractMapperGenerator.getElementType(element); + if (Type.isCollection(beanType)) { + new CollectionReaderGenerator().generate(element);; + } else + new BeanReaderGenerator().generate(element); + } + + public void generateWriter(Element element) throws IOException { + TypeMirror beanType = AbstractMapperGenerator.getElementType(element); + if (Type.isCollection(beanType)) { + new CollectionWriterGenerator().generate(element);; + } else + new BeanWriterGenerator().generate(element); + } +} diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/ObjectMapperProcessor.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/ObjectMapperProcessor.java index 71e7d68b..93d3afd7 100644 --- a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/ObjectMapperProcessor.java +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/ObjectMapperProcessor.java @@ -52,7 +52,7 @@ protected boolean doProcess(Set annotations, RoundEnviron private void generateMappers(Element element) { try { - new BeanMapperGenerator().generate(element); + new MapperGeneratorFactory().generate(element); } catch (Exception e) { handleError(e); } @@ -60,7 +60,7 @@ private void generateMappers(Element element) { private void generateMapperForReader(Element element) { try { - new BeanReaderGenerator().generate(element); + new MapperGeneratorFactory().generateReader(element); } catch (Exception e) { handleError(e); } @@ -68,7 +68,7 @@ private void generateMapperForReader(Element element) { private void generateMapperForWriter(Element element) { try { - new BeanWriterGenerator().generate(element); + new MapperGeneratorFactory().generateWriter(element); } catch (Exception e) { handleError(e); } diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/Type.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/Type.java index 3fa9cc8d..1f923678 100644 --- a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/Type.java +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/Type.java @@ -43,7 +43,6 @@ public class Type { public static final String BEAN_JSON_SERIALIZER_IMPL = "BeanJsonSerializerImpl"; /** Constant BEAN_JSON_DESERIALIZER_IMPL="BeanJsonDeserializerImpl" */ public static final String BEAN_JSON_DESERIALIZER_IMPL = "BeanJsonDeserializerImpl"; - /** *

wrapperType.

* diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/deserialization/FieldDeserializersChainBuilder.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/deserialization/FieldDeserializersChainBuilder.java index f9112ae5..2b938bcb 100644 --- a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/deserialization/FieldDeserializersChainBuilder.java +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/deserialization/FieldDeserializersChainBuilder.java @@ -60,6 +60,10 @@ public CodeBlock getInstance(Element field) { return builder.add(getFieldDeserializer(field.asType()), asClassesArray()).build(); } + public CodeBlock getInstance(TypeMirror type) { + return builder.add(getFieldDeserializer(type), asClassesArray()).build(); + } + private TypeName[] asClassesArray() { return deserializers.toArray(new TypeName[deserializers.size()]); } diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/registration/JSONRegistrationProcessor.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/registration/JSONRegistrationProcessor.java index 954151ab..051e37cd 100644 --- a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/registration/JSONRegistrationProcessor.java +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/registration/JSONRegistrationProcessor.java @@ -3,12 +3,14 @@ import com.google.auto.common.MoreTypes; import com.google.auto.service.AutoService; import com.squareup.javapoet.*; + import org.dominokit.jacksonapt.ObjectMapper; import org.dominokit.jacksonapt.ObjectReader; import org.dominokit.jacksonapt.ObjectWriter; import org.dominokit.jacksonapt.annotation.JSONRegistration; import org.dominokit.jacksonapt.processor.AbstractMapperProcessor; import org.dominokit.jacksonapt.registration.JsonRegistry; +import org.dominokit.jacksonapt.registration.Type; import javax.annotation.Generated; import javax.annotation.processing.Processor; @@ -16,7 +18,14 @@ import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ErrorType; +import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.util.SimpleTypeVisitor6; + import java.io.IOException; import java.util.*; @@ -91,7 +100,7 @@ private MethodSpec createGetMethod(String name, String mapName, Class returnT .addAnnotation(Override.class) .addTypeVariable(typeVariable) .returns(ParameterizedTypeName.get(ClassName.get(returnType), typeVariable)) - .addParameter(ParameterizedTypeName.get(ClassName.get(Class.class), typeVariable), "type"); + .addParameter(ClassName.get("org.dominokit.jacksonapt.registration", "Type"), "type"); if (lookupIfNotFound) { methodBuilder.beginControlFlow("if(" + mapName + ".containsKey(type))") @@ -106,11 +115,10 @@ private MethodSpec createGetMethod(String name, String mapName, Class returnT private FieldSpec createConstantMap(String name, Class jsonType) { ClassName mapType = ClassName.get(Map.class); - ParameterizedTypeName classOfWildcard = ParameterizedTypeName.get(ClassName.get(Class.class), - WildcardTypeName.subtypeOf(Object.class)); + ClassName typeClassName = ClassName.get(Type.class); ClassName jsonMapperType = ClassName.get(jsonType); + ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(mapType, typeClassName, jsonMapperType); - ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(mapType, classOfWildcard, jsonMapperType); return FieldSpec.builder(parameterizedTypeName, name, Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) .initializer("new $T()", HashMap.class) .build(); @@ -135,11 +143,72 @@ private CodeBlock registerWriterLine(Element element) { private CodeBlock registerLine(Element element, String mapName) { String className = enclosingName(element) + (useInterface(element) ? element.getSimpleName() : "Mapper") + "Impl"; String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString(); - TypeMirror beanType = getBeanType(element); + + StringBuilder typesCreationCode = new StringBuilder(); + List classNames = new ArrayList<>(); + processType(getBeanType(element), typesCreationCode, classNames); + return CodeBlock.builder() - .addStatement(mapName + ".put($T.class, new " + packageName + "." + className + "())", beanType) + .addStatement( + mapName + ".put("+ typesCreationCode.toString() + ", new " + packageName + "." + className + "())", + classNames.toArray(new Object[classNames.size()])) .build(); } + + private void processType(TypeMirror type, StringBuilder result, List classNames) { + type.accept(new SimpleTypeVisitor6() { + + @Override + public Void visitDeclared(DeclaredType declaredType, Void v) { + result.append("Type.of("); + classNames.add(ClassName.get((TypeElement) declaredType.asElement())); + result.append("$T"); + result.append(".class)"); + + for (TypeMirror type: declaredType.getTypeArguments()) { + result.append(".typeParam("); + processType(type, result, classNames); + result.append(")"); + } + + return null; + } + + @Override + public Void visitPrimitive(PrimitiveType primitiveType, Void v) { + result.append("Type.of("); + result.append(primitiveType); + result.append(".class)"); + return null; + } + + @Override + public Void visitArray(ArrayType arrayType, Void v) { + result.append("Type.array("); + processType(arrayType.getComponentType(), result, classNames); + result.append(")"); + + return null; + } + + @Override + public Void visitTypeVariable(TypeVariable typeVariable, Void v) { + processType(processingEnv.getTypeUtils().erasure(typeVariable), result, classNames); + return null; + } + + @Override + public Void visitError(ErrorType errorType, Void v) { + return null; + } + + @Override + protected Void defaultAction(TypeMirror typeMirror, Void v) { + return null; + } + }, + null); + } private String enclosingName(Element element) { if (useInterface(element)) diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/serialization/FieldSerializerChainBuilder.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/serialization/FieldSerializerChainBuilder.java index f004de50..4c585da3 100644 --- a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/serialization/FieldSerializerChainBuilder.java +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/serialization/FieldSerializerChainBuilder.java @@ -63,6 +63,10 @@ public CodeBlock getInstance(Element field) { return builder.add(getFieldSerializer(field.asType()), asClassesArray()).build(); } + public CodeBlock getInstance(TypeMirror fieldType) { + return builder.add(getFieldSerializer(fieldType), asClassesArray()).build(); + } + private TypeName[] asClassesArray() { return serializers.toArray(new TypeName[serializers.size()]); } diff --git a/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/CollectionMapperTest.java b/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/CollectionMapperTest.java new file mode 100644 index 00000000..ed158ec7 --- /dev/null +++ b/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/CollectionMapperTest.java @@ -0,0 +1,52 @@ +package org.dominokit.jacksonapt.processor; + +import static com.google.common.truth.Truth.assertThat; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +public class CollectionMapperTest { + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void DeserializerTest() { + List> lmp = SimpleBeanObject.MAPPER.read("[ {\"Dani\":{\"d\":5}; \"Lea\":{\"d\":15}}, {\"Teodor\":{\"d\":10}}]"); + assertThat(lmp.size()).isEqualTo(2); + assertThat(lmp.get(0).get("Dani").d).isEqualTo(5); + assertThat(lmp.get(0).get("Lea").d).isEqualTo(15); + assertThat(lmp.get(0).get("None")).isNull(); + assertThat(lmp.get(1).get("Teodor").d).isEqualTo(10); + } + + @Test + public void SerializerTest() { + Map entity = new HashMap<>(); + entity.put("Dani", new SimpleBeanObject()); + assertThat(SimpleBeanObject.MAPPER.write(Arrays.asList(entity))).isEqualTo("[{\"Dani\":{\"d\":5}}]"); + + + } +} diff --git a/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/JSONRegistrationProcessorTest.java b/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/JSONRegistrationProcessorTest.java index 92f20174..4a59b598 100644 --- a/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/JSONRegistrationProcessorTest.java +++ b/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/JSONRegistrationProcessorTest.java @@ -7,10 +7,14 @@ import org.dominokit.jacksonapt.annotation.JSONReader; import org.dominokit.jacksonapt.annotation.JSONWriter; import org.dominokit.jacksonapt.registration.JsonRegistry; +import org.dominokit.jacksonapt.registration.Type; import org.junit.Test; import static org.junit.Assert.assertNotNull; +import java.util.List; +import java.util.Map; + public class JSONRegistrationProcessorTest { @JSONMapper @@ -28,8 +32,9 @@ public interface PersonWriter extends ObjectWriter { @Test public void whenCompile_thenShouldRegisterMappersReadersAndWritersInTheirOwnRegistry() { JsonRegistry testJsonRegistry = new TestJsonRegistry(); - assertNotNull(testJsonRegistry.getMapper(Person.class)); - assertNotNull(testJsonRegistry.getReader(Person.class)); - assertNotNull(testJsonRegistry.getWriter(Person.class)); + assertNotNull(testJsonRegistry.getMapper(Type.of(Person.class))); + assertNotNull(testJsonRegistry.getReader(Type.of(Person.class))); + assertNotNull(testJsonRegistry.getWriter(Type.of(Person.class))); + assertNotNull(testJsonRegistry.getMapper(Type.of(List.class).typeParam(Type.of(Map.class).typeParam(Type.of(String.class)).typeParam(Type.of(SimpleBeanObject.class))))); } } diff --git a/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/SimpleBeanObject.java b/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/SimpleBeanObject.java new file mode 100644 index 00000000..99a85890 --- /dev/null +++ b/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/SimpleBeanObject.java @@ -0,0 +1,27 @@ +package org.dominokit.jacksonapt.processor; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.dominokit.jacksonapt.ObjectMapper; +import org.dominokit.jacksonapt.annotation.JSONMapper; + +public class SimpleBeanObject{ + @JSONMapper + interface CollectionMapper extends ObjectMapper>> { + } + + @JSONMapper + interface MapMapper extends ObjectMapper> { + } + + static CollectionMapper MAPPER = new SimpleBeanObject_CollectionMapperImpl(); + + + public Integer d; + public SimpleBeanObject() { + this.d = 5; + } + +} diff --git a/jackson-apt/src/main/java/org/dominokit/jacksonapt/registration/JsonRegistry.java b/jackson-apt/src/main/java/org/dominokit/jacksonapt/registration/JsonRegistry.java index bc50597a..67978f69 100644 --- a/jackson-apt/src/main/java/org/dominokit/jacksonapt/registration/JsonRegistry.java +++ b/jackson-apt/src/main/java/org/dominokit/jacksonapt/registration/JsonRegistry.java @@ -19,7 +19,7 @@ public interface JsonRegistry { * @param a T object. * @return a {@link org.dominokit.jacksonapt.ObjectMapper} object. */ - ObjectMapper getMapper(Class type); + ObjectMapper getMapper(Type type); /** *

getReader.

@@ -28,7 +28,7 @@ public interface JsonRegistry { * @param a T object. * @return a {@link org.dominokit.jacksonapt.ObjectReader} object. */ - ObjectReader getReader(Class type); + ObjectReader getReader(Type type); /** *

getWriter.

@@ -37,5 +37,5 @@ public interface JsonRegistry { * @param a T object. * @return a {@link org.dominokit.jacksonapt.ObjectWriter} object. */ - ObjectWriter getWriter(Class type); + ObjectWriter getWriter(Type type); } diff --git a/jackson-apt/src/main/java/org/dominokit/jacksonapt/registration/Type.java b/jackson-apt/src/main/java/org/dominokit/jacksonapt/registration/Type.java new file mode 100644 index 00000000..c81bb165 --- /dev/null +++ b/jackson-apt/src/main/java/org/dominokit/jacksonapt/registration/Type.java @@ -0,0 +1,118 @@ +package org.dominokit.jacksonapt.registration; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Recursive structure to model Java type meta-data + * associated with corresponding parameter or method + * return result. + */ +public class Type { + private final boolean array; + private final List typeParameters; + private final Class clazz; + + private Type(Class clazz) { + this(clazz, Collections.emptyList(), false); + } + + private Type(Class clazz, List typeParameters, boolean isArray) { + this.clazz = clazz; + this.typeParameters = typeParameters; + this.array = isArray; + } + + public boolean isArray() { + return array; + } + + public boolean isGeneric() { + return !typeParameters.isEmpty(); + } + + public boolean isDefined() { + return clazz == null; + } + + public static Type of(Class clazz) { + return new Type(clazz); + } + + public static Type array(Type type) { + return new Type(type.clazz, type.typeParameters, true); + } + + public static Type undefined() { + return new Type(null); + } + + public Type typeParam(Type type) { + List typeParams = new ArrayList<>(this.typeParameters); + typeParams.add(type); + return new Type(this.clazz, typeParams, type.array); + } + + public Class getClazz() { + return clazz; + } + + public List getTypeParams() { + return Collections.unmodifiableList(typeParameters); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(clazz.getSimpleName()); + + if (!typeParameters.isEmpty()) { + result.append("<"); + for(Type t: typeParameters) { + result.append(t.toString()); + result.append(", "); + } + result.delete(result.length()-2, result.length()); + result.append(">"); + } + + return result.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (array ? 1231 : 1237); + result = prime * result + ((clazz == null) ? 0 : clazz.hashCode()); + result = prime * result + ((typeParameters == null) ? 0 : typeParameters.hashCode()); + return result; + } + + @Override + public boolean equals(Object other) { + if (other == this) + return true; + + if(!(other instanceof Type)) + return false; + + Type otype = (Type)other; + if (array != otype.array) + return false; + + if (clazz == null) { + if (otype.clazz != null) + return false; + } else { + if (!clazz.equals(otype.clazz)) + return false; + } + + if (!typeParameters.equals(otype.typeParameters)) + return false; + + return true; + } +} From 688610bcf852285decf1fc4158a9fec0cb84bfe4 Mon Sep 17 00:00:00 2001 From: Teodor Naydenov Date: Tue, 20 Nov 2018 15:11:01 +0200 Subject: [PATCH 2/4] Refactor JSONRegistrartionProcessor, add some more unit tests --- .../AbstractCollectionMapperGenerator.java | 14 +-- .../processor/BeanMapperGenerator.java | 2 +- .../processor/BeanWriterGenerator.java | 2 +- .../processor/CollectionMapperGenerator.java | 2 +- .../processor/CollectionReaderGenerator.java | 2 +- .../processor/CollectionWriterGenerator.java | 2 +- .../JSONRegistrationProcessor.java | 91 +++++++-------- .../processor/CollectionMapperTest.java | 110 ++++++++++++++---- .../JSONRegistrationProcessorTest.java | 8 +- .../processor/SimpleBeanObject.java | 34 +++--- 10 files changed, 158 insertions(+), 109 deletions(-) diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractCollectionMapperGenerator.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractCollectionMapperGenerator.java index af8366fd..a366e855 100644 --- a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractCollectionMapperGenerator.java +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractCollectionMapperGenerator.java @@ -30,12 +30,7 @@ protected MethodSpec makeNewDeserializerMethod(Element element, Name beanName) { .addAnnotation(Override.class) .returns(ParameterizedTypeName.get(ClassName.get(JsonDeserializer.class), ClassName.get(getElementType(element)))) - .addCode( - CodeBlock.builder() - .add("return ") - .add(new FieldDeserializersChainBuilder(getElementType(element)).getInstance(getElementType(element))) - .add(";") - .build()) + .addStatement("return $L", new FieldDeserializersChainBuilder(getElementType(element)).getInstance(getElementType(element))) .build(); } @@ -44,12 +39,7 @@ protected MethodSpec makeNewSerializerMethod(Element element, Name beanName) { .addModifiers(Modifier.PROTECTED) .addAnnotation(Override.class) .returns(ParameterizedTypeName.get(ClassName.get(JsonSerializer.class), DEFAULT_WILDCARD)) - .addCode( - CodeBlock.builder() - .add("return ") - .add(new FieldSerializerChainBuilder(getElementType(element)).getInstance(getElementType(element))) - .add(";") - .build()) + .addStatement("return $L", new FieldSerializerChainBuilder(getElementType(element)).getInstance(getElementType(element))) .build(); } diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/BeanMapperGenerator.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/BeanMapperGenerator.java index 99e9623b..41103888 100644 --- a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/BeanMapperGenerator.java +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/BeanMapperGenerator.java @@ -26,7 +26,7 @@ protected Class getSuperClass() { @Override protected Iterable getMapperMethods(Element element, Name beanName) { return Stream.of(makeNewDeserializerMethod(element, beanName), makeNewSerializerMethod(beanName)) - .collect(Collectors.toSet()); + .collect(Collectors.toList()); } /** {@inheritDoc} */ diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/BeanWriterGenerator.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/BeanWriterGenerator.java index 92cd87ee..ee43dbe3 100644 --- a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/BeanWriterGenerator.java +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/BeanWriterGenerator.java @@ -26,7 +26,7 @@ protected Class getSuperClass() { @Override protected Iterable getMapperMethods(Element element, Name beanName) { return Stream.of(makeNewSerializerMethod(beanName)) - .collect(Collectors.toSet()); + .collect(Collectors.toList()); } /** {@inheritDoc} */ diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionMapperGenerator.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionMapperGenerator.java index 4ad8670e..1ae6c68f 100644 --- a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionMapperGenerator.java +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionMapperGenerator.java @@ -20,7 +20,7 @@ protected Class getSuperClass() { @Override protected Iterable getMapperMethods(Element element, Name beanName) { return Stream.of(makeNewDeserializerMethod(element, beanName), makeNewSerializerMethod(element, beanName)) - .collect(Collectors.toSet()); + .collect(Collectors.toList()); } @Override diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionReaderGenerator.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionReaderGenerator.java index 79da7db6..9e5697a0 100644 --- a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionReaderGenerator.java +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionReaderGenerator.java @@ -21,7 +21,7 @@ protected Class getSuperClass() { @Override protected Iterable getMapperMethods(Element element, Name beanName) { return Stream.of(makeNewDeserializerMethod(element, beanName)) - .collect(Collectors.toSet()); + .collect(Collectors.toList()); } @Override diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionWriterGenerator.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionWriterGenerator.java index feac64b1..69176699 100644 --- a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionWriterGenerator.java +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/CollectionWriterGenerator.java @@ -21,7 +21,7 @@ protected Class getSuperClass() { @Override protected Iterable getMapperMethods(Element element, Name beanName) { return Stream.of(makeNewSerializerMethod(element, beanName)) - .collect(Collectors.toSet()); + .collect(Collectors.toList()); } @Override diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/registration/JSONRegistrationProcessor.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/registration/JSONRegistrationProcessor.java index 051e37cd..07f82630 100644 --- a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/registration/JSONRegistrationProcessor.java +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/registration/JSONRegistrationProcessor.java @@ -115,7 +115,7 @@ private MethodSpec createGetMethod(String name, String mapName, Class returnT private FieldSpec createConstantMap(String name, Class jsonType) { ClassName mapType = ClassName.get(Map.class); - ClassName typeClassName = ClassName.get(Type.class); + ClassName typeClassName = ClassName.get("org.dominokit.jacksonapt.registration", "Type"); ClassName jsonMapperType = ClassName.get(jsonType); ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(mapType, typeClassName, jsonMapperType); @@ -144,71 +144,64 @@ private CodeBlock registerLine(Element element, String mapName) { String className = enclosingName(element) + (useInterface(element) ? element.getSimpleName() : "Mapper") + "Impl"; String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString(); - StringBuilder typesCreationCode = new StringBuilder(); - List classNames = new ArrayList<>(); - processType(getBeanType(element), typesCreationCode, classNames); - return CodeBlock.builder() .addStatement( - mapName + ".put("+ typesCreationCode.toString() + ", new " + packageName + "." + className + "())", - classNames.toArray(new Object[classNames.size()])) + mapName + ".put($L, new " + packageName + "." + className + "())", + createTypeExpression(getBeanType(element))) .build(); } - private void processType(TypeMirror type, StringBuilder result, List classNames) { - type.accept(new SimpleTypeVisitor6() { - + private CodeBlock createTypeExpression(TypeMirror type) { + return type.accept(new SimpleTypeVisitor6() { @Override - public Void visitDeclared(DeclaredType declaredType, Void v) { - result.append("Type.of("); - classNames.add(ClassName.get((TypeElement) declaredType.asElement())); - result.append("$T"); - result.append(".class)"); - + public CodeBlock visitDeclared(DeclaredType declaredType, Void v) { + CodeBlock.Builder builder = CodeBlock.builder(); + + builder.add( + "$T.of($T.class)", + ClassName.get("org.dominokit.jacksonapt.registration", "Type"), + ClassName.get((TypeElement) declaredType.asElement())); + for (TypeMirror type: declaredType.getTypeArguments()) { - result.append(".typeParam("); - processType(type, result, classNames); - result.append(")"); + builder.add(".typeParam($L)", type.accept(this, null)); } - return null; - } - - @Override - public Void visitPrimitive(PrimitiveType primitiveType, Void v) { - result.append("Type.of("); - result.append(primitiveType); - result.append(".class)"); - return null; + return builder.build(); } - + @Override - public Void visitArray(ArrayType arrayType, Void v) { - result.append("Type.array("); - processType(arrayType.getComponentType(), result, classNames); - result.append(")"); + public CodeBlock visitPrimitive(PrimitiveType primitiveType, Void v) { + CodeBlock.Builder builder = CodeBlock.builder(); + builder.add( + "$T.of($T.class)", + ClassName.get("org.dominokit.jacksonapt.registration", "Type"), + ClassName.get(primitiveType)); - return null; - } - - @Override - public Void visitTypeVariable(TypeVariable typeVariable, Void v) { - processType(processingEnv.getTypeUtils().erasure(typeVariable), result, classNames); - return null; + return builder.build(); } - + @Override - public Void visitError(ErrorType errorType, Void v) { - return null; + public CodeBlock visitArray(ArrayType arrayType, Void v) { + CodeBlock.Builder builder = CodeBlock.builder(); + builder.add( + "$T.array($L)", + ClassName.get("org.dominokit.jacksonapt.registration", "Type"), + arrayType.getComponentType().accept(this, null)); + + return builder.build(); } - + @Override - protected Void defaultAction(TypeMirror typeMirror, Void v) { - return null; + public CodeBlock visitTypeVariable(TypeVariable typeVariable, Void v) + { + CodeBlock.Builder builder = CodeBlock.builder(); + builder.add(processingEnv.getTypeUtils().erasure(typeVariable).accept(this, null)); + + return builder.build(); } - }, - null); - } + + }, null); + } private String enclosingName(Element element) { if (useInterface(element)) diff --git a/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/CollectionMapperTest.java b/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/CollectionMapperTest.java index ed158ec7..24fc297e 100644 --- a/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/CollectionMapperTest.java +++ b/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/CollectionMapperTest.java @@ -4,49 +4,111 @@ import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; +import org.dominokit.jacksonapt.ObjectMapper; +import org.dominokit.jacksonapt.ObjectReader; +import org.dominokit.jacksonapt.ObjectWriter; +import org.dominokit.jacksonapt.annotation.JSONMapper; +import org.dominokit.jacksonapt.annotation.JSONReader; +import org.dominokit.jacksonapt.annotation.JSONWriter; +import org.dominokit.jacksonapt.registration.JsonRegistry; +import org.dominokit.jacksonapt.registration.Type; import org.junit.Test; public class CollectionMapperTest { - @BeforeClass - public static void setUpBeforeClass() throws Exception { + @JSONMapper + interface ListOfMapMapper extends ObjectMapper>> { } - @AfterClass - public static void tearDownAfterClass() throws Exception { + static ListOfMapMapper LISTOFMAPMAPPER = new CollectionMapperTest_ListOfMapMapperImpl(); + + @JSONMapper + interface SetMapper extends ObjectMapper> { } - @Before - public void setUp() throws Exception { + static SetMapper SETMAPPER = new CollectionMapperTest_SetMapperImpl(); + + @JSONReader + interface SetReader extends ObjectReader> { } - - @After - public void tearDown() throws Exception { + + @JSONWriter + interface SetWriter extends ObjectWriter> { + } + + + @Test + public void listOfMapMapperDeserializerTest() { + List> simpleBeanObjectListOfMaps = LISTOFMAPMAPPER.read("[ {\"Dani\":{\"state\":1}; \"Lea\":{\"state\":2}}, {\"Teodor\":{\"state\":3}}]"); + assertThat(simpleBeanObjectListOfMaps.size()).isEqualTo(2); + assertThat(simpleBeanObjectListOfMaps.get(0).get("Dani").state).isEqualTo(1); + assertThat(simpleBeanObjectListOfMaps.get(0).get("Lea").state).isEqualTo(2); + assertThat(simpleBeanObjectListOfMaps.get(1).get("Teodor").state).isEqualTo(3); + assertThat(simpleBeanObjectListOfMaps.get(0).get("NotExists")).isNull(); + + } + + @Test + public void listOfMapMapperSerializerTest() { + Map stringToSimpleBeanObjectMap = new HashMap<>(); + stringToSimpleBeanObjectMap.put("Dani", new SimpleBeanObject(10)); + assertThat(LISTOFMAPMAPPER.write(Arrays.asList(stringToSimpleBeanObjectMap))).isEqualTo("[{\"Dani\":{\"state\":10}}]"); + + stringToSimpleBeanObjectMap.put("Lea", new SimpleBeanObject(20)); + assertThat(LISTOFMAPMAPPER.write(Arrays.asList(stringToSimpleBeanObjectMap))).isEqualTo("[{\"Dani\":{\"state\":10},\"Lea\":{\"state\":20}}]"); + } + + @Test + public void setMapperDeserializerTest() { + Set simpleBeanObjectSet = SETMAPPER.read("[{\"state\":1}]"); + assertThat(simpleBeanObjectSet.size()).isEqualTo(1); + assertThat(simpleBeanObjectSet.iterator().next().state).isEqualTo(1); + + simpleBeanObjectSet = SETMAPPER.read("[{\"state\":1}, {\"state\":2}]"); + assertThat(simpleBeanObjectSet.size()).isEqualTo(2); + simpleBeanObjectSet = SETMAPPER.read("[{\"state\":1}, {\"state\":1}, {\"state\":1}]"); + assertThat(simpleBeanObjectSet.size()).isEqualTo(1); + } + @Test - public void DeserializerTest() { - List> lmp = SimpleBeanObject.MAPPER.read("[ {\"Dani\":{\"d\":5}; \"Lea\":{\"d\":15}}, {\"Teodor\":{\"d\":10}}]"); - assertThat(lmp.size()).isEqualTo(2); - assertThat(lmp.get(0).get("Dani").d).isEqualTo(5); - assertThat(lmp.get(0).get("Lea").d).isEqualTo(15); - assertThat(lmp.get(0).get("None")).isNull(); - assertThat(lmp.get(1).get("Teodor").d).isEqualTo(10); + public void setMapperSerializerTest() { + Set simpleBeanObjectSet = new HashSet<>(); + simpleBeanObjectSet.add(new SimpleBeanObject(1)); + simpleBeanObjectSet.add(new SimpleBeanObject(2)); + assertThat(SETMAPPER.write(simpleBeanObjectSet)).isEqualTo("[{\"state\":1},{\"state\":2}]"); + + simpleBeanObjectSet.add(new SimpleBeanObject(2)); + simpleBeanObjectSet.add(new SimpleBeanObject(2)); + assertThat(SETMAPPER.write(simpleBeanObjectSet)).isEqualTo("[{\"state\":1},{\"state\":2}]"); } @Test - public void SerializerTest() { - Map entity = new HashMap<>(); - entity.put("Dani", new SimpleBeanObject()); - assertThat(SimpleBeanObject.MAPPER.write(Arrays.asList(entity))).isEqualTo("[{\"Dani\":{\"d\":5}}]"); + public void jsonRegistryTest() { + JsonRegistry jsonRegistry = new TestJsonRegistry(); + + ObjectMapper>> mapMapper = jsonRegistry.getMapper(Type.of(List.class).typeParam(Type.of(Map.class).typeParam(Type.of(String.class)).typeParam(Type.of(SimpleBeanObject.class)))); + assertThat(mapMapper).isNotNull(); + List> simpleBeanObjectListOfMaps = mapMapper.read("[{\"testObj\":{\"state\":10}}]"); + assertThat(simpleBeanObjectListOfMaps.size()).isEqualTo(1); + assertThat(simpleBeanObjectListOfMaps.get(0).get("testObj")).isNotNull(); + assertThat(simpleBeanObjectListOfMaps.get(0).get("testObj").state).isEqualTo(10); + + ObjectReader> setReader = jsonRegistry.getReader(Type.of(Set.class).typeParam(Type.of(SimpleBeanObject.class))); + assertThat(setReader).isNotNull(); + Set simpleBeanObjectSet = setReader.read("[{\"state\":20}]"); + assertThat(simpleBeanObjectSet.size()).isEqualTo(1); + assertThat(simpleBeanObjectSet.iterator().next().state).isEqualTo(20); + ObjectWriter> setWriter = jsonRegistry.getWriter(Type.of(Set.class).typeParam(Type.of(SimpleBeanObject.class))); + assertThat(setWriter).isNotNull(); + assertThat(setWriter.write(new HashSet<>(Arrays.asList(new SimpleBeanObject(30))))).isEqualTo("[{\"state\":30}]"); } } diff --git a/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/JSONRegistrationProcessorTest.java b/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/JSONRegistrationProcessorTest.java index 4a59b598..522e10a6 100644 --- a/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/JSONRegistrationProcessorTest.java +++ b/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/JSONRegistrationProcessorTest.java @@ -28,6 +28,12 @@ public interface PersonReader extends ObjectReader { @JSONWriter public interface PersonWriter extends ObjectWriter { } + + + @JSONMapper + interface ListOfMapMapper extends ObjectMapper>> { + } + @Test public void whenCompile_thenShouldRegisterMappersReadersAndWritersInTheirOwnRegistry() { @@ -35,6 +41,6 @@ public void whenCompile_thenShouldRegisterMappersReadersAndWritersInTheirOwnRegi assertNotNull(testJsonRegistry.getMapper(Type.of(Person.class))); assertNotNull(testJsonRegistry.getReader(Type.of(Person.class))); assertNotNull(testJsonRegistry.getWriter(Type.of(Person.class))); - assertNotNull(testJsonRegistry.getMapper(Type.of(List.class).typeParam(Type.of(Map.class).typeParam(Type.of(String.class)).typeParam(Type.of(SimpleBeanObject.class))))); + assertNotNull(testJsonRegistry.getMapper(Type.of(List.class).typeParam(Type.of(Map.class).typeParam(Type.of(Integer.class)).typeParam(Type.of(SimpleBeanObject.class))))); } } diff --git a/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/SimpleBeanObject.java b/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/SimpleBeanObject.java index 99a85890..0d2c2078 100644 --- a/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/SimpleBeanObject.java +++ b/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/SimpleBeanObject.java @@ -1,27 +1,25 @@ package org.dominokit.jacksonapt.processor; -import java.util.Date; -import java.util.List; -import java.util.Map; - -import org.dominokit.jacksonapt.ObjectMapper; -import org.dominokit.jacksonapt.annotation.JSONMapper; - public class SimpleBeanObject{ - @JSONMapper - interface CollectionMapper extends ObjectMapper>> { - } - - @JSONMapper - interface MapMapper extends ObjectMapper> { - } + public Integer state = -1; - static CollectionMapper MAPPER = new SimpleBeanObject_CollectionMapperImpl(); + public SimpleBeanObject() { + } + public SimpleBeanObject(int state) { + this.state = state; + } - public Integer d; - public SimpleBeanObject() { - this.d = 5; + @Override + public int hashCode() { + return state.hashCode(); } + @Override + public boolean equals(Object other) { + return + other instanceof SimpleBeanObject + && state.equals(((SimpleBeanObject)other).state); + } } + From cb03240960df7396eab697e1e26e5c357b315cb2 Mon Sep 17 00:00:00 2001 From: Teodor Naydenov Date: Wed, 12 Dec 2018 13:46:25 +0200 Subject: [PATCH 3/4] Replace Type with TypeToken --- .../JSONRegistrationProcessor.java | 69 ++++++-- .../processor/CollectionMapperTest.java | 14 +- .../JSONRegistrationProcessorTest.java | 16 +- .../jacksonapt/registration/JsonRegistry.java | 6 +- .../jacksonapt/registration/Type.java | 118 -------------- .../jacksonapt/registration/TypeToken.java | 154 ++++++++++++++++++ 6 files changed, 234 insertions(+), 143 deletions(-) delete mode 100644 jackson-apt/src/main/java/org/dominokit/jacksonapt/registration/Type.java create mode 100644 jackson-apt/src/main/java/org/dominokit/jacksonapt/registration/TypeToken.java diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/registration/JSONRegistrationProcessor.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/registration/JSONRegistrationProcessor.java index 07f82630..b2d89057 100644 --- a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/registration/JSONRegistrationProcessor.java +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/registration/JSONRegistrationProcessor.java @@ -10,7 +10,7 @@ import org.dominokit.jacksonapt.annotation.JSONRegistration; import org.dominokit.jacksonapt.processor.AbstractMapperProcessor; import org.dominokit.jacksonapt.registration.JsonRegistry; -import org.dominokit.jacksonapt.registration.Type; +import org.dominokit.jacksonapt.registration.TypeToken; import javax.annotation.Generated; import javax.annotation.processing.Processor; @@ -20,7 +20,6 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.ErrorType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; @@ -28,6 +27,7 @@ import java.io.IOException; import java.util.*; +import java.util.stream.Collectors; /** *

JSONRegistrationProcessor class.

@@ -100,7 +100,11 @@ private MethodSpec createGetMethod(String name, String mapName, Class returnT .addAnnotation(Override.class) .addTypeVariable(typeVariable) .returns(ParameterizedTypeName.get(ClassName.get(returnType), typeVariable)) - .addParameter(ClassName.get("org.dominokit.jacksonapt.registration", "Type"), "type"); + .addParameter( + ParameterizedTypeName.get( + ClassName.get("org.dominokit.jacksonapt.registration", "TypeToken"), + typeVariable), + "type"); if (lookupIfNotFound) { methodBuilder.beginControlFlow("if(" + mapName + ".containsKey(type))") @@ -115,7 +119,7 @@ private MethodSpec createGetMethod(String name, String mapName, Class returnT private FieldSpec createConstantMap(String name, Class jsonType) { ClassName mapType = ClassName.get(Map.class); - ClassName typeClassName = ClassName.get("org.dominokit.jacksonapt.registration", "Type"); + ClassName typeClassName = ClassName.get("org.dominokit.jacksonapt.registration", "TypeToken"); ClassName jsonMapperType = ClassName.get(jsonType); ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(mapType, typeClassName, jsonMapperType); @@ -144,10 +148,13 @@ private CodeBlock registerLine(Element element, String mapName) { String className = enclosingName(element) + (useInterface(element) ? element.getSimpleName() : "Mapper") + "Impl"; String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString(); + CodeBlock.Builder typeTokenBuilder = CodeBlock.builder(); + addTypeTokenLiteral(typeTokenBuilder, TypeName.get(getBeanType(element))); + return CodeBlock.builder() .addStatement( mapName + ".put($L, new " + packageName + "." + className + "())", - createTypeExpression(getBeanType(element))) + typeTokenBuilder.build()) .build(); } @@ -157,15 +164,18 @@ private CodeBlock createTypeExpression(TypeMirror type) { public CodeBlock visitDeclared(DeclaredType declaredType, Void v) { CodeBlock.Builder builder = CodeBlock.builder(); - builder.add( - "$T.of($T.class)", - ClassName.get("org.dominokit.jacksonapt.registration", "Type"), - ClassName.get((TypeElement) declaredType.asElement())); - + for (TypeMirror type: declaredType.getTypeArguments()) { builder.add(".typeParam($L)", type.accept(this, null)); } + builder.add( + "new $T.of($L)", + ClassName.get("org.dominokit.jacksonapt.registration", "TypeToken"), + declaredType.getTypeArguments().stream().map(type -> type.accept(this, null)).map(type -> type.toString()).collect(Collectors.joining(",")), + ClassName.get((TypeElement) declaredType.asElement())); + + return builder.build(); } @@ -173,8 +183,8 @@ public CodeBlock visitDeclared(DeclaredType declaredType, Void v) { public CodeBlock visitPrimitive(PrimitiveType primitiveType, Void v) { CodeBlock.Builder builder = CodeBlock.builder(); builder.add( - "$T.of($T.class)", - ClassName.get("org.dominokit.jacksonapt.registration", "Type"), + "new $T.of($T.class)", + ClassName.get("org.dominokit.jacksonapt.registration", "TypeToken"), ClassName.get(primitiveType)); return builder.build(); @@ -185,7 +195,7 @@ public CodeBlock visitArray(ArrayType arrayType, Void v) { CodeBlock.Builder builder = CodeBlock.builder(); builder.add( "$T.array($L)", - ClassName.get("org.dominokit.jacksonapt.registration", "Type"), + ClassName.get("org.dominokit.jacksonapt.registration", "TypeToken"), arrayType.getComponentType().accept(this, null)); return builder.build(); @@ -202,7 +212,40 @@ public CodeBlock visitTypeVariable(TypeVariable typeVariable, Void v) }, null); } + private void addTypeTokenLiteral(CodeBlock.Builder builder, TypeName name) { + builder.add("new $T<$L>(", TypeToken.class, name.isPrimitive()? name.box(): name); + TypeName rawType; + List typeArguments; + + if (name instanceof ParameterizedTypeName) { + ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName)name; + rawType = parameterizedTypeName.rawType; + typeArguments = parameterizedTypeName.typeArguments; + } else if (name instanceof ArrayTypeName) { + ArrayTypeName arrayTypeName = (ArrayTypeName)name; + + rawType = null; + typeArguments = Collections.singletonList(arrayTypeName.componentType); + } else if (name instanceof ClassName || name instanceof TypeName) { + rawType = name.isPrimitive()? name.box(): name; + typeArguments = Collections.emptyList(); + } else + throw new IllegalArgumentException("Unsupported type " + name); + + if(rawType == null) + builder.add("null"); + else + builder.add("$T.class", rawType); + + for (TypeName typeArgumentName: typeArguments) { + builder.add(", "); + addTypeTokenLiteral(builder, typeArgumentName); + } + + builder.add(") {}"); + } + private String enclosingName(Element element) { if (useInterface(element)) return element.getEnclosingElement().getSimpleName().toString() + "_"; diff --git a/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/CollectionMapperTest.java b/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/CollectionMapperTest.java index 24fc297e..9f1fa5d7 100644 --- a/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/CollectionMapperTest.java +++ b/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/CollectionMapperTest.java @@ -16,7 +16,7 @@ import org.dominokit.jacksonapt.annotation.JSONReader; import org.dominokit.jacksonapt.annotation.JSONWriter; import org.dominokit.jacksonapt.registration.JsonRegistry; -import org.dominokit.jacksonapt.registration.Type; +import org.dominokit.jacksonapt.registration.TypeToken; import org.junit.Test; public class CollectionMapperTest { @@ -93,20 +93,26 @@ public void setMapperSerializerTest() { public void jsonRegistryTest() { JsonRegistry jsonRegistry = new TestJsonRegistry(); - ObjectMapper>> mapMapper = jsonRegistry.getMapper(Type.of(List.class).typeParam(Type.of(Map.class).typeParam(Type.of(String.class)).typeParam(Type.of(SimpleBeanObject.class)))); + ObjectMapper>> mapMapper = jsonRegistry.getMapper( + new TypeToken>>( + List.class, + new TypeToken>( + Map.class, + TypeToken.of(String.class), + TypeToken.of(SimpleBeanObject.class)){} ){}); assertThat(mapMapper).isNotNull(); List> simpleBeanObjectListOfMaps = mapMapper.read("[{\"testObj\":{\"state\":10}}]"); assertThat(simpleBeanObjectListOfMaps.size()).isEqualTo(1); assertThat(simpleBeanObjectListOfMaps.get(0).get("testObj")).isNotNull(); assertThat(simpleBeanObjectListOfMaps.get(0).get("testObj").state).isEqualTo(10); - ObjectReader> setReader = jsonRegistry.getReader(Type.of(Set.class).typeParam(Type.of(SimpleBeanObject.class))); + ObjectReader> setReader = jsonRegistry.getReader(new TypeToken>(Set.class, TypeToken.of(SimpleBeanObject.class)){}); assertThat(setReader).isNotNull(); Set simpleBeanObjectSet = setReader.read("[{\"state\":20}]"); assertThat(simpleBeanObjectSet.size()).isEqualTo(1); assertThat(simpleBeanObjectSet.iterator().next().state).isEqualTo(20); - ObjectWriter> setWriter = jsonRegistry.getWriter(Type.of(Set.class).typeParam(Type.of(SimpleBeanObject.class))); + ObjectWriter> setWriter = jsonRegistry.getWriter(new TypeToken>(Set.class, TypeToken.of(SimpleBeanObject.class)){}); assertThat(setWriter).isNotNull(); assertThat(setWriter.write(new HashSet<>(Arrays.asList(new SimpleBeanObject(30))))).isEqualTo("[{\"state\":30}]"); diff --git a/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/JSONRegistrationProcessorTest.java b/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/JSONRegistrationProcessorTest.java index 522e10a6..c9638d4d 100644 --- a/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/JSONRegistrationProcessorTest.java +++ b/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/JSONRegistrationProcessorTest.java @@ -7,7 +7,7 @@ import org.dominokit.jacksonapt.annotation.JSONReader; import org.dominokit.jacksonapt.annotation.JSONWriter; import org.dominokit.jacksonapt.registration.JsonRegistry; -import org.dominokit.jacksonapt.registration.Type; +import org.dominokit.jacksonapt.registration.TypeToken; import org.junit.Test; import static org.junit.Assert.assertNotNull; @@ -38,9 +38,15 @@ interface ListOfMapMapper extends ObjectMapper>>( + List.class, + new TypeToken>( + Map.class, + TypeToken.of(Integer.class), + TypeToken.of(SimpleBeanObject.class)){}){})); } } diff --git a/jackson-apt/src/main/java/org/dominokit/jacksonapt/registration/JsonRegistry.java b/jackson-apt/src/main/java/org/dominokit/jacksonapt/registration/JsonRegistry.java index 67978f69..e8903701 100644 --- a/jackson-apt/src/main/java/org/dominokit/jacksonapt/registration/JsonRegistry.java +++ b/jackson-apt/src/main/java/org/dominokit/jacksonapt/registration/JsonRegistry.java @@ -19,7 +19,7 @@ public interface JsonRegistry { * @param a T object. * @return a {@link org.dominokit.jacksonapt.ObjectMapper} object. */ - ObjectMapper getMapper(Type type); + ObjectMapper getMapper(TypeToken type); /** *

getReader.

@@ -28,7 +28,7 @@ public interface JsonRegistry { * @param a T object. * @return a {@link org.dominokit.jacksonapt.ObjectReader} object. */ - ObjectReader getReader(Type type); + ObjectReader getReader(TypeToken type); /** *

getWriter.

@@ -37,5 +37,5 @@ public interface JsonRegistry { * @param a T object. * @return a {@link org.dominokit.jacksonapt.ObjectWriter} object. */ - ObjectWriter getWriter(Type type); + ObjectWriter getWriter(TypeToken type); } diff --git a/jackson-apt/src/main/java/org/dominokit/jacksonapt/registration/Type.java b/jackson-apt/src/main/java/org/dominokit/jacksonapt/registration/Type.java deleted file mode 100644 index c81bb165..00000000 --- a/jackson-apt/src/main/java/org/dominokit/jacksonapt/registration/Type.java +++ /dev/null @@ -1,118 +0,0 @@ -package org.dominokit.jacksonapt.registration; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Recursive structure to model Java type meta-data - * associated with corresponding parameter or method - * return result. - */ -public class Type { - private final boolean array; - private final List typeParameters; - private final Class clazz; - - private Type(Class clazz) { - this(clazz, Collections.emptyList(), false); - } - - private Type(Class clazz, List typeParameters, boolean isArray) { - this.clazz = clazz; - this.typeParameters = typeParameters; - this.array = isArray; - } - - public boolean isArray() { - return array; - } - - public boolean isGeneric() { - return !typeParameters.isEmpty(); - } - - public boolean isDefined() { - return clazz == null; - } - - public static Type of(Class clazz) { - return new Type(clazz); - } - - public static Type array(Type type) { - return new Type(type.clazz, type.typeParameters, true); - } - - public static Type undefined() { - return new Type(null); - } - - public Type typeParam(Type type) { - List typeParams = new ArrayList<>(this.typeParameters); - typeParams.add(type); - return new Type(this.clazz, typeParams, type.array); - } - - public Class getClazz() { - return clazz; - } - - public List getTypeParams() { - return Collections.unmodifiableList(typeParameters); - } - - @Override - public String toString() { - StringBuilder result = new StringBuilder(); - result.append(clazz.getSimpleName()); - - if (!typeParameters.isEmpty()) { - result.append("<"); - for(Type t: typeParameters) { - result.append(t.toString()); - result.append(", "); - } - result.delete(result.length()-2, result.length()); - result.append(">"); - } - - return result.toString(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (array ? 1231 : 1237); - result = prime * result + ((clazz == null) ? 0 : clazz.hashCode()); - result = prime * result + ((typeParameters == null) ? 0 : typeParameters.hashCode()); - return result; - } - - @Override - public boolean equals(Object other) { - if (other == this) - return true; - - if(!(other instanceof Type)) - return false; - - Type otype = (Type)other; - if (array != otype.array) - return false; - - if (clazz == null) { - if (otype.clazz != null) - return false; - } else { - if (!clazz.equals(otype.clazz)) - return false; - } - - if (!typeParameters.equals(otype.typeParameters)) - return false; - - return true; - } -} diff --git a/jackson-apt/src/main/java/org/dominokit/jacksonapt/registration/TypeToken.java b/jackson-apt/src/main/java/org/dominokit/jacksonapt/registration/TypeToken.java new file mode 100644 index 00000000..947f5fd9 --- /dev/null +++ b/jackson-apt/src/main/java/org/dominokit/jacksonapt/registration/TypeToken.java @@ -0,0 +1,154 @@ +package org.dominokit.jacksonapt.registration; + +import java.lang.reflect.Type; +import java.util.Arrays; + + +/** +* A "supertype" token capable of representing any Java type. +* +* While the purpose of this class is primarily to be instantiated by gwt-jackson-apt itself and then just used by the user code, it is a public API.

+* +* Based on ideas from http://gafter.blogspot.com/2006/12/super-type-tokens.html, +* as well as on the implementation of the same notion in various Java libraries (Guava's TypeToken, Jackson's TypeReference, Guice's TypeLiteral, +* Gson's TypeToken, Apache Commons TypeLiteral - too many to enumerate all of those here). +* +* Unlike all those other implementations however, this {@link TypeToken} is designed to operate in a GWT/J2CL environment, where Java reflection +* - and therefore, the {@link Type} class on which all other implementations are based of - is not available. +* +* How to use: +* For simple, non-parameterized types like {@link String}, {@link Integer} or non-parameterized Java Beans:
+*
TypeToken.of(String.class)

+* +* For parameterized types, like List<String>, in GWT/J2CL environments:
+*
new TypeToken<List<String>>(List.class, TypeToken.of(String)){}
+* +* A more advanced example with a multiple-nesting of a parameterized type like List<Map<Integer, String>>:
+* In GWT/J2CL environments:
+*
new TypeToken<List<Map<Integer, String>>(List.class, new TypeToken<Map<Integer, String>>(Map.class, TypeToken.of(Integer.class), TypeToken.of(String.class)) {}) {} 
+*
+* +* The syntax for GWT/J2CL is much more verbose and requires the captured type not only to be written in the type parameter of the type token (TypeToken<...>) +* but to be also explicitly enumerated as a pair of a raw class type reference (i.e. "List.class") plus a chain of nested type token instantiations describing all the +* instantiations of the type parameters of the raw type for the concrete type we are trying to capture with the type token. This verbosity is unavoidable, because GWT/J2CL is missing Java reflection, +* which in turn prohibits the {@link TypeToken} instance from "introspecting" itself and figuring out the type automatically. +*/ + +public class TypeToken implements Comparable> { + private Class rawType; + private TypeToken[] typeArguments; + + public static TypeToken of(Class type) { + return new TypeToken(type, new TypeToken[0]); + } + + protected TypeToken(Class rawType, TypeToken... typeArguments) { + if (rawType != null && rawType.isArray()) { + // User provided the array directly as a raw class + // Normalize to the standard type token representation of arrays, where the raw type is null, and the array component type is in the type arguments + if (typeArguments.length > 0) + throw new IllegalArgumentException("To create a type token for an array, either pass the non-generic array class instance as the raw type and keep the type argumetns empty, or pass null as raw type and provide a single type argument for the component type of the (possibly generic) array"); + + typeArguments = new TypeToken[] {TypeToken.of(rawType.getComponentType())}; + rawType = null; + } + + this.rawType = rawType; + this.typeArguments = typeArguments; + } + + /** + * Return the raw type represented by this {@link TypeToken} instance. E.g.:
+ * When called on {@code TypeToken} it will return {@code String.class}
+ * When called on {@code TypeToken>} it will return {@code List.class}

+ * + * For arrays, this method will return null. + */ + public final Class getRawType() { + return rawType; + } + + /** + * Return the type tokens corresponding to the type arguments of the parameterized type represented by this type token. If the type is not parameterized, + * an empty array is returned. For example:
+ * When called on {@code TypeToken} an empty array will be returned
+ * When called on {@code TypeToken>} a single-element array with a type token {@code TypeToken}
+ * When called on {@code TypeToken} a single-element array with a type token {@code TypeToken} will be returned as well + */ + public final TypeToken[] getTypeArguments() { + return typeArguments; + } + + /** + * The only reason we define this method (and require implementation + * of Comparable) is to prevent constructing a + * reference without type information. + */ + @Override + public final int compareTo(TypeToken o) { + return 0; + } + + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((rawType == null) ? 0 : rawType.hashCode()); + result = prime * result + Arrays.hashCode(typeArguments); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof TypeToken)) + return false; + TypeToken other = (TypeToken) obj; + if (rawType == null) { + if (other.rawType != null) + return false; + } else if (!rawType.equals(other.rawType)) + return false; + if (!Arrays.equals(typeArguments, other.typeArguments)) + return false; + return true; + } + + @Override + public String toString() { + return "TypeToken<" + stringify() + ">"; + } + + public final String stringify() { + StringBuilder buf = new StringBuilder(); + + stringify(buf); + + return buf.toString(); + } + + private void stringify(StringBuilder buf) { + if (getRawType() != null) { + buf.append(getRawType().getName()); + + if (typeArguments.length > 0) { + buf.append('<'); + + for (int i = 0; i < typeArguments.length; i++) { + if(i > 0) + buf.append(", "); + + typeArguments[i].stringify(buf); + } + + buf.append('>'); + } + } else { + typeArguments[0].stringify(buf); + + buf.append("[]"); + } + } +} From a455a962e95d4922934eb61dd126412d5fedd1e5 Mon Sep 17 00:00:00 2001 From: Teodor Naydenov Date: Wed, 12 Dec 2018 16:45:21 +0200 Subject: [PATCH 4/4] Remove unused method in JSONRegistrationProcessor --- .../JSONRegistrationProcessor.java | 70 ++----------------- 1 file changed, 5 insertions(+), 65 deletions(-) diff --git a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/registration/JSONRegistrationProcessor.java b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/registration/JSONRegistrationProcessor.java index b2d89057..64021949 100644 --- a/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/registration/JSONRegistrationProcessor.java +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/registration/JSONRegistrationProcessor.java @@ -18,16 +18,10 @@ import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.util.SimpleTypeVisitor6; import java.io.IOException; import java.util.*; -import java.util.stream.Collectors; /** *

JSONRegistrationProcessor class.

@@ -158,73 +152,19 @@ private CodeBlock registerLine(Element element, String mapName) { .build(); } - private CodeBlock createTypeExpression(TypeMirror type) { - return type.accept(new SimpleTypeVisitor6() { - @Override - public CodeBlock visitDeclared(DeclaredType declaredType, Void v) { - CodeBlock.Builder builder = CodeBlock.builder(); - - - for (TypeMirror type: declaredType.getTypeArguments()) { - builder.add(".typeParam($L)", type.accept(this, null)); - } - - builder.add( - "new $T.of($L)", - ClassName.get("org.dominokit.jacksonapt.registration", "TypeToken"), - declaredType.getTypeArguments().stream().map(type -> type.accept(this, null)).map(type -> type.toString()).collect(Collectors.joining(",")), - ClassName.get((TypeElement) declaredType.asElement())); - - - return builder.build(); - } - - @Override - public CodeBlock visitPrimitive(PrimitiveType primitiveType, Void v) { - CodeBlock.Builder builder = CodeBlock.builder(); - builder.add( - "new $T.of($T.class)", - ClassName.get("org.dominokit.jacksonapt.registration", "TypeToken"), - ClassName.get(primitiveType)); - - return builder.build(); - } - - @Override - public CodeBlock visitArray(ArrayType arrayType, Void v) { - CodeBlock.Builder builder = CodeBlock.builder(); - builder.add( - "$T.array($L)", - ClassName.get("org.dominokit.jacksonapt.registration", "TypeToken"), - arrayType.getComponentType().accept(this, null)); - - return builder.build(); - } - - @Override - public CodeBlock visitTypeVariable(TypeVariable typeVariable, Void v) - { - CodeBlock.Builder builder = CodeBlock.builder(); - builder.add(processingEnv.getTypeUtils().erasure(typeVariable).accept(this, null)); - - return builder.build(); - } - - }, null); - } private void addTypeTokenLiteral(CodeBlock.Builder builder, TypeName name) { builder.add("new $T<$L>(", TypeToken.class, name.isPrimitive()? name.box(): name); TypeName rawType; List typeArguments; - + if (name instanceof ParameterizedTypeName) { ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName)name; rawType = parameterizedTypeName.rawType; typeArguments = parameterizedTypeName.typeArguments; } else if (name instanceof ArrayTypeName) { ArrayTypeName arrayTypeName = (ArrayTypeName)name; - + rawType = null; typeArguments = Collections.singletonList(arrayTypeName.componentType); } else if (name instanceof ClassName || name instanceof TypeName) { @@ -232,17 +172,17 @@ private void addTypeTokenLiteral(CodeBlock.Builder builder, TypeName name) { typeArguments = Collections.emptyList(); } else throw new IllegalArgumentException("Unsupported type " + name); - + if(rawType == null) builder.add("null"); else builder.add("$T.class", rawType); - + for (TypeName typeArgumentName: typeArguments) { builder.add(", "); addTypeTokenLiteral(builder, typeArgumentName); } - + builder.add(") {}"); }