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

io.ultreia.java4all.bean.spi.GenerateJavaBeanDefinitionProcessor Maven / Gradle / Ivy

package io.ultreia.java4all.bean.spi;

/*-
 * #%L
 * Java Beans extends by Ultreia.io
 * %%
 * Copyright (C) 2018 Ultreia.io
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

import com.google.auto.service.AutoService;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import io.ultreia.java4all.bean.AbstractJavaBeanComparatorBuilder;
import io.ultreia.java4all.bean.AbstractJavaBeanInstanceBuilder;
import io.ultreia.java4all.bean.AbstractJavaBeanPredicateBuilder;
import io.ultreia.java4all.bean.AbstractJavaBeanStream;
import io.ultreia.java4all.bean.definition.AbstractJavaBeanDefinition;
import io.ultreia.java4all.bean.definition.JavaBeanDefinition;

import javax.annotation.Generated;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
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.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.NoType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.AbstractElementVisitor8;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import java.beans.Introspector;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.BiConsumer;
import java.util.function.Function;

/**
 * Created by tchemit on 07/01/2018.
 *
 * @author Tony Chemit - [email protected]
 */
@SupportedAnnotationTypes("io.ultreia.java4all.bean.spi.GenerateJavaBeanDefinition")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
@SupportedOptions({"debug"})
public class GenerateJavaBeanDefinitionProcessor extends AbstractProcessor {

    private static final String DEFINITION_JAVA_FILE = "package %s;\n" +
            "\n" +
            "%s" +
            "\n@AutoService(JavaBeanDefinition.class)" +
            "\n@Generated(value = \"%s\", date = \"%s\")" +
            "\npublic final class %sJavaBeanDefinition%s extends AbstractJavaBeanDefinition {\n\n" +
            "    @Override\n" +
            "    protected final ImmutableSet> loadAcceptedTypes() {\n" +
            "        return ImmutableSet.>builder()\n" +
            "%s" +
            "                .build();\n" +
            "    }\n\n" +
            "    @Override\n" +
            "    protected final ImmutableMap> loadTypes() {\n" +
            "        return ImmutableMap.>builder()\n" +
            "%s" +
            "                .build();\n" +
            "    }\n\n" +
            "    @SuppressWarnings(\"unchecked\")\n" +
            "    @Override\n" +
            "    protected final ImmutableMap> loadGetters() {\n" +
            "        return (ImmutableMap) ImmutableMap.>builder()\n" +
            "%s" +
            "                .build();\n" +
            "    }\n\n" +
            "    @SuppressWarnings(\"unchecked\")\n" +
            "    @Override\n" +
            "    protected final ImmutableMap> loadSetters() {\n" +
            "        return (ImmutableMap) ImmutableMap.>builder()\n" +
            "%s" +
            "                .build();\n" +
            "    }\n" +
            "%s%s%s" +
            "\n" +
            "}\n";

    private static final String PREDICATE_JAVA_FILE = "\n    public static final class %s%s extends AbstractJavaBeanPredicateBuilder<%s%s, %s%s> {\n" +
            "\n        protected %s() {\n" +
            "            super(%sJavaBeanDefinition.class);\n" +
            "        }\n" +
            "\n        protected %s(%s javaBeanDefinition) {\n" +
            "            super(javaBeanDefinition);\n" +
            "        }\n" +
            "%s" +
            "\n    }\n";

    private static final String STREAM_JAVA_FILE = "\n    public static final class %s%s extends AbstractJavaBeanStream<%s%s, %s%s> {\n" +
            "\n        protected %s(Collection<%s%s> elements) {\n" +
            "            super(%sJavaBeanDefinition.class, elements);\n" +
            "        }\n" +
            "\n        protected %s(%sJavaBeanDefinition javaBeanDefinition, Collection<%s%s> elements) {\n" +
            "            super(javaBeanDefinition, elements);\n" +
            "        }\n" +
            "%s" +
            "\n    }\n";

