Package net.karneim.pojobuilder.analysis

Source Code of net.karneim.pojobuilder.analysis.JavaModelAnalyzerUtil

package net.karneim.pojobuilder.analysis;

import static javax.lang.model.element.ElementKind.CLASS;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
import static javax.lang.model.type.TypeKind.VOID;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
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.type.DeclaredType;
import javax.lang.model.type.NoType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

import net.karneim.pojobuilder.model.BuilderM;

public class JavaModelAnalyzerUtil {

  private static final String BUILD_METHOD_NAME = "build";
  private static final String IS = "is";
  private static final String GET = "get";
  private static final String SET = "set";

  private final Elements elements;
  private final Types types;

  public JavaModelAnalyzerUtil(Elements elements, Types types) {
    this.elements = elements;
    this.types = types;
  }

  public PrimitiveType getPrimitiveBooleanType() {
    return types.getPrimitiveType(TypeKind.BOOLEAN);
  }

  public NoType getVoidType() {
    return types.getNoType(TypeKind.VOID);
  }

  /**
   * Returns the classname (without any package qualifier) of the given type element.
   *
   * @param typeElem the type element
   * @return the classname of the given type element
   */
  public String getClassname(TypeElement typeElem) {
    String qualifiedName = typeElem.getQualifiedName().toString();
    String packageName = getPackage(typeElem);
    if (packageName.isEmpty()) {
      return qualifiedName;
    }
    String result = qualifiedName.substring(packageName.length() + 1);
    return result;
  }

  /**
   * Returns the Java package the given type (or it's outer type) belongs to.
   *
   * @param type the type
   * @return the Java package the given type belongs to
   */
  public String getPackage(DeclaredType type) {
    return getPackage((TypeElement) type.asElement());
  }

  /**
   * Returns the Java package the given type element (or it's outer type) belongs to.
   *
   * @param typeElem the type element
   * @return the Java package the given type element belongs to
   */
  public String getPackage(TypeElement typeElem) {
    Element outerElem = typeElem.getEnclosingElement();
    while (!(outerElem instanceof PackageElement)) {
      outerElem = outerElem.getEnclosingElement();
    }

    PackageElement packEl = (PackageElement) outerElem;

    return packEl.getQualifiedName().toString();
  }

  /**
   * Returns the top-level Java class that contains the given element.
   *
   * @param elem the element
   * @return the top-level Java class that contains the given element
   */
  public TypeElement getCompilationUnit(Element elem) {
    if (elem instanceof TypeElement && elem.getEnclosingElement() instanceof PackageElement) {
      return (TypeElement) elem;
    }
    return getCompilationUnit(elem.getEnclosingElement());
  }

  /**
   * Returns true if the given element is accessible for the given builder.
   *
   * @param el the element
   * @param builderM the builder
   * @return true if the given element is accessible
   */
  public boolean isAccessibleForBuilder(Element el, BuilderM builderM) {
    if (el.getModifiers().contains(PUBLIC)) {
      return true;
    }
    if (el.getModifiers().contains(PRIVATE)) {
      return false;
    }
    // TODO Check if el is an accessible member for subclasses AND builderM actually is a subclass
    PackageElement fieldPackage = elements.getPackageOf(el);
    String builderPackge = builderM.getType().getPackageName();
    if (fieldPackage.isUnnamed()) {
      return builderPackge == null;
    } else {
      return fieldPackage.getQualifiedName().toString().equals(builderPackge);
    }
  }

  /**
   * Returns true if the given element is marked with a 'static' modifier.
   *
   * @param el the element
   * @return true if the given element is marked with a 'static' modifier
   */
  public boolean isStatic(Element el) {
    return el.getModifiers().contains(STATIC);
  }

  /**
   * Returns true if the given element is a Setter-method.
   *
   * @param el the element
   * @return true if the given element is a Setter-method
   */
  public boolean isSetterMethod(ExecutableElement el) {
    String methodName = el.getSimpleName().toString();
    TypeMirror retType = el.getReturnType();
    return methodName.startsWith(SET) && methodName.length() > SET.length()
        && retType.getKind() == VOID && el.getParameters().size() == 1;
  }

  /**
   * Returns true if the given element is a Getter-method.
   *
   * @param el the element
   * @return true if the given element is a Getter-method
   */
  public boolean isGetterMethod(ExecutableElement el) {
    String methodName = el.getSimpleName().toString();
    TypeMirror retType = el.getReturnType();
    return ((methodName.startsWith(GET) && methodName.length() > GET.length()) || (methodName
        .startsWith(IS) && methodName.length() > IS.length()))
        && retType.getKind() != VOID
        && el.getParameters().size() == 0;
  }

