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 or MappedSuperclass. 322 */ 323 public boolean isEntity() { 324 return EntityCheck.hasEntityAnnotation(classAnnotation); 325 } 326 327 /** 328 * Return true if the class has an Entity, Embeddable, or MappedSuperclass. 329 */ 330 private boolean isCheckEntity() { 331 return EntityCheck.hasEntityAnnotation(classAnnotation); 332 } 333 334 /** 335 * Return true for classes not already enhanced and yet annotated with entity, embeddable or mappedSuperclass. 336 */ 337 public boolean isEntityEnhancementRequired() { 338 return !alreadyEnhanced && isEntity(); 339 } 340 341 /** 342 * Return true if the bean is already enhanced. 343 */ 344 public boolean isAlreadyEnhanced() { 345 return alreadyEnhanced; 346 } 347 348 /** 349 * Return the className of this entity class. 350 */ 351 public String getClassName() { 352 return className; 353 } 354 355 /** 356 * Return true if this entity bean has a super class that is an entity. 357 */ 358 public boolean isSuperClassEntity() { 359 return superMeta != null && superMeta.isEntity(); 360 } 361 362 /** 363 * Add a class annotation. 364 */ 365 public void addClassAnnotation(String desc) { 366 classAnnotation.add(desc); 367 } 368 369 MethodVisitor createMethodVisitor(MethodVisitor mv, String name, String desc) { 370 371 MethodMeta methodMeta = new MethodMeta(annotationInfo, name, desc); 372 methodMetaList.add(methodMeta); 373 return new MethodReader(mv, methodMeta); 374 } 375 376 /** 377 * ACC_PUBLIC with maybe ACC_SYNTHETIC. 378 */ 379 public int accPublic() { 380 return enhanceContext.accPublic(); 381 } 382 383 /** 384 * ACC_PROTECTED with maybe ACC_SYNTHETIC. 385 */ 386 public int accProtected() { 387 return enhanceContext.accProtected(); 388 } 389 390 /** 391 * ACC_PRIVATE with maybe ACC_SYNTHETIC. 392 */ 393 public int accPrivate() { 394 return enhanceContext.accPrivate(); 395 } 396 397 public boolean isToManyGetField() { 398 return enhanceContext.isToManyGetField(); 399 } 400 401 /** 402 * Return the EntityBeanIntercept type that will be new'ed up for the EntityBean. 403 * For version 140+ EntityBeanIntercept is an interface and instead we new up InterceptReadWrite. 404 */ 405 public String interceptNew() { 406 return enhanceContext.interceptNew(); 407 } 408 409 /** 410 * Invoke a method on EntityBeanIntercept. 411 * For version 140+ EntityBeanIntercept is an interface and this uses INVOKEINTERFACE. 412 */ 413 public void visitMethodInsnIntercept(MethodVisitor mv, String name, String desc) { 414 enhanceContext.visitMethodInsnIntercept(mv, name, desc); 415 } 416 417 /** 418 * If 141+ Add InterceptReadOnly support. 419 */ 420 public boolean interceptAddReadOnly() { 421 return enhanceContext.interceptAddReadOnly(); 422 } 423 424 private static final class MethodReader extends MethodVisitor { 425 426 final MethodMeta methodMeta; 427 428 MethodReader(MethodVisitor mv, MethodMeta methodMeta) { 429 super(EBEAN_ASM_VERSION, mv); 430 this.methodMeta = methodMeta; 431 } 432 433 @Override 434 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 435 AnnotationVisitor av = null; 436 if (mv != null) { 437 av = mv.visitAnnotation(desc, visible); 438 } 439 if (!isInterestingAnnotation(desc)) { 440 return av; 441 } 442 return new AnnotationInfoVisitor(null, methodMeta.getAnnotationInfo(), av); 443 } 444 445 private boolean isInterestingAnnotation(String desc) { 446 return TRANSACTIONAL_ANNOTATION.equals(desc) 447 || TYPEQUERYBEAN_ANNOTATION.equals(desc); 448 } 449 } 450 451 /** 452 * Create and return a read only fieldVisitor for subclassing option. 453 */ 454 FieldVisitor createLocalFieldVisitor(String name, String desc) { 455 return createLocalFieldVisitor(null, name, desc); 456 } 457 458 /** 459 * Create and return a new fieldVisitor for use when enhancing a class. 460 */ 461 public FieldVisitor createLocalFieldVisitor(FieldVisitor fv, String name, String desc) { 462 463 FieldMeta fieldMeta = new FieldMeta(this, name, desc, className); 464 LocalFieldVisitor localField = new LocalFieldVisitor(fv, fieldMeta); 465 if (name.startsWith("_ebean")) { 466 // can occur when reading inheritance information on 467 // a entity that has already been enhanced 468 if (isLog(5)) { 469 log("... ignore field " + name); 470 } 471 } else { 472 fields.put(localField.getName(), fieldMeta); 473 } 474 return localField; 475 } 476 477 public void setAlreadyEnhanced(boolean alreadyEnhanced) { 478 this.alreadyEnhanced = alreadyEnhanced; 479 } 480 481 public boolean hasDefaultConstructor() { 482 return hasDefaultConstructor; 483 } 484 485 public void setHasDefaultConstructor(boolean hasDefaultConstructor) { 486 this.hasDefaultConstructor = hasDefaultConstructor; 487 } 488 489 public void setHasStaticInit(boolean hasStaticInit) { 490 this.hasStaticInit = hasStaticInit; 491 } 492 493 public boolean hasStaticInit() { 494 return hasStaticInit; 495 } 496 497 public String getDescription() { 498 StringBuilder sb = new StringBuilder(); 499 appendDescription(sb); 500 return sb.toString(); 501 } 502 503 private void appendDescription(StringBuilder sb) { 504 sb.append(className); 505 if (superMeta != null) { 506 sb.append(" : "); 507 superMeta.appendDescription(sb); 508 } 509 } 510 511 private boolean hasScalaInterface() { 512 return hasScalaInterface; 513 } 514 515 public void setScalaInterface(boolean hasScalaInterface) { 516 this.hasScalaInterface = hasScalaInterface; 517 } 518 519 public boolean hasEntityBeanInterface() { 520 return hasEntityBeanInterface; 521 } 522 523 public void setEntityBeanInterface(boolean hasEntityBeanInterface) { 524 this.hasEntityBeanInterface = hasEntityBeanInterface; 525 } 526 527 private boolean hasGroovyInterface() { 528 return hasGroovyInterface; 529 } 530 531 public void setGroovyInterface(boolean hasGroovyInterface) { 532 this.hasGroovyInterface = hasGroovyInterface; 533 } 534 535}