    private static final String COMPARATOR_JAVA_FILE = "\n    public static final class %s%s extends AbstractJavaBeanComparatorBuilder<%s%s, %s%s> {\n" +
            "\n        protected %s() {\n" +
            "            super(%sJavaBeanDefinition.class);\n" +
            "        }\n" +
            "\n        protected %s(%s javaBeanDefinition) {\n" +
            "            super(javaBeanDefinition);\n" +
            "        }\n" +
            "%s" +
            "\n    }\n";
    private static final String INSTANCE_JAVA_FILE = "\n    public static final class %s%s extends AbstractJavaBeanInstanceBuilder<%s%s, %s%s> {\n" +
            "\n        protected %s() {\n" +
            "            super(%sJavaBeanDefinition.class);\n" +
            "        }\n" +
            "\n        protected %s(%s javaBeanDefinition) {\n" +
            "            super(javaBeanDefinition);\n" +
            "        }\n" +
            "%s" +
            "\n    }\n";

    @SuppressWarnings("SpellCheckingInspection")
    private static final List excludedProperties = Arrays.asList("ters", "class", "propertyChangeListeners", "vetoableChangeListeners");

    private Set done = new TreeSet<>();

    @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {
        for (TypeElement annotation : annotations) {

            Set annotatedElements = roundEnv.getElementsAnnotatedWith(annotation);
            for (Element annotatedElement : annotatedElements) {
                TypeElement classElement = (TypeElement) annotatedElement;

                String fullyQualifiedName = classElement.getQualifiedName().toString();

                String packageName = processingEnv.getElementUtils().getPackageOf(classElement).toString();
                String fullClassName = fullyQualifiedName.substring(packageName.length() + 1);
                String className = fullClassName;
                int i = className.indexOf(".");
                if (i > -1) {
                    className = className.substring(i + 1);
                }
                String generatedClassName = packageName + "." + className + "JavaBeanDefinition";

                if (!done.add(generatedClassName)) {

                    // Already done
                    logWarning(String.format("Skip already processed class: %s", generatedClassName));
                    continue;
                }

                logDebug(String.format("Detect JavaBean: %s", classElement));

                List typeParameters = classElement.getTypeParameters();

                Map getters = classElement.accept(new GettersDetector(processingEnv), null);
                Map setters = classElement.accept(new SettersDetector(processingEnv), null);

                String classNameWithFormal;
                String classNameWithRaw;
                if (typeParameters.isEmpty()) {
                    classNameWithFormal = "";
                    classNameWithRaw = "";
                } else {

                    List formal = new LinkedList<>();
                    List raw = new LinkedList<>();
                    for (TypeParameterElement typeParameter : typeParameters) {
                        String simpleName = typeParameter.getSimpleName().toString();
                        String type = typeParameter.getBounds().get(0).toString();
                        formal.add(simpleName);
                        raw.add(simpleName + " extends " + type);
                    }
                    classNameWithFormal = String.format("<%s>", Joiner.on(", ").join(formal));
                    classNameWithRaw = String.format("<%s>", Joiner.on(", ").join(raw));
                }
                GenerateJavaBeanDefinition realAnnotation = classElement.getAnnotation(GenerateJavaBeanDefinition.class);
                try {
                    generateDefinitionFile(realAnnotation, packageName, generatedClassName, fullClassName, className, classNameWithFormal, classNameWithRaw, getters, setters);
                } catch (IOException e) {
                    throw new RuntimeException("Can't generate JavaBean definition file for: " + classElement, e);
                }
            }
        }
        return true;
    }

