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

io.ebean.enhance.common.ClassMeta Maven / Gradle / Ivy

package io.ebean.enhance.common;

import io.ebean.enhance.asm.AnnotationVisitor;
import io.ebean.enhance.asm.ClassVisitor;
import io.ebean.enhance.asm.FieldVisitor;
import io.ebean.enhance.asm.MethodVisitor;
import io.ebean.enhance.entity.*;

import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import static io.ebean.enhance.Transformer.EBEAN_ASM_VERSION;
import static io.ebean.enhance.common.EnhanceConstants.*;

/**
 * Holds the meta-data for an entity bean class that is being enhanced.
 */
public class ClassMeta {

  private static final Logger logger = Logger.getLogger(ClassMeta.class.getName());

  private final MessageOutput logout;
  private final int logLevel;
  private String className;
  private String superClassName;
  private ClassMeta superMeta;
  /**
   * Set to true if the class implements th GroovyObject interface.
   */
  private boolean hasGroovyInterface;
  /**
   * Set to true if the class implements the ScalaObject interface.
   */
  private boolean hasScalaInterface;
  /**
   * Set to true if the class already implements the EntityBean interface.
   */
  private boolean hasEntityBeanInterface;
  private boolean alreadyEnhanced;
  private boolean hasEqualsOrHashcode;
  private boolean hasToString;
  private boolean hasDefaultConstructor;
  private boolean hasStaticInit;

  /**
   * If enhancement is adding a default constructor - only single type is supported initialising transient fields.
   */
  private final Set unsupportedTransientMultipleTypes = new LinkedHashSet<>();
  /**
   * If enhancement is adding a default constructor - only default constructors are supported initialising transient fields.
   */
  private final Set unsupportedTransientInitialisation = new LinkedHashSet<>();
  private final Map transientInitCode = new LinkedHashMap<>();
  private final LinkedHashMap fields = new LinkedHashMap<>();
  private final HashSet classAnnotation = new HashSet<>();
  private final AnnotationInfo annotationInfo = new AnnotationInfo(null);
  private final ArrayList methodMetaList = new ArrayList<>();
  private final EnhanceContext enhanceContext;
  private List allFields;
  private boolean recordType;

  public ClassMeta(EnhanceContext enhanceContext, int logLevel, MessageOutput logout) {
    this.enhanceContext = enhanceContext;
    this.logLevel = logLevel;
    this.logout = logout;
  }

  /**
   * Return the enhance context which has options for enhancement.
   */
  public EnhanceContext context() {
    return enhanceContext;
  }

  /**
   * Return the AnnotationInfo collected on methods.
   * Used to determine Transactional method enhancement.
   */
  public AnnotationInfo annotationInfo() {
    return annotationInfo;
  }

  public boolean isAllowNullableDbArray() {
    return enhanceContext.isAllowNullableDbArray();
  }

  /**
   * Return the transactional annotation information for a matching interface method.
   */
  public AnnotationInfo interfaceTransactionalInfo(String methodName, String methodDesc) {
    AnnotationInfo annotationInfo = null;
    for (int i = 0; i < methodMetaList.size(); i++) {
      MethodMeta meta = methodMetaList.get(i);
      if (meta.isMatch(methodName, methodDesc)) {
        if (annotationInfo != null) {
          String msg = "Error in [" + className + "] searching the transactional methods[" + methodMetaList
            + "] found more than one match for the transactional method:" + methodName + " "
            + methodDesc;

          logger.log(Level.SEVERE, msg);
          log(msg);
        } else {
          annotationInfo = meta.getAnnotationInfo();
          if (isLog(9)) {
            log("... found transactional info from interface " + className + " " + methodName + " " + methodDesc);
          }
        }
      }
    }
    return annotationInfo;
  }

  public boolean isCheckSuperClassForEntity() {
    return !superClassName.equals(C_OBJECT) && isCheckEntity();
  }

  @Override
  public String toString() {
    return className;
  }

  public boolean isTransactional() {
    return classAnnotation.contains(TRANSACTIONAL_ANNOTATION);
  }

  public void setClassName(String className, String superClassName) {
    this.className = className;
    this.superClassName = superClassName;
    if (superClassName.equals(C_RECORDTYPE)) {
      recordType = true;
    }
  }

  public String superClassName() {
    return superClassName;
  }

  public boolean isLog(int level) {
    return level <= logLevel;
  }

  public void log(String msg) {
    if (className != null) {
      msg = "cls: " + className + "  msg: " + msg;
    }
    logout.println("ebean-enhance> " + msg);
  }

  public void logEnhanced() {
    String m = "enhanced ";
    if (hasScalaInterface()) {
      m += " (scala)";
    }
    if (hasGroovyInterface()) {
      m += " (groovy)";
    }
    log(m);
  }

  public void setSuperMeta(ClassMeta superMeta) {
    this.superMeta = superMeta;
  }

  /**
   * Set to true if the class has an existing equals() or hashcode() method.
   */
  public void setHasEqualsOrHashcode(boolean hasEqualsOrHashcode) {
    this.hasEqualsOrHashcode = hasEqualsOrHashcode;
  }

  public void setHasToString() {
    this.hasToString = true;
  }

  /**
   * Return true if Equals/hashCode is implemented on this class or a super class.
   */
  public boolean hasEqualsOrHashCode() {
    if (hasEqualsOrHashcode) {
      return true;
    } else {
      return (superMeta != null && superMeta.hasEqualsOrHashCode());
    }
  }

  public boolean hasToString() {
    if (hasToString) {
      return true;
    } else {
      return (superMeta != null && superMeta.hasToString());
    }
  }

  /**
   * Return true if the field is a persistent field.
   */
  public boolean isFieldPersistent(String fieldName) {
    FieldMeta f = field(fieldName);
    return (f != null) && f.isPersistent();
  }

  public boolean isTransient(String fieldName) {
    FieldMeta f = field(fieldName);
    return (f != null && f.isTransient());
  }

  public boolean isInitTransient(String fieldName) {
    if (!enhanceContext.isTransientInit()) {
      return false;
    }
    return isTransient(fieldName);
  }

  /**
   * Return true if the field is a persistent many field that we want to consume the init on.
   */
  public boolean isConsumeInitMany(String fieldName) {
    FieldMeta f = field(fieldName);
    return (f != null && f.isPersistent() && f.isInitMany());
  }

  /**
   * Return the field - null when not found.
   */
  public FieldMeta field(String fieldName) {
    FieldMeta f = fields.get(fieldName);
    if (f != null) {
      return f;
    }
    return (superMeta == null) ? null : superMeta.field(fieldName);
  }

  /**
   * Return the list of fields local to this type (not inherited).
   */
  private List localFields() {
    List list = new ArrayList<>();
    for (FieldMeta fm : fields.values()) {
      if (!fm.isObjectArray()) {
        // add field local to this entity type
        list.add(fm);
      }
    }
    return list;
  }

  /**
   * Return the list of fields inherited from super types that are entities.
   */
  private void addInheritedFields(List list) {
    if (superMeta != null) {
      superMeta.addFieldsForInheritance(list);
    }
  }

  /**
   * Add all fields to the list.
   */
  private void addFieldsForInheritance(List list) {
    if (isEntity()) {
      list.addAll(0, fields.values());
      if (superMeta != null) {
        superMeta.addFieldsForInheritance(list);
      }
    }
  }

  /**
   * Return true if the class contains persistent fields.
   */
  public boolean hasPersistentFields() {
    for (FieldMeta fieldMeta : fields.values()) {
      if (fieldMeta.isPersistent() || fieldMeta.isTransient()) {
        return true;
      }
    }
    return superMeta != null && superMeta.hasPersistentFields();
  }

  /**
   * Return the list of all fields including ones inherited from entity super
   * types and mappedSuperclasses.
   */
  public List allFields() {
    if (allFields != null) {
      return allFields;
    }
    List list = localFields();
    addInheritedFields(list);

    this.allFields = list;
    for (int i = 0; i < allFields.size(); i++) {
      allFields.get(i).setIndexPosition(i);
    }
    return list;
  }

  /**
   * Add field level get set methods for each field.
   */
  public void addFieldGetSetMethods(ClassVisitor cv) {
    if (isEntityEnhancementRequired()) {
      for (FieldMeta fm : fields.values()) {
        fm.addGetSetMethods(cv, this);
      }
    }
  }

  /**
   * Return true if this is a mapped superclass.
   */
  boolean isMappedSuper() {
    return classAnnotation.contains(Javax.MappedSuperclass) || classAnnotation.contains(Jakarta.MappedSuperclass);
  }

  /**
   * Return true if this is a query bean.
   */
  public boolean isQueryBean() {
    return classAnnotation.contains(TYPEQUERYBEAN_ANNOTATION);
  }

  /**
   * Return true if the class has an Entity, Embeddable or MappedSuperclass.
   */
  public boolean isEntity() {
    return EntityCheck.hasEntityAnnotation(classAnnotation);
  }

  /**
   * Return true if the class has an Entity, Embeddable, or MappedSuperclass.
   */
  private boolean isCheckEntity() {
    return EntityCheck.hasEntityAnnotation(classAnnotation);
  }

  /**
   * Return true for classes not already enhanced and yet annotated with entity, embeddable or mappedSuperclass.
   */
  public boolean isEntityEnhancementRequired() {
    return !alreadyEnhanced && isEntity();
  }

  /**
   * Return true if the bean is already enhanced.
   */
  public boolean isAlreadyEnhanced() {
    return alreadyEnhanced;
  }

  /**
   * Return the className of this entity class.
   */
  public String className() {
    return className;
  }

  /**
   * Return true if this entity bean has a super class that is an entity.
   */
  public boolean isSuperClassEntity() {
    return superMeta != null && superMeta.isEntity();
  }

  /**
   * Add a class annotation.
   */
  public void addClassAnnotation(String desc) {
    classAnnotation.add(desc);
  }

  MethodVisitor createMethodVisitor(MethodVisitor mv, String name, String desc) {
    MethodMeta methodMeta = new MethodMeta(annotationInfo, name, desc);
    methodMetaList.add(methodMeta);
    return new MethodReader(mv, methodMeta);
  }

  /**
   * ACC_PUBLIC with maybe ACC_SYNTHETIC.
   */
  public int accPublic() {
    return enhanceContext.accPublic();
  }

  /**
   * ACC_PROTECTED with maybe ACC_SYNTHETIC.
   */
  public int accProtected() {
    return enhanceContext.accProtected();
  }

  /**
   * If field access use public rather than protected plus usually with synthetic.
   */
  public int accAccessor() {
    return enhanceContext.isEnableEntityFieldAccess() ? accPublic() : accProtected();
  }

  /**
   * ACC_PRIVATE with maybe ACC_SYNTHETIC.
   */
  public int accPrivate() {
    return enhanceContext.accPrivate();
  }

  public boolean isToManyGetField() {
    return enhanceContext.isToManyGetField();
  }

  /**
   * Return the EntityBeanIntercept type that will be new'ed up for the EntityBean.
   * For version 140+ EntityBeanIntercept is an interface and instead we new up InterceptReadWrite.
   */
  public String interceptNew() {
    return enhanceContext.interceptNew();
  }

  /**
   * Invoke a method on EntityBeanIntercept.
   * For version 140+ EntityBeanIntercept is an interface and this uses INVOKEINTERFACE.
   */
  public void visitMethodInsnIntercept(MethodVisitor mv, String name, String desc) {
    enhanceContext.visitMethodInsnIntercept(mv, name, desc);
  }

  /**
   * If 141+ Add InterceptReadOnly support.
   */
  public boolean interceptAddReadOnly() {
    return enhanceContext.interceptAddReadOnly();
  }

  public boolean isRecordType() {
    return recordType;
  }

  public void addTransientInit(CapturedInitCode deferredInitCode) {
    CapturedInitCode old = transientInitCode.put(deferredInitCode.name(), deferredInitCode);
    if (old != null && !old.type().equals(deferredInitCode.type())) {
      transientInitCode.put(deferredInitCode.name(), old);
      unsupportedTransientMultipleTypes.add("field: " + old.name() + " types: " + old.type() + " " + deferredInitCode.type());
    }
  }

  public Collection transientInit() {
    return transientInitCode.values();
  }

  public void addUnsupportedTransientInit(String name) {
    unsupportedTransientInitialisation.add(name);
  }

  public boolean hasTransientFieldErrors() {
    return !unsupportedTransientMultipleTypes.isEmpty() || !unsupportedTransientInitialisation.isEmpty();
  }

  public String transientFieldErrorMessage() {
    String msg = "ERROR: Entity class without default constructor has unsupported initialisation of transient fields. Entity class: " + className;
    if (!unsupportedTransientMultipleTypes.isEmpty()) {
      msg += " - fields initialised in constructor with 2 different types - " + unsupportedTransientMultipleTypes;
    }
    if (!unsupportedTransientInitialisation.isEmpty()) {
      msg += " - Unsupported initialisation of transient fields - " + unsupportedTransientInitialisation;
    }
    msg += " Refer: https://ebean.io/docs/trouble-shooting#transient-initialisation";
    return msg;
  }

  private static final class MethodReader extends MethodVisitor {

    final MethodMeta methodMeta;

    MethodReader(MethodVisitor mv, MethodMeta methodMeta) {
      super(EBEAN_ASM_VERSION, mv);
      this.methodMeta = methodMeta;
    }

    @Override
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
      AnnotationVisitor av = null;
      if (mv != null) {
        av = mv.visitAnnotation(desc, visible);
      }
      if (!isInterestingAnnotation(desc)) {
        return av;
      }
      return new AnnotationInfoVisitor(null, methodMeta.getAnnotationInfo(), av);
    }

    private boolean isInterestingAnnotation(String desc) {
      return TRANSACTIONAL_ANNOTATION.equals(desc)
        || TYPEQUERYBEAN_ANNOTATION.equals(desc);
    }
  }

  /**
   * Create and return a read only fieldVisitor for subclassing option.
   */
  FieldVisitor createLocalFieldVisitor(String name, String desc) {
    return createLocalFieldVisitor(null, name, desc);
  }

  /**
   * Create and return a new fieldVisitor for use when enhancing a class.
   */
  public FieldVisitor createLocalFieldVisitor(FieldVisitor fv, String name, String desc) {
    FieldMeta fieldMeta = new FieldMeta(this, name, desc, className);
    LocalFieldVisitor localField = new LocalFieldVisitor(fv, fieldMeta);
    if (name.startsWith("_ebean")) {
      // can occur when reading inheritance information on
      // a entity that has already been enhanced
      if (isLog(5)) {
        log("... ignore field " + name);
      }
    } else {
      fields.put(localField.name(), fieldMeta);
    }
    return localField;
  }

  public void setAlreadyEnhanced(boolean alreadyEnhanced) {
    this.alreadyEnhanced = alreadyEnhanced;
  }

  public boolean hasDefaultConstructor() {
    return hasDefaultConstructor;
  }

  public void setHasDefaultConstructor(boolean hasDefaultConstructor) {
    this.hasDefaultConstructor = hasDefaultConstructor;
  }

  public void setHasStaticInit(boolean hasStaticInit) {
    this.hasStaticInit = hasStaticInit;
  }

  public boolean hasStaticInit() {
    return hasStaticInit;
  }

  public String description() {
    StringBuilder sb = new StringBuilder();
    appendDescription(sb);
    return sb.toString();
  }

  private void appendDescription(StringBuilder sb) {
    sb.append(className);
    if (superMeta != null) {
      sb.append(" : ");
      superMeta.appendDescription(sb);
    }
  }

  private boolean hasScalaInterface() {
    return hasScalaInterface;
  }

  public void setScalaInterface(boolean hasScalaInterface) {
    this.hasScalaInterface = hasScalaInterface;
  }

  public boolean hasEntityBeanInterface() {
    return hasEntityBeanInterface;
  }

  public void setEntityBeanInterface(boolean hasEntityBeanInterface) {
    this.hasEntityBeanInterface = hasEntityBeanInterface;
  }

  private boolean hasGroovyInterface() {
    return hasGroovyInterface;
  }

  public void setGroovyInterface(boolean hasGroovyInterface) {
    this.hasGroovyInterface = hasGroovyInterface;
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy