001package io.ebean.enhance.common; 002 003import io.ebean.enhance.asm.AnnotationVisitor; 004import io.ebean.enhance.asm.ClassVisitor; 005import io.ebean.enhance.asm.FieldVisitor; 006import io.ebean.enhance.asm.MethodVisitor; 007import io.ebean.enhance.asm.Opcodes; 008import io.ebean.enhance.entity.FieldMeta; 009import io.ebean.enhance.entity.LocalFieldVisitor; 010import io.ebean.enhance.entity.MessageOutput; 011import io.ebean.enhance.entity.MethodMeta; 012 013import java.util.ArrayList; 014import java.util.HashSet; 015import java.util.LinkedHashMap; 016import java.util.List; 017import java.util.logging.Level; 018import java.util.logging.Logger; 019 020/** 021 * Holds the meta data for an entity bean class that is being enhanced. 022 */ 023public class ClassMeta { 024 025 private static final Logger logger = Logger.getLogger(ClassMeta.class.getName()); 026 027 private static final String OBJECT_CLASS = Object.class.getName().replace('.', '/'); 028 029 private final MessageOutput logout; 030 031 private final int logLevel; 032 033 private String className; 034 035 private String superClassName; 036 037 private ClassMeta superMeta; 038 039 /** 040 * Set to true if the class implements th GroovyObject interface. 041 */ 042 private boolean hasGroovyInterface; 043 044 /** 045 * Set to true if the class implements the ScalaObject interface. 046 */ 047 private boolean hasScalaInterface; 048 049 /** 050 * Set to true if the class already implements the EntityBean interface. 051 */ 052 private boolean hasEntityBeanInterface; 053 054 private boolean alreadyEnhanced; 055 056 private boolean hasEqualsOrHashcode; 057 058 private boolean hasDefaultConstructor; 059 060 private boolean hasStaticInit; 061 062 private HashSet<String> existingMethods = new HashSet<String>(); 063 064 private LinkedHashMap<String, FieldMeta> fields = new LinkedHashMap<String, FieldMeta>(); 065 066 private HashSet<String> classAnnotation = new HashSet<String>(); 067 068 private AnnotationInfo annotationInfo = new AnnotationInfo(null); 069 070 private ArrayList<MethodMeta> methodMetaList = new ArrayList<MethodMeta>(); 071 072 private final EnhanceContext enhanceContext; 073 074 private List<FieldMeta> allFields; 075 076 public ClassMeta(EnhanceContext enhanceContext, int logLevel, MessageOutput logout) { 077 this.enhanceContext = enhanceContext; 078 this.logLevel = logLevel; 079 this.logout = logout; 080 } 081 082 /** 083 * Return the enhance context which has options for enhancement. 084 */ 085 public EnhanceContext getEnhanceContext() { 086 return enhanceContext; 087 } 088 089 /** 090 * Return the AnnotationInfo collected on methods. 091 * Used to determine Transactional method enhancement. 092 */ 093 public AnnotationInfo getAnnotationInfo() { 094 return annotationInfo; 095 } 096 097 /** 098 * Return the transactional annotation information for a matching interface method. 099 */ 100 public AnnotationInfo getInterfaceTransactionalInfo(String methodName, String methodDesc) { 101 102 AnnotationInfo annotationInfo = null; 103 104 for (int i = 0; i < methodMetaList.size(); i++) { 105 MethodMeta meta = methodMetaList.get(i); 106 if (meta.isMatch(methodName, methodDesc)) { 107 if (annotationInfo != null) { 108 String msg = "Error in [" + className + "] searching the transactional methods[" + methodMetaList 109 + "] found more than one match for the transactional method:" + methodName + " " 110 + methodDesc; 111 112 logger.log(Level.SEVERE, msg); 113 log(msg); 114 115 } else { 116 annotationInfo = meta.getAnnotationInfo(); 117 if (isLog(9)){ 118 log("... found transactional info from interface "+className+" "+methodName+" "+methodDesc); 119 } 120 } 121 } 122 } 123 124 return annotationInfo; 125 } 126 127 public boolean isCheckSuperClassForEntity() { 128 return !superClassName.equals(OBJECT_CLASS) && isCheckEntity(); 129 } 130 131 public String toString() { 132 return className; 133 } 134 135 public boolean isTransactional() { 136 return classAnnotation.contains(EnhanceConstants.AVAJE_TRANSACTIONAL_ANNOTATION); 137 } 138 139 public void setClassName(String className, String superClassName) { 140 this.className = className; 141 this.superClassName = superClassName; 142 } 143 144 public String getSuperClassName() { 145 return superClassName; 146 } 147 148 public boolean isLog(int level) { 149 return level <= logLevel; 150 } 151 152 public void log(String msg) { 153 if (className != null) { 154 msg = "cls: " + className + " msg: " + msg; 155 } 156 logout.println("ebean-enhance> " + msg); 157 } 158 159 public void logEnhanced() { 160 String m = "enhanced "; 161 if (hasScalaInterface()){ 162 m += " (scala)"; 163 } 164 if (hasGroovyInterface()){ 165 m += " (groovy)"; 166 } 167 log(m); 168 } 169 170 public void setSuperMeta(ClassMeta superMeta) { 171 this.superMeta = superMeta; 172 } 173 174 /** 175 * Set to true if the class has an existing equals() or hashcode() method. 176 */ 177 public void setHasEqualsOrHashcode(boolean hasEqualsOrHashcode) { 178 this.hasEqualsOrHashcode = hasEqualsOrHashcode; 179 } 180 181 /** 182 * Return true if Equals/hashCode is implemented on this class or a super class. 183 */ 184 public boolean hasEqualsOrHashCode() { 185 if (hasEqualsOrHashcode) { 186 return true; 187 188 } else { 189 return (superMeta != null && superMeta.hasEqualsOrHashCode()); 190 } 191 } 192 193 /** 194 * Return true if the field is a persistent field. 195 */ 196 public boolean isFieldPersistent(String fieldName) { 197 198 FieldMeta f = getFieldPersistent(fieldName); 199 return (f == null) ? false: f.isPersistent(); 200 } 201 202 /** 203 * Return true if the field is a persistent many field. 204 */ 205 public boolean isFieldPersistentMany(String fieldName) { 206 FieldMeta f = getFieldPersistent(fieldName); 207 return (f != null && f.isPersistent() && f.isMany()); 208 } 209 210 /** 211 * Return the field - null when not found. 212 */ 213 public FieldMeta getFieldPersistent(String fieldName) { 214 215 FieldMeta f = fields.get(fieldName); 216 if (f != null) { 217 return f; 218 } 219 return (superMeta == null) ? null : superMeta.getFieldPersistent(fieldName); 220 } 221 222 /** 223 * Return the list of fields local to this type (not inherited). 224 */ 225 public List<FieldMeta> getLocalFields() { 226 227 ArrayList<FieldMeta> list = new ArrayList<FieldMeta>(); 228 229 for (FieldMeta fm : fields.values()) { 230 if (!fm.isObjectArray()) { 231 // add field local to this entity type 232 list.add(fm); 233 } 234 } 235 236 return list; 237 } 238 239 /** 240 * Return the list of fields inherited from super types that are entities. 241 */ 242 private List<FieldMeta> getInheritedFields(List<FieldMeta> list) { 243 244 if (list == null){ 245 list = new ArrayList<FieldMeta>(); 246 } 247 248 if (superMeta != null) { 249 superMeta.addFieldsForInheritance(list); 250 } 251 return list; 252 } 253 254 /** 255 * Add all fields to the list. 256 */ 257 private void addFieldsForInheritance(List<FieldMeta> list) { 258 if (isEntity()) { 259 list.addAll(0, fields.values()); 260 if (superMeta != null) { 261 superMeta.addFieldsForInheritance(list); 262 } 263 } 264 } 265 266 /** 267 * Return true if the class contains persistent fields. 268 */ 269 public boolean hasPersistentFields() { 270 271 for (FieldMeta fieldMeta : fields.values()) { 272 if (fieldMeta.isPersistent() || fieldMeta.isTransient()) { 273 return true; 274 } 275 } 276 277 return superMeta != null && superMeta.hasPersistentFields(); 278 } 279 280 /** 281 * Return the list of all fields including ones inherited from entity super 282 * types and mappedSuperclasses. 283 */ 284 public List<FieldMeta> getAllFields() { 285 286 if (allFields != null) { 287 return allFields; 288 } 289 List<FieldMeta> list = getLocalFields(); 290 getInheritedFields(list); 291 292 this.allFields = list; 293 for (int i=0; i<allFields.size(); i++) { 294 allFields.get(i).setIndexPosition(i); 295 } 296 297 return list; 298 } 299 300 /** 301 * Add field level get set methods for each field. 302 */ 303 public void addFieldGetSetMethods(ClassVisitor cv) { 304 305 if (isEntityEnhancementRequired()) { 306 for (FieldMeta fm : fields.values()) { 307 fm.addGetSetMethods(cv, this); 308 } 309 } 310 } 311 312 /** 313 * Return true if the class has an Entity, Embeddable, MappedSuperclass (with persistent fields). 314 */ 315 public boolean isEntity() { 316 if (!EntityCheck.hasEntityAnnotation(classAnnotation)) { 317 return false; 318 } 319 if (classAnnotation.contains(EnhanceConstants.MAPPEDSUPERCLASS_ANNOTATION)) { 320 // only 'interesting' if it has persistent fields or equals/hashCode. 321 // Some MappedSuperclass like com.avaje.ebean.Model don't need any enhancement 322 boolean shouldEnhance = hasEqualsOrHashCode() || hasPersistentFields(); 323 if (isLog(8)) { 324 log("mappedSuperClass with equals/hashCode or persistentFields: "+shouldEnhance); 325 } 326 return shouldEnhance; 327 } 328 return true; 329 } 330 331 /** 332 * Return true if the class has an Entity, Embeddable, or MappedSuperclass. 333 */ 334 private boolean isCheckEntity() { 335 return EntityCheck.hasEntityAnnotation(classAnnotation); 336 } 337 338 /** 339 * Return true for classes not already enhanced and yet annotated with entity, embeddable or mappedSuperclass. 340 */ 341 public boolean isEntityEnhancementRequired() { 342 return !alreadyEnhanced && isEntity(); 343 } 344 345 /** 346 * Return true if the bean is already enhanced. 347 */ 348 public boolean isAlreadyEnhanced() { 349 return alreadyEnhanced; 350 } 351 352 /** 353 * Return the className of this entity class. 354 */ 355 public String getClassName() { 356 return className; 357 } 358 359 /** 360 * Return true if this entity bean has a super class that is an entity. 361 */ 362 public boolean isSuperClassEntity() { 363 return superMeta != null && superMeta.isEntity(); 364 } 365 366 /** 367 * Add a class annotation. 368 */ 369 public void addClassAnnotation(String desc) { 370 classAnnotation.add(desc); 371 } 372 373 /** 374 * Add an existing method. 375 */ 376 public void addExistingMethod(String methodName, String methodDesc) { 377 existingMethods.add(methodName + methodDesc); 378 } 379 380 /** 381 * Return true if the method already exists on the bean. 382 */ 383 public boolean isExistingMethod(String methodName, String methodDesc) { 384 return existingMethods.contains(methodName + methodDesc); 385 } 386 387 public MethodVisitor createMethodVisitor(MethodVisitor mv, int access, String name, String desc) { 388 389 MethodMeta methodMeta = new MethodMeta(annotationInfo, access, name, desc); 390 methodMetaList.add(methodMeta); 391 392 return new MethodReader(mv, methodMeta); 393 } 394 395 private static final class MethodReader extends MethodVisitor { 396 397 final MethodMeta methodMeta; 398 399 MethodReader(MethodVisitor mv, MethodMeta methodMeta) { 400 super(Opcodes.ASM6, mv); 401 this.methodMeta = methodMeta; 402 } 403 404 @Override 405 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 406 if (mv == null) { 407 return null; 408 } 409 AnnotationVisitor av = mv.visitAnnotation(desc, visible); 410 return new AnnotationInfoVisitor(null, methodMeta.getAnnotationInfo(), av); 411 } 412 413 } 414 415 /** 416 * Create and return a read only fieldVisitor for subclassing option. 417 */ 418 public FieldVisitor createLocalFieldVisitor(String name, String desc) { 419 return createLocalFieldVisitor(null, name, desc); 420 } 421 422 /** 423 * Create and return a new fieldVisitor for use when enhancing a class. 424 */ 425 public FieldVisitor createLocalFieldVisitor(FieldVisitor fv, String name, String desc) { 426 427 FieldMeta fieldMeta = new FieldMeta(this, name, desc, className); 428 LocalFieldVisitor localField = new LocalFieldVisitor(fv, fieldMeta); 429 if (name.startsWith("_ebean")) { 430 // can occur when reading inheritance information on 431 // a entity that has already been enhanced 432 if (isLog(5)) { 433 log("... ignore field " + name); 434 } 435 } else { 436 fields.put(localField.getName(), fieldMeta); 437 } 438 return localField; 439 } 440 441 public void setAlreadyEnhanced(boolean alreadyEnhanced) { 442 this.alreadyEnhanced = alreadyEnhanced; 443 } 444 445 public boolean hasDefaultConstructor() { 446 return hasDefaultConstructor; 447 } 448 449 public void setHasDefaultConstructor(boolean hasDefaultConstructor) { 450 this.hasDefaultConstructor = hasDefaultConstructor; 451 } 452 453 public void setHasStaticInit(boolean hasStaticInit) { 454 this.hasStaticInit = hasStaticInit; 455 } 456 457 public boolean hasStaticInit() { 458 return hasStaticInit; 459 } 460 461 public String getDescription() { 462 StringBuilder sb = new StringBuilder(); 463 appendDescription(sb); 464 return sb.toString(); 465 } 466 467 private void appendDescription(StringBuilder sb) { 468 sb.append(className); 469 if (superMeta != null) { 470 sb.append(" : "); 471 superMeta.appendDescription(sb); 472 } 473 } 474 475 public boolean hasScalaInterface() { 476 return hasScalaInterface; 477 } 478 479 public void setScalaInterface(boolean hasScalaInterface) { 480 this.hasScalaInterface = hasScalaInterface; 481 } 482 483 public boolean hasEntityBeanInterface() { 484 return hasEntityBeanInterface; 485 } 486 487 public void setEntityBeanInterface(boolean hasEntityBeanInterface) { 488 this.hasEntityBeanInterface = hasEntityBeanInterface; 489 } 490 491 public boolean hasGroovyInterface() { 492 return hasGroovyInterface; 493 } 494 495 public void setGroovyInterface(boolean hasGroovyInterface) { 496 this.hasGroovyInterface = hasGroovyInterface; 497 } 498 499}