    private void generateDefinitionFile(GenerateJavaBeanDefinition realAnnotation, String packageName, String generatedClassName, String fullClassName, String className, String classNameWithFormal, String classNameWithRaw, Map getters, Map setters) throws IOException {

        String[] classes = realAnnotation.types();
        boolean predicate = realAnnotation.predicate();
        boolean comparator = realAnnotation.comparator();
        boolean stream = realAnnotation.stream();
        boolean instance = realAnnotation.instance();

        StringBuilder acceptedTypesBuilder = new StringBuilder();
        if (classes.length == 0) {
            acceptedTypesBuilder.append(String.format("                .add(%s.class)\n", fullClassName));
        } else {
            for (String aClass : classes) {
                acceptedTypesBuilder.append(String.format("                .add(%s.class)\n", aClass));
            }
        }
        String acceptedTypesField = acceptedTypesBuilder.toString();
        StringBuilder typesBuilder = new StringBuilder();

        Set properties = new LinkedHashSet<>();
        StringBuilder gettersBuilder = new StringBuilder();
        for (Map.Entry entry : getters.entrySet()) {
            String propertyName = entry.getKey();
            ExecutableElement methodElement = entry.getValue();
            String methodName = methodElement.getSimpleName().toString();
            String methodType = processingEnv.getTypeUtils().erasure(methodElement.getReturnType()).toString();
            gettersBuilder.append(String.format("                .put(\"%s\", %s::%s)\n", propertyName, fullClassName, methodName));
            if (properties.add(propertyName)) {
                typesBuilder.append(String.format("                .put(\"%s\", %s.class)\n", propertyName, methodType));
            }
        }
        String typesField = typesBuilder.toString();
        StringBuilder settersBuilder = new StringBuilder();
        for (Map.Entry entry : setters.entrySet()) {
            String propertyName = entry.getKey();
            ExecutableElement methodElement = entry.getValue();
            String methodName = methodElement.getSimpleName().toString();
            String methodType = methodElement.getParameters().get(0).asType().toString();
            settersBuilder.append(String.format("                .put(\"%s\", (e, v) -> e.%s((%s) v))\n", propertyName, methodName, methodType));
            if (properties.add(propertyName)) {
                typesBuilder.append(String.format("                .put(\"%s\", %s.class)\n", propertyName, methodType));
            }
        }

        Set importSet = new LinkedHashSet<>();
        addImport(importSet, packageName, AutoService.class);
        addImport(importSet, packageName, Generated.class);
        addImport(importSet, packageName, JavaBeanDefinition.class);
        addImport(importSet, packageName, AbstractJavaBeanDefinition.class);
        addImport(importSet, packageName, Function.class);
        addImport(importSet, packageName, ImmutableSet.class);
        addImport(importSet, packageName, BiConsumer.class);
        addImport(importSet, packageName, ImmutableMap.class);
        addImport(importSet, packageName, ImmutableSet.class);
        if (!className.equals(fullClassName)) {
            addImport(importSet, packageName, packageName + "." + fullClassName);
        }
        boolean empty = classNameWithFormal.isEmpty();
        String addFormal = empty ? "" : "<>";
        StringBuilder extraStaticsMethods = new StringBuilder();
        StringBuilder extraMethods = new StringBuilder();
        StringBuilder extraClasses = new StringBuilder();
        if (predicate) {
            addImport(importSet, packageName, AbstractJavaBeanPredicateBuilder.class);

            extraClasses.append(generatePredicateBuilderFile(className + "PredicateBuilder", className, classNameWithFormal, classNameWithRaw, getters));

            extraMethods.append(String.format("\n    @Override\n" +
                    "    public %sPredicateBuilder predicateBuilder() {\n" +
                    "        return new %sPredicateBuilder(this);\n" +
                    "    }\n", className, className));
            extraStaticsMethods.append(String.format("\n    public static %s%sPredicateBuilder%s predicate() {\n" +
                    "        return new %sPredicateBuilder%s();\n" +
                    "    }\n", empty ? "" : classNameWithRaw + " ", className, classNameWithFormal, className, addFormal));

        }
        if (stream) {
            addImport(importSet, packageName, AbstractJavaBeanStream.class);
            addImport(importSet, packageName, Collection.class);

            extraClasses.append(generateStreamFile(className + "Stream", className, classNameWithFormal, classNameWithRaw, getters));

            extraStaticsMethods.append(String.format("\n    public static %s%sStream%s stream(Collection<%s%s> elements) {\n" +
                    "        return new %sStream%s(elements);\n" +
                    "    }\n", empty ? "" : classNameWithRaw + " ", className, classNameWithFormal, className, classNameWithFormal, className, addFormal));

        }
        if (comparator) {
            addImport(importSet, packageName, AbstractJavaBeanComparatorBuilder.class);

            extraClasses.append(generateComparatorBuilderFile(className + "ComparatorBuilder", className, classNameWithFormal, classNameWithRaw, getters));

            extraMethods.append(String.format("\n    @Override\n" +
                    "    public %sComparatorBuilder comparatorBuilder() {\n" +
                    "        return new %sComparatorBuilder(this);\n" +
                    "    }\n", className, className));
            extraStaticsMethods.append(String.format("\n    public static %s%sComparatorBuilder%s comparator() {\n" +
                    "        return new %sComparatorBuilder%s();\n" +
                    "    }\n", empty ? "" : classNameWithRaw + " ", className, classNameWithFormal, className, addFormal));
        }
        if (instance) {
            addImport(importSet, packageName, AbstractJavaBeanInstanceBuilder.class);
            extraClasses.append(generateInstanceBuilderFile(className + "InstanceBuilder", className, classNameWithFormal, classNameWithRaw, setters));

            extraMethods.append(String.format("\n    @Override\n" +
                    "    public %sInstanceBuilder instanceBuilder() {\n" +
                    "        return new %sInstanceBuilder(this);\n" +
                    "    }\n", className, className));
            extraStaticsMethods.append(String.format("\n    public static %s%sInstanceBuilder%s instance() {\n" +
                    "        return new %sInstanceBuilder%s();\n" +
                    "    }\n", empty ? "" : classNameWithRaw + " ", className, classNameWithFormal, className, addFormal));
        }
        StringBuilder importsBuilder = new StringBuilder();
        List importList = new LinkedList<>(importSet);
        importList.sort(Comparator.comparing(String::toString));
        for (String s : importSet) {
            importsBuilder.append("import ").append(s).append(";\n");
        }
        String imports = importsBuilder.toString();
        String content = String.format(DEFINITION_JAVA_FILE,
                packageName,
                imports,
                getClass().getName(),
                new Date(),
                className,
                classNameWithRaw,
                acceptedTypesField,
                typesField,
                fullClassName + classNameWithFormal, gettersBuilder.toString(),
                fullClassName + classNameWithFormal, settersBuilder.toString(),
                extraStaticsMethods.toString(),
                extraMethods.toString(),
                extraClasses.toString());
        generate(generatedClassName, content);
    }

