Package org.ftlines.metagen.eclipse

Source Code of org.ftlines.metagen.eclipse.BeanUnit$Visitor

package org.ftlines.metagen.eclipse;

import java.lang.reflect.Modifier;
import java.util.Stack;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.ftlines.metagen.eclipse.model.Bean;
import org.ftlines.metagen.eclipse.model.BeanContainer;
import org.ftlines.metagen.eclipse.model.Constants;
import org.ftlines.metagen.eclipse.model.ErrorCollector;
import org.ftlines.metagen.eclipse.model.Property;
import org.ftlines.metagen.eclipse.model.Visibility;

/**
* Metagen's view of a ICompilationUnit
*
* @author igor
*
*/
public class BeanUnit extends BeanContainer {

  private final ICompilationUnit compilationUnit;

  public BeanUnit(ICompilationUnit compilationUnit) {
    this.compilationUnit = compilationUnit;
  }

  @Override
  public String toString() {
    return "[BeanUnit cu=" + compilationUnit.toString() + "]";
  }

  public ICompilationUnit getCompilationUnit() {
    return compilationUnit;
  }

  /**
   * Checks if the type will generate a meta in the most efficient manner. This method should return {@code true} unless it is absolutely
   * sure this compilation unit will not generate meta.
   *
   * @return
   * @throws JavaModelException
   */
  public boolean canPossiblyGenerateMeta() throws JavaModelException {
    FqnResolver resolver = new FqnResolver(compilationUnit);
    for (IType type : compilationUnit.getAllTypes()) {

      boolean skip = false;

      for (IAnnotation annotation : type.getAnnotations()) {
        if (resolver.is(annotation, Constants.GENERATED)) {
          // we know generated types will not produce meta
          return false;
        }
        if (resolver.is(annotation, Constants.IGNORE)) {
          skip = true;
          break;
        }
        if (resolver.isAny(annotation, Constants.META, Constants.BEAN, Constants.ENTITY, Constants.MAPPEDSUPERCLASS)) {
          return true;
        } else if (resolver.is(annotation, Constants.ENTITY)) {
          return true;
        }
      }

      if (skip) {
        continue;
      }

      for (IField field : type.getFields()) {
        for (IAnnotation annotation : field.getAnnotations()) {
          if (resolver.is(annotation, Constants.PROPERTY)) {
            return true;
          }
        }
      }

      for (IMethod method : type.getMethods()) {
        for (IAnnotation annotation : method.getAnnotations()) {
          if (resolver.is(annotation, Constants.PROPERTY)) {
            return true;
          }
        }
      }
    }
    return false;
  }

  public void discover(ASTNode ast, ErrorCollector errors) {
    ast.accept(new Visitor(errors));
  }

  public class Visitor extends ASTVisitor {
    private final Stack<Bean> beans = new Stack<Bean>();
    private final ErrorCollector errors;

    private Visitor(ErrorCollector errors) {
      this.errors = errors;
    }

    private void visitBean(ITypeBinding type) {

      Bean bean = new Bean(getVisibility(type.getModifiers()), type.getQualifiedName());
      beans.push(bean);
      if (!populateBean(type, bean)) {
        return;
      }

      // check if we need to extend a super meta class
      ITypeBinding cursor = type.getSuperclass();
      while (cursor != null) {
        Bean superbean = new Bean(getVisibility(cursor.getModifiers()), cursor.getQualifiedName());
        if (!populateBean(cursor, superbean)) {
          break;
        }
        if (superbean.willGenerateMeta()) {
          String supername = getMetaSuperName(cursor);
          bean.setSuperclass(supername);
          break;
        }
        cursor = cursor.getSuperclass();
      }
    }

    /**
     * Build the name of the meta superclass. For example, given <code>B</code> in <code>foo.bar.A.B</code> it will return
     * <code>foo.bar.AMeta.BMeta</code>
     *
     * @param type
     * @return
     */
    private String getMetaSuperName(ITypeBinding type) {
      String supername = "";
      ITypeBinding cursor = type;
      while (cursor != null) {
        supername = "." + cursor.getErasure().getName() + "Meta" + supername;
        cursor = cursor.getDeclaringClass();
      }
      supername = type.getPackage().getName() + supername;
      return supername;
    }

