Package org.jboss.errai.processor

Source Code of org.jboss.errai.processor.BoundAnnotationChecker

package org.jboss.errai.processor;

import static org.jboss.errai.processor.AnnotationProcessors.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
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.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic.Kind;

/**
* Evaluates usage of the ErraiUI DataField annotation and emits errors and warnings when
* the annotation is not being used correctly.
*/
@SupportedAnnotationTypes(TypeNames.BOUND)
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class BoundAnnotationChecker extends AbstractProcessor {

  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    final Types types = processingEnv.getTypeUtils();
    final Elements elements = processingEnv.getElementUtils();
    final TypeMirror gwtWidgetType = elements.getTypeElement(TypeNames.GWT_WIDGET).asType();

    Map<TypeElement, List<Element>> classesWithBoundThings = new HashMap<TypeElement, List<Element>>();
    for (TypeElement annotation : annotations) {
      for (Element target : roundEnv.getElementsAnnotatedWith(annotation)) {
        TypeMirror targetType;
        if (target.getKind() == ElementKind.METHOD) {
          targetType = ((ExecutableElement) target).getReturnType();
        }
        else {
          targetType = target.asType();
        }
        if (!types.isAssignable(targetType, gwtWidgetType)) {
          processingEnv.getMessager().printMessage(
                  Kind.ERROR, "@Bound must target a type assignable to Widget", target); // FIXME actually HasText or HasValue
        }

        TypeElement enclosingClass = getEnclosingTypeElement(target);
        List<Element> boundThings = classesWithBoundThings.get(enclosingClass);
        if (boundThings == null) {
          boundThings = new ArrayList<Element>();
          classesWithBoundThings.put(enclosingClass, boundThings);
        }
        boundThings.add(target);
      }
    }

    for (Map.Entry<TypeElement, List<Element>> classWithItsBoundThings : classesWithBoundThings.entrySet()) {
      List<TypeMirror> modelTypes = findAllModelTypes(classWithItsBoundThings.getKey());
      if (modelTypes.size() == 0) {
        for (Element boundElement : classWithItsBoundThings.getValue()) {
          processingEnv.getMessager().printMessage(
                  Kind.ERROR, "@Bound requires that this class also contains a @Model or @AutoBound DataBinder",
                  boundElement, getAnnotation(boundElement, TypeNames.BOUND));
        }
      }
      else if (modelTypes.size() > 1) {
        // TODO mark everything annotated with @AutoBound or @Model with an error
      }
      else {
        TypeMirror modelType = modelTypes.get(0);
        Set<String> modelPropertyNames = getPropertyNames(modelType);
        for (Element boundElement : classWithItsBoundThings.getValue()) {
          switch (boundElement.getKind()) {
          case FIELD:
          case PARAMETER:
            if (!modelPropertyNames.contains(boundElement.getSimpleName().toString())) {
              processingEnv.getMessager().printMessage(
                      Kind.ERROR, "The model type " + ((DeclaredType) modelType).asElement().getSimpleName() + " does not have property \"" + boundElement.getSimpleName() + "\"",
                      boundElement, getAnnotation(boundElement, TypeNames.BOUND));
            }
            break;
          case METHOD:
            String propertyName = propertyNameOfMethod(boundElement);
            if (!modelPropertyNames.contains(propertyName)) {
              processingEnv.getMessager().printMessage(
                      Kind.ERROR, "The model type " + ((DeclaredType) modelType).asElement().getSimpleName() + " does not have property \"" + propertyName + "\"",
                      boundElement, getAnnotation(boundElement, TypeNames.BOUND));
            }
            break;
          default:
            break;
          }
        }
      }
    }

    return false;
  }

  /**
   * Returns the set of all bindable property names in the given model.
   */
  private Set<String> getPropertyNames(TypeMirror modelType) {
    final Elements elements = processingEnv.getElementUtils();
    final Types types = processingEnv.getTypeUtils();

    Set<String> result = new HashSet<String>();

    for (Element el : ElementFilter.methodsIn(elements.getAllMembers((TypeElement) types.asElement(modelType)))) {
      String propertyName = AnnotationProcessors.propertyNameOfMethod(el);
      if (propertyName != null) {
        result.add(propertyName);
      }
      // TODO extract type info from methods
//        for (VariableElement param : ((ExecutableElement) el).getParameters()) {
//          if (hasAnnotation(param, TypeNames.MODEL)) {
//            result.add(param.asType());
//          }
//          else if (hasAnnotation(param, TypeNames.AUTO_BOUND)) {
//            result.add(typeOfDataBinder(param.asType()));
//          }
//        }
    }
    return result;
  }

  /**
   * Returns the bindable model type of all things annotated with {@code @Model}
   * and DataBinders annotated with {@code @AutoBound}. Legally, there should
   * only be one; we return all of them as Elements so the caller can attach
   * error/warning messages to them if we found multiples.
   *
   * @param classContainingBindableThings
   * @return
   */
  private List<TypeMirror> findAllModelTypes(TypeElement classContainingBindableThings) {
    final List<TypeMirror> result = new ArrayList<TypeMirror>();
    final Elements elements = processingEnv.getElementUtils();

    for (Element el : elements.getAllMembers(classContainingBindableThings)) {
      switch (el.getKind()) {
      case METHOD:
      case CONSTRUCTOR:
        if (!hasAnnotation(el, TypeNames.JAVAX_INJECT)) continue;

        for (VariableElement param : ((ExecutableElement) el).getParameters()) {
          if (hasAnnotation(param, TypeNames.MODEL)) {
            result.add(param.asType());
          }
          else if (hasAnnotation(param, TypeNames.AUTO_BOUND)) {
            result.add(typeOfDataBinder(param.asType()));
          }
        }
        break;
      case FIELD:
        if (hasAnnotation(el, TypeNames.MODEL)) {
          result.add(el.asType());
        }
        else if (hasAnnotation(el, TypeNames.AUTO_BOUND)) {
          result.add(typeOfDataBinder(el.asType()));
        }
        break;
      default:
        break;
      }
    }
    return result;
  }

  /**
   * Returns the concrete type, type variable, or wildcard type of the given DataBinder declaration.
   *
   * @param dataBinderDeclaration
   * @return
   */
  private TypeMirror typeOfDataBinder(TypeMirror dataBinderDeclaration) {
    // in a superclass, this could return a type variable or a wildcard
    return ((DeclaredType) dataBinderDeclaration).getTypeArguments().get(0);
  }

}
TOP

Related Classes of org.jboss.errai.processor.BoundAnnotationChecker

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.