001// Licensed under the Apache License, Version 2.0 (the "License"); 002// you may not use this file except in compliance with the License. 003// You may obtain a copy of the License at 004// 005// http://www.apache.org/licenses/LICENSE-2.0 006// 007// Unless required by applicable law or agreed to in writing, software 008// distributed under the License is distributed on an "AS IS" BASIS, 009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 010// See the License for the specific language governing permissions and 011// limitations under the License. 012 013package org.apache.tapestry5.internal.plastic; 014 015import org.apache.tapestry5.internal.plastic.asm.AnnotationVisitor; 016import org.apache.tapestry5.internal.plastic.asm.Opcodes; 017import org.apache.tapestry5.internal.plastic.asm.Type; 018import org.apache.tapestry5.internal.plastic.asm.tree.*; 019import org.apache.tapestry5.plastic.*; 020 021import java.io.IOException; 022import java.lang.annotation.Annotation; 023import java.lang.reflect.Constructor; 024import java.lang.reflect.Method; 025import java.lang.reflect.Modifier; 026import java.util.*; 027 028@SuppressWarnings("all") 029public 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<>(); 101 102 final Set<String> methodNames = new HashSet<>(); 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 final ClassNode implementationClassNode; 171 172 private ClassNode interfaceClassNode; 173 174 /** 175 * @param classNode 176 * @param implementationClassNode 177 * @param pool 178 * @param parentInheritanceData 179 * @param parentStaticContext 180 * @param proxy 181 */ 182 public PlasticClassImpl(ClassNode classNode, ClassNode implementationClassNode, PlasticClassPool pool, InheritanceData parentInheritanceData, 183 StaticContext parentStaticContext, boolean proxy) 184 { 185 this.classNode = classNode; 186 this.pool = pool; 187 this.proxy = proxy; 188 this.implementationClassNode = implementationClassNode; 189 190 staticContext = parentStaticContext.dupe(); 191 192 className = PlasticInternalUtils.toClassName(classNode.name); 193 superClassName = PlasticInternalUtils.toClassName(classNode.superName); 194 int lastIndexOfDot = className.lastIndexOf('.'); 195 196 String packageName = lastIndexOfDot > -1 ? className.substring(0, lastIndexOfDot) : ""; 197 198 fieldInstrumentations = new FieldInstrumentations(classNode.superName); 199 200 annotationAccess = new DelegatingAnnotationAccess(pool.createAnnotationAccess(classNode.visibleAnnotations), 201 pool.createAnnotationAccess(superClassName)); 202 203 this.parentInheritanceData = parentInheritanceData; 204 205 inheritanceData = parentInheritanceData.createChild(packageName); 206 207 for (String interfaceName : classNode.interfaces) 208 { 209 inheritanceData.addInterface(interfaceName); 210 } 211 212 methods = new ArrayList<>(classNode.methods.size()); 213 214 String invalidConstructorMessage = invalidConstructorMessage(); 215 216 for (MethodNode node : classNode.methods) 217 { 218 if (node.name.equals(CONSTRUCTOR_NAME)) 219 { 220 if (node.desc.equals(NOTHING_TO_VOID)) 221 { 222 originalConstructor = node; 223 fieldTransformMethods.add(node); 224 } else 225 { 226 node.instructions.clear(); 227 228 newBuilder(node).throwException(IllegalStateException.class, invalidConstructorMessage); 229 } 230 231 continue; 232 } 233 234 /* 235 * Static methods are not visible to the main API methods, but they must still be transformed, 236 * in case they directly access fields. In addition, track their names to avoid collisions. 237 */ 238 if (Modifier.isStatic(node.access)) 239 { 240 if (isInheritableMethod(node)) 241 { 242 inheritanceData.addMethod(node.name, node.desc, node.access == 0); 243 } 244 245 methodNames.add(node.name); 246 247 fieldTransformMethods.add(node); 248 249 continue; 250 } 251 252 if (!Modifier.isAbstract(node.access)) 253 { 254 fieldTransformMethods.add(node); 255 } 256 257 PlasticMethodImpl pmi = new PlasticMethodImpl(this, node); 258 259 methods.add(pmi); 260 description2method.put(pmi.getDescription(), pmi); 261 262 if (isInheritableMethod(node)) 263 { 264 inheritanceData.addMethod(node.name, node.desc, node.access == 0); 265 } 266 267 methodNames.add(node.name); 268 } 269 270 methodNames.addAll(parentInheritanceData.methodNames()); 271 272 Collections.sort(methods); 273 274 fields = new ArrayList<>(classNode.fields.size()); 275 276 for (FieldNode node : classNode.fields) 277 { 278 fieldNames.add(node.name); 279 280 // Ignore static fields. 281 282 if (Modifier.isStatic(node.access)) 283 continue; 284 285 // When we instrument the field such that it must be private, we'll get an exception. 286 287 fields.add(new PlasticFieldImpl(this, node)); 288 } 289 290 Collections.sort(fields); 291 292 // TODO: Make the output class's constructor protected, and create a shim class to instantiate it 293 // efficiently (without reflection). 294 newConstructor = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, CONSTRUCTOR_DESC, null, null); 295 constructorBuilder = newBuilder(newConstructor); 296 297 // Start by calling the super-class no args constructor 298 299 if (parentInheritanceData.isTransformed()) 300 { 301 // If the parent is transformed, our first step is always to invoke its constructor. 302 303 constructorBuilder.loadThis().loadArgument(0).loadArgument(1); 304 constructorBuilder.invokeConstructor(superClassName, StaticContext.class.getName(), 305 InstanceContext.class.getName()); 306 } else 307 { 308 // Assumes the base class includes a visible constructor that takes no arguments. 309 // TODO: Do a proper check for this case and throw a meaningful exception 310 // if not present. 311 312 constructorBuilder.loadThis().invokeConstructor(superClassName); 313 } 314 315 // During the transformation, we'll be adding code to the constructor to pull values 316 // out of the static or instance context and assign them to fields. 317 318 // Later on, we'll add the RETURN opcode 319 } 320 321 private String invalidConstructorMessage() 322 { 323 return String.format("Class %s has been transformed and may not be directly instantiated.", className); 324 } 325 326 @Override 327 public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType) 328 { 329 check(); 330 331 return annotationAccess.hasAnnotation(annotationType); 332 } 333 334 @Override 335 public <T extends Annotation> T getAnnotation(Class<T> annotationType) 336 { 337 check(); 338 339 return annotationAccess.getAnnotation(annotationType); 340 } 341 342 private static void addMethodAndParameterAnnotationsFromExistingClass(MethodNode methodNode, MethodNode implementationMethodNode) 343 { 344 // visits the method attributes 345 int i, j, n; 346 if (implementationMethodNode.annotationDefault != null) 347 { 348 AnnotationVisitor av = methodNode.visitAnnotationDefault(); 349 AnnotationNode.accept(av, null, implementationMethodNode.annotationDefault); 350 if (av != null) 351 { 352 av.visitEnd(); 353 } 354 } 355 n = implementationMethodNode.visibleAnnotations == null ? 0 : implementationMethodNode.visibleAnnotations.size(); 356 for (i = 0; i < n; ++i) 357 { 358 AnnotationNode an = implementationMethodNode.visibleAnnotations.get(i); 359 an.accept(methodNode.visitAnnotation(an.desc, true)); 360 } 361 n = implementationMethodNode.invisibleAnnotations == null ? 0 : implementationMethodNode.invisibleAnnotations.size(); 362 for (i = 0; i < n; ++i) 363 { 364 AnnotationNode an = implementationMethodNode.invisibleAnnotations.get(i); 365 an.accept(methodNode.visitAnnotation(an.desc, false)); 366 } 367 n = implementationMethodNode.visibleParameterAnnotations == null 368 ? 0 369 : implementationMethodNode.visibleParameterAnnotations.length; 370 for (i = 0; i < n; ++i) 371 { 372 List<?> l = implementationMethodNode.visibleParameterAnnotations[i]; 373 if (l == null) 374 { 375 continue; 376 } 377 for (j = 0; j < l.size(); ++j) 378 { 379 AnnotationNode an = (AnnotationNode) l.get(j); 380 an.accept(methodNode.visitParameterAnnotation(i, an.desc, true)); 381 } 382 } 383 n = implementationMethodNode.invisibleParameterAnnotations == null 384 ? 0 385 : implementationMethodNode.invisibleParameterAnnotations.length; 386 for (i = 0; i < n; ++i) 387 { 388 List<?> l = implementationMethodNode.invisibleParameterAnnotations[i]; 389 if (l == null) 390 { 391 continue; 392 } 393 for (j = 0; j < l.size(); ++j) 394 { 395 AnnotationNode an = (AnnotationNode) l.get(j); 396 an.accept(methodNode.visitParameterAnnotation(i, an.desc, false)); 397 } 398 } 399 400 methodNode.visitEnd(); 401 402 } 403 404 private static void removeDuplicatedAnnotations(MethodNode node) 405 { 406 407 removeDuplicatedAnnotations(node.visibleAnnotations); 408 removeDuplicatedAnnotations(node.invisibleAnnotations); 409 410 if (node.visibleParameterAnnotations != null) 411 { 412 for (List<AnnotationNode> list : node.visibleParameterAnnotations) 413 { 414 removeDuplicatedAnnotations(list); 415 } 416 } 417 418 if (node.invisibleParameterAnnotations != null) 419 { 420 for (List<AnnotationNode> list : node.invisibleParameterAnnotations) 421 { 422 removeDuplicatedAnnotations(list); 423 } 424 } 425 426 } 427 428 private static void removeDuplicatedAnnotations(ClassNode node) 429 { 430 removeDuplicatedAnnotations(node.visibleAnnotations, true); 431 removeDuplicatedAnnotations(node.invisibleAnnotations, true); 432 } 433 434 private static void removeDuplicatedAnnotations(List<AnnotationNode> list) { 435 removeDuplicatedAnnotations(list, false); 436 } 437 438 private static void removeDuplicatedAnnotations(List<AnnotationNode> list, boolean reverse) { 439 440 if (list != null) 441 { 442 443 final Set<String> annotations = new HashSet<>(); 444 final List<AnnotationNode> toBeRemoved = new ArrayList<>(); 445 final List<AnnotationNode> toBeIterated; 446 447 if (reverse) 448 { 449 toBeIterated = new ArrayList<>(list); 450 Collections.reverse(toBeIterated); 451 } 452 else { 453 toBeIterated = list; 454 } 455 456 for (AnnotationNode annotationNode : toBeIterated) 457 { 458 if (annotations.contains(annotationNode.desc)) 459 { 460 toBeRemoved.add(annotationNode); 461 } 462 else 463 { 464 annotations.add(annotationNode.desc); 465 } 466 } 467 468 for (AnnotationNode annotationNode : toBeRemoved) 469 { 470 list.remove(annotationNode); 471 } 472 473 } 474 475 } 476 477 private static String getParametersDesc(MethodNode methodNode) { 478 return methodNode.desc.substring(methodNode.desc.indexOf('(') + 1, methodNode.desc.lastIndexOf(')')); 479 } 480 481 private static MethodNode findExactMatchMethod(MethodNode methodNode, ClassNode source) { 482 483 MethodNode found = null; 484 485 final String methodDescription = getParametersDesc(methodNode); 486 487 for (MethodNode implementationMethodNode : source.methods) 488 { 489 490 final String implementationMethodDescription = getParametersDesc(implementationMethodNode); 491 if (methodNode.name.equals(implementationMethodNode.name) && 492 // We don't want synthetic methods. 493 ((implementationMethodNode.access & Opcodes.ACC_SYNTHETIC) == 0) 494 && (methodDescription.equals(implementationMethodDescription))) 495 { 496 found = implementationMethodNode; 497 break; 498 } 499 } 500 501 return found; 502 503 } 504 505 private static List<Class> getJavaParameterTypes(MethodNode methodNode) { 506 final ClassLoader classLoader = PlasticInternalUtils.class.getClassLoader(); 507 Type[] parameterTypes = Type.getArgumentTypes(methodNode.desc); 508 List<Class> list = new ArrayList<>(); 509 for (Type type : parameterTypes) 510 { 511 try 512 { 513 list.add(PlasticInternalUtils.toClass(classLoader, type.getClassName())); 514 } 515 catch (ClassNotFoundException e) 516 { 517 throw new RuntimeException(e); // shouldn't happen anyway 518 } 519 } 520 return list; 521 } 522 523 /** 524 * Returns the first method which matches the given methodNode. 525 * FIXME: this may not find the correct method if the correct one is declared after 526 * another in which all parameters are supertypes of the parameters of methodNode. 527 * To solve this, we would need to dig way deeper than we have time for this. 528 * @param methodNode 529 * @param classNode 530 * @return 531 */ 532 private static MethodNode findGenericMethod(MethodNode methodNode, ClassNode classNode) 533 { 534 535 MethodNode found = null; 536 537 List<Class> parameterTypes = getJavaParameterTypes(methodNode); 538 539 for (MethodNode implementationMethodNode : classNode.methods) 540 { 541 542 if (methodNode.name.equals(implementationMethodNode.name)) 543 { 544 545 final List<Class> implementationParameterTypes = getJavaParameterTypes(implementationMethodNode); 546 547 if (parameterTypes.size() == implementationParameterTypes.size()) 548 { 549 550 boolean matches = true; 551 for (int i = 0; i < parameterTypes.size(); i++) 552 { 553 final Class implementationParameterType = implementationParameterTypes.get(i); 554 final Class parameterType = parameterTypes.get(i); 555 if (!parameterType.isAssignableFrom(implementationParameterType)) { 556 matches = false; 557 break; 558 } 559 560 } 561 562 if (matches && !isBridge(implementationMethodNode)) 563 { 564 found = implementationMethodNode; 565 break; 566 } 567 568 } 569 570 } 571 572 } 573 574 return found; 575 576 } 577 578 private static void addMethodAndParameterAnnotationsFromExistingClass(MethodNode methodNode, ClassNode source) 579 { 580 if (source != null) 581 { 582 583 MethodNode candidate = findExactMatchMethod(methodNode, source); 584 585 final String parametersDesc = getParametersDesc(methodNode); 586 587 // candidate will be null when the method has generic parameters 588 if (candidate == null && parametersDesc.trim().length() > 0) 589 { 590 candidate = findGenericMethod(methodNode, source); 591 } 592 593 if (candidate != null) 594 { 595 addMethodAndParameterAnnotationsFromExistingClass(methodNode, candidate); 596 } 597 598 } 599 600 } 601 602 /** 603 * Tells whether a given method is a bridge one or not. 604 * Notice the flag for bridge method is the same as volatile field. Java 6 doesn't have 605 * Modifiers.isBridge(), so we use a workaround. 606 */ 607 private static boolean isBridge(MethodNode methodNode) 608 { 609 return Modifier.isVolatile(methodNode.access); 610 } 611 612 @Override 613 public PlasticClass proxyInterface(Class interfaceType, PlasticField field) 614 { 615 check(); 616 617 assert field != null; 618 619 introduceInterface(interfaceType); 620 621 for (Method m : getUniqueMethods(interfaceType)) 622 { 623 final MethodDescription description = new MethodDescription(m); 624 if(Modifier.isStatic(description.modifiers)) 625 { 626 continue; 627 } 628 introduceMethod(description).delegateTo(field); 629 } 630 631 return this; 632 } 633 634 @Override 635 public ClassInstantiator createInstantiator() 636 { 637 lock(); 638 639 addClassAnnotations(implementationClassNode); 640 removeDuplicatedAnnotations(classNode); 641 642 createShimIfNeeded(); 643 644 interceptFieldAccess(); 645 646 rewriteAdvisedMethods(); 647 648 completeConstructor(); 649 650 transformedClass = pool.realizeTransformedClass(classNode, inheritanceData, staticContext); 651 652 return createInstantiatorFromClass(transformedClass); 653 } 654 655 private void addClassAnnotations(ClassNode otherClassNode) 656 { 657 // Copy annotations from implementation if available. 658 // Code adapted from ClassNode.accept(), as we just want to copy 659 // the annotations and nothing more. 660 if (otherClassNode != null) 661 { 662 663 int i, n; 664 n = otherClassNode.visibleAnnotations == null ? 0 : otherClassNode.visibleAnnotations.size(); 665 for (i = 0; i < n; ++i) 666 { 667 AnnotationNode an = otherClassNode.visibleAnnotations.get(i); 668 an.accept(classNode.visitAnnotation(an.desc, true)); 669 } 670 n = otherClassNode.invisibleAnnotations == null ? 0 : otherClassNode.invisibleAnnotations.size(); 671 for (i = 0; i < n; ++i) 672 { 673 AnnotationNode an = otherClassNode.invisibleAnnotations.get(i); 674 an.accept(classNode.visitAnnotation(an.desc, false)); 675 } 676 677 } 678 } 679 680 private ClassInstantiator createInstantiatorFromClass(Class clazz) 681 { 682 try 683 { 684 Constructor ctor = clazz.getConstructor(StaticContext.class, InstanceContext.class); 685 686 return new ClassInstantiatorImpl(clazz, ctor, staticContext); 687 } catch (Exception ex) 688 { 689 throw new RuntimeException(String.format("Unable to create ClassInstantiator for class %s: %s", 690 clazz.getName(), PlasticInternalUtils.toMessage(ex)), ex); 691 } 692 } 693 694 private void completeConstructor() 695 { 696 if (originalConstructor != null) 697 { 698 convertOriginalConstructorToMethod(); 699 } 700 701 invokeCallbacks(); 702 703 constructorBuilder.returnResult(); 704 705 classNode.methods.add(newConstructor); 706 } 707 708 private void invokeCallbacks() 709 { 710 for (ConstructorCallback callback : constructorCallbacks) 711 { 712 invokeCallback(callback); 713 } 714 } 715 716 private void invokeCallback(ConstructorCallback callback) 717 { 718 int index = staticContext.store(callback); 719 720 // First, load the callback 721 722 constructorBuilder.loadArgument(0).loadConstant(index).invoke(STATIC_CONTEXT_GET_METHOD).castOrUnbox(ConstructorCallback.class.getName()); 723 724 // Load this and the InstanceContext 725 constructorBuilder.loadThis().loadArgument(1); 726 727 constructorBuilder.invoke(CONSTRUCTOR_CALLBACK_METHOD); 728 } 729 730 731 /** 732 * Convert the original constructor into a private method invoked from the 733 * generated constructor. 734 */ 735 private void convertOriginalConstructorToMethod() 736 { 737 String initializerName = makeUnique(methodNames, "initializeInstance"); 738 739 int originalAccess = originalConstructor.access; 740 741 originalConstructor.access = ACC_PRIVATE; 742 originalConstructor.name = initializerName; 743 744 stripOutSuperConstructorCall(originalConstructor); 745 746 constructorBuilder.loadThis().invokeVirtual(className, "void", initializerName); 747 748 // And replace it with a constructor that throws an exception 749 750 MethodNode replacementConstructor = new MethodNode(originalAccess, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null, 751 null); 752 753 newBuilder(replacementConstructor).throwException(IllegalStateException.class, invalidConstructorMessage()); 754 755 classNode.methods.add(replacementConstructor); 756 } 757 758 private void stripOutSuperConstructorCall(MethodNode cons) 759 { 760 InsnList ins = cons.instructions; 761 762 ListIterator li = ins.iterator(); 763 764 // Look for the ALOAD 0 (i.e., push this on the stack) 765 while (li.hasNext()) 766 { 767 AbstractInsnNode node = (AbstractInsnNode) li.next(); 768 769 if (node.getOpcode() == ALOAD) 770 { 771 VarInsnNode varNode = (VarInsnNode) node; 772 773 assert varNode.var == 0; 774 775 // Remove the ALOAD 776 li.remove(); 777 break; 778 } 779 } 780 781 // Look for the call to the super-class, an INVOKESPECIAL 782 while (li.hasNext()) 783 { 784 AbstractInsnNode node = (AbstractInsnNode) li.next(); 785 786 if (node.getOpcode() == INVOKESPECIAL) 787 { 788 MethodInsnNode mnode = (MethodInsnNode) node; 789 790 assert mnode.owner.equals(classNode.superName); 791 assert mnode.name.equals(CONSTRUCTOR_NAME); 792 assert mnode.desc.equals(cons.desc); 793 794 li.remove(); 795 return; 796 } 797 } 798 799 throw new AssertionError("Could not convert constructor to simple method."); 800 } 801 802 @Override 803 public <T extends Annotation> List<PlasticField> getFieldsWithAnnotation(Class<T> annotationType) 804 { 805 check(); 806 807 List<PlasticField> result = getAllFields(); 808 809 Iterator<PlasticField> iterator = result.iterator(); 810 811 while (iterator.hasNext()) 812 { 813 PlasticField plasticField = iterator.next(); 814 815 if (!plasticField.hasAnnotation(annotationType)) 816 iterator.remove(); 817 } 818 819 return result; 820 } 821 822 @Override 823 public List<PlasticField> getAllFields() 824 { 825 check(); 826 827 return new ArrayList<>(fields); 828 } 829 830 @Override 831 public List<PlasticField> getUnclaimedFields() 832 { 833 check(); 834 835 // Initially null, and set back to null by PlasticField.claim(). 836 837 if (unclaimedFields == null) 838 { 839 unclaimedFields = new ArrayList<>(fields.size()); 840 841 for (PlasticField f : fields) 842 { 843 if (!f.isClaimed()) 844 unclaimedFields.add(f); 845 } 846 } 847 848 return unclaimedFields; 849 } 850 851 @Override 852 public PlasticMethod introducePrivateMethod(String typeName, String suggestedName, String[] argumentTypes, 853 String[] exceptionTypes) 854 { 855 check(); 856 857 assert PlasticInternalUtils.isNonBlank(typeName); 858 assert PlasticInternalUtils.isNonBlank(suggestedName); 859 860 String name = makeUnique(methodNames, suggestedName); 861 862 MethodDescription description = new MethodDescription(Modifier.PRIVATE, typeName, name, argumentTypes, null, 863 exceptionTypes); 864 865 return introduceMethod(description); 866 } 867 868 @Override 869 public PlasticField introduceField(String className, String suggestedName) 870 { 871 check(); 872 873 assert PlasticInternalUtils.isNonBlank(className); 874 assert PlasticInternalUtils.isNonBlank(suggestedName); 875 876 String name = makeUnique(fieldNames, suggestedName); 877 878 // No signature and no initial value 879 880 FieldNode fieldNode = new FieldNode(ACC_PRIVATE, name, PlasticInternalUtils.toDescriptor(className), null, null); 881 882 classNode.fields.add(fieldNode); 883 884 fieldNames.add(name); 885 886 PlasticFieldImpl newField = new PlasticFieldImpl(this, fieldNode); 887 888 return newField; 889 } 890 891 @Override 892 public PlasticField introduceField(Class fieldType, String suggestedName) 893 { 894 assert fieldType != null; 895 896 return introduceField(nameCache.toTypeName(fieldType), suggestedName); 897 } 898 899 String makeUnique(Set<String> values, String input) 900 { 901 return values.contains(input) ? input + "$" + PlasticUtils.nextUID() : input; 902 } 903 904 @Override 905 public <T extends Annotation> List<PlasticMethod> getMethodsWithAnnotation(Class<T> annotationType) 906 { 907 check(); 908 909 List<PlasticMethod> result = getMethods(); 910 911 result.removeIf(method -> !method.hasAnnotation(annotationType)); 912 913 return result; 914 } 915 916 @Override 917 public List<PlasticMethod> getMethods() 918 { 919 check(); 920 921 return new ArrayList<>(methods); 922 } 923 924 @Override 925 public PlasticMethod introduceMethod(MethodDescription description) 926 { 927 check(); 928 929 if (Modifier.isAbstract(description.modifiers)) 930 { 931 description = description.withModifiers(description.modifiers & ~ACC_ABSTRACT); 932 } 933 934 PlasticMethod result = description2method.get(description); 935 936 if (result == null) 937 { 938 result = createNewMethod(description); 939 940 description2method.put(description, result); 941 } 942 943 methodNames.add(description.methodName); 944 945 // Note that is it not necessary to add the new MethodNode to 946 // fieldTransformMethods (the default implementations provided by introduceMethod() do not 947 // ever access instance fields) ... unless the caller invokes changeImplementation(). 948 949 return result; 950 } 951 952 @Override 953 public PlasticMethod introduceMethod(MethodDescription description, InstructionBuilderCallback callback) 954 { 955 check(); 956 957 // TODO: optimize this so that a default implementation is not created. 958 959 return introduceMethod(description).changeImplementation(callback); 960 } 961 962 @Override 963 public PlasticMethod introduceMethod(Method method) 964 { 965 check(); 966 return introduceMethod(new MethodDescription(method)); 967 } 968 969 void addMethod(MethodNode methodNode) 970 { 971 classNode.methods.add(methodNode); 972 973 methodNames.add(methodNode.name); 974 975 if (isInheritableMethod(methodNode)) 976 { 977 inheritanceData.addMethod(methodNode.name, methodNode.desc, methodNode.access == 0); 978 } 979 } 980 981 private PlasticMethod createNewMethod(MethodDescription description) 982 { 983 if (Modifier.isStatic(description.modifiers)) 984 throw new IllegalArgumentException(String.format( 985 "Unable to introduce method '%s' into class %s: introduced methods may not be static.", 986 description, className)); 987 988 String desc = nameCache.toDesc(description); 989 990 String[] exceptions = new String[description.checkedExceptionTypes.length]; 991 for (int i = 0; i < exceptions.length; i++) 992 { 993 exceptions[i] = PlasticInternalUtils.toInternalName(description.checkedExceptionTypes[i]); 994 } 995 996 MethodNode methodNode = new MethodNode(description.modifiers, description.methodName, desc, 997 description.genericSignature, exceptions); 998 boolean isOverride = inheritanceData.isImplemented(methodNode.name, desc); 999 1000 if (!isOverride) 1001 { 1002 addMethodAndParameterAnnotationsFromExistingClass(methodNode, implementationClassNode); 1003 addMethodAndParameterAnnotationsFromExistingClass(methodNode, interfaceClassNode); 1004 removeDuplicatedAnnotations(methodNode); 1005 } 1006 1007 if (isOverride) 1008 createOverrideOfBaseClassImpl(description, methodNode); 1009 else 1010 createNewMethodImpl(description, methodNode); 1011 1012 addMethod(methodNode); 1013 1014 return new PlasticMethodImpl(this, methodNode); 1015 } 1016 1017 private void createNewMethodImpl(MethodDescription methodDescription, MethodNode methodNode) 1018 { 1019 newBuilder(methodDescription, methodNode).returnDefaultValue(); 1020 } 1021 1022 private void createOverrideOfBaseClassImpl(MethodDescription methodDescription, MethodNode methodNode) 1023 { 1024 InstructionBuilder builder = newBuilder(methodDescription, methodNode); 1025 1026 builder.loadThis(); 1027 builder.loadArguments(); 1028 builder.invokeSpecial(superClassName, methodDescription); 1029 builder.returnResult(); 1030 } 1031 1032 /** 1033 * Iterates over all non-introduced methods, including the original constructor. For each 1034 * method, the bytecode is scanned for field reads and writes. When a match is found against an intercepted field, 1035 * the operation is replaced with a method invocation. This is invoked only after the {@link PlasticClassHandleShim} 1036 * for the class has been created, as the shim may create methods that contain references to fields that may be 1037 * subject to field access interception. 1038 */ 1039 private void interceptFieldAccess() 1040 { 1041 for (MethodNode node : fieldTransformMethods) 1042 { 1043 // Intercept field access inside the method, tracking which access methods 1044 // are actually used by removing them from accessMethods 1045 1046 interceptFieldAccess(node); 1047 } 1048 } 1049 1050 /** 1051 * Determines if any fields or methods have provided FieldHandles or MethodHandles; if so 1052 * a shim class must be created to facilitate read/write access to fields, or invocation of methods. 1053 */ 1054 private void createShimIfNeeded() 1055 { 1056 if (shimFields.isEmpty() && shimMethods.isEmpty()) 1057 return; 1058 1059 PlasticClassHandleShim shim = createShimInstance(); 1060 1061 installShim(shim); 1062 } 1063 1064 public void installShim(PlasticClassHandleShim shim) 1065 { 1066 for (PlasticFieldImpl f : shimFields) 1067 { 1068 f.installShim(shim); 1069 } 1070 1071 for (PlasticMethodImpl m : shimMethods) 1072 { 1073 m.installShim(shim); 1074 } 1075 } 1076 1077 public PlasticClassHandleShim createShimInstance() 1078 { 1079 String shimClassName = String.format("%s$Shim_%s", classNode.name, PlasticUtils.nextUID()); 1080 1081 ClassNode shimClassNode = new ClassNode(); 1082 1083 shimClassNode.visit(PlasticConstants.DEFAULT_VERSION_OPCODE, ACC_PUBLIC | ACC_FINAL, shimClassName, null, HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME, 1084 null); 1085 1086 implementConstructor(shimClassNode); 1087 1088 if (!shimFields.isEmpty()) 1089 { 1090 implementShimGet(shimClassNode); 1091 implementShimSet(shimClassNode); 1092 } 1093 1094 if (!shimMethods.isEmpty()) 1095 { 1096 implementShimInvoke(shimClassNode); 1097 } 1098 1099 return instantiateShim(shimClassNode); 1100 } 1101 1102 private void implementConstructor(ClassNode shimClassNode) 1103 { 1104 MethodNode mn = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null, null); 1105 1106 InstructionBuilder builder = newBuilder(mn); 1107 1108 builder.loadThis().invokeConstructor(PlasticClassHandleShim.class).returnResult(); 1109 1110 shimClassNode.methods.add(mn); 1111 1112 } 1113 1114 private PlasticClassHandleShim instantiateShim(ClassNode shimClassNode) 1115 { 1116 try 1117 { 1118 Class shimClass = pool.realize(className, ClassType.SUPPORT, shimClassNode); 1119 1120 return (PlasticClassHandleShim) shimClass.newInstance(); 1121 } catch (Exception ex) 1122 { 1123 throw new RuntimeException( 1124 String.format("Unable to instantiate shim class %s for plastic class %s: %s", 1125 PlasticInternalUtils.toClassName(shimClassNode.name), className, 1126 PlasticInternalUtils.toMessage(ex)), ex); 1127 } 1128 } 1129 1130 private void implementShimGet(ClassNode shimClassNode) 1131 { 1132 MethodNode mn = new MethodNode(ACC_PUBLIC, "get", OBJECT_INT_TO_OBJECT, null, null); 1133 1134 InstructionBuilder builder = newBuilder(mn); 1135 1136 // Arg 0 is the target instance 1137 // Arg 1 is the index 1138 1139 builder.loadArgument(0).checkcast(className); 1140 builder.loadArgument(1); 1141 1142 builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback() 1143 { 1144 @Override 1145 public void doSwitch(SwitchBlock block) 1146 { 1147 for (PlasticFieldImpl f : shimFields) 1148 { 1149 f.extendShimGet(block); 1150 } 1151 } 1152 }); 1153 1154 shimClassNode.methods.add(mn); 1155 } 1156 1157 private void implementShimSet(ClassNode shimClassNode) 1158 { 1159 MethodNode mn = new MethodNode(ACC_PUBLIC, "set", OBJECT_INT_OBJECT_TO_VOID, null, null); 1160 1161 InstructionBuilder builder = newBuilder(mn); 1162 1163 // Arg 0 is the target instance 1164 // Arg 1 is the index 1165 // Arg 2 is the new value 1166 1167 builder.loadArgument(0).checkcast(className); 1168 builder.loadArgument(2); 1169 1170 builder.loadArgument(1); 1171 1172 builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback() 1173 { 1174 @Override 1175 public void doSwitch(SwitchBlock block) 1176 { 1177 for (PlasticFieldImpl f : shimFields) 1178 { 1179 f.extendShimSet(block); 1180 } 1181 } 1182 }); 1183 1184 builder.returnResult(); 1185 1186 shimClassNode.methods.add(mn); 1187 } 1188 1189 private void implementShimInvoke(ClassNode shimClassNode) 1190 { 1191 MethodNode mn = new MethodNode(ACC_PUBLIC, "invoke", OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT, null, 1192 null); 1193 1194 InstructionBuilder builder = newBuilder(mn); 1195 1196 // Arg 0 is the target instance 1197 // Arg 1 is the index 1198 // Arg 2 is the object array of parameters 1199 1200 builder.loadArgument(0).checkcast(className); 1201 1202 builder.loadArgument(1); 1203 1204 builder.startSwitch(0, nextMethodIndex - 1, new SwitchCallback() 1205 { 1206 @Override 1207 public void doSwitch(SwitchBlock block) 1208 { 1209 for (PlasticMethodImpl m : shimMethods) 1210 { 1211 m.extendShimInvoke(block); 1212 } 1213 } 1214 }); 1215 1216 shimClassNode.methods.add(mn); 1217 } 1218 1219 private void rewriteAdvisedMethods() 1220 { 1221 for (PlasticMethodImpl method : advisedMethods) 1222 { 1223 method.rewriteMethodForAdvice(); 1224 } 1225 } 1226 1227 private void interceptFieldAccess(MethodNode methodNode) 1228 { 1229 InsnList insns = methodNode.instructions; 1230 1231 ListIterator<AbstractInsnNode> it = insns.iterator(); 1232 1233 while (it.hasNext()) 1234 { 1235 AbstractInsnNode node = it.next(); 1236 1237 int opcode = node.getOpcode(); 1238 1239 if (opcode != GETFIELD && opcode != PUTFIELD) 1240 { 1241 continue; 1242 } 1243 1244 FieldInsnNode fnode = (FieldInsnNode) node; 1245 1246 FieldInstrumentation instrumentation = findFieldNodeInstrumentation(fnode, opcode == GETFIELD); 1247 1248 if (instrumentation == null) 1249 { 1250 continue; 1251 } 1252 1253 // Replace the field access node with the appropriate method invocation. 1254 1255 insns.insertBefore(fnode, new MethodInsnNode(INVOKEVIRTUAL, fnode.owner, instrumentation.methodName, instrumentation.methodDescription, false)); 1256 1257 it.remove(); 1258 } 1259 } 1260 1261 private FieldInstrumentation findFieldNodeInstrumentation(FieldInsnNode node, boolean forRead) 1262 { 1263 // First look in the local fieldInstrumentations, which contains private field instrumentations 1264 // (as well as non-private ones). 1265 1266 String searchStart = node.owner; 1267 1268 if (searchStart.equals(classNode.name)) 1269 { 1270 FieldInstrumentation result = fieldInstrumentations.get(node.name, forRead); 1271 1272 if (result != null) 1273 { 1274 return result; 1275 } 1276 1277 // Slight optimization: start the search in the super-classes' fields, since we've already 1278 // checked this classes fields. 1279 1280 searchStart = classNode.superName; 1281 } 1282 1283 return pool.getFieldInstrumentation(searchStart, node.name, forRead); 1284 } 1285 1286 String getInstanceContextFieldName() 1287 { 1288 if (instanceContextFieldName == null) 1289 { 1290 instanceContextFieldName = makeUnique(fieldNames, "instanceContext"); 1291 1292 // TODO: We could use a protected field and only initialize 1293 // it once, in the first base class where it is needed, though that raises the possibilities 1294 // of name conflicts (a subclass might introduce a field with a conflicting name). 1295 1296 FieldNode node = new FieldNode(ACC_PRIVATE | ACC_FINAL, instanceContextFieldName, INSTANCE_CONTEXT_DESC, 1297 null, null); 1298 1299 classNode.fields.add(node); 1300 1301 // Extend the constructor to store the context in a field. 1302 1303 constructorBuilder.loadThis().loadArgument(1) 1304 .putField(className, instanceContextFieldName, InstanceContext.class); 1305 } 1306 1307 return instanceContextFieldName; 1308 } 1309 1310 /** 1311 * Creates a new private final field and initializes its value (using the StaticContext). 1312 */ 1313 String createAndInitializeFieldFromStaticContext(String suggestedFieldName, String fieldType, 1314 Object injectedFieldValue) 1315 { 1316 String name = makeUnique(fieldNames, suggestedFieldName); 1317 1318 FieldNode field = new FieldNode(ACC_PRIVATE | ACC_FINAL, name, nameCache.toDesc(fieldType), null, null); 1319 1320 classNode.fields.add(field); 1321 1322 initializeFieldFromStaticContext(name, fieldType, injectedFieldValue); 1323 1324 return name; 1325 } 1326 1327 /** 1328 * Initializes a field from the static context. The injected value is added to the static 1329 * context and the class constructor updated to assign the value from the context (which includes casting and 1330 * possibly unboxing). 1331 */ 1332 void initializeFieldFromStaticContext(String fieldName, String fieldType, Object injectedFieldValue) 1333 { 1334 int index = staticContext.store(injectedFieldValue); 1335 1336 // Although it feels nicer to do the loadThis() later and then swap(), that breaks 1337 // on primitive longs and doubles, so its just easier to do the loadThis() first 1338 // so its at the right place on the stack for the putField(). 1339 1340 constructorBuilder.loadThis(); 1341 1342 constructorBuilder.loadArgument(0).loadConstant(index); 1343 constructorBuilder.invoke(STATIC_CONTEXT_GET_METHOD); 1344 constructorBuilder.castOrUnbox(fieldType); 1345 1346 constructorBuilder.putField(className, fieldName, fieldType); 1347 } 1348 1349 void pushInstanceContextFieldOntoStack(InstructionBuilder builder) 1350 { 1351 builder.loadThis().getField(className, getInstanceContextFieldName(), InstanceContext.class); 1352 } 1353 1354 @Override 1355 public PlasticClass getPlasticClass() 1356 { 1357 return this; 1358 } 1359 1360 @Override 1361 public Class<?> getTransformedClass() 1362 { 1363 if (transformedClass == null) 1364 throw new IllegalStateException(String.format( 1365 "Transformed class %s is not yet available because the transformation is not yet complete.", 1366 className)); 1367 1368 return transformedClass; 1369 } 1370 1371 private boolean isInheritableMethod(MethodNode node) 1372 { 1373 return !Modifier.isPrivate(node.access); 1374 } 1375 1376 @Override 1377 public String getClassName() 1378 { 1379 return className; 1380 } 1381 1382 InstructionBuilderImpl newBuilder(MethodNode mn) 1383 { 1384 return newBuilder(PlasticInternalUtils.toMethodDescription(mn), mn); 1385 } 1386 1387 InstructionBuilderImpl newBuilder(MethodDescription description, MethodNode mn) 1388 { 1389 return new InstructionBuilderImpl(description, mn, nameCache); 1390 } 1391 1392 public Set<PlasticMethod> introduceInterface(Class interfaceType) 1393 { 1394 return introduceInterface(interfaceType, null); 1395 } 1396 1397 private Set<PlasticMethod> introduceInterface(Class interfaceType, PlasticMethod method) 1398 { 1399 check(); 1400 1401 assert interfaceType != null; 1402 1403 if (!interfaceType.isInterface()) 1404 throw new IllegalArgumentException(String.format( 1405 "Class %s is not an interface; only interfaces may be introduced.", interfaceType.getName())); 1406 1407 String interfaceName = nameCache.toInternalName(interfaceType); 1408 1409 try 1410 { 1411 interfaceClassNode = PlasticClassPool.readClassNode(interfaceType.getName(), getClass().getClassLoader()); 1412 } catch (IOException e) 1413 { 1414 throw new RuntimeException(e); 1415 } 1416 1417 if (!inheritanceData.isInterfaceImplemented(interfaceName)) 1418 { 1419 classNode.interfaces.add(interfaceName); 1420 inheritanceData.addInterface(interfaceName); 1421 } 1422 1423 addClassAnnotations(interfaceClassNode); 1424 1425 Set<PlasticMethod> introducedMethods = new HashSet<PlasticMethod>(); 1426 Set<Method> alreadyIntroducedMethods = new HashSet<>(); 1427 1428 Method[] sortedMethods = interfaceType.getMethods(); 1429 Arrays.sort(sortedMethods, METHOD_COMPARATOR); 1430 for (Method m : sortedMethods) 1431 { 1432 MethodDescription description = new MethodDescription(m); 1433 1434 if (!isMethodImplemented(description) && 1435 !(m.isDefault() && m.isBridge()) && 1436 !Modifier.isStatic(description.modifiers) && 1437 !contains(alreadyIntroducedMethods, m)) 1438 { 1439 PlasticMethod introducedMethod = introduceMethod(m); 1440 introducedMethods.add(introducedMethod); 1441 if (method != null) { 1442 introducedMethod.delegateTo(method); 1443 } 1444 alreadyIntroducedMethods.add(m); 1445 } 1446 } 1447 1448 interfaceClassNode = null; 1449 1450 return introducedMethods; 1451 } 1452 1453 @Override 1454 public PlasticClass proxyInterface(Class interfaceType, PlasticMethod method) 1455 { 1456 check(); 1457 assert method != null; 1458 1459 introduceInterface(interfaceType, method); 1460 1461 return this; 1462 } 1463 1464 private boolean contains(Set<Method> alreadyIntroducedMethods, Method m) { 1465 boolean contains = false; 1466 for (Method method : alreadyIntroducedMethods) 1467 { 1468 if (METHOD_COMPARATOR.compare(method, m) == 0) 1469 { 1470 contains = true; 1471 break; 1472 } 1473 } 1474 return false; 1475 } 1476 1477 private Map<MethodSignature, MethodDescription> createMethodSignatureMap(Class interfaceType) { 1478 // TAP-2582: preprocessing the method list so we don't add duplicated 1479 // methods, something that happens when an interface has superinterfaces 1480 // and they define the same method signature. 1481 // In addition, we collect all the thrown checked exceptions, just in case. 1482 Map<MethodSignature, MethodDescription> map = new HashMap<>(); 1483 for (Method m : interfaceType.getMethods()) 1484 { 1485 final MethodSignature methodSignature = new MethodSignature(m); 1486 final MethodDescription newMethodDescription = new MethodDescription(m); 1487 if (!map.containsKey(methodSignature)) 1488 { 1489 map.put(methodSignature, newMethodDescription); 1490 } 1491 else 1492 { 1493 if (newMethodDescription.checkedExceptionTypes != null && newMethodDescription.checkedExceptionTypes.length > 0) 1494 { 1495 final MethodDescription methodDescription = map.get(methodSignature); 1496 final Set<String> checkedExceptionTypes = new HashSet<>(); 1497 checkedExceptionTypes.addAll(Arrays.asList(methodDescription.checkedExceptionTypes)); 1498 checkedExceptionTypes.addAll(Arrays.asList(newMethodDescription.checkedExceptionTypes)); 1499 map.put(methodSignature, new MethodDescription( 1500 methodDescription, 1501 checkedExceptionTypes.toArray(new String[checkedExceptionTypes.size()]))); 1502 } 1503 } 1504 } 1505 return map; 1506 } 1507 1508 final private static class MethodSignature implements Comparable<MethodSignature> { 1509 final private Method method; 1510 final private String name; 1511 final private Class<?>[] parameterTypes; 1512 1513 public MethodSignature(Method method) { 1514 this.method = method; 1515 this.name = method.getName(); 1516 this.parameterTypes = method.getParameterTypes(); 1517 } 1518 1519 @Override 1520 public int hashCode() { 1521 final int prime = 31; 1522 int result = 1; 1523 result = prime * result + Arrays.hashCode(parameterTypes); 1524 result = prime * result + ((name == null) ? 0 : name.hashCode()); 1525 return result; 1526 } 1527 1528 @Override 1529 public boolean equals(Object obj) { 1530 if (this == obj) return true; 1531 if (obj == null) return false; 1532 if (getClass() != obj.getClass()) return false; 1533 1534 MethodSignature other = (MethodSignature) obj; 1535 if (!Arrays.equals(parameterTypes, other.parameterTypes)) return false; 1536 1537 return name == null ? other.name == null : name.equals(other.name); 1538 } 1539 1540 @Override 1541 public int compareTo(MethodSignature o) { 1542 return method.getName().compareTo(o.method.getName()); 1543 } 1544 } 1545 1546 @Override 1547 public PlasticClass addToString(final String toStringValue) 1548 { 1549 check(); 1550 1551 if (!isMethodImplemented(PlasticUtils.TO_STRING_DESCRIPTION)) 1552 { 1553 introduceMethod(PlasticUtils.TO_STRING_DESCRIPTION, new InstructionBuilderCallback() 1554 { 1555 @Override 1556 public void doBuild(InstructionBuilder builder) 1557 { 1558 builder.loadConstant(toStringValue).returnResult(); 1559 } 1560 }); 1561 } 1562 1563 return this; 1564 } 1565 1566 @Override 1567 public boolean isMethodImplemented(MethodDescription description) 1568 { 1569 return inheritanceData.isImplemented(description.methodName, nameCache.toDesc(description)); 1570 } 1571 1572 @Override 1573 public boolean isInterfaceImplemented(Class interfaceType) 1574 { 1575 assert interfaceType != null; 1576 assert interfaceType.isInterface(); 1577 1578 String interfaceName = nameCache.toInternalName(interfaceType); 1579 1580 return inheritanceData.isInterfaceImplemented(interfaceName); 1581 } 1582 1583 @Override 1584 public String getSuperClassName() 1585 { 1586 return superClassName; 1587 } 1588 1589 @Override 1590 public PlasticClass onConstruct(ConstructorCallback callback) 1591 { 1592 check(); 1593 1594 assert callback != null; 1595 1596 constructorCallbacks.add(callback); 1597 1598 return this; 1599 } 1600 1601 void redirectFieldWrite(String fieldName, boolean privateField, MethodNode method) 1602 { 1603 FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc); 1604 1605 fieldInstrumentations.write.put(fieldName, fi); 1606 1607 if (!proxy) 1608 { 1609 pool.setFieldWriteInstrumentation(classNode.name, fieldName, fi); 1610 } 1611 } 1612 1613 void redirectFieldRead(String fieldName, boolean privateField, MethodNode method) 1614 { 1615 FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc); 1616 1617 fieldInstrumentations.read.put(fieldName, fi); 1618 1619 if (!proxy) 1620 { 1621 pool.setFieldReadInstrumentation(classNode.name, fieldName, fi); 1622 } 1623 } 1624 1625 final private MethodComparator METHOD_COMPARATOR = new MethodComparator(); 1626 1627 final private class MethodComparator implements Comparator<Method> 1628 { 1629 1630 @Override 1631 public int compare(Method o1, Method o2) 1632 { 1633 1634 int comparison = comparison = o1.getName().compareTo(o2.getName()); 1635 1636 if (comparison == 0) 1637 { 1638 comparison = o1.getParameterTypes().length - o2.getParameterTypes().length; 1639 } 1640 1641 if (comparison == 0) 1642 { 1643 final int count = o1.getParameterTypes().length; 1644 for (int i = 0; i < count; i++) 1645 { 1646 Class p1 = o1.getParameterTypes()[i]; 1647 Class p2 = o1.getParameterTypes()[i]; 1648 if (!p1.equals(p2)) 1649 { 1650 comparison = p1.getName().compareTo(p2.getName()); 1651 break; 1652 } 1653 } 1654 } 1655 1656 if (comparison == 0) 1657 { 1658 // Trying to get methods lower in the interface hierarchy sorted before the same methods 1659 // higher in the hierarchy 1660 Class<?> declaringClass1 = o1.getDeclaringClass(); 1661 Class<?> declaringClass2 = o2.getDeclaringClass(); 1662 if (declaringClass1.isInterface() && declaringClass2.isInterface() && 1663 !declaringClass1.equals(declaringClass2)) { 1664 if (declaringClass1.isAssignableFrom(declaringClass2)) 1665 { 1666 comparison = -1; 1667 } 1668 else if (declaringClass1.isAssignableFrom(declaringClass2)) 1669 { 1670 comparison = 1; 1671 } 1672 } 1673 } 1674 1675 return comparison; 1676 } 1677 } 1678 1679 private List<Method> getUniqueMethods(Class interfaceType) 1680 { 1681 final List<Method> unique = new ArrayList<>(Arrays.asList(interfaceType.getMethods())); 1682 Collections.sort(unique, METHOD_COMPARATOR); 1683 Method last = null; 1684 Iterator<Method> iterator = unique.iterator(); 1685 while (iterator.hasNext()) 1686 { 1687 Method m = iterator.next(); 1688 if (last != null && METHOD_COMPARATOR.compare(m, last) == 0) 1689 { 1690 last = m; 1691 iterator.remove(); 1692 } 1693 } 1694 return unique; 1695 } 1696 1697 @Override 1698 public String toString() 1699 { 1700 return String.format("PlasticClassImpl[%s]", className); 1701 } 1702}