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 }