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