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.*; 008 009import java.util.*; 010import java.util.logging.Level; 011import java.util.logging.Logger; 012 013import static io.ebean.enhance.Transformer.EBEAN_ASM_VERSION; 014import static io.ebean.enhance.common.EnhanceConstants.*; 015 016/** 017 * Holds the meta-data for an entity bean class that is being enhanced. 018 */ 019public class ClassMeta { 020 021 private static final Logger logger = Logger.getLogger(ClassMeta.class.getName()); 022 023 private final MessageOutput logout; 024 private final int logLevel; 025 private String className; 026 private String superClassName; 027 private ClassMeta superMeta; 028 /** 029 * Set to true if the class implements th GroovyObject interface. 030 */ 031 private boolean hasGroovyInterface; 032 /** 033 * Set to true if the class implements the ScalaObject interface. 034 */ 035 private boolean hasScalaInterface; 036 /** 037 * Set to true if the class already implements the EntityBean interface. 038 */ 039 private boolean hasEntityBeanInterface; 040 private boolean alreadyEnhanced; 041 private boolean hasEqualsOrHashcode; 042 private boolean hasToString; 043 private boolean hasDefaultConstructor; 044 private boolean hasStaticInit; 045 046 /** 047 * If enhancement is adding a default constructor - only single type is supported initialising transient fields. 048 */ 049 private final Set<String> unsupportedTransientMultipleTypes = new LinkedHashSet<>(); 050 /** 051 * If enhancement is adding a default constructor - only default constructors are supported initialising transient fields. 052 */ 053 private final Set<String> unsupportedInitMany = new LinkedHashSet<>(); 054 private final Set<String> unsupportedTransientInitialisation = new LinkedHashSet<>(); 055 private final Map<String, CapturedInitCode> transientInitCode = new LinkedHashMap<>(); 056 private final LinkedHashMap<String, FieldMeta> fields = new LinkedHashMap<>(); 057 private final HashSet<String> classAnnotation = new HashSet<>(); 058 private final AnnotationInfo annotationInfo = new AnnotationInfo(null); 059 private final ArrayList<MethodMeta> methodMetaList = new ArrayList<>(); 060 private final EnhanceContext enhanceContext; 061 private List<FieldMeta> allFields; 062 private boolean recordType; 063 064 public ClassMeta(EnhanceContext enhanceContext, int logLevel, MessageOutput logout) { 065 this.enhanceContext = enhanceContext; 066 this.logLevel = logLevel; 067 this.logout = logout; 068 } 069 070 /** 071 * Return the enhance context which has options for enhancement. 072 */ 073 public EnhanceContext context() { 074 return enhanceContext; 075 } 076 077 /** 078 * Return the AnnotationInfo collected on methods. 079 * Used to determine Transactional method enhancement. 080 */ 081 public AnnotationInfo annotationInfo() { 082 return annotationInfo; 083 } 084 085 public boolean isAllowNullableDbArray() { 086 return enhanceContext.isAllowNullableDbArray(); 087 } 088 089 /** 090 * Return the transactional annotation information for a matching interface method. 091 */ 092 public AnnotationInfo interfaceTransactionalInfo(String methodName, String methodDesc) { 093 AnnotationInfo annotationInfo = null; 094 for (int i = 0; i < methodMetaList.size(); i++) { 095 MethodMeta meta = methodMetaList.get(i); 096 if (meta.isMatch(methodName, methodDesc)) { 097 if (annotationInfo != null) { 098 String msg = "Error in [" + className + "] searching the transactional methods[" + methodMetaList 099 + "] found more than one match for the transactional method:" + methodName + " " 100 + methodDesc; 101 102 logger.log(Level.SEVERE, msg); 103 log(msg); 104 } else { 105 annotationInfo = meta.getAnnotationInfo(); 106 if (isLog(9)) { 107 log("... found transactional info from interface " + className + " " + methodName + " " + methodDesc); 108 } 109 } 110 } 111 } 112 return annotationInfo; 113 } 114 115 public boolean isCheckSuperClassForEntity() { 116 return !superClassName.equals(C_OBJECT) && isCheckEntity(); 117 } 118 119 @Override 120 public String toString() { 121 return className; 122 } 123 124 public boolean isTransactional() { 125 return classAnnotation.contains(TRANSACTIONAL_ANNOTATION); 126 } 127 128 public void setClassName(String className, String superClassName) { 129 this.className = className; 130 this.superClassName = superClassName; 131 if (superClassName.equals(C_RECORDTYPE)) { 132 recordType = true; 133 } 134 } 135 136 public String superClassName() { 137 return superClassName; 138 } 139 140 public boolean isLog(int level) { 141 return level <= logLevel; 142 } 143 144 public void log(String msg) { 145 if (className != null) { 146 msg = "cls: " + className + " msg: " + msg; 147 } 148 logout.println("ebean-enhance> " + msg); 149 } 150 151 public void logEnhanced() { 152 String m = "enhanced "; 153 if (hasScalaInterface()) { 154 m += " (scala)"; 155 } 156 if (hasGroovyInterface()) { 157 m += " (groovy)"; 158 } 159 log(m); 160 } 161 162 public void setSuperMeta(ClassMeta superMeta) { 163 this.superMeta = superMeta; 164 } 165 166 /** 167 * Set to true if the class has an existing equals() or hashcode() method. 168 */ 169 public void setHasEqualsOrHashcode(boolean hasEqualsOrHashcode) { 170 this.hasEqualsOrHashcode = hasEqualsOrHashcode; 171 } 172 173 public void setHasToString() { 174 this.hasToString = true; 175 } 176 177 /** 178 * Return true if Equals/hashCode is implemented on this class or a super class. 179 */ 180 public boolean hasEqualsOrHashCode() { 181 if (hasEqualsOrHashcode) { 182 return true; 183 } else { 184 return (superMeta != null && superMeta.hasEqualsOrHashCode()); 185 } 186 } 187 188 public boolean hasToString() { 189 if (hasToString) { 190 return true; 191 } else { 192 return (superMeta != null && superMeta.hasToString()); 193 } 194 } 195 196 /** 197 * Return true if the field is a persistent field. 198 */ 199 public boolean isFieldPersistent(String fieldName) { 200 FieldMeta f = field(fieldName); 201 return (f != null) && f.isPersistent(); 202 } 203 204 public boolean isTransient(String fieldName) { 205 FieldMeta f = field(fieldName); 206 return (f != null && f.isTransient()); 207 } 208 209 public boolean isInitTransient(String fieldName) { 210 if (!enhanceContext.isTransientInit()) { 211 return false; 212 } 213 return isTransient(fieldName); 214 } 215 216 /** 217 * Return true if the field is a persistent many field that we want to consume the init on. 218 */ 219 public boolean isConsumeInitMany(String fieldName) { 220 FieldMeta f = field(fieldName); 221 return (f != null && f.isPersistent() && f.isInitMany()); 222 } 223 224 /** 225 * Return the field - null when not found. 226 */ 227 public FieldMeta field(String fieldName) { 228 FieldMeta f = fields.get(fieldName); 229 if (f != null) { 230 return f; 231 } 232 return (superMeta == null) ? null : superMeta.field(fieldName); 233 } 234 235 /** 236 * Return the list of fields local to this type (not inherited). 237 */ 238 private List<FieldMeta> localFields() { 239 List<FieldMeta> list = new ArrayList<>(); 240 for (FieldMeta fm : fields.values()) { 241 if (!fm.isObjectArray()) { 242 // add field local to this entity type 243 list.add(fm); 244 } 245 } 246 return list; 247 } 248 249 /** 250 * Return the list of fields inherited from super types that are entities. 251 */ 252 private void addInheritedFields(List<FieldMeta> list) { 253 if (superMeta != null) { 254 superMeta.addFieldsForInheritance(list); 255 } 256 } 257 258 /** 259 * Add all fields to the list. 260 */ 261 private void addFieldsForInheritance(List<FieldMeta> list) { 262 if (isEntity()) { 263 list.addAll(0, fields.values()); 264 if (superMeta != null) { 265 superMeta.addFieldsForInheritance(list); 266 } 267 } 268 } 269 270 /** 271 * Return true if the class contains persistent fields. 272 */ 273 public boolean hasPersistentFields() { 274 for (FieldMeta fieldMeta : fields.values()) { 275 if (fieldMeta.isPersistent() || fieldMeta.isTransient()) { 276 return true; 277 } 278 } 279 return superMeta != null && superMeta.hasPersistentFields(); 280 } 281 282 /** 283 * Return the list of all fields including ones inherited from entity super 284 * types and mappedSuperclasses. 285 */ 286 public List<FieldMeta> allFields() { 287 if (allFields != null) { 288 return allFields; 289 } 290 List<FieldMeta> list = localFields(); 291 addInheritedFields(list); 292 293 this.allFields = list; 294 for (int i = 0; i < allFields.size(); i++) { 295 allFields.get(i).setIndexPosition(i); 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 if (isEntityEnhancementRequired()) { 305 for (FieldMeta fm : fields.values()) { 306 fm.addGetSetMethods(cv, this); 307 } 308 } 309 } 310 311 /** 312 * Return true if this is a mapped superclass. 313 */ 314 boolean isMappedSuper() { 315 return classAnnotation.contains(Javax.MappedSuperclass) || classAnnotation.contains(Jakarta.MappedSuperclass); 316 } 317 318 /** 319 * Return true if this is a query bean. 320 */ 321 public boolean isQueryBean() { 322 return classAnnotation.contains(TYPEQUERYBEAN_ANNOTATION); 323 } 324 325 /** 326 * Return true if the class has an Entity, Embeddable or MappedSuperclass. 327 */ 328 public boolean isEntity() { 329 return EntityCheck.hasEntityAnnotation(classAnnotation); 330 } 331 332 /** 333 * Return true if the class has an Entity, Embeddable, or MappedSuperclass. 334 */ 335 private boolean isCheckEntity() { 336 return EntityCheck.hasEntityAnnotation(classAnnotation); 337 } 338 339 /** 340 * Return true for classes not already enhanced and yet annotated with entity, embeddable or mappedSuperclass. 341 */ 342 public boolean isEntityEnhancementRequired() { 343 return !alreadyEnhanced && isEntity(); 344 } 345 346 /** 347 * Return true if the bean is already enhanced. 348 */ 349 public boolean isAlreadyEnhanced() { 350 return alreadyEnhanced; 351 } 352 353 /** 354 * Return the className of this entity class. 355 */ 356 public String className() { 357 return className; 358 } 359 360 /** 361 * Return true if this entity bean has a super class that is an entity. 362 */ 363 public boolean isSuperClassEntity() { 364 return superMeta != null && superMeta.isEntity(); 365 } 366 367 /** 368 * Add a class annotation. 369 */ 370 public void addClassAnnotation(String desc) { 371 classAnnotation.add(desc); 372 } 373 374 MethodVisitor createMethodVisitor(MethodVisitor mv, String name, String desc) { 375 MethodMeta methodMeta = new MethodMeta(annotationInfo, name, desc); 376 methodMetaList.add(methodMeta); 377 return new MethodReader(mv, methodMeta); 378 } 379 380 /** 381 * ACC_PUBLIC with maybe ACC_SYNTHETIC. 382 */ 383 public int accPublic() { 384 return enhanceContext.accPublic(); 385 } 386 387 /** 388 * ACC_PROTECTED with maybe ACC_SYNTHETIC. 389 */ 390 public int accProtected() { 391 return enhanceContext.accProtected(); 392 } 393 394 /** 395 * If field access use public rather than protected plus usually with synthetic. 396 */ 397 public int accAccessor() { 398 return enhanceContext.isEnableEntityFieldAccess() ? accPublic() : accProtected(); 399 } 400 401 /** 402 * ACC_PRIVATE with maybe ACC_SYNTHETIC. 403 */ 404 public int accPrivate() { 405 return enhanceContext.accPrivate(); 406 } 407 408 public boolean isToManyGetField() { 409 return enhanceContext.isToManyGetField(); 410 } 411 412 /** 413 * Return the EntityBeanIntercept type that will be new'ed up for the EntityBean. 414 * For version 140+ EntityBeanIntercept is an interface and instead we new up InterceptReadWrite. 415 */ 416 public String interceptNew() { 417 return enhanceContext.interceptNew(); 418 } 419 420 /** 421 * Invoke a method on EntityBeanIntercept. 422 * For version 140+ EntityBeanIntercept is an interface and this uses INVOKEINTERFACE. 423 */ 424 public void visitMethodInsnIntercept(MethodVisitor mv, String name, String desc) { 425 enhanceContext.visitMethodInsnIntercept(mv, name, desc); 426 } 427 428 /** 429 * If 141+ Add InterceptReadOnly support. 430 */ 431 public boolean interceptAddReadOnly() { 432 return enhanceContext.interceptAddReadOnly(); 433 } 434 435 public boolean isRecordType() { 436 return recordType; 437 } 438 439 public void addTransientInit(CapturedInitCode deferredInitCode) { 440 CapturedInitCode old = transientInitCode.put(deferredInitCode.name(), deferredInitCode); 441 if (old != null && !old.type().equals(deferredInitCode.type())) { 442 transientInitCode.put(deferredInitCode.name(), old); 443 unsupportedTransientMultipleTypes.add("field: " + old.name() + " types: " + old.type() + " " + deferredInitCode.type()); 444 } 445 } 446 447 public Collection<CapturedInitCode> transientInit() { 448 return transientInitCode.values(); 449 } 450 451 public void addUnsupportedInitMany(String name) { 452 unsupportedInitMany.add(name); 453 } 454 455 public boolean hasUnsupportedInitMany() { 456 return !unsupportedInitMany.isEmpty(); 457 } 458 459 public String initFieldErrorMessage() { 460 return "ERROR: Unsupported initialisation of @OneToMany or @ManyToMany on: " 461 + className + " fields: " + unsupportedInitMany 462 + " Refer: https://ebean.io/docs/trouble-shooting#initialisation-error"; 463 } 464 465 public void addUnsupportedTransientInit(String name) { 466 unsupportedTransientInitialisation.add(name); 467 } 468 469 public boolean hasTransientFieldErrors() { 470 return !unsupportedTransientMultipleTypes.isEmpty() || !unsupportedTransientInitialisation.isEmpty(); 471 } 472 473 public String transientFieldErrorMessage() { 474 String msg = "ERROR: Entity class without default constructor has unsupported initialisation of transient fields. Entity class: " + className; 475 if (!unsupportedTransientMultipleTypes.isEmpty()) { 476 msg += " - fields initialised in constructor with 2 different types - " + unsupportedTransientMultipleTypes; 477 } 478 if (!unsupportedTransientInitialisation.isEmpty()) { 479 msg += " - Unsupported initialisation of transient fields - " + unsupportedTransientInitialisation; 480 } 481 msg += " Refer: https://ebean.io/docs/trouble-shooting#transient-initialisation"; 482 return msg; 483 } 484 485 private static final class MethodReader extends MethodVisitor { 486 487 final MethodMeta methodMeta; 488 489 MethodReader(MethodVisitor mv, MethodMeta methodMeta) { 490 super(EBEAN_ASM_VERSION, mv); 491 this.methodMeta = methodMeta; 492 } 493 494 @Override 495 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 496 AnnotationVisitor av = null; 497 if (mv != null) { 498 av = mv.visitAnnotation(desc, visible); 499 } 500 if (!isInterestingAnnotation(desc)) { 501 return av; 502 } 503 return new AnnotationInfoVisitor(null, methodMeta.getAnnotationInfo(), av); 504 } 505 506 private boolean isInterestingAnnotation(String desc) { 507 return TRANSACTIONAL_ANNOTATION.equals(desc) 508 || TYPEQUERYBEAN_ANNOTATION.equals(desc); 509 } 510 } 511 512 /** 513 * Create and return a read only fieldVisitor for subclassing option. 514 */ 515 FieldVisitor createLocalFieldVisitor(String name, String desc) { 516 return createLocalFieldVisitor(null, name, desc); 517 } 518 519 /** 520 * Create and return a new fieldVisitor for use when enhancing a class. 521 */ 522 public FieldVisitor createLocalFieldVisitor(FieldVisitor fv, String name, String desc) { 523 FieldMeta fieldMeta = new FieldMeta(this, name, desc, className); 524 LocalFieldVisitor localField = new LocalFieldVisitor(fv, fieldMeta); 525 if (name.startsWith("_ebean")) { 526 // can occur when reading inheritance information on 527 // a entity that has already been enhanced 528 if (isLog(5)) { 529 log("... ignore field " + name); 530 } 531 } else { 532 fields.put(localField.name(), fieldMeta); 533 } 534 return localField; 535 } 536 537 public void setAlreadyEnhanced(boolean alreadyEnhanced) { 538 this.alreadyEnhanced = alreadyEnhanced; 539 } 540 541 public boolean hasDefaultConstructor() { 542 return hasDefaultConstructor; 543 } 544 545 public void setHasDefaultConstructor(boolean hasDefaultConstructor) { 546 this.hasDefaultConstructor = hasDefaultConstructor; 547 } 548 549 public void setHasStaticInit(boolean hasStaticInit) { 550 this.hasStaticInit = hasStaticInit; 551 } 552 553 public boolean hasStaticInit() { 554 return hasStaticInit; 555 } 556 557 public String description() { 558 StringBuilder sb = new StringBuilder(); 559 appendDescription(sb); 560 return sb.toString(); 561 } 562 563 private void appendDescription(StringBuilder sb) { 564 sb.append(className); 565 if (superMeta != null) { 566 sb.append(" : "); 567 superMeta.appendDescription(sb); 568 } 569 } 570 571 private boolean hasScalaInterface() { 572 return hasScalaInterface; 573 } 574 575 public void setScalaInterface(boolean hasScalaInterface) { 576 this.hasScalaInterface = hasScalaInterface; 577 } 578 579 public boolean hasEntityBeanInterface() { 580 return hasEntityBeanInterface; 581 } 582 583 public void setEntityBeanInterface(boolean hasEntityBeanInterface) { 584 this.hasEntityBeanInterface = hasEntityBeanInterface; 585 } 586 587 private boolean hasGroovyInterface() { 588 return hasGroovyInterface; 589 } 590 591 public void setGroovyInterface(boolean hasGroovyInterface) { 592 this.hasGroovyInterface = hasGroovyInterface; 593 } 594 595}