    private boolean populateBean(ITypeBinding type, Bean bean) {
      boolean autobean = false;

      // scan type annotations

      for (IAnnotationBinding annotation : type.getAnnotations()) {
        String name = annotation.getAnnotationType().getQualifiedName();
        if (Constants.META.equals(name)) {
          bean.setForced(true);
        } else if (Constants.BEAN.equals(name)) {
          autobean = true;
        } else if (Constants.ENTITY.equals(name)) {
          autobean = true;
        } else if (Constants.MAPPEDSUPERCLASS.equals(name)) {
          autobean = true;
        } else if (Constants.GENERATED.equals(name)) {
          // this is a generated class, we dont need to process it for metagen annotations
          return false;
        } else if (Constants.IGNORE.equals(name)) {
          // this class is marked as ignored, skip it
          return false;
        }
      }

      // discover fields that can be properties

      for (IVariableBinding field : type.getDeclaredFields()) {
        boolean property = false;
        boolean deprecated = false;
        if (autobean && getVisibility(field.getModifiers()) == Visibility.PUBLIC) {
          property = true;
        } else {
          for (IAnnotationBinding annotation : field.getAnnotations()) {
            String name = annotation.getAnnotationType().getQualifiedName();
            if (Constants.PROPERTY.equals(name)) {
              if ((field.getModifiers() & Modifier.PRIVATE) > 0) {
                errors.error(compilationUnit.getResource(), 1, "Private fields (" + field.getName()
                    + ") do not support @Property annotations");
              } else {
                property = true;
              }
            } else if (Constants.DEPRECATED.equals(name)) {
              deprecated = true;
            }
          }
        }

        if (property) {
          ITypeBinding fieldType = field.getType();
          String fieldTypeName = new TypeResolver(fieldType).resolve();
          Property prop = new Property(getVisibility(field.getModifiers()), field.getDeclaringClass().getQualifiedName(),
              fieldTypeName, field.getName());
          prop.setDeprecated(deprecated);
          prop.setFieldName(field.getName());
          bean.addProperty(prop);
        }
      }

      // discover getters that can be properties

      for (IMethodBinding method : type.getDeclaredMethods()) {

        String name = method.getName();

        // we are only interested in getters

        if (method.getParameterTypes().length > 0) {
          continue;
        }

        boolean set = name.length() > 3 && name.startsWith("get") && Character.isUpperCase(name.charAt(3));
        boolean is = name.length() > 2 && name.startsWith("is") && Character.isUpperCase(name.charAt(2));

        if (!set && !is) {
          continue;
        }

        name = set ? Character.toLowerCase(name.charAt(3)) + name.substring(4) : Character.toLowerCase(name.charAt(2))
            + name.substring(3);

        // scan annotations of the method

        boolean property = false;
        boolean deprecated = false;
        for (IAnnotationBinding annotation : method.getAnnotations()) {
          String annot = annotation.getAnnotationType().getQualifiedName();
          if (Constants.PROPERTY.equals(annot)) {
            property = true;
          } else if (Constants.DEPRECATED.equals(annot)) {
            deprecated = true;
          }
        }

        if (autobean || property) {

          // add a property for this method

          Property prop = bean.getProperty(name);
          ITypeBinding returnType = method.getReturnType();
          String typeName = new TypeResolver(returnType).resolve();
          if (prop == null) {
            prop = new Property(getVisibility(method.getModifiers()), method.getDeclaringClass().getQualifiedName(), typeName,
                name);
            bean.addProperty(prop);
          } else {
            prop.relaxVisibility(getVisibility(method.getModifiers()));
            // prefer type of the getter
            prop.setType(typeName);
          }
          prop.setDeprecated(deprecated);
          prop.setGetterName(method.getName());

        }
      }

      // discover property setters

      for (IMethodBinding method : type.getDeclaredMethods()) {

        String name = method.getName();

        // we are only interested in setters

        if (!(name.length() > 3 && name.startsWith("set") && Character.isUpperCase(name.charAt(3)))) {
          continue;
        }

        if (method.getParameterTypes().length != 1) {
          continue;
        }

        name = Character.toLowerCase(name.charAt(3)) + name.substring(4);
        Property property = bean.getProperty(name);
        if (property != null) {

          String typeName = new TypeResolver(method.getParameterTypes()[0]).resolve();
          if (typeName.equals(property.getType())) {
            property.setSetterName(method.getName());
            property.relaxVisibility(getVisibility(method.getModifiers()));
          }
        }
      }

      return true;
    }

