/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* 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 org.kie.workbench.common.forms.adf.processors;
import java.beans.Introspector;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.MirroredTypeException;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.apache.commons.lang3.StringUtils;
import org.kie.workbench.common.forms.adf.definitions.annotations.FieldParam;
import org.kie.workbench.common.forms.adf.definitions.annotations.FormDefinition;
import org.kie.workbench.common.forms.adf.definitions.annotations.FormField;
import org.kie.workbench.common.forms.adf.definitions.annotations.SkipFormField;
import org.kie.workbench.common.forms.adf.definitions.annotations.field.selector.SelectorDataProvider;
import org.kie.workbench.common.forms.adf.definitions.annotations.i18n.I18nSettings;
import org.kie.workbench.common.forms.adf.definitions.annotations.layout.Column;
import org.kie.workbench.common.forms.adf.definitions.annotations.metaModel.FieldDefinition;
import org.kie.workbench.common.forms.adf.definitions.annotations.metaModel.FieldLabel;
import org.kie.workbench.common.forms.adf.definitions.annotations.metaModel.FieldReadOnly;
import org.kie.workbench.common.forms.adf.definitions.annotations.metaModel.FieldRequired;
import org.kie.workbench.common.forms.adf.definitions.annotations.metaModel.FieldValue;
import org.kie.workbench.common.forms.adf.definitions.annotations.metaModel.LabelMode;
import org.kie.workbench.common.forms.adf.definitions.settings.ColSpan;
import org.kie.workbench.common.forms.adf.definitions.settings.FieldPolicy;
import org.kie.workbench.common.forms.adf.processors.util.FormGenerationUtils;
import org.kie.workbench.common.forms.model.FieldType;
import org.uberfire.annotations.processors.AbstractErrorAbsorbingProcessor;
import org.uberfire.annotations.processors.GenerationCompleteCallback;
import org.uberfire.annotations.processors.GeneratorUtils;
import org.uberfire.annotations.processors.exceptions.GenerationException;
import static org.kie.workbench.common.forms.adf.processors.FormDefinitionsProcessor.FIELD_DEFINITION_ANNOTATION;
import static org.kie.workbench.common.forms.adf.processors.FormDefinitionsProcessor.FORM_DEFINITON_ANNOTATION;
@SupportedAnnotationTypes({
FORM_DEFINITON_ANNOTATION,
FIELD_DEFINITION_ANNOTATION
})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class FormDefinitionsProcessor extends AbstractErrorAbsorbingProcessor {
public static final String FORM_DEFINITON_ANNOTATION = "org.kie.workbench.common.forms.adf.definitions.annotations.FormDefinition";
public static final String FIELD_DEFINITION_ANNOTATION = "org.kie.workbench.common.forms.adf.definitions.annotations.metaModel.FieldDefinition";
private TypeMirror listType;
private GenerationCompleteCallback callback = null;
private Elements elementUtils;
private RoundEnvironment roundEnvironment;
private SourceGenerationContext context;
public FormDefinitionsProcessor() {
}
// Constructor for tests only, to prevent code being written to file. The generated code will be sent to the callback
FormDefinitionsProcessor(GenerationCompleteCallback callback) {
this();
this.callback = callback;
System.out.println("GenerationCompleteCallback has been provided. Generated source code will not be compiled and hence classes will not be available.");
}
@Override
protected boolean processWithExceptions(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnvironment) throws Exception {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
"Searching FormDefinitions on project");
//If prior processing threw an error exit
if (roundEnvironment.errorRaised()) {
return false;
}
this.elementUtils = processingEnv.getElementUtils();
this.roundEnvironment = roundEnvironment;
context = new SourceGenerationContext();
processFieldDefinitions();
processFormDefinitions();
if (!context.getForms().isEmpty() || !context.getFieldDefinitions().isEmpty()) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
"Generating sources for [" + context.getForms().size() + "] FormDefinitions & [" + context.getFieldDefinitions().size() + "] FieldDefinitions");
String packageName = context.getForms().get(0).get("package") + ".formBuilder.provider";
Map<String, Object> templateContext = new HashMap<>();
templateContext.put("package",
packageName);
templateContext.put("generatedByClassName",
this.getClass().getName());
templateContext.put("forms",
context.getForms());
templateContext.put("fieldModifiers",
context.getFieldDefinitions());
templateContext.put("fieldDefinitions",
context.getFieldDefinitions());
StringBuffer code = writeTemplate("templates/FormGenerationResourcesProvider.ftl",
templateContext);
// If code is successfully created write files, or send generated code to callback.
// The callback function is used primarily for testing when we don't necessarily want
// the generated code to be stored as a compilable file for javac to process.
if (callback == null) {
writeCode(packageName,
"ModuleFormGenerationResourcesProvider",
code);
} else {
callback.generationComplete(code.toString());
}
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
"Succesfully Generated sources for [" + context.getForms().size() + "] FormDefinitions & [" + context.getFieldDefinitions().size() + "] FieldDefinitions");
} else {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
"No FormDefinitions found on module");
}
return true;
}
protected void processFieldDefinitions() throws Exception {
TypeElement annotation = elementUtils.getTypeElement(FIELD_DEFINITION_ANNOTATION);
Set<? extends Element> fieldDefintions = roundEnvironment.getElementsAnnotatedWith(annotation);
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
"FieldDefinitions found: " + fieldDefintions.size());
for (Element element : fieldDefintions) {
if (element.getKind().equals(ElementKind.CLASS)) {
processFieldDefinition((TypeElement) element);
}
}
}
private void processFieldDefinition(TypeElement fieldDefinitionElement) throws Exception {
final Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE,
"Discovered FieldDefinition class [" + fieldDefinitionElement.getSimpleName() + "]");
Collection<FieldInfo> fieldInfos = extractFieldInfos(fieldDefinitionElement,
null);
String modelClassName = fieldDefinitionElement.getQualifiedName().toString();
String fieldModifierName = fixClassName(modelClassName) + "_FieldStatusModifier";
Map<String, String> fieldDefinition = new HashMap<>();
fieldDefinition.put("className",
modelClassName);
fieldDefinition.put("fieldModifierName",
fieldModifierName);
Map<String, Object> templateContext = new HashMap<>();
templateContext.put("modelClassName",
modelClassName);
templateContext.put("fieldModifierName",
fieldModifierName);
FieldDefinition fieldDefinitionAnnotation = fieldDefinitionElement.getAnnotation(FieldDefinition.class);
for (FieldInfo fieldInfo : fieldInfos) {
AnnotationMirror annotation = GeneratorUtils.getAnnotation(elementUtils,
fieldInfo.fieldElement,
FieldValue.class.getName());
if (annotation != null) {
if (fieldDefinition.containsKey("value")) {
throw new Exception("Problem processing FieldDefinition [" + modelClassName + "]: it should have only one @FieldValue");
}
if (fieldInfo.getter == null || fieldInfo.setter == null) {
throw new Exception("Problem processing FieldDefinition [" + modelClassName + "]: field marked as @FieldValue should have setter & getter");
}
fieldDefinition.put("value",
fieldInfo.fieldElement.getSimpleName().toString());
} else {
annotation = GeneratorUtils.getAnnotation(elementUtils,
fieldInfo.fieldElement,
FieldReadOnly.class.getName());
if (annotation != null) {
if (templateContext.containsKey("readOnly")) {
throw new Exception("Problem processing FieldDefinition [" + modelClassName + "]: it should have only one @FieldReadOnly");
}
if (!fieldInfo.fieldElement.asType().getKind().equals(TypeKind.BOOLEAN) &&
!fieldInfo.fieldElement.asType().toString().equals(Boolean.class.getName())) {
throw new Exception("Problem processing FieldDefinition [" + modelClassName + "]: field marked as @FieldReadOnly must be boolean or Boolean");
}
if (fieldInfo.getter == null) {
throw new Exception("Problem processing FieldDefinition [" + modelClassName + "]: field marked as @FieldReadOnly should have getter");
}
templateContext.put("readOnly",
fieldInfo.getter);
}
annotation = GeneratorUtils.getAnnotation(elementUtils,
fieldInfo.fieldElement,
FieldRequired.class.getName());
if (annotation != null) {
if (templateContext.containsKey("required")) {
throw new Exception("Problem processing FieldDefinition [" + modelClassName + "]: it should have only one @FieldRequired");
}
if (!fieldInfo.fieldElement.asType().getKind().equals(TypeKind.BOOLEAN) &&
!fieldInfo.fieldElement.asType().toString().equals(Boolean.class.getName())) {
throw new Exception("Problem processing FieldDefinition [" + modelClassName + "]: field marked as @FieldRequired must be boolean or Boolean");
}
if (fieldInfo.getter == null) {
throw new Exception("Problem processing FieldDefinition [" + modelClassName + "]: field marked as @FieldRequired should have getter");
}
templateContext.put("required",
fieldInfo.getter);
}
if (fieldDefinitionAnnotation.labelMode().equals(LabelMode.OVERRIDE)) {
annotation = GeneratorUtils.getAnnotation(elementUtils,
fieldInfo.fieldElement,
FieldLabel.class.getName());
if (annotation != null) {
if (templateContext.containsKey("label")) {
throw new Exception("Problem processing FieldDefinition [" + modelClassName + "]: it should have only one @FieldLabel");
}
if (!fieldInfo.fieldElement.asType().toString().equals(String.class.getName())) {
throw new Exception("Problem processing FieldDefinition [" + modelClassName + "]: field marked as @FieldLabel must be a String");
}
if (fieldInfo.getter == null) {
throw new Exception("Problem processing FieldDefinition [" + modelClassName + "]: field marked as @FieldLabel should have getter");
}
templateContext.put("label",
fieldInfo.getter);
}
}
}
}
StringBuffer source = writeTemplate("templates/FieldDefinitionModifier.ftl",
templateContext);
fieldDefinition.put("sourceCode",
source.toString());
context.getFieldDefinitions().add(fieldDefinition);
}
protected void processFormDefinitions() throws Exception {
TypeElement annotation = elementUtils.getTypeElement(FORM_DEFINITON_ANNOTATION);
Set<? extends Element> formDefinitions = roundEnvironment.getElementsAnnotatedWith(annotation);
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
"FormDefinitions found: " + formDefinitions.size());
// Initializing list type to avoid getting the element each time.
listType = elementUtils.getTypeElement(java.util.List.class.getName()).asType();
for (Element element : formDefinitions) {
if (element.getKind().equals(ElementKind.CLASS)) {
processFormDefinition((TypeElement) element);
}
}
}
protected void processFormDefinition(TypeElement formElement) throws Exception {
final Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE,
"Discovered FormDefintion class [" + formElement.getSimpleName() + "]");
boolean checkInheritance = false;
FormDefinition defintion = formElement.getAnnotation(FormDefinition.class);
checkInheritance = defintion.allowInheritance();
List<Map<String, String>> formElements = new ArrayList<>();
if (checkInheritance) {
TypeElement parent = getParent(formElement);
formElements.addAll(extractParentFormFields(parent,
defintion.policy(),
defintion.i18n()));
}
formElements.addAll(extracFormFields(formElement,
defintion.policy(),
defintion.i18n()));
FormGenerationUtils.sort(defintion.startElement(),
formElements);
messager.printMessage(Diagnostic.Kind.NOTE,
"Discovered " + formElements.size() + " elements for form [" + formElement.getQualifiedName().toString() + "]");
String modelClassName = formElement.getQualifiedName().toString();
String builderClassName = fixClassName(formElement.getQualifiedName().toString()) + "FormBuilder";
Map<String, Object> templateContext = new HashMap<>();
templateContext.put("modelClass",
modelClassName);
templateContext.put("builderClassName",
builderClassName);
templateContext.put("startElement",
defintion.startElement());
templateContext.put("i18n_bundle",
StringUtils.isEmpty(defintion.i18n().bundle()) ? formElement.asType().toString() : defintion.i18n().bundle());
Column[] columns = defintion.layout().value();
List<String> layoutColumns = new ArrayList<>();
if (columns.length == 0) {
layoutColumns.add(ColSpan.SPAN_12.getName());
} else {
for (Column column : columns) {
layoutColumns.add(column.value().getName());
}
}
templateContext.put("layout_columns",
layoutColumns);
templateContext.put("elements",
formElements);
StringBuffer builder = writeTemplate("templates/FormDefinitionSettingsBuilder.ftl",
templateContext);
Map<String, String> form = new HashMap<>();
form.put("package",
((PackageElement) formElement.getEnclosingElement()).getQualifiedName().toString());
form.put("modelClass",
modelClassName);
form.put("builderClass",
builderClassName);
form.put("builderCode",
builder.toString());
context.getForms().add(form);
}
private List<Map<String, String>> extractParentFormFields(TypeElement element,
FieldPolicy policy,
I18nSettings i18nSettings) throws Exception {
if (element.toString().equals(Object.class.getName())) {
return new ArrayList<>();
}
TypeElement parentElement = getParent(element);
List<Map<String, String>> result = extractParentFormFields(parentElement,
policy,
i18nSettings);
result.addAll(extracFormFields(element,
policy,
i18nSettings));
return result;
}
private List<Map<String, String>> extracFormFields(TypeElement type,
FieldPolicy policy,
I18nSettings i18nSettings) throws Exception {
final Elements elementUtils = processingEnv.getElementUtils();
Collection<FieldInfo> fieldInfos = extractFieldInfos(type,
fieldElement -> {
if (policy.equals(FieldPolicy.ALL)) {
AnnotationMirror annotation = GeneratorUtils.getAnnotation(elementUtils,
fieldElement,
SkipFormField.class.getName());
if (annotation != null) {
return false;
}
} else {
AnnotationMirror annotation = GeneratorUtils.getAnnotation(elementUtils,
fieldElement,
FormField.class.getName());
if (annotation == null) {
return false;
}
}
return true;
});
List<Map<String, String>> elementsSettings = new ArrayList<>();
for (FieldInfo fieldInfo : fieldInfos) {
if (fieldInfo.getter != null && fieldInfo.setter != null) {
String fieldName = fieldInfo.fieldElement.getSimpleName().toString();
String fieldLabel = fieldName;
String binding = fieldName;
String methodName = "getFormElement_" + fieldName;
Map<String, Object> elementContext = new HashMap<>();
boolean isList = false;
boolean isEnum = false;
boolean overrideI18nLabel = false;
TypeMirror finalType = fieldInfo.fieldElement.asType();
String fieldModifier = "";
if (finalType instanceof DeclaredType) {
Element finalTypeElement = processingEnv.getTypeUtils().asElement(finalType);
if (finalTypeElement.getKind().equals(ElementKind.CLASS)) {
FieldDefinition fieldDefinitionAnnotation = finalTypeElement.getAnnotation(FieldDefinition.class);
if (fieldDefinitionAnnotation != null) {
// Override the using the i18n mechanism
if (fieldDefinitionAnnotation.labelMode().equals(LabelMode.OVERRIDE_I18N_KEY)) {
Collection<FieldInfo> labelInfos = extractFieldInfos((TypeElement) finalTypeElement,
fieldElement -> fieldElement.getAnnotation(FieldLabel.class) != null);
if (labelInfos == null || labelInfos.size() != 1) {
throw new Exception("Problem processing FieldDefinition [" + finalType + "]: it should have one field marked as @FieldLabel");
}
FieldInfo labelInfo = labelInfos.iterator().next();
fieldLabel = finalType.toString() + i18nSettings.separator() + labelInfo.fieldElement.getSimpleName();
}
Collection<FieldInfo> fieldValue = extractFieldInfos((TypeElement) finalTypeElement,
fieldElement -> fieldElement.getAnnotation(FieldValue.class) != null);
if (fieldValue == null || fieldValue.size() != 1) {
throw new Exception("Problem processing FieldDefinition [" + finalType + "]: it should have one field marked as @FieldValue");
}
FieldInfo valueInfo = fieldValue.iterator().next();
binding += "." + valueInfo.getFieldElement().getSimpleName();
fieldModifier = fixClassName(finalType.toString()) + "_FieldStatusModifier";
finalType = valueInfo.getFieldElement().asType();
overrideI18nLabel = !fieldDefinitionAnnotation.labelMode().equals(LabelMode.DONT_OVERRIDE);
} else {
FormDefinition formDefinitionAnnotation = finalTypeElement.getAnnotation(FormDefinition.class);
if (formDefinitionAnnotation != null) {
Collection<FieldInfo> labelInfos = extractFieldInfos((TypeElement) finalTypeElement,
fieldElement -> fieldElement.getAnnotation(FieldLabel.class) != null);
if (labelInfos != null & labelInfos.size() == 1) {
FieldInfo labelInfo = labelInfos.iterator().next();
fieldLabel = finalType.toString() + i18nSettings.separator() + labelInfo.fieldElement.getSimpleName();
overrideI18nLabel = true;
}
}
}
}
DeclaredType fieldType = (DeclaredType) finalType;
if (processingEnv.getTypeUtils().isAssignable(fieldType.asElement().asType(),
listType)) {
if (fieldType.getTypeArguments().size() != 1) {
throw new IllegalArgumentException("Impossible to generate a field for type " + fieldType.toString() + ". Type should have one and only one Type arguments.");
}
isList = true;
finalType = fieldType.getTypeArguments().get(0);
} else {
isEnum = elementUtils.getTypeElement(finalType.toString()).getSuperclass().toString().startsWith("java.lang.Enum");
}
}
elementContext.put("formModel",
type.getQualifiedName().toString());
elementContext.put("methodName",
methodName);
elementContext.put("fieldName",
fieldName);
elementContext.put("binding",
binding);
elementContext.put("className",
finalType.toString());
elementContext.put("isList",
String.valueOf(isList));
elementContext.put("isEnum",
String.valueOf(isEnum));
elementContext.put("fieldModifier",
fieldModifier);
Map<String, String> params = new HashMap<>();
elementContext.put("params",
params);
String afterElement = "";
FormField settings = fieldInfo.fieldElement.getAnnotation(FormField.class);
if (settings != null) {
String typeName;
try {
typeName = settings.type().getName();
} catch (MirroredTypeException exception) {
typeName = exception.getTypeMirror().toString();
}
if (StringUtils.isEmpty(typeName)) {
typeName = FieldType.class.getName();
}
afterElement = settings.afterElement();
elementContext.put("preferredType",
typeName);
if (!overrideI18nLabel) {
fieldLabel = settings.labelKey();
}
elementContext.put("required",
Boolean.valueOf(settings.required()).toString());
elementContext.put("readOnly",
Boolean.valueOf(settings.readonly()).toString());
for (FieldParam fieldParam : settings.settings()) {
params.put(fieldParam.name(),
fieldParam.value());
}
elementContext.put("wrap",
Boolean.valueOf(settings.layoutSettings().wrap()).toString());
elementContext.put("horizontalSpan",
String.valueOf(settings.layoutSettings().horizontalSpan()));
elementContext.put("verticalSpan",
String.valueOf(settings.layoutSettings().verticalSpan()));
} else {
elementContext.put("preferredType",
FieldType.class.getName());
elementContext.put("required",
Boolean.FALSE.toString());
elementContext.put("readOnly",
Boolean.FALSE.toString());
elementContext.put("wrap",
Boolean.FALSE.toString());
elementContext.put("horizontalSpan",
"1");
elementContext.put("verticalSpan",
"1");
}
if (!overrideI18nLabel) {
if (!StringUtils.isEmpty(i18nSettings.keyPreffix())) {
fieldLabel = i18nSettings.keyPreffix() + i18nSettings.separator() + fieldLabel;
}
}
elementContext.put("labelKey",
fieldLabel);
elementContext.put("afterElement",
afterElement);
extractFieldExtraSettings(elementContext,
fieldInfo.fieldElement);
StringBuffer methodCode = writeTemplate("templates/FieldElement.ftl",
elementContext);
Map<String, String> fieldSettings = new HashMap<>();
fieldSettings.put("elementName",
fieldName);
fieldSettings.put("afterElement",
afterElement);
fieldSettings.put("methodName",
methodName);
fieldSettings.put("method",
methodCode.toString());
elementsSettings.add(fieldSettings);
}
}
return elementsSettings;
}
protected void extractFieldExtraSettings(Map<String, Object> fieldContext,
VariableElement fieldElement) {
SelectorDataProvider selectorDataProvider = fieldElement.getAnnotation(SelectorDataProvider.class);
if (selectorDataProvider != null) {
String providerSettings = selectorDataProvider.type().getCode() + ":" + selectorDataProvider.className();
((Map<String, String>) fieldContext.get("params")).put(SelectorDataProvider.class.getName(),
providerSettings);
}
}
protected StringBuffer writeTemplate(String templateName,
Map<String, Object> context) throws GenerationException {
//Generate code
final StringWriter sw = new StringWriter();
final BufferedWriter bw = new BufferedWriter(sw);
// The code used to contain 'new InputStreamReader(this.getClass().getResourceAsStream(templateName))' which for
// some reason was causing issues during concurrent invocation of this method (e.g. in parallel Maven build).
// The stream returned by 'getResourceAsStream(templateName)' was sometimes already closed (!) and as the
// Template class tried to read from the stream it resulted in IOException. Changing the code to
// 'getResource(templateName).openStream()' seems to be a sensible workaround
try (InputStream templateIs = this.getClass().getResource(templateName).openStream()) {
Configuration config = new Configuration();
Template template = new Template("",
new InputStreamReader(templateIs),
config);
template.process(context,
bw);
} catch (IOException ioe) {
throw new GenerationException(ioe);
} catch (TemplateException te) {
throw new GenerationException(te);
} finally {
try {
bw.close();
sw.close();
} catch (IOException ioe) {
throw new GenerationException(ioe);
}
}
return sw.getBuffer();
}
private FieldInfo getInfoFromMap(String fieldName,
Map<String, FieldInfo> map) {
FieldInfo info = map.get(fieldName);
if (info == null) {
info = new FieldInfo();
map.put(fieldName,
info);
}
return info;
}
protected Collection<FieldInfo> extractFieldInfos(TypeElement typeElement,
VariableElementValidator validator) {
Map<String, FieldInfo> allFields = new HashMap<>();
typeElement.getEnclosedElements().forEach(element -> {
if (element.getKind().equals(ElementKind.FIELD)) {
VariableElement fieldElement = (VariableElement) element;
if (validator != null && !validator.isValid(fieldElement)) {
return;
}
FieldInfo fieldInfo = getInfoFromMap(fieldElement.getSimpleName().toString(),
allFields);
if (fieldInfo == null) {
fieldInfo = new FieldInfo();
allFields.put(fieldElement.getSimpleName().toString(),
fieldInfo);
}
fieldInfo.fieldElement = fieldElement;
} else if (element.getKind().equals(ElementKind.METHOD)) {
ExecutableElement method = (ExecutableElement) element;
String methodName = method.getSimpleName().toString();
if (isGetter(method)) {
String fieldName = extractFieldName(methodName,
3);
FieldInfo info = getInfoFromMap(fieldName,
allFields);
info.getter = methodName;
} else if (isBooleanGetter(method)) {
String fieldName = extractFieldName(methodName,
2);
FieldInfo info = getInfoFromMap(fieldName,
allFields);
info.getter = methodName;
} else if (isSetter(method)) {
String fieldName = extractFieldName(methodName,
3);
FieldInfo info = getInfoFromMap(fieldName,
allFields);
info.setter = methodName;
}
}
});
return allFields.values().stream().filter(fieldInfo -> fieldInfo.fieldElement != null).collect(Collectors.toCollection(() -> new ArrayList<>()));
}
private interface VariableElementValidator {
boolean isValid(VariableElement fieldElement);
}
private class FieldInfo {
private VariableElement fieldElement = null;
private String setter = null;
private String getter = null;
public VariableElement getFieldElement() {
return fieldElement;
}
public void setFieldElement(VariableElement fieldElement) {
this.fieldElement = fieldElement;
}
}
private String extractFieldName(String methodName,
int index) {
if (methodName.length() <= index) {
throw new IllegalArgumentException("MethodName ( '" + methodName + "' ) size < " + index);
}
return Introspector.decapitalize(methodName.substring(index));
}
private boolean isGetter(final ExecutableElement method) {
String name = method.getSimpleName().toString();
if (method.getReturnType().getKind().equals(TypeKind.VOID)) {
return false;
}
int parameterCount = method.getParameters().size();
if (parameterCount != 0) {
return false;
}
return (name.length() > 3 && name.startsWith("get"));
}
private boolean isBooleanGetter(final ExecutableElement method) {
String name = method.getSimpleName().toString();
if (!method.getReturnType().getKind().equals(TypeKind.BOOLEAN)) {
return false;
}
int parameterCount = method.getParameters().size();
if (parameterCount != 0) {
return false;
}
return (name.length() > 2 && name.startsWith("is"));
}
private boolean isSetter(final ExecutableElement method) {
String name = method.getSimpleName().toString();
int parameterCount = method.getParameters().size();
if (parameterCount != 1) {
return false;
}
return (name.length() > 3 && name.startsWith("set"));
}
private String fixClassName(String className) {
return className.replaceAll("\\.",
"_");
}
private TypeElement getParent(TypeElement classElement) {
return (TypeElement) processingEnv.getTypeUtils().asElement(classElement.getSuperclass());
}
}