    private String generatePredicateBuilderFile(String predicateSimpleClassName, String className, String classNameWithFormal, String classNameWithRaw, Map getters) {

        List methods = new LinkedList<>();
        Types typeUtils = processingEnv.getTypeUtils();
        TypeMirror t2 = typeUtils.erasure(processingEnv.getElementUtils().getTypeElement(Comparable.class.getName()).asType());

        for (Map.Entry entry : getters.entrySet()) {
            ExecutableElement value = entry.getValue();
            TypeMirror returnType = value.getReturnType();
            boolean comparable = false;
            for (TypeMirror typeMirror : typeUtils.directSupertypes(returnType)) {
                TypeMirror erasure = typeUtils.erasure(typeMirror);
                if (t2.equals(erasure)) {
                    comparable = true;
                    break;
                }
            }
            String propertyName = entry.getKey();
            String returnTypeName;
            boolean primitiveType = returnType instanceof PrimitiveType;
            if (primitiveType) {
                returnTypeName = typeUtils.boxedClass((PrimitiveType) returnType).toString();
                comparable = true;
            } else {
                returnTypeName = typeUtils.erasure(returnType).toString();
            }
            boolean booleanType = returnType.toString().toLowerCase().endsWith("boolean");
            String methodName = value.getSimpleName().toString();
            if (methodName.startsWith("is")) {
                methodName = "where" + methodName.substring(2);
            } else {
                methodName = "where" + methodName.substring(3);
            }
            if (booleanType) {
                methods.add(String.format("\n" +
                                "        public %s<%s, %s> %s() {\n" +
                                "            return %s(\"%s\");\n" +
                                "        }"
                        , primitiveType ? "PrimitiveBooleanQuery" : "BooleanQuery"
                        , className + classNameWithFormal
                        , predicateSimpleClassName + classNameWithFormal
                        , methodName
                        , primitiveType ? "wherePrimitiveBoolean" : "whereBoolean"
                        , propertyName
                ));
            } else if (comparable) {
                if (returnTypeName.contains(".String")) {
                    methods.add(String.format("\n" +
                                    "        public StringQuery<%s, %s> %s() {\n" +
                                    "            return whereString(\"%s\");\n" +
                                    "        }"
                            , className + classNameWithFormal
                            , predicateSimpleClassName + classNameWithFormal
                            , methodName
                            , propertyName));
                } else {
                    methods.add(String.format("\n" +
                                    "        public %s<%s, %s, %s> %s() {\n" +
                                    "            return %s(\"%s\");\n" +
                                    "        }"
                            , primitiveType ? "PrimitiveObjectQuery" : "ComparableQuery"
                            , className + classNameWithFormal
                            , returnTypeName
                            , predicateSimpleClassName + classNameWithFormal
                            , methodName
                            , primitiveType ? "wherePrimitiveComparable" : "whereComparable"
                            , propertyName));
                }
            } else {
                methods.add(String.format("\n" +
                                "        public ObjectQuery<%s, %s, %s> %s() {\n" +
                                "            return where(\"%s\");\n" +
                                "        }"
                        , className + classNameWithFormal
                        , returnTypeName
                        , predicateSimpleClassName + classNameWithFormal
                        , methodName
                        , propertyName
                ));
            }
        }
        return String.format(PREDICATE_JAVA_FILE,

                predicateSimpleClassName,
                classNameWithRaw,
                className,
                classNameWithFormal,
                predicateSimpleClassName,
                classNameWithFormal,

                predicateSimpleClassName,
                className,
                predicateSimpleClassName,
                className + "JavaBeanDefinition",
                Joiner.on("\n").join(methods));
    }