  /**
   * Returns whether the given element is directly declared in {@link Object}.
   *
   * @param el the element
   * @return true if the element is declared in {@link Object}
   */
  public boolean isDeclaredInObject(Element el) {
    Element ownerEl = el.getEnclosingElement();
    if (ownerEl.getKind() == CLASS) {
      TypeElement typeEl = (TypeElement) ownerEl;
      return typeEl.getQualifiedName().toString().equals(Object.class.getName());
    }
    return false;
  }

  /**
   * Returns the name of the property that is accessed by the given [G|S]etter method.
   *
   * @param methodEl the method element
   * @return the name of the property
   */
  public String getPropertyName(ExecutableElement methodEl) {
    String name = methodEl.getSimpleName().toString();
    int prefixLength = -1;
    if (name.startsWith(SET)) {
      prefixLength = SET.length();
    } else if (name.startsWith(GET)) {
      prefixLength = GET.length();
    } else if (name.startsWith(IS)) {
      prefixLength = IS.length();
    }

    if (prefixLength > 0) {
      name = name.substring(prefixLength);
      name = firstCharToLowerCase(name);
      return name;
    } else {
      throw new IllegalArgumentException(String.format("Not a setter or getter method name: %s!",
          name));
    }
  }

  /**
   * Returns a copy of the given text where the first character is a lower case letter.
   *
   * @param text the text
   * @return a copy of the text with first letter lower case
   */
  private String firstCharToLowerCase(String text) {
    char[] vals = text.toCharArray();
    vals[0] = Character.toLowerCase(vals[0]);
    return String.valueOf(vals);
  }

  /**
   * Returns the effective type of the given element when is is viewed as a member of the given
   * owner type.
   *
   * @param ownerType the owner type
   * @param element the element
   * @return the effective type of the given element
   */
  public TypeMirror getType(DeclaredType ownerType, Element element) {
    TypeMirror execType = element.asType();
    try {
      execType = types.asMemberOf(ownerType, element);
    } catch (IllegalArgumentException e) {
      // Ignore
    }
    return execType;
  }

  /**
   * Returns true, if the given type element has a method called "build" with no parameters and
   * which has an actual return type that is compatible with the given return type.
   *
   * @param typeElement the type element
   * @param requiredReturnType the required return type (maybe {@link NoType})
   * @return true, if the type element has a build method
   */
  public boolean hasBuildMethod(TypeElement typeElement, TypeMirror requiredReturnType) {
    return hasMethod(typeElement, BUILD_METHOD_NAME, requiredReturnType, null);
  }

  /**
   * Returns true, if the given type element has a method with the given name and has an actual
   * return type that is compatible with the given return type, and has an actual parameter that is
   * compatible with the given parameter type.
   *
   * @param typeElement the type element
   * @param name the required name of the method
   * @param requiredReturnType the required return type (maybe {@link NoType}).
   * @param requiredParamType the type of the required (first) parameter, or <code>null</code> if no
   *        parameter is required
   * @return true, if the type element has the required method
   */
  public boolean hasMethod(TypeElement typeElement, String name, TypeMirror requiredReturnType,
      TypeMirror requiredParamType) {
    List<? extends Element> memberEls = elements.getAllMembers(typeElement);
    List<ExecutableElement> methodEls = ElementFilter.methodsIn(memberEls);
    for (ExecutableElement methodEl : methodEls) {
      String actualName = methodEl.getSimpleName().toString();
      if (!actualName.equals(name)) {
        continue;
      }
      TypeMirror actualReturnType = methodEl.getReturnType();
      if (actualReturnType.getKind() == TypeKind.TYPEVAR) {
        TypeVariable tv = (TypeVariable) actualReturnType;
        actualReturnType = tv.getUpperBound();
      }
      if (requiredReturnType != null && !types.isSubtype(requiredReturnType, actualReturnType)) {
        continue;
      }
      if (requiredParamType == null && methodEl.getParameters().size() > 0) {
        continue;
      }
      if (requiredParamType != null) {
        if (methodEl.getParameters().size() != 1) {
          continue;
        }
        TypeMirror actParamType = methodEl.getParameters().get(0).asType();
        if (actParamType.getKind() == TypeKind.TYPEVAR) {
          TypeVariable tv = (TypeVariable) actualReturnType;
          actParamType = tv.getUpperBound();
        }
        if (!types.isSubtype(requiredParamType, actParamType)) {
          continue;
        }
      }
      return true;
    }
    return false;
  }

