001 // Copyright 2011, 2012 The Apache Software Foundation 002 // 003 // Licensed under the Apache License, Version 2.0 (the "License"); 004 // you may not use this file except in compliance with the License. 005 // You may obtain a copy of the License at 006 // 007 // http://www.apache.org/licenses/LICENSE-2.0 008 // 009 // Unless required by applicable law or agreed to in writing, software 010 // distributed under the License is distributed on an "AS IS" BASIS, 011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 // See the License for the specific language governing permissions and 013 // limitations under the License. 014 015 package org.apache.tapestry5.internal.plastic; 016 017 import org.apache.tapestry5.internal.plastic.asm.Opcodes; 018 import org.apache.tapestry5.internal.plastic.asm.Type; 019 import org.apache.tapestry5.internal.plastic.asm.tree.*; 020 import org.apache.tapestry5.plastic.*; 021 022 import java.lang.annotation.Annotation; 023 import java.lang.reflect.Constructor; 024 import java.lang.reflect.Method; 025 import java.lang.reflect.Modifier; 026 import java.util.*; 027 028 @SuppressWarnings("all") 029 public class PlasticClassImpl extends Lockable implements PlasticClass, InternalPlasticClassTransformation, Opcodes 030 { 031 private static final String NOTHING_TO_VOID = "()V"; 032 033 static final String CONSTRUCTOR_NAME = "<init>"; 034 035 private static final String OBJECT_INT_TO_OBJECT = "(Ljava/lang/Object;I)Ljava/lang/Object;"; 036 037 private static final String OBJECT_INT_OBJECT_TO_VOID = "(Ljava/lang/Object;ILjava/lang/Object;)V"; 038 039 private static final String OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT = String.format( 040 "(Ljava/lang/Object;I[Ljava/lang/Object;)%s", toDesc(Type.getInternalName(MethodInvocationResult.class))); 041 042 static final String ABSTRACT_METHOD_INVOCATION_INTERNAL_NAME = PlasticInternalUtils 043 .toInternalName(AbstractMethodInvocation.class.getName()); 044 045 private static final String HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME = Type 046 .getInternalName(PlasticClassHandleShim.class); 047 048 static final String STATIC_CONTEXT_INTERNAL_NAME = Type.getInternalName(StaticContext.class); 049 050 private static final String INSTANCE_CONTEXT_INTERNAL_NAME = Type.getInternalName(InstanceContext.class); 051 052 private static final String INSTANCE_CONTEXT_DESC = toDesc(INSTANCE_CONTEXT_INTERNAL_NAME); 053 054 private static final String CONSTRUCTOR_DESC = String.format("(L%s;L%s;)V", STATIC_CONTEXT_INTERNAL_NAME, 055 INSTANCE_CONTEXT_INTERNAL_NAME); 056 057 static final Method STATIC_CONTEXT_GET_METHOD = toMethod(StaticContext.class, "get", int.class); 058 059 static final Method COMPUTED_VALUE_GET_METHOD = toMethod(ComputedValue.class, "get", InstanceContext.class); 060 061 private static final Method CONSTRUCTOR_CALLBACK_METHOD = toMethod(ConstructorCallback.class, "onConstruct", 062 Object.class, InstanceContext.class); 063 064 private static String toDesc(String internalName) 065 { 066 return "L" + internalName + ";"; 067 } 068 069 private static Method toMethod(Class declaringClass, String methodName, Class... parameterTypes) 070 { 071 return PlasticUtils.getMethod(declaringClass, methodName, parameterTypes); 072 } 073 074 static <T> T safeArrayDeref(T[] array, int index) 075 { 076 if (array == null) 077 return null; 078 079 return array[index]; 080 } 081 082 // Now past the inner classes; these are the instance variables of PlasticClassImpl proper: 083 084 final ClassNode classNode; 085 086 final PlasticClassPool pool; 087 088 private final boolean proxy; 089 090 final String className; 091 092 private final String superClassName; 093 094 private final AnnotationAccess annotationAccess; 095 096 // All the non-introduced (and non-constructor) methods, in sorted order 097 098 private final List<PlasticMethodImpl> methods; 099 100 private final Map<MethodDescription, PlasticMethod> description2method = new HashMap<MethodDescription, PlasticMethod>(); 101 102 final Set<String> methodNames = new HashSet<String>(); 103 104 private final List<ConstructorCallback> constructorCallbacks = PlasticInternalUtils.newList(); 105 106 // All non-introduced instance fields 107 108 private final List<PlasticFieldImpl> fields; 109 110 /** 111 * Methods that require special attention inside {@link #createInstantiator()} because they 112 * have method advice. 113 */ 114 final Set<PlasticMethodImpl> advisedMethods = PlasticInternalUtils.newSet(); 115 116 final NameCache nameCache = new NameCache(); 117 118 // This is generated from fields, as necessary 119 List<PlasticField> unclaimedFields; 120 121 private final Set<String> fieldNames = PlasticInternalUtils.newSet(); 122 123 final StaticContext staticContext; 124 125 final InheritanceData parentInheritanceData, inheritanceData; 126 127 // MethodNodes in which field transformations should occur; this is most existing and 128 // introduced methods, outside of special access methods. 129 130 final Set<MethodNode> fieldTransformMethods = PlasticInternalUtils.newSet(); 131 132 // Tracks any methods that the Shim class uses to gain access to fields; used to ensure that 133 // such methods are not optimized away incorrectly. 134 final Set<MethodNode> shimInvokedMethods = PlasticInternalUtils.newSet(); 135 136 137 /** 138 * Tracks instrumentations of fields of this class, including private fields which are not published into the 139 * {@link PlasticClassPool}. 140 */ 141 private final FieldInstrumentations fieldInstrumentations; 142 143 /** 144 * This normal no-arguments constructor, or null. By the end of the transformation 145 * this will be converted into an ordinary method. 146 */ 147 private MethodNode originalConstructor; 148 149 private final MethodNode newConstructor; 150 151 final InstructionBuilder constructorBuilder; 152 153 private String instanceContextFieldName; 154 155 private Class<?> transformedClass; 156 157 // Indexes used to identify fields or methods in the shim 158 int nextFieldIndex = 0; 159 160 int nextMethodIndex = 0; 161 162 // Set of fields that need to contribute to the shim and gain access to it 163 164 final Set<PlasticFieldImpl> shimFields = PlasticInternalUtils.newSet(); 165 166 // Set of methods that need to contribute to the shim and gain access to it 167 168 final Set<PlasticMethodImpl> shimMethods = PlasticInternalUtils.newSet(); 169 170 /** 171 * @param classNode 172 * @param pool 173 * @param parentInheritanceData 174 * @param parentStaticContext 175 * @param proxy 176 */ 177 public PlasticClassImpl(ClassNode classNode, PlasticClassPool pool, InheritanceData parentInheritanceData, 178 StaticContext parentStaticContext, boolean proxy) 179 { 180 this.classNode = classNode; 181 this.pool = pool; 182 this.proxy = proxy; 183 184 staticContext = parentStaticContext.dupe(); 185 186 className = PlasticInternalUtils.toClassName(classNode.name); 187 superClassName = PlasticInternalUtils.toClassName(classNode.superName); 188 189 fieldInstrumentations = new FieldInstrumentations(classNode.superName); 190 191 annotationAccess = new DelegatingAnnotationAccess(pool.createAnnotationAccess(classNode.visibleAnnotations), 192 pool.createAnnotationAccess(superClassName)); 193 194 this.parentInheritanceData = parentInheritanceData; 195 inheritanceData = parentInheritanceData.createChild(className); 196 197 for (String interfaceName : classNode.interfaces) 198 { 199 inheritanceData.addInterface(interfaceName); 200 } 201 202 methods = new ArrayList(classNode.methods.size()); 203 204 String invalidConstructorMessage = invalidConstructorMessage(); 205 206 for (MethodNode node : classNode.methods) 207 { 208 if (node.name.equals(CONSTRUCTOR_NAME)) 209 { 210 if (node.desc.equals(NOTHING_TO_VOID)) 211 { 212 originalConstructor = node; 213 fieldTransformMethods.add(node); 214 } else 215 { 216 node.instructions.clear(); 217 218 newBuilder(node).throwException(IllegalStateException.class, invalidConstructorMessage); 219 } 220 221 continue; 222 } 223 224 /** 225 * Static methods are not visible to the main API methods, but they must still be transformed, 226 * in case they directly access fields. In addition, track their names to avoid collisions. 227 */ 228 if (Modifier.isStatic(node.access)) 229 { 230 if (!Modifier.isPrivate(node.access)) 231 { 232 inheritanceData.addMethod(node.name, node.desc); 233 } 234 235 methodNames.add(node.name); 236 237 fieldTransformMethods.add(node); 238 239 continue; 240 } 241 242 if (!Modifier.isAbstract(node.access)) 243 { 244 fieldTransformMethods.add(node); 245 } 246 247 PlasticMethodImpl pmi = new PlasticMethodImpl(this, node); 248 249 methods.add(pmi); 250 description2method.put(pmi.getDescription(), pmi); 251 252 if (isInheritableMethod(node)) 253 { 254 inheritanceData.addMethod(node.name, node.desc); 255 } 256 257 methodNames.add(node.name); 258 } 259 260 methodNames.addAll(parentInheritanceData.methodNames()); 261 262 Collections.sort(methods); 263 264 fields = new ArrayList(classNode.fields.size()); 265 266 for (FieldNode node : classNode.fields) 267 { 268 fieldNames.add(node.name); 269 270 // Ignore static fields. 271 272 if (Modifier.isStatic(node.access)) 273 continue; 274 275 // When we instrument the field such that it must be private, we'll get an exception. 276 277 fields.add(new PlasticFieldImpl(this, node)); 278 } 279 280 Collections.sort(fields); 281 282 // TODO: Make the output class's constructor protected, and create a shim class to instantiate it 283 // efficiently (without reflection). 284 newConstructor = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, CONSTRUCTOR_DESC, null, null); 285 constructorBuilder = newBuilder(newConstructor); 286 287 // Start by calling the super-class no args constructor 288 289 if (parentInheritanceData.isTransformed()) 290 { 291 // If the parent is transformed, our first step is always to invoke its constructor. 292 293 constructorBuilder.loadThis().loadArgument(0).loadArgument(1); 294 constructorBuilder.invokeConstructor(superClassName, StaticContext.class.getName(), 295 InstanceContext.class.getName()); 296 } else 297 { 298 // Assumes the base class includes a visible constructor that takes no arguments. 299 // TODO: Do a proper check for this case and throw a meaningful exception 300 // if not present. 301 302 constructorBuilder.loadThis().invokeConstructor(superClassName); 303 } 304 305 // During the transformation, we'll be adding code to the constructor to pull values 306 // out of the static or instance context and assign them to fields. 307 308 // Later on, we'll add the RETURN opcode 309 } 310 311 private String invalidConstructorMessage() 312 { 313 return String.format("Class %s has been transformed and may not be directly instantiated.", className); 314 } 315 316 public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType) 317 { 318 check(); 319 320 return annotationAccess.hasAnnotation(annotationType); 321 } 322 323 public <T extends Annotation> T getAnnotation(Class<T> annotationType) 324 { 325 check(); 326 327 return annotationAccess.getAnnotation(annotationType); 328 } 329 330 public PlasticClass proxyInterface(Class interfaceType, PlasticField field) 331 { 332 check(); 333 334 assert field != null; 335 336 introduceInterface(interfaceType); 337 338 for (Method m : interfaceType.getMethods()) 339 { 340 introduceMethod(m).delegateTo(field); 341 } 342 343 return this; 344 } 345 346 public ClassInstantiator createInstantiator() 347 { 348 lock(); 349 350 createShimIfNeeded(); 351 352 interceptFieldAccess(); 353 354 rewriteAdvisedMethods(); 355 356 completeConstructor(); 357 358 transformedClass = pool.realizeTransformedClass(classNode, inheritanceData, staticContext); 359 360 return createInstantiatorFromClass(transformedClass); 361 } 362 363 private ClassInstantiator createInstantiatorFromClass(Class clazz) 364 { 365 try 366 { 367 Constructor ctor = clazz.getConstructor(StaticContext.class, InstanceContext.class); 368 369 return new ClassInstantiatorImpl(clazz, ctor, staticContext); 370 } catch (Exception ex) 371 { 372 throw new RuntimeException(String.format("Unable to create ClassInstantiator for class %s: %s", 373 clazz.getName(), PlasticInternalUtils.toMessage(ex)), ex); 374 } 375 } 376 377 private void completeConstructor() 378 { 379 if (originalConstructor != null) 380 { 381 convertOriginalConstructorToMethod(); 382 } 383 384 invokeCallbacks(); 385 386 constructorBuilder.returnResult(); 387 388 classNode.methods.add(newConstructor); 389 } 390 391 private void invokeCallbacks() 392 { 393 for (ConstructorCallback callback : constructorCallbacks) 394 { 395 invokeCallback(callback); 396 } 397 } 398 399 private void invokeCallback(ConstructorCallback callback) 400 { 401 int index = staticContext.store(callback); 402 403 // First, load the callback 404 405 constructorBuilder.loadArgument(0).loadConstant(index).invoke(STATIC_CONTEXT_GET_METHOD).castOrUnbox(ConstructorCallback.class.getName()); 406 407 // Load this and the InstanceContext 408 constructorBuilder.loadThis().loadArgument(1); 409 410 constructorBuilder.invoke(CONSTRUCTOR_CALLBACK_METHOD); 411 } 412 413 414 /** 415 * Convert the original constructor into a private method invoked from the 416 * generated constructor. 417 */ 418 private void convertOriginalConstructorToMethod() 419 { 420 String initializerName = makeUnique(methodNames, "initializeInstance"); 421 422 int originalAccess = originalConstructor.access; 423 424 originalConstructor.access = ACC_PRIVATE; 425 originalConstructor.name = initializerName; 426 427 stripOutSuperConstructorCall(originalConstructor); 428 429 constructorBuilder.loadThis().invokeVirtual(className, "void", initializerName); 430 431 // And replace it with a constructor that throws an exception 432 433 MethodNode replacementConstructor = new MethodNode(originalAccess, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null, 434 null); 435 436 newBuilder(replacementConstructor).throwException(IllegalStateException.class, invalidConstructorMessage()); 437 438 classNode.methods.add(replacementConstructor); 439 } 440 441 private void stripOutSuperConstructorCall(MethodNode cons) 442 { 443 InsnList ins = cons.instructions; 444 445 ListIterator li = ins.iterator(); 446 447 // Look for the ALOAD 0 (i.e., push this on the stack) 448 while (li.hasNext()) 449 { 450 AbstractInsnNode node = (AbstractInsnNode) li.next(); 451 452 if (node.getOpcode() == ALOAD) 453 { 454 VarInsnNode varNode = (VarInsnNode) node; 455 456 assert varNode.var == 0; 457 458 // Remove the ALOAD 459 li.remove(); 460 break; 461 } 462 } 463 464 // Look for the call to the super-class, an INVOKESPECIAL 465 while (li.hasNext()) 466 { 467 AbstractInsnNode node = (AbstractInsnNode) li.next(); 468 469 if (node.getOpcode() == INVOKESPECIAL) 470 { 471 MethodInsnNode mnode = (MethodInsnNode) node; 472 473 assert mnode.owner.equals(classNode.superName); 474 assert mnode.name.equals(CONSTRUCTOR_NAME); 475 assert mnode.desc.equals(cons.desc); 476 477 li.remove(); 478 return; 479 } 480 } 481 482 throw new AssertionError("Could not convert constructor to simple method."); 483 } 484 485 public <T extends Annotation> List<PlasticField> getFieldsWithAnnotation(Class<T> annotationType) 486 { 487 check(); 488 489 List<PlasticField> result = getAllFields(); 490 491 Iterator<PlasticField> iterator = result.iterator(); 492 493 while (iterator.hasNext()) 494 { 495 PlasticField plasticField = iterator.next(); 496 497 if (!plasticField.hasAnnotation(annotationType)) 498 iterator.remove(); 499 } 500 501 return result; 502 } 503 504 public List<PlasticField> getAllFields() 505 { 506 check(); 507 508 return new ArrayList<PlasticField>(fields); 509 } 510 511 public List<PlasticField> getUnclaimedFields() 512 { 513 check(); 514 515 // Initially null, and set back to null by PlasticField.claim(). 516 517 if (unclaimedFields == null) 518 { 519 unclaimedFields = new ArrayList<PlasticField>(fields.size()); 520 521 for (PlasticField f : fields) 522 { 523 if (!f.isClaimed()) 524 unclaimedFields.add(f); 525 } 526 } 527 528 return unclaimedFields; 529 } 530 531 public PlasticMethod introducePrivateMethod(String typeName, String suggestedName, String[] argumentTypes, 532 String[] exceptionTypes) 533 { 534 check(); 535 536 assert PlasticInternalUtils.isNonBlank(typeName); 537 assert PlasticInternalUtils.isNonBlank(suggestedName); 538 539 String name = makeUnique(methodNames, suggestedName); 540 541 MethodDescription description = new MethodDescription(Modifier.PRIVATE, typeName, name, argumentTypes, null, 542 exceptionTypes); 543 544 return introduceMethod(description); 545 } 546 547 public PlasticField introduceField(String className, String suggestedName) 548 { 549 check(); 550 551 assert PlasticInternalUtils.isNonBlank(className); 552 assert PlasticInternalUtils.isNonBlank(suggestedName); 553 554 String name = makeUnique(fieldNames, suggestedName); 555 556 // No signature and no initial value 557 558 FieldNode fieldNode = new FieldNode(ACC_PRIVATE, name, PlasticInternalUtils.toDescriptor(className), null, null); 559 560 classNode.fields.add(fieldNode); 561 562 fieldNames.add(name); 563 564 PlasticFieldImpl newField = new PlasticFieldImpl(this, fieldNode); 565 566 return newField; 567 } 568 569 public PlasticField introduceField(Class fieldType, String suggestedName) 570 { 571 assert fieldType != null; 572 573 return introduceField(nameCache.toTypeName(fieldType), suggestedName); 574 } 575 576 String makeUnique(Set<String> values, String input) 577 { 578 return values.contains(input) ? input + "$" + PlasticUtils.nextUID() : input; 579 } 580 581 public <T extends Annotation> List<PlasticMethod> getMethodsWithAnnotation(Class<T> annotationType) 582 { 583 check(); 584 585 List<PlasticMethod> result = getMethods(); 586 Iterator<PlasticMethod> iterator = result.iterator(); 587 588 while (iterator.hasNext()) 589 { 590 PlasticMethod method = iterator.next(); 591 592 if (!method.hasAnnotation(annotationType)) 593 iterator.remove(); 594 } 595 596 return result; 597 } 598 599 public List<PlasticMethod> getMethods() 600 { 601 check(); 602 603 return new ArrayList<PlasticMethod>(methods); 604 } 605 606 public PlasticMethod introduceMethod(MethodDescription description) 607 { 608 check(); 609 610 if (Modifier.isAbstract(description.modifiers)) 611 { 612 description = description.withModifiers(description.modifiers & ~ACC_ABSTRACT); 613 } 614 615 PlasticMethod result = description2method.get(description); 616 617 if (result == null) 618 { 619 result = createNewMethod(description); 620 621 description2method.put(description, result); 622 } 623 624 methodNames.add(description.methodName); 625 626 // Note that is it not necessary to add the new MethodNode to 627 // fieldTransformMethods (the default implementations provided by introduceMethod() do not 628 // ever access instance fields) ... unless the caller invokes changeImplementation(). 629 630 return result; 631 } 632 633 public PlasticMethod introduceMethod(MethodDescription description, InstructionBuilderCallback callback) 634 { 635 check(); 636 637 // TODO: optimize this so that a default implementation is not created. 638 639 return introduceMethod(description).changeImplementation(callback); 640 } 641 642 public PlasticMethod introduceMethod(Method method) 643 { 644 check(); 645 646 return introduceMethod(new MethodDescription(method)); 647 } 648 649 void addMethod(MethodNode methodNode) 650 { 651 classNode.methods.add(methodNode); 652 653 methodNames.add(methodNode.name); 654 655 if (!Modifier.isPrivate(methodNode.access)) 656 inheritanceData.addMethod(methodNode.name, methodNode.desc); 657 } 658 659 private PlasticMethod createNewMethod(MethodDescription description) 660 { 661 if (Modifier.isStatic(description.modifiers)) 662 throw new IllegalArgumentException(String.format( 663 "Unable to introduce method '%s' into class %s: introduced methods may not be static.", 664 description, className)); 665 666 String desc = nameCache.toDesc(description); 667 668 String[] exceptions = new String[description.checkedExceptionTypes.length]; 669 for (int i = 0; i < exceptions.length; i++) 670 { 671 exceptions[i] = PlasticInternalUtils.toInternalName(description.checkedExceptionTypes[i]); 672 } 673 674 MethodNode methodNode = new MethodNode(description.modifiers, description.methodName, desc, 675 description.genericSignature, exceptions); 676 boolean isOverride = inheritanceData.isImplemented(methodNode.name, desc); 677 678 if (isOverride) 679 createOverrideOfBaseClassImpl(description, methodNode); 680 else 681 createNewMethodImpl(description, methodNode); 682 683 addMethod(methodNode); 684 685 return new PlasticMethodImpl(this, methodNode); 686 } 687 688 private void createNewMethodImpl(MethodDescription methodDescription, MethodNode methodNode) 689 { 690 newBuilder(methodDescription, methodNode).returnDefaultValue(); 691 } 692 693 private void createOverrideOfBaseClassImpl(MethodDescription methodDescription, MethodNode methodNode) 694 { 695 InstructionBuilder builder = newBuilder(methodDescription, methodNode); 696 697 builder.loadThis(); 698 builder.loadArguments(); 699 builder.invokeSpecial(superClassName, methodDescription); 700 builder.returnResult(); 701 } 702 703 /** 704 * Iterates over all non-introduced methods, including the original constructor. For each 705 * method, the bytecode is scanned for field reads and writes. When a match is found against an intercepted field, 706 * the operation is replaced with a method invocation. This is invoked only after the {@link PlasticClassHandleShim} 707 * for the class has been created, as the shim may create methods that contain references to fields that may be 708 * subject to field access interception. 709 */ 710 private void interceptFieldAccess() 711 { 712 for (MethodNode node : fieldTransformMethods) 713 { 714 // Intercept field access inside the method, tracking which access methods 715 // are actually used by removing them from accessMethods 716 717 interceptFieldAccess(node); 718 } 719 } 720 721 /** 722 * Determines if any fields or methods have provided FieldHandles or MethodHandles; if so 723 * a shim class must be created to facilitate read/write access to fields, or invocation of methods. 724 */ 725 private void createShimIfNeeded() 726 { 727 if (shimFields.isEmpty() && shimMethods.isEmpty()) 728 return; 729 730 PlasticClassHandleShim shim = createShimInstance(); 731 732 installShim(shim); 733 } 734 735 public void installShim(PlasticClassHandleShim shim) 736 { 737 for (PlasticFieldImpl f : shimFields) 738 { 739 f.installShim(shim); 740 } 741 742 for (PlasticMethodImpl m : shimMethods) 743 { 744 m.installShim(shim); 745 } 746 } 747 748 public PlasticClassHandleShim createShimInstance() 749 { 750 String shimClassName = String.format("%s$Shim_%s", classNode.name, PlasticUtils.nextUID()); 751 752 ClassNode shimClassNode = new ClassNode(); 753 754 shimClassNode.visit(V1_5, ACC_PUBLIC | ACC_FINAL, shimClassName, null, HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME, 755 null); 756 757 implementConstructor(shimClassNode); 758 759 if (!shimFields.isEmpty()) 760 { 761 implementShimGet(shimClassNode); 762 implementShimSet(shimClassNode); 763 } 764 765 if (!shimMethods.isEmpty()) 766 { 767 implementShimInvoke(shimClassNode); 768 } 769 770 return instantiateShim(shimClassNode); 771 } 772 773 private void implementConstructor(ClassNode shimClassNode) 774 { 775 MethodNode mn = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null, null); 776 777 InstructionBuilder builder = newBuilder(mn); 778 779 builder.loadThis().invokeConstructor(PlasticClassHandleShim.class).returnResult(); 780 781 shimClassNode.methods.add(mn); 782 783 } 784 785 private PlasticClassHandleShim instantiateShim(ClassNode shimClassNode) 786 { 787 try 788 { 789 Class shimClass = pool.realize(className, ClassType.SUPPORT, shimClassNode); 790 791 return (PlasticClassHandleShim) shimClass.newInstance(); 792 } catch (Exception ex) 793 { 794 throw new RuntimeException( 795 String.format("Unable to instantiate shim class %s for plastic class %s: %s", 796 PlasticInternalUtils.toClassName(shimClassNode.name), className, 797 PlasticInternalUtils.toMessage(ex)), ex); 798 } 799 } 800 801 private void implementShimGet(ClassNode shimClassNode) 802 { 803 MethodNode mn = new MethodNode(ACC_PUBLIC, "get", OBJECT_INT_TO_OBJECT, null, null); 804 805 InstructionBuilder builder = newBuilder(mn); 806 807 // Arg 0 is the target instance 808 // Arg 1 is the index 809 810 builder.loadArgument(0).checkcast(className); 811 builder.loadArgument(1); 812 813 builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback() 814 { 815 public void doSwitch(SwitchBlock block) 816 { 817 for (PlasticFieldImpl f : shimFields) 818 { 819 f.extendShimGet(block); 820 } 821 } 822 }); 823 824 shimClassNode.methods.add(mn); 825 } 826 827 private void implementShimSet(ClassNode shimClassNode) 828 { 829 MethodNode mn = new MethodNode(ACC_PUBLIC, "set", OBJECT_INT_OBJECT_TO_VOID, null, null); 830 831 InstructionBuilder builder = newBuilder(mn); 832 833 // Arg 0 is the target instance 834 // Arg 1 is the index 835 // Arg 2 is the new value 836 837 builder.loadArgument(0).checkcast(className); 838 builder.loadArgument(2); 839 840 builder.loadArgument(1); 841 842 builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback() 843 { 844 public void doSwitch(SwitchBlock block) 845 { 846 for (PlasticFieldImpl f : shimFields) 847 { 848 f.extendShimSet(block); 849 } 850 } 851 }); 852 853 builder.returnResult(); 854 855 shimClassNode.methods.add(mn); 856 } 857 858 private void implementShimInvoke(ClassNode shimClassNode) 859 { 860 MethodNode mn = new MethodNode(ACC_PUBLIC, "invoke", OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT, null, 861 null); 862 863 InstructionBuilder builder = newBuilder(mn); 864 865 // Arg 0 is the target instance 866 // Arg 1 is the index 867 // Arg 2 is the object array of parameters 868 869 builder.loadArgument(0).checkcast(className); 870 871 builder.loadArgument(1); 872 873 builder.startSwitch(0, nextMethodIndex - 1, new SwitchCallback() 874 { 875 public void doSwitch(SwitchBlock block) 876 { 877 for (PlasticMethodImpl m : shimMethods) 878 { 879 m.extendShimInvoke(block); 880 } 881 } 882 }); 883 884 shimClassNode.methods.add(mn); 885 } 886 887 private void rewriteAdvisedMethods() 888 { 889 for (PlasticMethodImpl method : advisedMethods) 890 { 891 method.rewriteMethodForAdvice(); 892 } 893 } 894 895 private void interceptFieldAccess(MethodNode methodNode) 896 { 897 InsnList insns = methodNode.instructions; 898 899 ListIterator it = insns.iterator(); 900 901 while (it.hasNext()) 902 { 903 AbstractInsnNode node = (AbstractInsnNode) it.next(); 904 905 int opcode = node.getOpcode(); 906 907 if (opcode != GETFIELD && opcode != PUTFIELD) 908 { 909 continue; 910 } 911 912 FieldInsnNode fnode = (FieldInsnNode) node; 913 914 FieldInstrumentation instrumentation = findFieldNodeInstrumentation(fnode, opcode == GETFIELD); 915 916 if (instrumentation == null) 917 { 918 continue; 919 } 920 921 // Replace the field access node with the appropriate method invocation. 922 923 insns.insertBefore(fnode, new MethodInsnNode(INVOKEVIRTUAL, fnode.owner, instrumentation.methodName, instrumentation.methodDescription)); 924 925 it.remove(); 926 } 927 } 928 929 private FieldInstrumentation findFieldNodeInstrumentation(FieldInsnNode node, boolean forRead) 930 { 931 // First look in the local fieldInstrumentations, which contains private field instrumentations 932 // (as well as non-private ones). 933 934 String searchStart = node.owner; 935 936 if (searchStart.equals(classNode.name)) 937 { 938 FieldInstrumentation result = fieldInstrumentations.get(node.name, forRead); 939 940 if (result != null) 941 { 942 return result; 943 } 944 945 // Slight optimization: start the search in the super-classes' fields, since we've already 946 // checked this classes fields. 947 948 searchStart = classNode.superName; 949 } 950 951 return pool.getFieldInstrumentation(searchStart, node.name, forRead); 952 } 953 954 String getInstanceContextFieldName() 955 { 956 if (instanceContextFieldName == null) 957 { 958 instanceContextFieldName = makeUnique(fieldNames, "instanceContext"); 959 960 // TODO: We could use a protected field and only initialize 961 // it once, in the first base class where it is needed, though that raises the possibilities 962 // of name conflicts (a subclass might introduce a field with a conflicting name). 963 964 FieldNode node = new FieldNode(ACC_PRIVATE | ACC_FINAL, instanceContextFieldName, INSTANCE_CONTEXT_DESC, 965 null, null); 966 967 classNode.fields.add(node); 968 969 // Extend the constructor to store the context in a field. 970 971 constructorBuilder.loadThis().loadArgument(1) 972 .putField(className, instanceContextFieldName, InstanceContext.class); 973 } 974 975 return instanceContextFieldName; 976 } 977 978 /** 979 * Creates a new private final field and initializes its value (using the StaticContext). 980 */ 981 String createAndInitializeFieldFromStaticContext(String suggestedFieldName, String fieldType, 982 Object injectedFieldValue) 983 { 984 String name = makeUnique(fieldNames, suggestedFieldName); 985 986 FieldNode field = new FieldNode(ACC_PRIVATE | ACC_FINAL, name, nameCache.toDesc(fieldType), null, null); 987 988 classNode.fields.add(field); 989 990 initializeFieldFromStaticContext(name, fieldType, injectedFieldValue); 991 992 return name; 993 } 994 995 /** 996 * Initializes a field from the static context. The injected value is added to the static 997 * context and the class constructor updated to assign the value from the context (which includes casting and 998 * possibly unboxing). 999 */ 1000 void initializeFieldFromStaticContext(String fieldName, String fieldType, Object injectedFieldValue) 1001 { 1002 int index = staticContext.store(injectedFieldValue); 1003 1004 // Although it feels nicer to do the loadThis() later and then swap(), that breaks 1005 // on primitive longs and doubles, so its just easier to do the loadThis() first 1006 // so its at the right place on the stack for the putField(). 1007 1008 constructorBuilder.loadThis(); 1009 1010 constructorBuilder.loadArgument(0).loadConstant(index); 1011 constructorBuilder.invoke(STATIC_CONTEXT_GET_METHOD); 1012 constructorBuilder.castOrUnbox(fieldType); 1013 1014 constructorBuilder.putField(className, fieldName, fieldType); 1015 } 1016 1017 void pushInstanceContextFieldOntoStack(InstructionBuilder builder) 1018 { 1019 builder.loadThis().getField(className, getInstanceContextFieldName(), InstanceContext.class); 1020 } 1021 1022 public PlasticClass getPlasticClass() 1023 { 1024 return this; 1025 } 1026 1027 public Class<?> getTransformedClass() 1028 { 1029 if (transformedClass == null) 1030 throw new IllegalStateException(String.format( 1031 "Transformed class %s is not yet available because the transformation is not yet complete.", 1032 className)); 1033 1034 return transformedClass; 1035 } 1036 1037 private boolean isInheritableMethod(MethodNode node) 1038 { 1039 return (node.access & (ACC_ABSTRACT | ACC_PRIVATE)) == 0; 1040 } 1041 1042 public String getClassName() 1043 { 1044 return className; 1045 } 1046 1047 InstructionBuilderImpl newBuilder(MethodNode mn) 1048 { 1049 return newBuilder(PlasticInternalUtils.toMethodDescription(mn), mn); 1050 } 1051 1052 InstructionBuilderImpl newBuilder(MethodDescription description, MethodNode mn) 1053 { 1054 return new InstructionBuilderImpl(description, mn, nameCache); 1055 } 1056 1057 public Set<PlasticMethod> introduceInterface(Class interfaceType) 1058 { 1059 check(); 1060 1061 assert interfaceType != null; 1062 1063 if (!interfaceType.isInterface()) 1064 throw new IllegalArgumentException(String.format( 1065 "Class %s is not an interface; ony interfaces may be introduced.", interfaceType.getName())); 1066 1067 String interfaceName = nameCache.toInternalName(interfaceType); 1068 1069 if (!inheritanceData.isInterfaceImplemented(interfaceName)) 1070 { 1071 classNode.interfaces.add(interfaceName); 1072 inheritanceData.addInterface(interfaceName); 1073 } 1074 1075 Set<PlasticMethod> introducedMethods = new HashSet<PlasticMethod>(); 1076 1077 for (Method m : interfaceType.getMethods()) 1078 { 1079 MethodDescription description = new MethodDescription(m); 1080 1081 if (!isMethodImplemented(description)) 1082 { 1083 introducedMethods.add(introduceMethod(m)); 1084 } 1085 } 1086 1087 return introducedMethods; 1088 } 1089 1090 public PlasticClass addToString(final String toStringValue) 1091 { 1092 check(); 1093 1094 if (!isMethodImplemented(PlasticUtils.TO_STRING_DESCRIPTION)) 1095 { 1096 introduceMethod(PlasticUtils.TO_STRING_DESCRIPTION, new InstructionBuilderCallback() 1097 { 1098 public void doBuild(InstructionBuilder builder) 1099 { 1100 builder.loadConstant(toStringValue).returnResult(); 1101 } 1102 }); 1103 } 1104 1105 return this; 1106 } 1107 1108 public boolean isMethodImplemented(MethodDescription description) 1109 { 1110 return inheritanceData.isImplemented(description.methodName, nameCache.toDesc(description)); 1111 } 1112 1113 public boolean isInterfaceImplemented(Class interfaceType) 1114 { 1115 assert interfaceType != null; 1116 assert interfaceType.isInterface(); 1117 1118 String interfaceName = nameCache.toInternalName(interfaceType); 1119 1120 return inheritanceData.isInterfaceImplemented(interfaceName); 1121 } 1122 1123 public String getSuperClassName() 1124 { 1125 return superClassName; 1126 } 1127 1128 public PlasticClass onConstruct(ConstructorCallback callback) 1129 { 1130 check(); 1131 1132 assert callback != null; 1133 1134 constructorCallbacks.add(callback); 1135 1136 return this; 1137 } 1138 1139 void redirectFieldWrite(String fieldName, boolean privateField, MethodNode method) 1140 { 1141 FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc); 1142 1143 fieldInstrumentations.write.put(fieldName, fi); 1144 1145 if (!(proxy || privateField)) 1146 { 1147 pool.setFieldWriteInstrumentation(classNode.name, fieldName, fi); 1148 } 1149 } 1150 1151 void redirectFieldRead(String fieldName, boolean privateField, MethodNode method) 1152 { 1153 FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc); 1154 1155 fieldInstrumentations.read.put(fieldName, fi); 1156 1157 if (!(proxy || privateField)) 1158 { 1159 pool.setFieldReadInstrumentation(classNode.name, fieldName, fi); 1160 } 1161 } 1162 }