    private String generateStreamFile(String predicateSimpleClassName, String className, String classNameWithFormal, String classNameWithRaw, Map getters) {

        List methods = new LinkedList<>();
        Types typeUtils = processingEnv.getTypeUtils();
        TypeMirror t2 = typeUtils.erasure(processingEnv.getElementUtils().getTypeElement(Comparable.class.getName()).asType());

        for (Map.Entry entry : getters.entrySet()) {
            ExecutableElement value = entry.getValue();
            TypeMirror returnType = value.getReturnType();
            boolean comparable = false;
            for (TypeMirror typeMirror : typeUtils.directSupertypes(returnType)) {
                TypeMirror erasure = typeUtils.erasure(typeMirror);
                if (t2.equals(erasure)) {
                    comparable = true;
                    break;
                }
            }
            String propertyName = entry.getKey();
            String returnTypeName;
            boolean primitiveType = returnType instanceof PrimitiveType;
            if (primitiveType) {
                returnTypeName = typeUtils.boxedClass((PrimitiveType) returnType).toString();
                comparable = true;
            } else {
                returnTypeName = typeUtils.erasure(returnType).toString();
            }
            boolean booleanType = returnType.toString().toLowerCase().endsWith("boolean");
            String methodName = value.getSimpleName().toString();
            if (methodName.startsWith("is")) {
                methodName = "where" + methodName.substring(2);
            } else {
                methodName = "where" + methodName.substring(3);
            }
            if (booleanType) {
                methods.add(String.format("\n" +
                                "        public %s<%s, %s> %s() {\n" +
                                "            return %s(\"%s\");\n" +
                                "        }"
                        , primitiveType ? "PrimitiveBooleanQuery" : "BooleanQuery"
                        , className + classNameWithFormal
                        , predicateSimpleClassName + classNameWithFormal
                        , methodName
                        , primitiveType ? "wherePrimitiveBoolean" : "whereBoolean"
                        , propertyName
                ));
            } else if (comparable) {
                if (returnTypeName.contains(".String")) {
                    methods.add(String.format("\n" +
                                    "        public StringQuery<%s, %s> %s() {\n" +
                                    "            return whereString(\"%s\");\n" +
                                    "        }"
                            , className + classNameWithFormal
                            , predicateSimpleClassName + classNameWithFormal
                            , methodName
                            , propertyName));
                } else {
                    methods.add(String.format("\n" +
                                    "        public %s<%s, %s, %s> %s() {\n" +
                                    "            return %s(\"%s\");\n" +
                                    "        }"
                            , primitiveType ? "PrimitiveObjectQuery" : "ComparableQuery"
                            , className + classNameWithFormal
                            , returnTypeName
                            , predicateSimpleClassName + classNameWithFormal
                            , methodName
                            , primitiveType ? "wherePrimitiveComparable" : "whereComparable"
                            , propertyName));
                }
            } else {
                methods.add(String.format("\n" +
                                "        public ObjectQuery<%s, %s, %s> %s() {\n" +
                                "            return where(\"%s\");\n" +
                                "        }"
                        , className + classNameWithFormal
                        , returnTypeName
                        , predicateSimpleClassName + classNameWithFormal
                        , methodName
                        , propertyName
                ));
            }
        }
        return String.format(STREAM_JAVA_FILE,

                predicateSimpleClassName,
                classNameWithRaw,
                className,
                classNameWithFormal,
                predicateSimpleClassName,
                classNameWithFormal,

                predicateSimpleClassName,
                className,
                classNameWithFormal,
                className,

                predicateSimpleClassName,
                className,
                className,
                classNameWithFormal,
                Joiner.on("\n").join(methods));
    }


    private String generateComparatorBuilderFile(String comparatorSimpleClassName, String className, String classNameWithFormal, String classNameWithRaw, Map getters) {

        List methods = new LinkedList<>();
        Types typeUtils = processingEnv.getTypeUtils();
        TypeMirror t2 = typeUtils.erasure(processingEnv.getElementUtils().getTypeElement(Comparable.class.getName()).asType());

        for (Map.Entry entry : getters.entrySet()) {
            ExecutableElement value = entry.getValue();
            TypeMirror returnType = value.getReturnType();
            boolean comparable = false;
            for (TypeMirror typeMirror : typeUtils.directSupertypes(returnType)) {
                TypeMirror erasure = typeUtils.erasure(typeMirror);
                if (t2.equals(erasure)) {
                    comparable = true;
                    break;
                }
            }
            String propertyName = entry.getKey();
            String returnTypeName;
            boolean primitiveType = returnType instanceof PrimitiveType;
            if (primitiveType) {
                returnTypeName = typeUtils.boxedClass((PrimitiveType) returnType).toString();
                comparable = true;
            } else {
                returnTypeName = typeUtils.erasure(returnType).toString();
            }
            if (comparable) {

                String methodName = value.getSimpleName().toString();
                methodName = methodName.startsWith("is") ? "where" + methodName.substring(2) : "where" + methodName.substring(3);

                methods.add(String.format("\n" +
                                "        public Query<%s, %s, %s> %s() {\n" +
                                "            return on(\"%s\");\n" +
                                "        }"
                        , className + classNameWithFormal
                        , returnTypeName
                        , comparatorSimpleClassName + classNameWithFormal
                        , methodName
                        , propertyName));
            }

        }
        return String.format(COMPARATOR_JAVA_FILE,

                comparatorSimpleClassName,
                classNameWithRaw,
                className,
                classNameWithFormal,
                comparatorSimpleClassName,
                classNameWithFormal,

                comparatorSimpleClassName,
                className,
                comparatorSimpleClassName,
                className + "JavaBeanDefinition",
                Joiner.on("\n").join(methods));
    }

    private String generateInstanceBuilderFile(String instanceSimpleClassName, String className, String classNameWithFormal, String classNameWithRaw, Map setters) {

        List methods = new LinkedList<>();
        Types typeUtils = processingEnv.getTypeUtils();

        for (Map.Entry entry : setters.entrySet()) {
            ExecutableElement value = entry.getValue();
            TypeMirror methodType = value.getParameters().get(0).asType();
            String propertyName = entry.getKey();
            String returnTypeName = methodType instanceof PrimitiveType
                    ? typeUtils.boxedClass((PrimitiveType) methodType).toString()
                    : typeUtils.erasure(methodType).toString();
            String methodName = Introspector.decapitalize(value.getSimpleName().toString().substring(3));
            methods.add(String.format("\n" +
                            "        public %s %s(%s value) {\n" +
                            "            return set(\"%s\", value);\n" +
                            "        }"
                    , instanceSimpleClassName + classNameWithFormal, methodName, returnTypeName
                    , propertyName));
        }
        return String.format(INSTANCE_JAVA_FILE,
                instanceSimpleClassName,
                classNameWithRaw,
                className,
                classNameWithFormal,
                instanceSimpleClassName,
                classNameWithFormal,

                instanceSimpleClassName,
                className,
                instanceSimpleClassName,
                className + "JavaBeanDefinition",
                Joiner.on("\n").join(methods));
    }

    private void addImport(Set imports, String defaultPackage, Class type) {
        if (defaultPackage == null || !defaultPackage.equals(type.getPackage().getName())) {
            imports.add(type.getName());
        }
    }

    private void addImport(Set imports, String defaultPackage, String type) {
        if (defaultPackage == null || type.indexOf(".", defaultPackage.length()) > -1) {
            imports.add(type);
        }
    }

    private void generate(String generatedClassName, String content) throws IOException {
        JavaFileObject builderFile = processingEnv.getFiler().createSourceFile(generatedClassName);
        try (PrintWriter out = new PrintWriter(builderFile.openWriter())) {
            out.print(content);
        }
    }

