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.entity.FieldMeta; 008import io.ebean.enhance.entity.LocalFieldVisitor; 009import io.ebean.enhance.entity.MessageOutput; 010import io.ebean.enhance.entity.MethodMeta; 011 012import java.util.ArrayList; 013import java.util.HashSet; 014import java.util.LinkedHashMap; 015import java.util.List; 016import java.util.logging.Level; 017import java.util.logging.Logger; 018 019import static io.ebean.enhance.Transformer.EBEAN_ASM_VERSION; 020import static io.ebean.enhance.common.EnhanceConstants.C_OBJECT; 021import static io.ebean.enhance.common.EnhanceConstants.TRANSACTIONAL_ANNOTATION; 022import static io.ebean.enhance.common.EnhanceConstants.TYPEQUERYBEAN_ANNOTATION; 023 024/** 025 * Holds the meta data for an entity bean class that is being enhanced. 026 */ 027public class ClassMeta { 028 029 private static final Logger logger = Logger.getLogger(ClassMeta.class.getName()); 030 031 private final MessageOutput logout; 032 033 private final int logLevel; 034 035 private String className; 036 037 private String superClassName; 038 039 private ClassMeta superMeta; 040 041 /** 042 * Set to true if the class implements th GroovyObject interface. 043 */ 044 private boolean hasGroovyInterface; 045 046 /** 047 * Set to true if the class implements the ScalaObject interface. 048 */ 049 private boolean hasScalaInterface; 050 051 /** 052 * Set to true if the class already implements the EntityBean interface. 053 */ 054 private boolean hasEntityBeanInterface; 055 056 private boolean alreadyEnhanced; 057 058 private boolean hasEqualsOrHashcode; 059 060 private boolean hasDefaultConstructor; 061 062 private boolean hasStaticInit; 063 064 private final LinkedHashMap<String, FieldMeta> fields = new LinkedHashMap<>(); 065 066 private final HashSet<String> classAnnotation = new HashSet<>(); 067 068 private final AnnotationInfo annotationInfo = new AnnotationInfo(null); 069 070 private final ArrayList<MethodMeta> methodMetaList = new ArrayList<>(); 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(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 FieldMeta f = getFieldPersistent(fieldName); 199 return (f != null) && f.isPersistent(); 200 } 201 202 /** 203 * Return true if the field is a persistent many field that we want to consume the init on. 204 */ 205 public boolean isConsumeInitMany(String fieldName) { 206 FieldMeta f = getFieldPersistent(fieldName); 207 return (f != null && f.isPersistent() && f.isInitMany()); 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 private List<FieldMeta> getLocalFields() { 226 227 List<FieldMeta> list = new ArrayList<>(); 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 void addInheritedFields(List<FieldMeta> list) { 243 if (superMeta != null) { 244 superMeta.addFieldsForInheritance(list); 245 } 246 } 247 248 /** 249 * Add all fields to the list. 250 */ 251 private void addFieldsForInheritance(List<FieldMeta> list) { 252 if (isEntity()) { 253 list.addAll(0, fields.values()); 254 if (superMeta != null) { 255 superMeta.addFieldsForInheritance(list); 256 } 257 } 258 } 259 260 /** 261 * Return true if the class contains persistent fields. 262 */ 263 public boolean hasPersistentFields() { 264 265 for (FieldMeta fieldMeta : fields.values()) { 266 if (fieldMeta.isPersistent() || fieldMeta.isTransient()) { 267 return true; 268 } 269 } 270 271 return superMeta != null && superMeta.hasPersistentFields(); 272 } 273 274 /** 275 * Return the list of all fields including ones inherited from entity super 276 * types and mappedSuperclasses. 277 */ 278 public List<FieldMeta> getAllFields() { 279 280 if (allFields != null) { 281 return allFields; 282 } 283 List<FieldMeta> list = getLocalFields(); 284 addInheritedFields(list); 285 286 this.allFields = list; 287 for (int i = 0; i < allFields.size(); i++) { 288 allFields.get(i).setIndexPosition(i); 289 } 290 291 return list; 292 } 293 294 /** 295 * Add field level get set methods for each field. 296 */ 297 public void addFieldGetSetMethods(ClassVisitor cv) { 298 299 if (isEntityEnhancementRequired()) { 300 for (FieldMeta fm : fields.values()) { 301 fm.addGetSetMethods(cv, this); 302 } 303 } 304 } 305 306 /** 307 * Return true if this is a mapped superclass. 308 */ 309 boolean isMappedSuper() { 310 return classAnnotation.contains(EnhanceConstants.MAPPEDSUPERCLASS_ANNOTATION); 311 } 312 313 /** 314 * Return true if this is a query bean. 315 */ 316 public boolean isQueryBean() { 317 return classAnnotation.contains(EnhanceConstants.TYPEQUERYBEAN_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 MethodVisitor createMethodVisitor(MethodVisitor mv, String name, String desc) { 382 383 MethodMeta methodMeta = new MethodMeta(annotationInfo, name, desc); 384 methodMetaList.add(methodMeta); 385 return new MethodReader(mv, methodMeta); 386 } 387 388 /** 389 * ACC_PUBLIC with maybe ACC_SYNTHETIC. 390 */ 391 public int accPublic() { 392 return enhanceContext.accPublic(); 393 } 394 395 /** 396 * ACC_PROTECTED with maybe ACC_SYNTHETIC. 397 */ 398 public int accProtected() { 399 return enhanceContext.accProtected(); 400 } 401 402 /** 403 * ACC_PRIVATE with maybe ACC_SYNTHETIC. 404 */ 405 public int accPrivate() { 406 return enhanceContext.accPrivate(); 407 } 408 409 public boolean isToManyGetField() { 410 return enhanceContext.isToManyGetField(); 411 } 412 413 private static final class MethodReader extends MethodVisitor { 414 415 final MethodMeta methodMeta; 416 417 MethodReader(MethodVisitor mv, MethodMeta methodMeta) { 418 super(EBEAN_ASM_VERSION, mv); 419 this.methodMeta = methodMeta; 420 } 421 422 @Override 423 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 424 AnnotationVisitor av = null; 425 if (mv != null) { 426 av = mv.visitAnnotation(desc, visible); 427 } 428 if (!isInterestingAnnotation(desc)) { 429 return av; 430 } 431 return new AnnotationInfoVisitor(null, methodMeta.getAnnotationInfo(), av); 432 } 433 434 private boolean isInterestingAnnotation(String desc) { 435 return TRANSACTIONAL_ANNOTATION.equals(desc) 436 || TYPEQUERYBEAN_ANNOTATION.equals(desc); 437 } 438 } 439 440 /** 441 * Create and return a read only fieldVisitor for subclassing option. 442 */ 443 FieldVisitor createLocalFieldVisitor(String name, String desc) { 444 return createLocalFieldVisitor(null, name, desc); 445 } 446 447 /** 448 * Create and return a new fieldVisitor for use when enhancing a class. 449 */ 450 public FieldVisitor createLocalFieldVisitor(FieldVisitor fv, String name, String desc) { 451 452 FieldMeta fieldMeta = new FieldMeta(this, name, desc, className); 453 LocalFieldVisitor localField = new LocalFieldVisitor(fv, fieldMeta); 454 if (name.startsWith("_ebean")) { 455 // can occur when reading inheritance information on 456 // a entity that has already been enhanced 457 if (isLog(5)) { 458 log("... ignore field " + name); 459 } 460 } else { 461 fields.put(localField.getName(), fieldMeta); 462 } 463 return localField; 464 } 465 466 public void setAlreadyEnhanced(boolean alreadyEnhanced) { 467 this.alreadyEnhanced = alreadyEnhanced; 468 } 469 470 public boolean hasDefaultConstructor() { 471 return hasDefaultConstructor; 472 } 473 474 public void setHasDefaultConstructor(boolean hasDefaultConstructor) { 475 this.hasDefaultConstructor = hasDefaultConstructor; 476 } 477 478 public void setHasStaticInit(boolean hasStaticInit) { 479 this.hasStaticInit = hasStaticInit; 480 } 481 482 public boolean hasStaticInit() { 483 return hasStaticInit; 484 } 485 486 public String getDescription() { 487 StringBuilder sb = new StringBuilder(); 488 appendDescription(sb); 489 return sb.toString(); 490 } 491 492 private void appendDescription(StringBuilder sb) { 493 sb.append(className); 494 if (superMeta != null) { 495 sb.append(" : "); 496 superMeta.appendDescription(sb); 497 } 498 } 499 500 private boolean hasScalaInterface() { 501 return hasScalaInterface; 502 } 503 504 public void setScalaInterface(boolean hasScalaInterface) { 505 this.hasScalaInterface = hasScalaInterface; 506 } 507 508 public boolean hasEntityBeanInterface() { 509 return hasEntityBeanInterface; 510 } 511 512 public void setEntityBeanInterface(boolean hasEntityBeanInterface) { 513 this.hasEntityBeanInterface = hasEntityBeanInterface; 514 } 515 516 private boolean hasGroovyInterface() { 517 return hasGroovyInterface; 518 } 519 520 public void setGroovyInterface(boolean hasGroovyInterface) { 521 this.hasGroovyInterface = hasGroovyInterface; 522 } 523 524}