io.ultreia.java4all.bean.spi.GenerateJavaBeanDefinitionProcessor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-bean Show documentation
Show all versions of java-bean Show documentation
Java Bean api extends by Ultreia.io
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.AbstractJavaBeanDefinition;
import io.ultreia.java4all.bean.AbstractJavaBeanPredicateBuilder;
import io.ultreia.java4all.bean.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.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.Comparator;
import java.util.Date;
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" +
" public %sJavaBeanDefinition() {\n" +
" super(ImmutableSet.>builder()\n" +
"%s" +
" .build(),\n" +
" ImmutableMap.>builder()\n" +
"%s" +
" .build(),\n" +
" 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 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";
@SuppressWarnings("SpellCheckingInspection")
private static final List excludedProperties = Arrays.asList("ters", "class", "propertyChangeListeners", "vetoableChangeListeners");
private Set done = new TreeSet<>();
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
Set extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(annotation);
for (Element annotatedElement : annotatedElements) {
TypeElement classElement = (TypeElement) annotatedElement;
String fullyQualifiedName = classElement.getQualifiedName().toString();
String packageName = null;
int lastDot = fullyQualifiedName.lastIndexOf('.');
if (lastDot > 0) {
packageName = fullyQualifiedName.substring(0, lastDot);
}
String className = classElement.getSimpleName().toString();
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 extends TypeParameterElement> 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);
String[] classes = realAnnotation.types();
boolean predicate = realAnnotation.predicate();
boolean comparator = realAnnotation.comparator();
try {
generateDefinitionFile(packageName, generatedClassName, className, classNameWithFormal, classNameWithRaw, getters, setters, classes, predicate, comparator);
} catch (IOException e) {
throw new RuntimeException("Can't generate JavaBean definition file for: " + classElement, e);
}
}
}
return true;
}
private void generateDefinitionFile(String packageName, String generatedClassName, String className, String classNameWithFormal, String classNameWithRaw, Map getters, Map setters, String[] classes, boolean predicate, boolean comparator) throws IOException {
StringBuilder typesBuilder = new StringBuilder();
if (classes.length == 0) {
typesBuilder.append(String.format(" .add(%s.class)\n", className));
} else {
for (String aClass : classes) {
typesBuilder.append(String.format(" .add(%s.class)\n", aClass));
}
}
String typesField = typesBuilder.toString();
StringBuilder gettersBuilder = new StringBuilder();
for (Map.Entry entry : getters.entrySet()) {
String propertyName = entry.getKey();
String methodName = entry.getValue().getSimpleName().toString();
gettersBuilder.append(String.format(" .put(\"%s\", %s::%s)\n", propertyName, className, methodName));
}
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));
}
List importList = new LinkedList<>();
addImport(importList, packageName, AutoService.class);
addImport(importList, packageName, Generated.class);
addImport(importList, packageName, JavaBeanDefinition.class);
addImport(importList, packageName, AbstractJavaBeanDefinition.class);
addImport(importList, packageName, Function.class);
addImport(importList, packageName, ImmutableSet.class);
addImport(importList, packageName, BiConsumer.class);
addImport(importList, packageName, ImmutableMap.class);
StringBuilder extraStaticsMethods = new StringBuilder();
StringBuilder extraMethods = new StringBuilder();
StringBuilder extraClasses = new StringBuilder();
if (predicate) {
addImport(importList, 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 %sPredicateBuilder newPredicateBuilder() {\n" +
" return new %sPredicateBuilder();\n" +
" }\n", className, className));
}
if (comparator) {
addImport(importList, 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 %sComparatorBuilder newComparatorBuilder() {\n" +
" return new %sComparatorBuilder();\n" +
" }\n", className, className));
}
StringBuilder importsBuilder = new StringBuilder();
importList.sort(Comparator.comparing(String::toString));
for (String s : importList) {
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,
className,
typesField,
className + classNameWithFormal, gettersBuilder.toString(),
className + 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<>();
TypeMirror t2 = processingEnv.getTypeUtils().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 : processingEnv.getTypeUtils().directSupertypes(returnType)) {
TypeMirror erasure = processingEnv.getTypeUtils().erasure(typeMirror);
if (t2.equals(erasure)) {
comparable = true;
break;
}
}
String propertyName = entry.getKey();
String returnTypeName;
boolean primitiveType = returnType instanceof PrimitiveType;
if (primitiveType) {
returnTypeName = processingEnv.getTypeUtils().boxedClass((PrimitiveType) returnType).toString();
comparable = true;
} else {
returnTypeName = 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 ? "PrimitiveComparableQuery" : "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 generateComparatorBuilderFile(String comparatorSimpleClassName, String className, String classNameWithFormal, String classNameWithRaw, Map getters) {
List methods = new LinkedList<>();
TypeMirror t2 = processingEnv.getTypeUtils().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 : processingEnv.getTypeUtils().directSupertypes(returnType)) {
TypeMirror erasure = processingEnv.getTypeUtils().erasure(typeMirror);
if (t2.equals(erasure)) {
comparable = true;
break;
}
}
String propertyName = entry.getKey();
String returnTypeName;
boolean primitiveType = returnType instanceof PrimitiveType;
if (primitiveType) {
returnTypeName = processingEnv.getTypeUtils().boxedClass((PrimitiveType) returnType).toString();
comparable = true;
} else {
returnTypeName = returnType.toString();
}
if (comparable) {
String methodName = value.getSimpleName().toString();
if (methodName.startsWith("is")) {
methodName = "where" + methodName.substring(2);
} else {
methodName = "where" + methodName.substring(3);
}
methods.add(String.format("\n" +
" public QuerySupport<%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 void addImport(List imports, String defaultPackage, Class> type) {
if (defaultPackage == null || !defaultPackage.equals(type.getPackage().getName())) {
imports.add(type.getName());
}
}
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
© 2015 - 2025 Weber Informatics LLC | Privacy Policy