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

com.tmtron.enums.processor.WriteMapperFull Maven / Gradle / Ivy

Go to download

Annotation processor that builds an enum mapper which causes a compile-time error when you forget an enum

The newest version!
/*
 * Copyright © 2018 Martin Trummer ([email protected])
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.tmtron.enums.processor;

import com.google.auto.common.MoreElements;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import com.tmtron.enums.EnumMapperFull;

import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.List;

import javax.annotation.Generated;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;

class WriteMapperFull {
    private final Collection annotatedElements;
    private final TypeElement enumsClassTypeElement;
    private final List enumConstants;
    private final TypeVariableName typeVariableName4Value;

    WriteMapperFull(Collection annotatedElements, TypeElement enumsClassTypeElement
            , List enumConstants, TypeVariableName typeVariableName4Value) {
        this.annotatedElements = annotatedElements;
        this.enumsClassTypeElement = enumsClassTypeElement;
        this.enumConstants = enumConstants;
        this.typeVariableName4Value = typeVariableName4Value;
    }

    /**
     * Creates a Generated annotation
     * 

     *  {@literal @}Generated(
     *    value = "com.tmtron.enums.processor.EnumsAnnotationProcessor",
     *    date = "1976-12-14T15:16:17.234+02:00",
     *    comments = "origin=[com.test.TwoEnums_SourceA,com.test.TwoEnums_SourceB]"
     *    )
     * 
* * @param annotationProcessorClass the class-name will be used for the value item */ private AnnotationSpec createGeneratedAnnotation(Class annotationProcessorClass) { String dateString = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(AnnotationProcessingUtil.now()); AnnotationSpec.Builder annotationSpecBuilder = AnnotationSpec.builder(Generated.class) .addMember("value", "$S", annotationProcessorClass.getCanonicalName()) .addMember("date", "$S", dateString); StringBuilder sbOrigin = new StringBuilder(); annotatedElements.forEach(annotatedElement -> { if (sbOrigin.length() > 0) { sbOrigin.append(","); } sbOrigin.append(MoreElements.asType(annotatedElement).getQualifiedName().toString()); }); String commentsString = "origin="; if (annotatedElements.size() > 1) { commentsString += "["; } commentsString += sbOrigin.toString(); if (annotatedElements.size() > 1) { commentsString += "]"; } annotationSpecBuilder.addMember("comments", "$S", commentsString); return annotationSpecBuilder.build(); } /** * Will add the required interfaces for the build-stages to the mapperFullTypeBuilder. *
    *
  • we need no interface for the first enum-constant, because we create a static method for this
  • *
  • the last interface will return the fully initialized mapper
  • *
* * @param mapperFullTypeBuilder the interfaces will be added to this builder * @param typeVariableName4Value the type-variable name: e.g. "V" * @param lastReturnType the return type for the last stage: e.g. EnumMapperFull */ private void addStagedBuilderInterfaces(TypeSpec.Builder mapperFullTypeBuilder , TypeVariableName typeVariableName4Value, TypeName lastReturnType) { TypeName returnType = lastReturnType; // iterate backwards and skip the first - no interface needed for (int i = enumConstants.size() - 1; i > 0; i--) { CodeGenEnumConst enumConst = enumConstants.get(i); TypeSpec interfaceTypeSpec = TypeSpec.interfaceBuilder(enumConst.interfaceClassName) .addTypeVariable(typeVariableName4Value) .addModifiers(Modifier.PUBLIC) .addMethod(MethodSpec.methodBuilder(enumConst.setterName) .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) .addParameter(typeVariableName4Value, "value") .returns(returnType) .build() ).build(); mapperFullTypeBuilder.addType(interfaceTypeSpec); // set return type for next lower enum-interface returnType = enumConst.interfaceTypeName; } } /** * Will create the TypeSpec.Builder for the "_MapperFull" class including the {@link Generated} annotation */ private TypeSpec.Builder createFullMapperTypeSpecBuilder() { // LauncherAge_MapperFull ClassName className = ClassName.bestGuess(enumsClassTypeElement.getQualifiedName() + "_MapperFull"); TypeSpec.Builder result = TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC, Modifier.FINAL); /* * We must add the originating elements in order to use Gradles "Incremental annotation processing" * https://docs.gradle.org/4.8-rc-2/userguide/java_plugin.html#sec:incremental_annotation_processing */ annotatedElements.forEach(result::addOriginatingElement); // @Generated annotation result.addAnnotation( createGeneratedAnnotation(EnumsAnnotationProcessor.class) ); return result; } /** * will return a type spec for the "_MapperFull" class (including the staged builder and interfaces) */ TypeSpec work() { /* Example: * enum LauncherAge { * ONLINE, OLD, OFFLINE * } */ TypeSpec.Builder mapperFullTypeBuilder = createFullMapperTypeSpecBuilder(); // package.LauncherAge ClassName enumClassName = ClassName.bestGuess(enumsClassTypeElement.toString()); ClassName enumMapperClassName = ClassName.get(EnumMapperFull.class); // EnumMapperFull TypeName lastReturnType = ParameterizedTypeName.get(enumMapperClassName , enumClassName.withoutAnnotations(), typeVariableName4Value); addStagedBuilderInterfaces(mapperFullTypeBuilder, typeVariableName4Value, lastReturnType); // add StagedBuilder as inner class StagedBuilder stagedBuilder = new StagedBuilder(typeVariableName4Value , enumClassName, enumConstants, lastReturnType); mapperFullTypeBuilder.addType(stagedBuilder.getTypeSpec()); // add the staged-builder entry method (for the first enum constant) // it will create a staged-builder instance and return it (as the interface for the next stage) CodeGenEnumConst firstEnumConst = enumConstants.get(0); CodeGenEnumConst secondEnumConst = enumConstants.get(1); MethodSpec setFirstEnum = MethodSpec.methodBuilder(firstEnumConst.setterName) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addTypeVariable(typeVariableName4Value) .returns(secondEnumConst.interfaceTypeName) .addParameter(typeVariableName4Value, "value") .addStatement("$T result = new $T<>()", stagedBuilder.getFullType(), stagedBuilder.getClassName()) .addStatement("result." + StagedBuilder.STATEMENT_PUT_CONSTANT_TO_ENUM_MAPPER, enumClassName, firstEnumConst .identifier) .addStatement("return result") .build(); mapperFullTypeBuilder.addMethod(setFirstEnum); return mapperFullTypeBuilder.build(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy