All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.peckb1.processor.util.ImplementationCreator Maven / Gradle / Ivy

package com.github.peckb1.processor.util;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.github.peckb1.processor.AutoJackson;
import com.google.common.base.CaseFormat;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;

import javax.annotation.processing.Filer;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

/**
 * A creator which implements interfaces annotated with {@link AutoJackson}
 * with the needed Jackson annotations to facilitate serialization and deserialization
 * through an {@link com.fasterxml.jackson.databind.ObjectMapper}
 */
public class ImplementationCreator {

    final static String CLASS_IMPLEMENTATION_NAME_SUFFIX = "_AutoJacksonImpl";

    private final Types typeUtils;
    private final Elements elementUtils;
    private final Filer filer;
    private final ProcessorUtil processorUtil;

    public ImplementationCreator(Types typeUtils, Elements elementUtils, Filer filer, ProcessorUtil processorUtil) {
        this.typeUtils = typeUtils;
        this.elementUtils = elementUtils;
        this.filer = filer;
        this.processorUtil = processorUtil;
    }

    /**
     * Creates a Java source file at the same package level of the given
     * {@link TypeElement} interface which implements it and contains all
     * of the needed Jackson {@link JsonProperty} annotations for serialization
     * and deserialization.
     *
     * @param typeElement The interface to create the implementation for
     */
    public void implementInterface(TypeElement typeElement) {
        Name className = typeElement.getSimpleName();

        // class level annotation to only deserialize existent objects
        AnnotationSpec nonEmptyJsonAnnotation = AnnotationSpec.builder(JsonInclude.class)
                .addMember("value", "$T.$L", Include.class, Include.NON_EMPTY)
                .build();

        TypeSpec.Builder classBuilder = TypeSpec
                .classBuilder(className + CLASS_IMPLEMENTATION_NAME_SUFFIX)
                .addSuperinterface(ClassName.get(typeElement))
                .addAnnotation(nonEmptyJsonAnnotation)
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL);

        MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC);

        Stream methodsStream = loadMethodsToImplement(typeElement);
        methodsStream.forEach(method -> {
            TypeMirror returnType = method.getReturnType();
            TypeName returnTypeName = ClassName.get(returnType);
            String memberVariableName = this.processorUtil.createMemberVariableName(method);
            String constantName = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, memberVariableName);
            AnnotationSpec jsonPropertyAnnotation = createJsonPropertyAnnotation(returnType, constantName);

            classBuilder.addMethod(MethodSpec.overriding(method)
                    .addStatement("return $L", memberVariableName)
                    .build());
            classBuilder.addField(FieldSpec.builder(returnTypeName, memberVariableName, Modifier.PRIVATE, Modifier.FINAL)
                    .addAnnotation(jsonPropertyAnnotation)
                    .build());
            classBuilder.addField(FieldSpec.builder(String.class, constantName, Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
                    .initializer("$S", memberVariableName)
                    .build());

            constructorBuilder.addStatement("this.$L = $L", memberVariableName, memberVariableName);
            constructorBuilder.addParameter(ParameterSpec.builder(returnTypeName, memberVariableName)
                    .addAnnotation(jsonPropertyAnnotation)
                    .build());
        });

        TypeSpec typeSpec = classBuilder.addMethod(constructorBuilder.build()).build();

        PackageElement packageElement = this.elementUtils.getPackageOf(typeElement);
        JavaFile javaFile = JavaFile
                .builder(packageElement.getQualifiedName().toString(), typeSpec)
                .build();

        try {
            javaFile.writeTo(this.filer);
        } catch (IOException e) {
            this.processorUtil.error(typeElement, e.getMessage());
        }
    }

    /**
     * Creates the {@link JsonProperty} annotation to apply to a parameter, or member variable
     *
     * @param returnType   The return type of the item (to check for {@link Optional})
     * @param constantName The name of the constant which holds our {@link JsonProperty#value()}
     * @return An Annotation that can be applied using JavaPoet
     */
    private AnnotationSpec createJsonPropertyAnnotation(TypeMirror returnType, String constantName) {
        TypeMirror optional = this.elementUtils.getTypeElement(Optional.class.getCanonicalName()).asType();
        TypeMirror erasure = this.typeUtils.erasure(returnType);
        boolean optionalItem = this.typeUtils.isAssignable(optional, erasure);

        AnnotationSpec.Builder builder = AnnotationSpec.builder(JsonProperty.class).addMember("value", constantName);

        if (!optionalItem) {
            builder.addMember("required", "true");
        }

        return builder.build();
    }

    /**
     * Loads the methods of our main interface, and any interfaces that it implements
     *
     * @param typeElement The element to load methods from
     * @return A Stream of ExecutableElement objects to create implementations for
     */
    private Stream loadMethodsToImplement(TypeElement typeElement) {
        List interfaces = typeElement.getInterfaces();
        Stream parentInterfaceElements = interfaces.stream().flatMap(interfaceTypeMirror -> {
            Element element = this.typeUtils.asElement(interfaceTypeMirror);
            return element.getEnclosedElements().stream();
        });

        return Stream.concat(typeElement.getEnclosedElements().stream(), parentInterfaceElements)
                .filter(element -> element.getKind() == ElementKind.METHOD)
                .map(element -> (ExecutableElement) element);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy