package com.avaje.ebean.enhance.agent; import java.io.PrintStream; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import com.avaje.ebean.enhance.asm.AnnotationVisitor; import com.avaje.ebean.enhance.asm.ClassVisitor; import com.avaje.ebean.enhance.asm.FieldVisitor; import com.avaje.ebean.enhance.asm.MethodVisitor; import com.avaje.ebean.enhance.asm.Opcodes; /** * 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 static final String OBJECT_CLASS = Object.class.getName().replace('.', '/'); private final PrintStream 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 hasDefaultConstructor; private HashSet<String> existingMethods = new HashSet<String>(); private HashSet<String> existingSuperMethods = new HashSet<String>(); private LinkedHashMap<String, FieldMeta> fields = new LinkedHashMap<String, FieldMeta>(); private HashSet<String> classAnnotation = new HashSet<String>(); private AnnotationInfo annotationInfo = new AnnotationInfo(null); private ArrayList<MethodMeta> methodMetaList = new ArrayList<MethodMeta>(); private final EnhanceContext enhanceContext; public ClassMeta(EnhanceContext enhanceContext, int logLevel, PrintStream logout) { this.enhanceContext = enhanceContext; this.logLevel = logLevel; this.logout = logout; } /** * Return the enhance context which has options for enhancement. */ public EnhanceContext getEnhanceContext() { return enhanceContext; } /** * Return the class level annotations. */ public Set<String> getClassAnnotations() { return classAnnotation; } /** * Return the AnnotationInfo collected on methods. * Used to determine Transactional method enhancement. */ public AnnotationInfo getAnnotationInfo() { return annotationInfo; } /** * Return the transactional annotation information for a matching interface method. */ public AnnotationInfo getInterfaceTransactionalInfo(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() { if (isEntity()) { return !superClassName.equals(OBJECT_CLASS); } return false; } public String toString() { return className; } public boolean isTransactional() { if (classAnnotation.contains(EnhanceConstants.AVAJE_TRANSACTIONAL_ANNOTATION)) { return true; } return false; } public ArrayList<MethodMeta> getMethodMeta() { return methodMetaList; } public void setClassName(String className, String superClassName) { this.className = className; this.superClassName = superClassName; } public String getSuperClassName() { 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("transform> " + msg); } public void logEnhanced() { String m = "enhanced "; if (hasScalaInterface()){ m += " (scala)"; } if (hasGroovyInterface()){ m += " (groovy)"; } log(m); } /** * Set to true if the class has an existing equals() or hashcode() method. */ public void setHasEqualsOrHashcode(boolean hasEqualsOrHashcode) { this.hasEqualsOrHashcode = hasEqualsOrHashcode; } public boolean hasEqualsOrHashCode() { return hasEqualsOrHashcode; } /** * Return true if the field is a persistent field. */ public boolean isFieldPersistent(String fieldName) { FieldMeta f = fields.get(fieldName); if (f != null) { return f.isPersistent(); } if (superMeta == null) { // the field is unknown? return false; } else { // look in the inheritance hierarchy return superMeta.isFieldPersistent(fieldName); } } public void setSuperMeta(ClassMeta superMeta) { this.superMeta = superMeta; } /** * Return the list of fields local to this type (not inherited). */ public List<FieldMeta> getLocalFields() { ArrayList<FieldMeta> list = new ArrayList<FieldMeta>(); Iterator<FieldMeta> it = fields.values().iterator(); while (it.hasNext()) { FieldMeta fm = it.next(); if (!fm.isObjectArray()) { // add field local to this entity type list.add(fm); } } return list; } /** * Return a List of inherited fields. These are fields from inherited objects * via MappedSuperClass or Entity inheritance. */ public List<FieldMeta> getInheritedFields() { return getInheritedFields(new ArrayList<FieldMeta>()); } /** * Return the list of fields inherited from super types that are entities. */ private List<FieldMeta> getInheritedFields(List<FieldMeta> list) { if (list == null){ list = new ArrayList<FieldMeta>(); } if (superMeta != null) { superMeta.addFieldsForInheritance(list); } return list; } /** * Add all fields to the list. */ private void addFieldsForInheritance(List<FieldMeta> list) { if (isEntity()) { list.addAll(0, fields.values()); if (superMeta != null) { superMeta.addFieldsForInheritance(list); } } } /** * Return the list of all fields including ones inherited from entity super * types and mappedSuperclasses. */ public List<FieldMeta> getAllFields() { List<FieldMeta> list = getLocalFields(); getInheritedFields(list); return list; } /** * Add field level get set methods for each field. */ public void addFieldGetSetMethods(ClassVisitor cv) { if (isEntityEnhancementRequired()) { Iterator<FieldMeta> it = fields.values().iterator(); while (it.hasNext()) { FieldMeta fm = it.next(); fm.addGetSetMethods(cv, this); } } } /** * Return true if the class has an Entity, Embeddable, MappedSuperclass or LdapDomain annotation. */ public boolean isEntity() { if (classAnnotation.contains(EnhanceConstants.ENTITY_ANNOTATION)) { return true; } if (classAnnotation.contains(EnhanceConstants.EMBEDDABLE_ANNOTATION)) { return true; } if (classAnnotation.contains(EnhanceConstants.MAPPEDSUPERCLASS_ANNOTATION)) { return true; } if (classAnnotation.contains(EnhanceConstants.LDAPDOMAIN_ANNOTATION)) { return true; } return false; } /** * Return true for classes not already enhanced and yet annotated with entity, embeddable or mappedSuperclass. */ public boolean isEntityEnhancementRequired() { if (alreadyEnhanced) { return false; } if (isEntity()){ return true; } return false; } /** * Return the className of this entity class. */ public String getClassName() { return className; } /** * Return true if this entity bean has a super class that is an entity. */ public boolean isSuperClassEntity() { if (superMeta == null) { return false; } else { return superMeta.isEntity(); } } /** * Add a class annotation. */ public void addClassAnnotation(String desc) { classAnnotation.add(desc); } /** * Only for subclassing, add known methods on the original entity class. * <p> * Used to check that the methods exist. They may not in special cases such * as entity beans that use a finder etc with read only properties. * </p> */ public void addExistingSuperMethod(String methodName, String methodDesc) { existingSuperMethods.add(methodName + methodDesc); } /** * Add an existing method. */ public void addExistingMethod(String methodName, String methodDesc) { existingMethods.add(methodName + methodDesc); } /** * Return true if the method already exists on the bean. */ public boolean isExistingMethod(String methodName, String methodDesc) { return existingMethods.contains(methodName + methodDesc); } /** * Only for subclassing return true if the method exists on the original * entity class. */ public boolean isExistingSuperMethod(String methodName, String methodDesc) { return existingSuperMethods.contains(methodName + methodDesc); } public MethodVisitor createMethodVisitor(MethodVisitor mv, int access, String name, String desc) { MethodMeta methodMeta = new MethodMeta(annotationInfo, access, name, desc); methodMetaList.add(methodMeta); return new MethodReader(mv, methodMeta); } private static final class MethodReader extends MethodVisitor { final MethodVisitor mv; final MethodMeta methodMeta; MethodReader(MethodVisitor mv, MethodMeta methodMeta) { super(Opcodes.ASM5, mv); this.mv = mv; this.methodMeta = methodMeta; } public AnnotationVisitor visitAnnotation(String desc, boolean visible) { AnnotationVisitor av = mv.visitAnnotation(desc, visible); return new AnnotationInfoVisitor(null, methodMeta.annotationInfo, av); } } /** * Create and return a read only fieldVisitor for subclassing option. */ public FieldVisitor createLocalFieldVisitor(String name, String desc) { return createLocalFieldVisitor(null, null, name, desc); } /** * Create and return a new fieldVisitor for use when enhancing a class. */ public FieldVisitor createLocalFieldVisitor(ClassVisitor cv, 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(0)) { log("... ignore field " + name); } } else { fields.put(localField.getName(), fieldMeta); } return localField; } public boolean isAlreadyEnhanced() { return alreadyEnhanced; } public void setAlreadyEnhanced(boolean alreadyEnhanced) { this.alreadyEnhanced = alreadyEnhanced; } public boolean hasDefaultConstructor() { return hasDefaultConstructor; } public void setHasDefaultConstructor(boolean hasDefaultConstructor) { this.hasDefaultConstructor = hasDefaultConstructor; } public String getDescription() { 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); } } public 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; } public boolean hasGroovyInterface() { return hasGroovyInterface; } public void setGroovyInterface(boolean hasGroovyInterface) { this.hasGroovyInterface = hasGroovyInterface; } }