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