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 020import static io.ebean.enhance.common.EnhanceConstants.C_OBJECT; 021 022/** 023 * Holds the meta data for an entity bean class that is being enhanced. 024 */ 025public class ClassMeta { 026 027 private static final Logger logger = Logger.getLogger(ClassMeta.class.getName()); 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<>(); 065 066 private HashSet<String> classAnnotation = new HashSet<>(); 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(C_OBJECT) && isCheckEntity(); 129 } 130 131 @Override 132 public String toString() { 133 return className; 134 } 135 136 public boolean isTransactional() { 137 return classAnnotation.contains(EnhanceConstants.AVAJE_TRANSACTIONAL_ANNOTATION); 138 } 139 140 public void setClassName(String className, String superClassName) { 141 this.className = className; 142 this.superClassName = superClassName; 143 } 144 145 public String getSuperClassName() { 146 return superClassName; 147 } 148 149 public boolean isLog(int level) { 150 return level <= logLevel; 151 } 152 153 public void log(String msg) { 154 if (className != null) { 155 msg = "cls: " + className + " msg: " + msg; 156 } 157 logout.println("ebean-enhance> " + msg); 158 } 159 160 public void logEnhanced() { 161 String m = "enhanced "; 162 if (hasScalaInterface()){ 163 m += " (scala)"; 164 } 165 if (hasGroovyInterface()){ 166 m += " (groovy)"; 167 } 168 log(m); 169 } 170 171 public void setSuperMeta(ClassMeta superMeta) { 172 this.superMeta = superMeta; 173 } 174 175 /** 176 * Set to true if the class has an existing equals() or hashcode() method. 177 */ 178 public void setHasEqualsOrHashcode(boolean hasEqualsOrHashcode) { 179 this.hasEqualsOrHashcode = hasEqualsOrHashcode; 180 } 181 182 /** 183 * Return true if Equals/hashCode is implemented on this class or a super class. 184 */ 185 public boolean hasEqualsOrHashCode() { 186 if (hasEqualsOrHashcode) { 187 return true; 188 189 } else { 190 return (superMeta != null && superMeta.hasEqualsOrHashCode()); 191 } 192 } 193 194 /** 195 * Return true if the field is a persistent field. 196 */ 197 public boolean isFieldPersistent(String fieldName) { 198 199 FieldMeta f = getFieldPersistent(fieldName); 200 return (f == null) ? false: f.isPersistent(); 201 } 202 203 /** 204 * Return true if the field is a persistent many field. 205 */ 206 public boolean isFieldPersistentMany(String fieldName) { 207 FieldMeta f = getFieldPersistent(fieldName); 208 return (f != null && f.isPersistent() && f.isMany()); 209 } 210 211 /** 212 * Return the field - null when not found. 213 */ 214 public FieldMeta getFieldPersistent(String fieldName) { 215 216 FieldMeta f = fields.get(fieldName); 217 if (f != null) { 218 return f; 219 } 220 return (superMeta == null) ? null : superMeta.getFieldPersistent(fieldName); 221 } 222 223 /** 224 * Return the list of fields local to this type (not inherited). 225 */ 226 public List<FieldMeta> getLocalFields() { 227 228 ArrayList<FieldMeta> list = new ArrayList<FieldMeta>(); 229 230 for (FieldMeta fm : fields.values()) { 231 if (!fm.isObjectArray()) { 232 // add field local to this entity type 233 list.add(fm); 234 } 235 } 236 237 return list; 238 } 239 240 /** 241 * Return the list of fields inherited from super types that are entities. 242 */ 243 private List<FieldMeta> getInheritedFields(List<FieldMeta> list) { 244 245 if (list == null){ 246 list = new ArrayList<FieldMeta>(); 247 } 248 249 if (superMeta != null) { 250 superMeta.addFieldsForInheritance(list); 251 } 252 return list; 253 } 254 255 /** 256 * Add all fields to the list. 257 */ 258 private void addFieldsForInheritance(List<FieldMeta> list) { 259 if (isEntity()) { 260 list.addAll(0, fields.values()); 261 if (superMeta != null) { 262 superMeta.addFieldsForInheritance(list); 263 } 264 } 265 } 266 267 /** 268 * Return true if the class contains persistent fields. 269 */ 270 public boolean hasPersistentFields() { 271 272 for (FieldMeta fieldMeta : fields.values()) { 273 if (fieldMeta.isPersistent() || fieldMeta.isTransient()) { 274 return true; 275 } 276 } 277 278 return superMeta != null && superMeta.hasPersistentFields(); 279 } 280 281 /** 282 * Return the list of all fields including ones inherited from entity super 283 * types and mappedSuperclasses. 284 */ 285 public List<FieldMeta> getAllFields() { 286 287 if (allFields != null) { 288 return allFields; 289 } 290 List<FieldMeta> list = getLocalFields(); 291 getInheritedFields(list); 292 293 this.allFields = list; 294 for (int i=0; i<allFields.size(); i++) { 295 allFields.get(i).setIndexPosition(i); 296 } 297 298 return list; 299 } 300 301 /** 302 * Add field level get set methods for each field. 303 */ 304 public void addFieldGetSetMethods(ClassVisitor cv) { 305 306 if (isEntityEnhancementRequired()) { 307 for (FieldMeta fm : fields.values()) { 308 fm.addGetSetMethods(cv, this); 309 } 310 } 311 } 312 313 /** 314 * Return true if this is a mapped superclass. 315 */ 316 public boolean isMappedSuper() { 317 return classAnnotation.contains(EnhanceConstants.MAPPEDSUPERCLASS_ANNOTATION); 318 } 319 320 /** 321 * Return true if the class has an Entity, Embeddable, MappedSuperclass (with persistent fields). 322 */ 323 public boolean isEntity() { 324 if (!EntityCheck.hasEntityAnnotation(classAnnotation)) { 325 return false; 326 } 327 if (classAnnotation.contains(EnhanceConstants.MAPPEDSUPERCLASS_ANNOTATION)) { 328 // only 'interesting' if it has persistent fields or equals/hashCode. 329 // Some MappedSuperclass like com.avaje.ebean.Model don't need any enhancement 330 boolean shouldEnhance = hasEqualsOrHashCode() || hasPersistentFields(); 331 if (isLog(8)) { 332 log("mappedSuperClass with equals/hashCode or persistentFields: "+shouldEnhance); 333 } 334 return shouldEnhance; 335 } 336 return true; 337 } 338 339 /** 340 * Return true if the class has an Entity, Embeddable, or MappedSuperclass. 341 */ 342 private boolean isCheckEntity() { 343 return EntityCheck.hasEntityAnnotation(classAnnotation); 344 } 345 346 /** 347 * Return true for classes not already enhanced and yet annotated with entity, embeddable or mappedSuperclass. 348 */ 349 public boolean isEntityEnhancementRequired() { 350 return !alreadyEnhanced && isEntity(); 351 } 352 353 /** 354 * Return true if the bean is already enhanced. 355 */ 356 public boolean isAlreadyEnhanced() { 357 return alreadyEnhanced; 358 } 359 360 /** 361 * Return the className of this entity class. 362 */ 363 public String getClassName() { 364 return className; 365 } 366 367 /** 368 * Return true if this entity bean has a super class that is an entity. 369 */ 370 public boolean isSuperClassEntity() { 371 return superMeta != null && superMeta.isEntity(); 372 } 373 374 /** 375 * Add a class annotation. 376 */ 377 public void addClassAnnotation(String desc) { 378 classAnnotation.add(desc); 379 } 380 381 /** 382 * Add an existing method. 383 */ 384 public void addExistingMethod(String methodName, String methodDesc) { 385 existingMethods.add(methodName + methodDesc); 386 } 387 388 /** 389 * Return true if the method already exists on the bean. 390 */ 391 public boolean isExistingMethod(String methodName, String methodDesc) { 392 return existingMethods.contains(methodName + methodDesc); 393 } 394 395 public MethodVisitor createMethodVisitor(MethodVisitor mv, int access, String name, String desc) { 396 397 MethodMeta methodMeta = new MethodMeta(annotationInfo, access, name, desc); 398 methodMetaList.add(methodMeta); 399 400 return new MethodReader(mv, methodMeta); 401 } 402 403 private static final class MethodReader extends MethodVisitor { 404 405 final MethodMeta methodMeta; 406 407 MethodReader(MethodVisitor mv, MethodMeta methodMeta) { 408 super(Opcodes.ASM7, mv); 409 this.methodMeta = methodMeta; 410 } 411 412 @Override 413 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 414 if (mv == null) { 415 return null; 416 } 417 AnnotationVisitor av = mv.visitAnnotation(desc, visible); 418 return new AnnotationInfoVisitor(null, methodMeta.getAnnotationInfo(), av); 419 } 420 421 } 422 423 /** 424 * Create and return a read only fieldVisitor for subclassing option. 425 */ 426 public FieldVisitor createLocalFieldVisitor(String name, String desc) { 427 return createLocalFieldVisitor(null, name, desc); 428 } 429 430 /** 431 * Create and return a new fieldVisitor for use when enhancing a class. 432 */ 433 public FieldVisitor createLocalFieldVisitor(FieldVisitor fv, String name, String desc) { 434 435 FieldMeta fieldMeta = new FieldMeta(this, name, desc, className); 436 LocalFieldVisitor localField = new LocalFieldVisitor(fv, fieldMeta); 437 if (name.startsWith("_ebean")) { 438 // can occur when reading inheritance information on 439 // a entity that has already been enhanced 440 if (isLog(5)) { 441 log("... ignore field " + name); 442 } 443 } else { 444 fields.put(localField.getName(), fieldMeta); 445 } 446 return localField; 447 } 448 449 public void setAlreadyEnhanced(boolean alreadyEnhanced) { 450 this.alreadyEnhanced = alreadyEnhanced; 451 } 452 453 public boolean hasDefaultConstructor() { 454 return hasDefaultConstructor; 455 } 456 457 public void setHasDefaultConstructor(boolean hasDefaultConstructor) { 458 this.hasDefaultConstructor = hasDefaultConstructor; 459 } 460 461 public void setHasStaticInit(boolean hasStaticInit) { 462 this.hasStaticInit = hasStaticInit; 463 } 464 465 public boolean hasStaticInit() { 466 return hasStaticInit; 467 } 468 469 public String getDescription() { 470 StringBuilder sb = new StringBuilder(); 471 appendDescription(sb); 472 return sb.toString(); 473 } 474 475 private void appendDescription(StringBuilder sb) { 476 sb.append(className); 477 if (superMeta != null) { 478 sb.append(" : "); 479 superMeta.appendDescription(sb); 480 } 481 } 482 483 public boolean hasScalaInterface() { 484 return hasScalaInterface; 485 } 486 487 public void setScalaInterface(boolean hasScalaInterface) { 488 this.hasScalaInterface = hasScalaInterface; 489 } 490 491 public boolean hasEntityBeanInterface() { 492 return hasEntityBeanInterface; 493 } 494 495 public void setEntityBeanInterface(boolean hasEntityBeanInterface) { 496 this.hasEntityBeanInterface = hasEntityBeanInterface; 497 } 498 499 public boolean hasGroovyInterface() { 500 return hasGroovyInterface; 501 } 502 503 public void setGroovyInterface(boolean hasGroovyInterface) { 504 this.hasGroovyInterface = hasGroovyInterface; 505 } 506 507}