  /**
   * Returns true if the given type element defines a public no-args constructor.
   *
   * @param typeEl
   * @return true if the given type element defines a public no-args constructor
   */
  public boolean hasPublicNoArgsConstructor(TypeElement typeEl) {
    List<? extends Element> memberEls = elements.getAllMembers(typeEl);
    List<ExecutableElement> constrEls = ElementFilter.constructorsIn(memberEls);
    for (ExecutableElement constrEl : constrEls) {
      if (!constrEl.getModifiers().contains(Modifier.PUBLIC)) {
        continue;
      }
      if (!constrEl.getParameters().isEmpty()) {
        continue;
      }
      return true;
    }
    return false;
  }

  /**
   * Returns true if the given typeElement is a subtype of the given type parameter's upper bound.
   *
   * @param typeElement the type element
   * @param typeParamEl the type parameter element
   * @return true if the given typeElement is a subtype of the given type parameter's upper bound
   */
  public boolean matchesUpperBound(TypeElement typeElement, TypeParameterElement typeParamEl) {
    TypeMirror typeParam = typeParamEl.asType();
    if (typeParam.getKind() != TypeKind.TYPEVAR) {
      throw new RuntimeException(String.format("Unexpected kind of type parameter for %s: %s",
          typeParamEl.getSimpleName(), typeParam.getKind()));
    }
    TypeVariable tv = (TypeVariable) typeParam;
    return types.isSubtype(typeElement.asType(), tv.getUpperBound());
  }

  /**
   * Returns true if the given type parameter has an upper bound of type {@link Object}.
   *
   * @param typeParamEl the type parameter
   * @return true if the given type parameter has an upper bound of type {@link Object}
   */
  public boolean isUpperBoundToObject(TypeParameterElement typeParamEl) {
    TypeMirror typeParam = typeParamEl.asType();
    if (typeParam.getKind() != TypeKind.TYPEVAR) {
      throw new RuntimeException(String.format("Unexpected kind of type parameter for %s: %s",
          typeParamEl.getSimpleName(), typeParam.getKind()));
    }
    TypeVariable tv = (TypeVariable) typeParam;
    TypeElement objectElem = elements.getTypeElement(Object.class.getName());
    return types.isSameType(objectElem.asType(), tv.getUpperBound());
  }

  /**
   * Returns <code>true</code> if the given string is a valid Java identifier.
   *
   * @param string the string
   * @return <code>true</code> if the given string is a valid Java identifier
   */
  public boolean isValidJavaIdentifier(String string) {
    char[] chars = string.toCharArray();
    if (chars.length > 0 && !Character.isJavaIdentifierStart(chars[0])) {
      return false;
    }
    for (int i = 1; i < chars.length; ++i) {
      if (!Character.isJavaIdentifierPart(chars[i])) {
        return false;
      }
    }
    return true;
  }

  /**
   * Returns <code>true</code> if the given string is a valid Java package name.
   * <p>
   * This does not check if the package exists.
   *
   * @param string the string
   * @return <code>true</code> if the given string is a valid Java package name.
   */
  public boolean isValidJavaPackageName(String string) {
    String[] parts = string.split("\\.");
    for (String part : parts) {
      if (!isValidJavaIdentifier(part)) {
        return false;
      }
    }
    return true;
  }

  public Collection<? extends Element> findAnnotatedElements(Collection<TypeElement> typeElements,
      Class<?> annotationType) {
    List<Element> result = new ArrayList<Element>();
    for (Element unit : typeElements) {
      findAnnotatedElements(result, unit, annotationType);
    }
    return result;
  }

  private void findAnnotatedElements(List<Element> result, Element element, Class<?> annotationType) {
    switch (element.getKind()) {
      case CLASS:
        TypeElement typeEl = (TypeElement) element;
        for (AnnotationMirror anno : typeEl.getAnnotationMirrors()) {
          if (annotationType.getName().equals((getName(anno)))) {
            result.add(typeEl);
          }
        }
        List<? extends Element> enclosedElems = typeEl.getEnclosedElements();
        for (Element el : enclosedElems) {
          findAnnotatedElements(result, el, annotationType);
        }
        break;
      case CONSTRUCTOR:
      case METHOD:
        ExecutableElement exeEl = (ExecutableElement) element;
        for (AnnotationMirror anno : exeEl.getAnnotationMirrors()) {
          if (annotationType.getName().equals(getName(anno))) {
            result.add(exeEl);
          }
        }
        break;
      default:
        break;
    }
  }

  private String getName(AnnotationMirror anno) {
    TypeElement el = (TypeElement) anno.getAnnotationType().asElement();
    return el.getQualifiedName().toString();
  }



}
TOP

Related Classes of net.karneim.pojobuilder.analysis.JavaModelAnalyzerUtil

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.