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