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