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

de.knightsoftnet.gwtp.spring.annotation.processor.AbstractBackofficeCreator Maven / Gradle / Ivy

There is a newer version: 2.2.2
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You 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 de.knightsoftnet.gwtp.spring.annotation.processor;

import de.knightsoftnet.validators.annotation.processor.TypeUtils;
import de.knightsoftnet.validators.shared.data.FieldTypeEnum;

import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.annotation.processing.FilerException;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;

/**
 * Create class or interface.
 */
public abstract class AbstractBackofficeCreator {

  protected static final String JAVAX_VALIDATION_PATH = "javax.validation.constraints.";
  protected static final String JAKARTA_VALIDATION_PATH = "jakarta.validation.constraints.";
  protected static final String MT_VALIDATION_PATH = "de.knightsoftnet.validators.shared.";
  protected static final String WIDGET_PATH = "de.knightsoftnet.mtwidgets.client.ui.widget.";

  private final List imports;
  protected final String suffix;

  protected AbstractBackofficeCreator(final String suffix) {
    super();
    imports = new ArrayList<>();
    this.suffix = suffix;
  }

  public String getSuffix() {
    return suffix;
  }

  /**
   * write class or interface.
   *
   * @param element the element which represents the entity
   * @param annotationInterface annotation interface
   * @param processingEnv processing environment
   */
  public void writeClassOrInterface(final Element element, final T annotationInterface,
      final ProcessingEnvironment processingEnv) {
    try {
      final String serverPackage = processingEnv.getElementUtils()
          .getPackageOf(element.getEnclosingElement()).getQualifiedName().toString();
      final String entityType = getEntityNameOfElement(element);

      final JavaFileObject builderFile =
          processingEnv.getFiler().createSourceFile(serverPackage + "." + entityType + suffix);
      try (PrintWriter out = new PrintWriter(builderFile.openWriter())) {
        out.print("package ");
        out.print(serverPackage);
        out.println(";");
        out.println();

        addAdditionalImports(serverPackage, element, annotationInterface, processingEnv);

        writeImports(out, serverPackage);

        out.println();

        writeBody(out, serverPackage, element, annotationInterface, processingEnv);
      }
    } catch (final FilerException e) {
      // happens when trying to recreate an existing interface
      processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, e.getMessage());
    } catch (final IOException e) {
      processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage());
      e.printStackTrace();
    }
  }

  protected String getPackageOfElement(final Element element) {
    final String entityName = element.asType().toString();
    final int packagePos = entityName.lastIndexOf('.');
    return entityName.substring(0, packagePos);
  }

  protected String getEntityNameOfElement(final Element element) {
    final String entityName = element.asType().toString();
    final int packagePos = entityName.lastIndexOf('.');
    return entityName.substring(packagePos + 1);
  }

  /**
   * add import line.
   *
   * @param classWithPaths class including pass to import
   */
  protected void addImports(final String... classWithPaths) {
    Stream.of(classWithPaths).forEach(this::addImport);
  }

  /**
   * add import line.
   *
   * @param typeMirrors class including pass to import
   */
  protected void addImports(final TypeMirror... typeMirrors) {
    Stream.of(typeMirrors).forEach(this::addImport);
  }

  /**
   * add import line.
   *
   * @param classWithPath class including pass to import
   */
  protected void addImport(final String classWithPath) {
    imports.add(classWithPath);
  }

  /**
   * add import line.
   *
   * @param typeMirror type mirror of the class to add
   */
  protected void addImport(final TypeMirror typeMirror) {
    addImport(typeMirror.toString());
  }

  /**
   * write import lines.
   *
   * @param out print writer
   * @param classPath path of the class (or interface) to crate
   */
  protected void writeImports(final PrintWriter out, final String classPath) {
    String lastPrefix = firstTwoPartsOfPath(classPath);
    String lastImport = StringUtils.EMPTY;

    imports.sort(new ImportComparator(lastPrefix));

    for (final String importValue : imports) {
      if (!StringUtils.equals(importValue, lastImport)) {
        lastImport = importValue;
        if (!importValue.startsWith(lastPrefix)) {
          out.println();
          lastPrefix = firstPartOfPath(importValue);
        }
        out.print("import ");
        out.print(importValue);
        out.println(";");
      }
    }
  }

  protected String firstPartOfPath(final String classPath) {
    final int pos1 = classPath.indexOf('.');
    return classPath.substring(0, pos1);
  }

  protected String firstTwoPartsOfPath(final String classPath) {
    final int pos1 = classPath.indexOf('.');
    final int pos2 = classPath.indexOf('.', pos1 + 1);
    return classPath.substring(0, pos2);
  }

  protected List getFields(final Element element) {
    return TypeUtils.getFields(element.asType()).stream().sorted((o1, o2) -> {
      // sort id on top, keep the rest unchanged
      if ("id".equals(o1.getSimpleName().toString())) {
        return "id".equals(o2.getSimpleName().toString()) ? 0 : -1;
      }
      return 0;
    }).collect(Collectors.toList());
  }

  protected boolean hasEnum(final Element element) {
    return getFields(element).stream().map(Element::asType).anyMatch(this::isEnum);
  }

  protected boolean isEnum(final TypeMirror typeMirror) {
    return StringUtils.isNoneEmpty(getEnumName(typeMirror));
  }

  protected String getEnumName(final TypeMirror typeMirror) {
    if ("java.lang.Enum".equals(TypeUtils.getClassName(typeMirror))) {
      return TypeUtils.getClassName(((DeclaredType) typeMirror).getTypeArguments().get(0));
    }
    if (typeMirror.getKind() == TypeKind.DECLARED) {
      final TypeMirror superclass =
          ((TypeElement) ((DeclaredType) typeMirror).asElement()).getSuperclass();
      if (superclass != null) {
        return getEnumName(superclass);
      }
    }
    return null;
  }

  protected String getEnumNameWithoutPackage(final TypeMirror typeMirror) {
    final String[] splittedName = StringUtils.split(getEnumName(typeMirror), '.');
    return splittedName[splittedName.length - 1];
  }

  /**
   * read all values of a enumeration.
   *
   * @param enumTypeElement type element of the enumeration
   * @return list of values
   */
  protected List getEnumValues(final TypeElement enumTypeElement) {
    return enumTypeElement.getEnclosedElements().stream()
        .filter(element -> element.getKind().equals(ElementKind.ENUM_CONSTANT))
        .map(Object::toString).collect(Collectors.toList());
  }

  /**
   * map given field to FieldTypeEnum.
   *
   * @param field the field to detect
   * @param elementType declared type of the structure
   * @param processingEnv progressing environment
   * @return the detected field type enum
   */
  protected FieldTypeEnum mapElementToSearchType(final Element field,
      final DeclaredType elementType, final ProcessingEnvironment processingEnv) {
    // TODO incomplete implementation, handle additional stuff
    switch (TypeUtils.getClassName(processingEnv.getTypeUtils().asMemberOf(elementType, field))) {
      case "byte":
      case "short":
      case "int":
      case "long":
      case "float":
      case "double":
      case "java.lang.Byte":
      case "java.lang.Short":
      case "java.lang.Integer":
      case "java.lang.Long":
      case "java.lang.Double":
      case "java.lang.Float":
      case "java.lang.Number":
      case "java.math.BigDecimal":
      case "java.math.BigInteger":
        return FieldTypeEnum.NUMERIC;
      case "boolean":
      case "java.lang.Boolean":
        return FieldTypeEnum.BOOLEAN;
      case "java.time.LocalDate":
        return FieldTypeEnum.DATE;
      case "java.util.Calendar":
      case "java.util.Date":
      case "java.time.LocalDateTime":
        return FieldTypeEnum.DATETIME;
      case "java.time.LocalTime":
        return FieldTypeEnum.TIME;
      case "java.lang.Enum":
        return FieldTypeEnum.ENUM_FIXED;
      case "java.lang.String":
        return FieldTypeEnum.STRING;
      default:
        if (isEnum(processingEnv.getTypeUtils().asMemberOf(elementType, field))) {
          return FieldTypeEnum.ENUM_FIXED;
        }
        return FieldTypeEnum.STRING;
    }
  }

  /**
   * detect the matching widget for given field.
   *
   * @param field the field to detect
   * @param elementType declared type of the structure
   * @param processingEnv progressing environment
   * @return BackofficeWidget
   */
  protected BackofficeWidget detectBackofficeWidgetOfField(final Element field,
      final DeclaredType elementType, final ProcessingEnvironment processingEnv) {
    final String fieldName = field.getSimpleName().toString();
    final List imports = new ArrayList<>();
    final String widgetName;
    final List widgetParameter = new ArrayList<>();
    boolean provided = false;
    String providedConstructor = StringUtils.EMPTY;
    final List annotations = field.getAnnotationMirrors();
    final List annotationNames = annotations.stream()
        .map(annotationMirror -> annotationMirror.getAnnotationType().toString())
        .collect(Collectors.toList());

    if ("id".equals(fieldName)) {
      widgetParameter.add(BackofficeWidgetParameter.of("enabled", "false", true));
    }

    switch (TypeUtils.getClassName(processingEnv.getTypeUtils().asMemberOf(elementType, field))) {
      case "double":
      case "java.lang.Double":
        widgetName = "DecimalDoubleBox";
        imports.add(WIDGET_PATH + widgetName);
        notNullRequiredCheck(widgetParameter, annotationNames);
        break;
      case "java.math.BigDecimal":
        widgetName = "DecimalBigDecimalBox";
        imports.add(WIDGET_PATH + widgetName);
        notNullRequiredCheck(widgetParameter, annotationNames);
        break;
      case "long":
      case "java.lang.Long":
        widgetName = "LongBox";
        imports.add(WIDGET_PATH + widgetName);
        notNullRequiredCheck(widgetParameter, annotationNames);
        break;
      case "boolean":
      case "java.lang.Boolean":
        widgetName = "CheckBox";
        imports.add(WIDGET_PATH + widgetName);
        break;
      case "java.time.LocalDate":
        widgetName = "DateBoxLocalDate";
        imports.add(WIDGET_PATH + widgetName);
        futurePastMinMaxCheck(imports, widgetParameter, annotationNames, "java.time.LocalDate",
            "LocalDate", "minusDays(1L)", "plusDays(1L)");
        notNullRequiredCheck(widgetParameter, annotationNames);
        break;
      case "java.util.Date":
        widgetName = "DateTimeLocalBox";
        imports.add(WIDGET_PATH + widgetName);
        notNullRequiredCheck(widgetParameter, annotationNames);
        break;
      case "java.time.LocalDateTime":
        widgetName = "DateTimeLocalBoxLocalDateTime";
        imports.add(WIDGET_PATH + widgetName);
        futurePastMinMaxCheck(imports, widgetParameter, annotationNames, "java.time.LocalDateTime",
            "LocalDateTime", "minusMinutes(1L)", "plusMinutes(1L)");
        notNullRequiredCheck(widgetParameter, annotationNames);
        break;
      case "java.time.LocalTime":
        widgetName = "TimeBoxLocalTime";
        imports.add(WIDGET_PATH + widgetName);
        notNullRequiredCheck(widgetParameter, annotationNames);
        break;
      case "de.knightsoftnet.validators.shared.data.CountryEnum":
        widgetName = "CountryListBox";
        imports.add(WIDGET_PATH + widgetName);
        widgetParameter.add(BackofficeWidgetParameter.of("sort", "NAME_ASC", true));
        break;
      case "de.knightsoftnet.gwtp.spring.shared.db.LocalizedEntity":
        widgetName = "MultiLanguageTextBox";
        imports.add(WIDGET_PATH + widgetName);
        notNullNotBlankNotEmptyRequiredCheck(widgetParameter, annotationNames);
        break;
      default:
        if (isEnum(processingEnv.getTypeUtils().asMemberOf(elementType, field))) {
          final List enumValues = getEnumValues((TypeElement) ((DeclaredType) processingEnv
              .getTypeUtils().asMemberOf(elementType, field)).asElement());
          if (enumValues.size() < 4) {
            widgetName =
                "SortableIdAndNameRadioButton<" + getEnumNameWithoutPackage(field.asType()) + ">";
            imports.add(WIDGET_PATH + "SortableIdAndNameRadioButton");
            providedConstructor = "new SortableIdAndNameRadioButton<"
                + getEnumNameWithoutPackage(field.asType()) + ">(\"" + fieldName + "\",\n" //
                + "        Stream.of(" + getEnumNameWithoutPackage(field.asType()) + ".values())\n"
                + "            .map(entry -> new IdAndNameBean<"
                + getEnumNameWithoutPackage(field.asType()) + ">(entry, " + "messages." + fieldName
                + "(entry)))\n" //
                + "            .collect(Collectors.toList()));";
          } else {
            widgetName = "IdAndNameListBox<" + getEnumNameWithoutPackage(field.asType()) + ">";
            imports.add(WIDGET_PATH + "IdAndNameListBox");
            providedConstructor =
                "new IdAndNameListBox<" + getEnumNameWithoutPackage(field.asType()) + ">(Stream.of("
                    + getEnumNameWithoutPackage(field.asType()) + ".values())\n" //
                    + "            .map(entry -> new IdAndNameBean<"
                    + getEnumNameWithoutPackage(field.asType()) + ">(entry, " + "messages."
                    + fieldName + "(entry)))\n" //
                    + "            .collect(Collectors.toList()));";
          }
          provided = true;
          imports.add(WIDGET_PATH + "helper.IdAndNameBean");
          imports.add("java.util.stream.Collectors");
          imports.add("java.util.stream.Stream");
          imports.add(getEnumName(field.asType()));
        } else if (annotationNames.contains(MT_VALIDATION_PATH + "Email")
            || annotationNames.contains(JAVAX_VALIDATION_PATH + "Email")
            || annotationNames.contains(JAKARTA_VALIDATION_PATH + "Email")) {
          widgetName = "EmailTextBox";
          imports.add(WIDGET_PATH + widgetName);
          notNullNotBlankNotEmptyRequiredCheck(widgetParameter, annotationNames);
        } else if (annotationNames.contains(MT_VALIDATION_PATH + "Bic")
            || annotationNames.contains(MT_VALIDATION_PATH + "BicValue")) {
          widgetName = "BicSuggestBox";
          imports.add(WIDGET_PATH + widgetName);
          notNullNotBlankNotEmptyRequiredCheck(widgetParameter, annotationNames);
        } else if (annotationNames.contains(MT_VALIDATION_PATH + "Iban")) {
          widgetName = "IbanTextBox";
          imports.add(WIDGET_PATH + widgetName);
          notNullNotBlankNotEmptyRequiredCheck(widgetParameter, annotationNames);
        } else if (annotationNames.contains(MT_VALIDATION_PATH + "PhoneNumber")) {
          widgetName = detectPhoneWidget(annotations);
          imports.add(WIDGET_PATH + widgetName);
          notNullNotBlankNotEmptyRequiredCheck(widgetParameter, annotationNames);
        } else {
          widgetName = "TextBox";
          imports.add(WIDGET_PATH + widgetName);
          notNullNotBlankNotEmptyRequiredCheck(widgetParameter, annotationNames);
          sizeMaxLengthCheck(widgetParameter, annotations, annotationNames);
        }
    }
    return BackofficeWidget.of(fieldName, imports, widgetName, widgetParameter, provided,
        providedConstructor);
  }

  private String detectPhoneWidget(final List annotations) {
    final Optional phoneNumberValidator =
        annotations.stream().filter(annotationMirror -> (MT_VALIDATION_PATH + "PhoneNumber")
            .equals(annotationMirror.getAnnotationType().toString())).findFirst();
    if (phoneNumberValidator.isPresent()) {

      if (BooleanUtils.isTrue(phoneNumberValidator.get().getElementValues().entrySet().stream()
          .filter(
              elementValue -> "allowDin5008".contentEquals(elementValue.getKey().getSimpleName()))
          .map(elementValue -> (Boolean) elementValue.getValue().getValue()).findAny()
          .orElse(Boolean.FALSE))) {
        return "PhoneNumberDin5008InterSuggestBox";
      }

      if (BooleanUtils.isTrue(phoneNumberValidator.get().getElementValues().entrySet().stream()
          .filter(elementValue -> "allowE123".contentEquals(elementValue.getKey().getSimpleName()))
          .map(elementValue -> (Boolean) elementValue.getValue().getValue()).findAny()
          .orElse(Boolean.FALSE))) {
        return "PhoneNumberE123InterSuggestBox";
      }

      if (BooleanUtils.isTrue(phoneNumberValidator.get().getElementValues().entrySet().stream()
          .filter(elementValue -> "allowUri".contentEquals(elementValue.getKey().getSimpleName()))
          .map(elementValue -> (Boolean) elementValue.getValue().getValue()).findAny()
          .orElse(Boolean.FALSE))) {
        return "PhoneNumberUriSuggestBox";
      }

      if (BooleanUtils.isTrue(phoneNumberValidator.get().getElementValues().entrySet().stream()
          .filter(
              elementValue -> "allowCommon".contentEquals(elementValue.getKey().getSimpleName()))
          .map(elementValue -> (Boolean) elementValue.getValue().getValue()).findAny()
          .orElse(Boolean.FALSE))) {
        return "PhoneNumberCommonInterSuggestBox";
      }
    }
    return "PhoneNumberMsSuggestBox";
  }

  private void futurePastMinMaxCheck(final List imports,
      final List widgetParameter, final List annotationNames,
      final String importString, final String classString, final String minusMethodString,
      final String plusMethodString) {
    if (annotationNames.contains(JAVAX_VALIDATION_PATH + "PastOrPresent")
        || annotationNames.contains(JAKARTA_VALIDATION_PATH + "PastOrPresent")) {
      imports.add(importString);
      widgetParameter.add(BackofficeWidgetParameter.of("max", classString + ".now()", false));
    }
    if (annotationNames.contains(JAVAX_VALIDATION_PATH + "Past")
        || annotationNames.contains(JAKARTA_VALIDATION_PATH + "Past")) {
      imports.add(importString);
      widgetParameter.add(
          BackofficeWidgetParameter.of("max", classString + ".now()." + minusMethodString, false));
    }
    if (annotationNames.contains(JAVAX_VALIDATION_PATH + "FutureOrPresent")
        || annotationNames.contains(JAKARTA_VALIDATION_PATH + "FutureOrPresent")) {
      imports.add(importString);
      widgetParameter.add(BackofficeWidgetParameter.of("min", classString + ".now()", false));
    }
    if (annotationNames.contains(JAVAX_VALIDATION_PATH + "Future")
        || annotationNames.contains(JAKARTA_VALIDATION_PATH + "Future")) {
      imports.add(importString);
      widgetParameter.add(
          BackofficeWidgetParameter.of("min", classString + ".now()." + plusMethodString, false));
    }
  }

  private void sizeMaxLengthCheck(final List widgetParameter,
      final List annotations, final List annotationNames) {
    if (annotationNames.contains(JAVAX_VALIDATION_PATH + "Size")
        || annotationNames.contains(JAKARTA_VALIDATION_PATH + "Size")) {
      final Optional sizeValidator = annotations.stream()
          .filter(annotationMirror -> StringUtils.equals(JAKARTA_VALIDATION_PATH + "Size",
              annotationMirror.getAnnotationType().toString())
              || StringUtils.equals(JAVAX_VALIDATION_PATH + "Size",
                  annotationMirror.getAnnotationType().toString()))
          .findFirst();
      if (sizeValidator.isPresent()) {
        final Optional maxSize = sizeValidator.get().getElementValues().entrySet().stream()
            .filter(elementValue -> "max".contentEquals(elementValue.getKey().getSimpleName()))
            .map(elementValue -> Objects.toString(elementValue.getValue().getValue(), null))
            .findAny();
        if (maxSize.isPresent()) {
          widgetParameter.add(BackofficeWidgetParameter.of("maxLength", maxSize.get(), true));
        }
      }
    }
  }

  private void notNullNotBlankNotEmptyRequiredCheck(
      final List widgetParameter, final List annotationNames) {
    if (annotationNames.contains(JAVAX_VALIDATION_PATH + "NotNull")
        || annotationNames.contains(JAKARTA_VALIDATION_PATH + "NotNull")
        || annotationNames.contains(JAVAX_VALIDATION_PATH + "NotBlank")
        || annotationNames.contains(JAKARTA_VALIDATION_PATH + "NotBlank")
        || annotationNames.contains(JAVAX_VALIDATION_PATH + "NotEmpty")
        || annotationNames.contains(JAKARTA_VALIDATION_PATH + "NotEmpty")) {
      widgetParameter.add(BackofficeWidgetParameter.of("required", "true", true));
    }
  }

  private void notNullRequiredCheck(final List widgetParameter,
      final List annotationNames) {
    if (annotationNames.contains(JAVAX_VALIDATION_PATH + "NotNull")
        || annotationNames.contains(JAKARTA_VALIDATION_PATH + "NotNull")) {
      widgetParameter.add(BackofficeWidgetParameter.of("required", "true", true));
    }
  }

  /**
   * write additional imports.
   *
   * @param element the element which represents the entity
   */
  protected abstract void addAdditionalImports(final String serverPackage, final Element element,
      final T annotationInterface, final ProcessingEnvironment processingEnv);

  /**
   * write class or interface body.
   *
   * @param out print writer
   * @param serverPackage package to generate stuff in
   * @param element the element which represents the entity
   * @param annotationInterface annotation interface
   * @param processingEnv processing environment
   */
  protected abstract void writeBody(final PrintWriter out, final String serverPackage,
      final Element element, final T annotationInterface, ProcessingEnvironment processingEnv);
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy