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 7c21fbbf..e485451f 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,79 +13,13 @@ * @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)) - .addField(FieldSpec.builder(ClassName.bestGuess(className), "INSTANCE") - .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) - .initializer(CodeBlock.builder().add("new $T()", ClassName.bestGuess(className)).build()). - build()) - .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(); } @@ -102,29 +32,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..a366e855 --- /dev/null +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractCollectionMapperGenerator.java @@ -0,0 +1,71 @@ +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)))) + .addStatement("return $L", new FieldDeserializersChainBuilder(getElementType(element)).getInstance(getElementType(element))) + .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)) + .addStatement("return $L", new FieldSerializerChainBuilder(getElementType(element)).getInstance(getElementType(element))) + .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 3e29a52d..ab280735 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 @@ -81,7 +81,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..5d90b83b --- /dev/null +++ b/jackson-apt-processor/src/main/java/org/dominokit/jacksonapt/processor/AbstractMapperGenerator.java @@ -0,0 +1,121 @@ +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.CodeBlock; +import com.squareup.javapoet.FieldSpec; +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 = enclosingName(element, "_") + (useInterface(element) ? element.getSimpleName() : "Mapper") + "Impl"; + 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)) + .addField(FieldSpec.builder(ClassName.bestGuess(className), "INSTANCE") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .initializer(CodeBlock.builder().add("new $T()", ClassName.bestGuess(className)).build()). + build()) + .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 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/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 new file mode 100644 index 00000000..1ae6c68f --- /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.toList()); + } + + @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..9e5697a0 --- /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.toList()); + } + + @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..69176699 --- /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.toList()); + } + + @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..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 @@ -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.TypeToken; import javax.annotation.Generated; import javax.annotation.processing.Processor; @@ -17,6 +19,7 @@ import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; + import java.io.IOException; import java.util.*; @@ -91,7 +94,11 @@ 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( + ParameterizedTypeName.get( + ClassName.get("org.dominokit.jacksonapt.registration", "TypeToken"), + typeVariable), + "type"); if (lookupIfNotFound) { methodBuilder.beginControlFlow("if(" + mapName + ".containsKey(type))") @@ -106,11 +113,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("org.dominokit.jacksonapt.registration", "TypeToken"); 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,12 +141,51 @@ 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); + + CodeBlock.Builder typeTokenBuilder = CodeBlock.builder(); + addTypeTokenLiteral(typeTokenBuilder, TypeName.get(getBeanType(element))); + return CodeBlock.builder() - .addStatement(mapName + ".put($T.class, new " + packageName + "." + className + "())", beanType) + .addStatement( + mapName + ".put($L, new " + packageName + "." + className + "())", + typeTokenBuilder.build()) .build(); } - + + 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/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..9f1fa5d7 --- /dev/null +++ b/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/CollectionMapperTest.java @@ -0,0 +1,120 @@ +package org.dominokit.jacksonapt.processor; + +import static com.google.common.truth.Truth.assertThat; + +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.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.TypeToken; +import org.junit.Test; + +public class CollectionMapperTest { + + @JSONMapper + interface ListOfMapMapper extends ObjectMapper>> { + } + + static ListOfMapMapper LISTOFMAPMAPPER = new CollectionMapperTest_ListOfMapMapperImpl(); + + @JSONMapper + interface SetMapper extends ObjectMapper> { + } + + static SetMapper SETMAPPER = new CollectionMapperTest_SetMapperImpl(); + + @JSONReader + interface SetReader extends ObjectReader> { + } + + @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 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 jsonRegistryTest() { + JsonRegistry jsonRegistry = new TestJsonRegistry(); + + 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(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(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 92f20174..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,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.TypeToken; import org.junit.Test; import static org.junit.Assert.assertNotNull; +import java.util.List; +import java.util.Map; + public class JSONRegistrationProcessorTest { @JSONMapper @@ -24,12 +28,25 @@ public interface PersonReader extends ObjectReader { @JSONWriter public interface PersonWriter extends ObjectWriter { } + + + @JSONMapper + interface ListOfMapMapper extends ObjectMapper>> { + } + @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(TypeToken.of(Person.class))); + assertNotNull(testJsonRegistry.getReader(TypeToken.of(Person.class))); + assertNotNull(testJsonRegistry.getWriter(TypeToken.of(Person.class))); + assertNotNull(testJsonRegistry.getMapper( + new TypeToken>>( + List.class, + new TypeToken>( + Map.class, + TypeToken.of(Integer.class), + TypeToken.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..0d2c2078 --- /dev/null +++ b/jackson-apt-processor/src/test/java/org/dominokit/jacksonapt/processor/SimpleBeanObject.java @@ -0,0 +1,25 @@ +package org.dominokit.jacksonapt.processor; + +public class SimpleBeanObject{ + public Integer state = -1; + + public SimpleBeanObject() { + } + + public SimpleBeanObject(int state) { + this.state = state; + } + + @Override + public int hashCode() { + return state.hashCode(); + } + + @Override + public boolean equals(Object other) { + return + other instanceof SimpleBeanObject + && state.equals(((SimpleBeanObject)other).state); + } +} + 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..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(Class 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(Class 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(Class type); + ObjectWriter getWriter(TypeToken type); } 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("[]"); + } + } +}