001package io.ebean.enhance.entity; 002 003import io.ebean.enhance.asm.ClassVisitor; 004import io.ebean.enhance.asm.Label; 005import io.ebean.enhance.asm.MethodVisitor; 006import io.ebean.enhance.asm.Opcodes; 007import io.ebean.enhance.asm.Type; 008import io.ebean.enhance.common.ClassMeta; 009import io.ebean.enhance.common.EnhanceConstants; 010import io.ebean.enhance.common.VisitUtil; 011 012import java.util.HashSet; 013 014/** 015 * Holds meta data for a field. 016 * <p> 017 * This can then generate the appropriate byte code for this field. 018 * </p> 019 */ 020public class FieldMeta implements Opcodes, EnhanceConstants { 021 022 private final ClassMeta classMeta; 023 private final String fieldClass; 024 private final String fieldName; 025 private final String fieldDesc; 026 027 private final HashSet<String> annotations = new HashSet<>(); 028 029 private final Type asmType; 030 031 private final boolean primitiveType; 032 private final boolean objectType; 033 034 private final String getMethodName; 035 private final String getMethodDesc; 036 private final String setMethodName; 037 private final String setMethodDesc; 038 private final String getNoInterceptMethodName; 039 private final String setNoInterceptMethodName; 040 041 private int indexPosition; 042 043 /** 044 * Construct based on field name and desc from reading byte code. 045 * <p> 046 * Used for reading local fields (not inherited) via visiting the class bytes. 047 * </p> 048 */ 049 public FieldMeta(ClassMeta classMeta, String name, String desc, String fieldClass) { 050 051 this.classMeta = classMeta; 052 this.fieldName = name; 053 this.fieldDesc = desc; 054 this.fieldClass = fieldClass; 055 this.asmType = Type.getType(desc); 056 057 int sort = asmType.getSort(); 058 this.primitiveType = sort > Type.VOID && sort <= Type.DOUBLE; 059 this.objectType = sort == Type.OBJECT; 060 061 this.getMethodDesc = "()" + desc; 062 this.setMethodDesc = "(" + desc + ")V"; 063 064 this.getMethodName = "_ebean_get_" + name; 065 this.setMethodName = "_ebean_set_" + name; 066 067 this.getNoInterceptMethodName = "_ebean_getni_" + name; 068 this.setNoInterceptMethodName = "_ebean_setni_" + name; 069 } 070 071 public void setIndexPosition(int indexPosition) { 072 this.indexPosition = indexPosition; 073 } 074 075 @Override 076 public String toString() { 077 return fieldName; 078 } 079 080 /** 081 * Return the field name. 082 */ 083 public String getFieldName() { 084 return fieldName; 085 } 086 087 /** 088 * Return true if this is a primitiveType. 089 */ 090 public boolean isPrimitiveType() { 091 return primitiveType; 092 } 093 094 /** 095 * Add a field annotation. 096 */ 097 void addAnnotationDesc(String desc) { 098 annotations.add(desc); 099 } 100 101 /** 102 * Return the field name. 103 */ 104 public String getName() { 105 return fieldName; 106 } 107 108 private boolean isInterceptGet() { 109 if (isId()) { 110 return false; 111 } 112 if (isTransient()) { 113 return false; 114 } 115 if (isMany()) { 116 return true; 117 } 118 return true; 119 } 120 121 private boolean isInterceptSet() { 122 return !isId() && !isTransient() && !isMany(); 123 } 124 125 /** 126 * Return true if this field type is an Array of Objects. 127 * <p> 128 * We can not support Object Arrays for field types. 129 * </p> 130 */ 131 public boolean isObjectArray() { 132 if (fieldDesc.charAt(0) == '[') { 133 if (fieldDesc.length() > 2) { 134 if (!isTransient()) { 135 System.err.println("ERROR: We can not support Object Arrays... for field: " + fieldName); 136 } 137 return true; 138 } 139 } 140 return false; 141 } 142 143 /** 144 * Return true is this is a persistent field. 145 */ 146 public boolean isPersistent() { 147 return !isTransient(); 148 } 149 150 /** 151 * Return true if this is a transient field. 152 */ 153 public boolean isTransient() { 154 return annotations.contains("Ljavax/persistence/Transient;") 155 || annotations.contains(L_DRAFT) 156 || fieldName.equals(MOCKITO_INTERCEPTOR); 157 } 158 159 /** 160 * Return true if this is an ID field. 161 * <p> 162 * ID fields are used in generating equals() logic based on identity. 163 * </p> 164 */ 165 public boolean isId() { 166 return (annotations.contains("Ljavax/persistence/Id;") 167 || annotations.contains("Ljavax/persistence/EmbeddedId;")); 168 } 169 170 /** 171 * Return true if this is a OneToMany or ManyToMany field. 172 */ 173 public boolean isMany() { 174 return annotations.contains("Ljavax/persistence/OneToMany;") 175 || annotations.contains("Ljavax/persistence/ManyToMany;"); 176 } 177 178 private boolean isManyToMany() { 179 return annotations.contains("Ljavax/persistence/ManyToMany;"); 180 } 181 182 /** 183 * Return true if this is an Embedded field. 184 */ 185 boolean isEmbedded() { 186 return annotations.contains("Ljavax/persistence/Embedded;"); 187 } 188 189 /** 190 * Return true if the field is local to this class. Returns false if the field 191 * is actually on a super class. 192 */ 193 boolean isLocalField(ClassMeta classMeta) { 194 return fieldClass.equals(classMeta.getClassName()); 195 } 196 197 /** 198 * Append byte code to return the Id value (for primitives). 199 */ 200 void appendGetPrimitiveIdValue(MethodVisitor mv, ClassMeta classMeta) { 201 mv.visitMethodInsn(INVOKEVIRTUAL, classMeta.getClassName(), getMethodName, getMethodDesc, false); 202 } 203 204 /** 205 * Append compare instructions if its a long, float or double. 206 */ 207 void appendCompare(MethodVisitor mv, ClassMeta classMeta) { 208 if (primitiveType) { 209 if (classMeta.isLog(4)) { 210 classMeta.log(" ... getIdentity compare primitive field[" + fieldName + "] type[" + fieldDesc + "]"); 211 } 212 if (fieldDesc.equals("J")) { 213 // long compare to 0 214 mv.visitInsn(LCONST_0); 215 mv.visitInsn(LCMP); 216 217 } else if (fieldDesc.equals("D")) { 218 // double compare to 0 219 mv.visitInsn(DCONST_0); 220 mv.visitInsn(DCMPL); 221 222 } else if (fieldDesc.equals("F")) { 223 // float compare to 0 224 mv.visitInsn(FCONST_0); 225 mv.visitInsn(FCMPL); 226 227 } 228 // no extra instructions required for 229 // int, short, byte, char 230 } 231 } 232 233 /** 234 * Append code to get the Object value of a primitive. 235 * <p> 236 * This becomes a Integer.valueOf(someInt); or similar. 237 * </p> 238 */ 239 void appendValueOf(MethodVisitor mv) { 240 if (primitiveType) { 241 // use valueOf methods to return primitives as objects 242 Type objectWrapperType = PrimitiveHelper.getObjectWrapper(asmType); 243 244 String objDesc = objectWrapperType.getInternalName(); 245 String primDesc = asmType.getDescriptor(); 246 247 mv.visitMethodInsn(Opcodes.INVOKESTATIC, objDesc, "valueOf", "(" + primDesc + ")L" + objDesc + ";", false); 248 } 249 } 250 251 /** 252 * As part of the switch statement to read the fields generate the get code. 253 */ 254 void appendSwitchGet(MethodVisitor mv, ClassMeta classMeta, boolean intercept) { 255 256 if (intercept) { 257 // use the special get method with interception... 258 mv.visitMethodInsn(INVOKEVIRTUAL, classMeta.getClassName(), getMethodName, getMethodDesc, false); 259 } else { 260 if (isLocalField(classMeta)) { 261 mv.visitFieldInsn(GETFIELD, classMeta.getClassName(), fieldName, fieldDesc); 262 } else { 263 // field is on a superclass... so use virtual getNoInterceptMethodName 264 mv.visitMethodInsn(INVOKEVIRTUAL, classMeta.getClassName(), getNoInterceptMethodName, getMethodDesc, false); 265 } 266 } 267 268 if (primitiveType) { 269 appendValueOf(mv); 270 } 271 } 272 273 void appendSwitchSet(MethodVisitor mv, ClassMeta classMeta, boolean intercept) { 274 275 if (primitiveType) { 276 // convert Object to primitive first... 277 Type objectWrapperType = PrimitiveHelper.getObjectWrapper(asmType); 278 279 String primDesc = asmType.getDescriptor(); 280 String primType = asmType.getClassName(); 281 String objInt = objectWrapperType.getInternalName(); 282 mv.visitTypeInsn(CHECKCAST, objInt); 283 284 mv.visitMethodInsn(INVOKEVIRTUAL, objInt, primType + "Value", "()" + primDesc, false); 285 } else { 286 // check correct object type 287 mv.visitTypeInsn(CHECKCAST, asmType.getInternalName()); 288 } 289 290 if (intercept) { 291 // go through the set method to check for interception... 292 mv.visitMethodInsn(INVOKEVIRTUAL, classMeta.getClassName(), setMethodName, setMethodDesc, false); 293 294 } else { 295 mv.visitMethodInsn(INVOKEVIRTUAL, classMeta.getClassName(), setNoInterceptMethodName, setMethodDesc, false); 296 } 297 } 298 299 300 /** 301 * Add get and set methods for field access/interception. 302 */ 303 public void addGetSetMethods(ClassVisitor cv, ClassMeta classMeta) { 304 305 if (!isLocalField(classMeta)) { 306 String msg = "ERROR: " + fieldClass + " != " + classMeta.getClassName() + " for field " 307 + fieldName + " " + fieldDesc; 308 throw new RuntimeException(msg); 309 } 310 // add intercepting methods that are used to replace the 311 // standard GETFIELD PUTFIELD byte codes for field access 312 addGet(cv, classMeta); 313 addSet(cv, classMeta); 314 315 // add non-interception methods... so that we can get access 316 // to private fields on super classes 317 addGetNoIntercept(cv, classMeta); 318 addSetNoIntercept(cv, classMeta); 319 } 320 321 private String getEbeanCollectionClass() { 322 if (fieldDesc.equals("Ljava/util/List;")) { 323 return BEANLIST; 324 } 325 if (fieldDesc.equals("Ljava/util/Set;")) { 326 return BEANSET; 327 } 328 if (fieldDesc.equals("Ljava/util/Map;")) { 329 return BEANMAP; 330 } 331 return null; 332 } 333 334 /** 335 * Return true if null check should be added to this many field. 336 */ 337 private boolean isInterceptMany() { 338 339 if (isMany() && !isTransient()) { 340 341 String ebCollection = getEbeanCollectionClass(); 342 if (ebCollection != null) { 343 return true; 344 } else { 345 classMeta.log("Error unexpected many type " + fieldDesc); 346 } 347 } 348 return false; 349 } 350 351 /** 352 * Add a get field method with interception. 353 */ 354 private void addGet(ClassVisitor cw, ClassMeta classMeta) { 355 356 if (classMeta.isLog(3)) { 357 classMeta.log(getMethodName + " " + getMethodDesc + " intercept:" + isInterceptGet() + " " + annotations); 358 } 359 360 MethodVisitor mv = cw.visitMethod(ACC_PROTECTED + ACC_SYNTHETIC, getMethodName, getMethodDesc, null, null); 361 mv.visitCode(); 362 363 if (isInterceptMany()) { 364 addGetForMany(mv); 365 return; 366 } 367 368 // ARETURN or IRETURN 369 int iReturnOpcode = asmType.getOpcode(Opcodes.IRETURN); 370 371 String className = classMeta.getClassName(); 372 373 Label labelEnd = new Label(); 374 Label labelStart = null; 375 376 int maxVars = 1; 377 if (isId()) { 378 labelStart = new Label(); 379 mv.visitLabel(labelStart); 380 mv.visitLineNumber(5, labelStart); 381 mv.visitVarInsn(ALOAD, 0); 382 mv.visitFieldInsn(GETFIELD, className, INTERCEPT_FIELD, L_INTERCEPT); 383 mv.visitMethodInsn(INVOKEVIRTUAL, C_INTERCEPT, "preGetId", NOARG_VOID, false); 384 385 } else if (isInterceptGet()) { 386 maxVars = 2; 387 labelStart = new Label(); 388 mv.visitLabel(labelStart); 389 mv.visitLineNumber(6, labelStart); 390 mv.visitVarInsn(ALOAD, 0); 391 mv.visitFieldInsn(GETFIELD, className, INTERCEPT_FIELD, L_INTERCEPT); 392 VisitUtil.visitIntInsn(mv, indexPosition); 393 mv.visitMethodInsn(INVOKEVIRTUAL, C_INTERCEPT, "preGetter", "(I)V", false); 394 } 395 if (labelStart == null) { 396 labelStart = labelEnd; 397 } 398 mv.visitLabel(labelEnd); 399 mv.visitLineNumber(7, labelEnd); 400 mv.visitVarInsn(ALOAD, 0); 401 mv.visitFieldInsn(GETFIELD, className, fieldName, fieldDesc); 402 mv.visitInsn(iReturnOpcode);// ARETURN or IRETURN 403 Label labelEnd1 = new Label(); 404 mv.visitLabel(labelEnd1); 405 mv.visitLocalVariable("this", "L" + className + ";", null, labelStart, labelEnd1, 0); 406 mv.visitMaxs(maxVars, 1); 407 mv.visitEnd(); 408 } 409 410 private void addGetForMany(MethodVisitor mv) { 411 412 String className = classMeta.getClassName(); 413 String ebCollection = getEbeanCollectionClass(); 414 415 Label l0 = new Label(); 416 mv.visitLabel(l0); 417 mv.visitLineNumber(1, l0); 418 mv.visitVarInsn(ALOAD, 0); 419 mv.visitFieldInsn(GETFIELD, className, INTERCEPT_FIELD, L_INTERCEPT); 420 VisitUtil.visitIntInsn(mv, indexPosition); 421 mv.visitMethodInsn(INVOKEVIRTUAL, C_INTERCEPT, "preGetter", "(I)V", false); 422 423 Label l4 = new Label(); 424 if (classMeta.getEnhanceContext().isCheckNullManyFields()) { 425 426 if (classMeta.isLog(3)) { 427 classMeta.log("... add Many null check on " + fieldName + " ebtype:" + ebCollection); 428 } 429 430 Label l3 = new Label(); 431 mv.visitLabel(l3); 432 mv.visitLineNumber(2, l3); 433 mv.visitVarInsn(ALOAD, 0); 434 mv.visitFieldInsn(GETFIELD, className, fieldName, fieldDesc); 435 436 mv.visitJumpInsn(IFNONNULL, l4); 437 Label l5 = new Label(); 438 mv.visitLabel(l5); 439 mv.visitLineNumber(3, l5); 440 mv.visitVarInsn(ALOAD, 0); 441 mv.visitTypeInsn(NEW, ebCollection); 442 mv.visitInsn(DUP); 443 mv.visitMethodInsn(INVOKESPECIAL, ebCollection, INIT, NOARG_VOID, false); 444 mv.visitFieldInsn(PUTFIELD, className, fieldName, fieldDesc); 445 446 mv.visitVarInsn(ALOAD, 0); 447 mv.visitFieldInsn(GETFIELD, className, INTERCEPT_FIELD, L_INTERCEPT); 448 VisitUtil.visitIntInsn(mv, indexPosition); 449 mv.visitMethodInsn(INVOKEVIRTUAL, C_INTERCEPT, "initialisedMany", "(I)V", false); 450 451 if (isManyToMany()) { 452 // turn on modify listening for ManyToMany 453 if (classMeta.isLog(3)) { 454 classMeta.log("... add ManyToMany modify listening to " + fieldName); 455 } 456 457 Label l6 = new Label(); 458 mv.visitLabel(l6); 459 mv.visitLineNumber(4, l6); 460 mv.visitVarInsn(ALOAD, 0); 461 mv.visitFieldInsn(GETFIELD, className, fieldName, fieldDesc); 462 mv.visitTypeInsn(CHECKCAST, C_BEANCOLLECTION); 463 mv.visitFieldInsn(GETSTATIC, C_BEANCOLLECTION + "$ModifyListenMode", "ALL", "L" + C_BEANCOLLECTION + "$ModifyListenMode;"); 464 mv.visitMethodInsn(INVOKEINTERFACE, C_BEANCOLLECTION, "setModifyListening", "(L" + C_BEANCOLLECTION + "$ModifyListenMode;)V", true); 465 } 466 } 467 468 469 mv.visitLabel(l4); 470 mv.visitLineNumber(5, l4); 471 mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 472 mv.visitVarInsn(ALOAD, 0); 473 mv.visitFieldInsn(GETFIELD, className, fieldName, fieldDesc); 474 mv.visitInsn(ARETURN); 475 Label l7 = new Label(); 476 mv.visitLabel(l7); 477 mv.visitLocalVariable("this", "L" + className + ";", null, l0, l7, 0); 478 mv.visitMaxs(3, 1); 479 mv.visitEnd(); 480 } 481 482 /** 483 * This is a get method with no interception. 484 * <p> 485 * It exists to be able to read private fields that are on super classes. 486 * </p> 487 */ 488 private void addGetNoIntercept(ClassVisitor cw, ClassMeta classMeta) { 489 490 // ARETURN or IRETURN 491 int iReturnOpcode = asmType.getOpcode(Opcodes.IRETURN); 492 493 if (classMeta.isLog(3)) { 494 classMeta.log(getNoInterceptMethodName + " " + getMethodDesc); 495 } 496 497 MethodVisitor mv = cw.visitMethod(ACC_PROTECTED + ACC_SYNTHETIC, getNoInterceptMethodName, getMethodDesc, null, null); 498 mv.visitCode(); 499 500 Label l0 = new Label(); 501 mv.visitLabel(l0); 502 mv.visitLineNumber(1, l0); 503 mv.visitVarInsn(ALOAD, 0); 504 mv.visitFieldInsn(GETFIELD, fieldClass, fieldName, fieldDesc); 505 mv.visitInsn(iReturnOpcode);// ARETURN or IRETURN 506 Label l2 = new Label(); 507 mv.visitLabel(l2); 508 mv.visitLocalVariable("this", "L" + fieldClass + ";", null, l0, l2, 0); 509 mv.visitMaxs(2, 1); 510 mv.visitEnd(); 511 } 512 513 /** 514 * Setter method with interception. 515 * 516 * <pre> 517 * public void _ebean_set_propname(String newValue) { 518 * ebi.preSetter(true, propertyIndex, _ebean_get_propname(), newValue); 519 * this.propname = newValue; 520 * } 521 * </pre> 522 */ 523 private void addSet(ClassVisitor cw, ClassMeta classMeta) { 524 525 String preSetterArgTypes = "Ljava/lang/Object;Ljava/lang/Object;"; 526 if (!objectType) { 527 // preSetter method overloaded for primitive type comparison 528 preSetterArgTypes = fieldDesc + fieldDesc; 529 } 530 531 // ALOAD or ILOAD etc 532 int iLoadOpcode = asmType.getOpcode(Opcodes.ILOAD); 533 534 // double and long have a size of 2 535 int iPosition = asmType.getSize(); 536 537 if (classMeta.isLog(3)) { 538 classMeta.log(setMethodName + " " + setMethodDesc + " intercept:" + isInterceptSet() 539 + " opCode:" + iLoadOpcode + "," + iPosition + " preSetterArgTypes" + preSetterArgTypes); 540 } 541 542 MethodVisitor mv = cw.visitMethod(ACC_PROTECTED + ACC_SYNTHETIC, setMethodName, setMethodDesc, null, null); 543 mv.visitCode(); 544 545 Label l0 = new Label(); 546 mv.visitLabel(l0); 547 mv.visitLineNumber(1, l0); 548 mv.visitVarInsn(ALOAD, 0); 549 mv.visitFieldInsn(GETFIELD, fieldClass, INTERCEPT_FIELD, L_INTERCEPT); 550 if (isInterceptSet()) { 551 mv.visitInsn(ICONST_1); 552 } else { 553 // id or OneToMany field etc 554 mv.visitInsn(ICONST_0); 555 } 556 VisitUtil.visitIntInsn(mv, indexPosition); 557 mv.visitVarInsn(ALOAD, 0); 558 if (isId()) { 559 // skip getter on Id as we now intercept that via preGetId() for automatic jdbc batch flushing 560 mv.visitFieldInsn(GETFIELD, fieldClass, fieldName, fieldDesc); 561 } else { 562 mv.visitMethodInsn(INVOKEVIRTUAL, fieldClass, getMethodName, getMethodDesc, false); 563 } 564 mv.visitVarInsn(iLoadOpcode, 1); 565 String preSetterMethod = "preSetter"; 566 if (isMany()) { 567 preSetterMethod = "preSetterMany"; 568 } 569 mv.visitMethodInsn(INVOKEVIRTUAL, C_INTERCEPT, preSetterMethod, "(ZI" + preSetterArgTypes + ")V", false); 570 Label l1 = new Label(); 571 mv.visitLabel(l1); 572 mv.visitLineNumber(2, l1); 573 mv.visitVarInsn(ALOAD, 0); 574 mv.visitVarInsn(iLoadOpcode, 1);// ALOAD or ILOAD 575 mv.visitFieldInsn(PUTFIELD, fieldClass, fieldName, fieldDesc); 576 577 Label l3 = new Label(); 578 mv.visitLabel(l3); 579 mv.visitLineNumber(4, l3); 580 mv.visitInsn(RETURN); 581 Label l4 = new Label(); 582 mv.visitLabel(l4); 583 mv.visitLocalVariable("this", "L" + fieldClass + ";", null, l0, l4, 0); 584 mv.visitLocalVariable("newValue", fieldDesc, null, l0, l4, 1); 585 mv.visitMaxs(5, 2); 586 mv.visitEnd(); 587 } 588 589 /** 590 * Add a non-intercepting field set method. 591 * <p> 592 * So we can set private fields on super classes. 593 * </p> 594 */ 595 private void addSetNoIntercept(ClassVisitor cw, ClassMeta classMeta) { 596 597 // ALOAD or ILOAD etc 598 int iLoadOpcode = asmType.getOpcode(Opcodes.ILOAD); 599 600 // double and long have a size of 2 601 int iPosition = asmType.getSize(); 602 603 if (classMeta.isLog(3)) { 604 classMeta.log(setNoInterceptMethodName + " " + setMethodDesc + " opCode:" + iLoadOpcode + "," + iPosition); 605 } 606 607 MethodVisitor mv = cw.visitMethod(ACC_PROTECTED + ACC_SYNTHETIC, setNoInterceptMethodName, setMethodDesc, null, null); 608 mv.visitCode(); 609 Label l0 = new Label(); 610 611 mv.visitLabel(l0); 612 mv.visitLineNumber(1, l0); 613 mv.visitVarInsn(ALOAD, 0); 614 mv.visitVarInsn(iLoadOpcode, 1);// ALOAD or ILOAD 615 mv.visitFieldInsn(PUTFIELD, fieldClass, fieldName, fieldDesc); 616 617 Label l1 = new Label(); 618 mv.visitLabel(l1); 619 mv.visitLineNumber(2, l1); 620 mv.visitVarInsn(ALOAD, 0); 621 mv.visitFieldInsn(GETFIELD, fieldClass, INTERCEPT_FIELD, L_INTERCEPT); 622 VisitUtil.visitIntInsn(mv, indexPosition); 623 mv.visitMethodInsn(INVOKEVIRTUAL, C_INTERCEPT, "setLoadedProperty", "(I)V", false); 624 625 Label l2 = new Label(); 626 mv.visitLabel(l2); 627 mv.visitLineNumber(1, l2); 628 mv.visitInsn(RETURN); 629 Label l3 = new Label(); 630 mv.visitLabel(l3); 631 mv.visitLocalVariable("this", "L" + fieldClass + ";", null, l0, l3, 0); 632 mv.visitLocalVariable("_newValue", fieldDesc, null, l0, l3, 1); 633 mv.visitMaxs(4, 2); 634 mv.visitEnd(); 635 } 636 637}