    private void endVisitBean() {
      Bean bean = beans.pop();

      if (bean.getVisibility() == Visibility.PRIVATE) {
        return;
      }

      // TODO this check should be moved into Bean - Bean.isEmpty()
      if (bean.willGenerateMeta()) {
        if (beans.size() > 0) {
          beans.peek().add(bean);
        } else {
          BeanUnit.this.add(bean);
        }
      }
    }

    @Override
    public boolean visit(TypeDeclaration node) {
      visitBean(node.resolveBinding());
      return true;
    }

    @Override
    public void endVisit(TypeDeclaration node) {
      endVisitBean();
    }

    @Override
    public boolean visit(EnumDeclaration node) {
      visitBean(node.resolveBinding());
      return true;
    }

    @Override
    public void endVisit(EnumDeclaration node) {
      endVisitBean();
    }

  }

  public boolean isEmpty() {
    return getBeans() == null || getBeans().isEmpty();
  }

  public String generate(ICompilationUnit compilationUnit, IFile file) throws CoreException {

    StringBuilder source = new StringBuilder();

    String pack = compilationUnit.getParent().getElementName();
    if (pack != null && pack.length() > 0) {
      source.append("package " + pack + ";\n\n");
    }

    for (Bean bean : getBeans()) {
      generate(bean, source, false);
    }

    return source.toString();
  }

  public void generate(Bean bean, StringBuilder source, boolean nested) {
    source.append("@SuppressWarnings(\"all\")\n");
    source.append("@javax.annotation.Generated(\"metagen\")\n");
    source.append(bean.getVisibility().getTerm());
    if (nested) {
      source.append(" static ");
    }
    source.append(" class " + bean.getSimpleName() + "Meta ");
    if (bean.getSuperclass() != null) {
      source.append(" extends " + bean.getSuperclass());
    }
    source.append("{\n");

    source.append("public static class C {\n");
    source.append("public static final String name=\"" + bean.getName() + "\";\n");
    source.append("public static final String simpleName=\"" + bean.getSimpleName() + "\";\n");
    source.append("}\n");

    if (bean.getProperties() != null) {

      for (Property property : bean.getProperties().values()) {

        if (property.isDeprecated()) {
          source.append("@Deprecated ");
        }

        source.append(property.getVisibility().getTerm());
        source.append(" static final ").append(Constants.SINGULAR_PROPERTY);
        source.append("<").append(bean.getName()).append(", ").append(property.getBoxedType()).append("> ");
        source.append(property.getSafeName()).append(" = new ").append(Constants.SINGULAR_PROPERTY);
        // source.append("<").append(bean.getName()).append(", ").append(property.getBoxedType()).append(">");
        source.append("(\"").append(property.getName()).append("\", " + property.getOwner() + ".class");

        source.append(", ");
        if (property.getFieldName() != null) {
          source.append("\"").append(property.getName()).append("\"");
        } else {
          source.append("null");
        }

        source.append(", ");
        if (property.getGetterName() != null) {
          source.append("\"").append(property.getGetterName()).append("\"");
        } else {
          source.append("null");
        }

        source.append(", ");
        if (property.getSetterName() != null) {
          source.append("\"").append(property.getSetterName()).append("\"");
        } else {
          source.append("null");
        }

        source.append(");\n");

      }

      source.append("public static class P {\n");
      for (Property property : bean.getProperties().values()) {
        source.append("public static final String " + property.getSafeName() + "=\"" + property.getName() + "\";\n");
      }
      source.append("}\n");
    }

    if (bean.getBeans() != null) {
      for (Bean inner : bean.getBeans()) {
        generate(inner, source, true);
      }
    }

    source.append("}");

  }

  private static Visibility getVisibility(int modifiers) {
    if ((modifiers & Modifier.PUBLIC) > 0) {
      return Visibility.PUBLIC;
    } else if ((modifiers & Modifier.PROTECTED) > 0) {
      return Visibility.PROTECTED;
    } else if ((modifiers & Modifier.PRIVATE) > 0) {
      return Visibility.PRIVATE;
    } else {
      return Visibility.DEFAULT;
    }
  }

}
TOP

Related Classes of org.ftlines.metagen.eclipse.BeanUnit$Visitor

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.