    private void logDebug(String msg) {
        if (processingEnv.getOptions().containsKey("debug")) {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, msg);
        }
    }

    private void logWarning(String msg) {
//        if (processingEnv.getOptions().containsKey("debug")) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, msg);
//        }
    }

    private static abstract class Detector extends AbstractElementVisitor8, Void> {
        final Map result = new TreeMap<>();
        private ProcessingEnvironment processingEnv;
        private Types typeUtils;
        private Elements elementUtils;

        private Detector(ProcessingEnvironment processingEnv) {
            this.processingEnv = processingEnv;
            this.typeUtils = processingEnv.getTypeUtils();
            this.elementUtils = processingEnv.getElementUtils();
        }

        private void logDebug(String msg) {
            if (processingEnv.getOptions().containsKey("debug")) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, msg);
            }
        }

        @Override
        public final Map visitPackage(PackageElement e, Void aVoid) {
            return null;
        }

        @Override
        public final Map visitType(TypeElement e, Void aVoid) {

            if (e.getKind() == ElementKind.CLASS) {
                TypeMirror superclass = e.getSuperclass();
                if (superclass != null) {

                    TypeElement typeElement = elementUtils.getTypeElement(typeUtils.erasure(superclass).toString());
                    if (typeElement != null) {
                        visitType(typeElement, aVoid);
                    }
                }
            } else if (e.getKind() == ElementKind.INTERFACE) {
                List interfaces = e.getInterfaces();
                for (TypeMirror anInterface : interfaces) {
                    TypeElement typeElement = elementUtils.getTypeElement(typeUtils.erasure(anInterface).toString());
                    if (typeElement != null) {
                        visitType(typeElement, aVoid);
                    }
                }
            }

            for (Element element : e.getEnclosedElements()) {
                if (element.getKind() == ElementKind.METHOD) {
                    visitExecutable((ExecutableElement) element, aVoid);
                }
            }
            return result;
        }

        @Override
        public final Map visitVariable(VariableElement e, Void aVoid) {
            return null;
        }

        protected abstract String acceptExecutable(ExecutableElement e);

        @Override
        public final Map visitExecutable(ExecutableElement e, Void aVoid) {
            String simpleName = e.getSimpleName().toString();
            String propertyName = acceptExecutable(e);
            if (propertyName != null) {
                logDebug(String.format("%s - Found a matching method: %s (%s)", getClass().getSimpleName(), simpleName, propertyName));
                result.put(propertyName, e);
            }
            return null;
        }

        @Override
        public final Map visitTypeParameter(TypeParameterElement e, Void aVoid) {
            return null;
        }
    }

    private final static class GettersDetector extends Detector {

        private GettersDetector(ProcessingEnvironment processingEnv) {
            super(processingEnv);
        }

        @Override
        protected String acceptExecutable(ExecutableElement e) {
            String simpleName = e.getSimpleName().toString();
            boolean objectPrefix = simpleName.startsWith("get");
            boolean booleanPrefix = simpleName.startsWith("is");
            boolean result = (objectPrefix || booleanPrefix)
                    && !e.getModifiers().contains(Modifier.STATIC)
                    && e.getModifiers().contains(Modifier.PUBLIC)
                    && e.getParameters().isEmpty()
                    && !(e.getReturnType() instanceof NoType);
            if (result) {
                String propertyName = Introspector.decapitalize(booleanPrefix ? simpleName.substring(2) : simpleName.substring(3));
                return excludedProperties.contains(propertyName) ? null : propertyName;
            }
            return null;
        }

    }

    private final static class SettersDetector extends Detector {

        private SettersDetector(ProcessingEnvironment processingEnv) {
            super(processingEnv);
        }

        @Override
        protected String acceptExecutable(ExecutableElement e) {
            String simpleName = e.getSimpleName().toString();
            boolean result = simpleName.startsWith("set")
                    && !e.getModifiers().contains(Modifier.STATIC)
                    && e.getModifiers().contains(Modifier.PUBLIC)
                    && e.getParameters().size() == 1
                    && e.getReturnType() instanceof NoType;
            if (result) {
                String propertyName = Introspector.decapitalize(simpleName.substring(3));
                return excludedProperties.contains(propertyName) ? null : propertyName;
            }
            return null;
        }

    }

}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy