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.ClassReader;
018 import org.apache.tapestry5.internal.plastic.asm.ClassWriter;
019 import org.apache.tapestry5.internal.plastic.asm.Opcodes;
020 import org.apache.tapestry5.internal.plastic.asm.tree.*;
021 import org.apache.tapestry5.plastic.*;
022
023 import java.lang.annotation.Annotation;
024 import java.lang.reflect.Modifier;
025 import java.util.*;
026 import java.util.concurrent.CopyOnWriteArrayList;
027
028 /**
029 * Responsible for managing a class loader that allows ASM {@link ClassNode}s
030 * to be instantiated as runtime classes.
031 */
032 @SuppressWarnings("rawtypes")
033 public class PlasticClassPool implements ClassLoaderDelegate, Opcodes, PlasticClassListenerHub
034 {
035 final PlasticClassLoader loader;
036
037 private final PlasticManagerDelegate delegate;
038
039 private final Set<String> controlledPackages;
040
041
042 // Would use Deque, but that's added in 1.6 and we're still striving for 1.5 code compatibility.
043
044 private final Stack<String> activeInstrumentClassNames = new Stack<String>();
045
046 /**
047 * Maps class names to instantiators for that class name.
048 * Synchronized on the loader.
049 */
050 private final Map<String, ClassInstantiator> instantiators = PlasticInternalUtils.newMap();
051
052 private final InheritanceData emptyInheritanceData = new InheritanceData();
053
054 private final StaticContext emptyStaticContext = new StaticContext();
055
056 private final List<PlasticClassListener> listeners = new CopyOnWriteArrayList<PlasticClassListener>();
057
058 private final Cache<String, TypeCategory> typeName2Category = new Cache<String, TypeCategory>()
059 {
060 protected TypeCategory convert(String typeName)
061 {
062 ClassNode cn = constructClassNodeFromBytecode(typeName);
063
064 return Modifier.isInterface(cn.access) ? TypeCategory.INTERFACE : TypeCategory.CLASS;
065 }
066 };
067
068 static class BaseClassDef
069 {
070 final InheritanceData inheritanceData;
071
072 final StaticContext staticContext;
073
074 public BaseClassDef(InheritanceData inheritanceData, StaticContext staticContext)
075 {
076 this.inheritanceData = inheritanceData;
077 this.staticContext = staticContext;
078 }
079 }
080
081 /**
082 * Map from FQCN to BaseClassDef. Synchronized on the loader.
083 */
084 private final Map<String, BaseClassDef> baseClassDefs = PlasticInternalUtils.newMap();
085
086
087 private final Map<String, FieldInstrumentations> instrumentations = PlasticInternalUtils.newMap();
088
089 private final FieldInstrumentations placeholder = new FieldInstrumentations(null);
090
091
092 private final Set<TransformationOption> options;
093
094 /**
095 * Creates the pool with a set of controlled packages; all classes in the controlled packages are loaded by the
096 * pool's class loader, and all top-level classes in the controlled packages are transformed via the delegate.
097 *
098 * @param parentLoader typically, the Thread's context class loader
099 * @param delegate responsible for end stages of transforming top-level classes
100 * @param controlledPackages set of package names (note: retained, not copied)
101 * @param options used when transforming classes
102 */
103 public PlasticClassPool(ClassLoader parentLoader, PlasticManagerDelegate delegate, Set<String> controlledPackages,
104 Set<TransformationOption> options)
105 {
106 loader = new PlasticClassLoader(parentLoader, this);
107 this.delegate = delegate;
108 this.controlledPackages = controlledPackages;
109 this.options = options;
110 }
111
112 public ClassLoader getClassLoader()
113 {
114 return loader;
115 }
116
117 public Class realizeTransformedClass(ClassNode classNode, InheritanceData inheritanceData,
118 StaticContext staticContext)
119 {
120 synchronized (loader)
121 {
122 Class result = realize(PlasticInternalUtils.toClassName(classNode.name), ClassType.PRIMARY, classNode);
123
124 baseClassDefs.put(result.getName(), new BaseClassDef(inheritanceData, staticContext));
125
126 return result;
127 }
128
129 }
130
131 public Class realize(String primaryClassName, ClassType classType, ClassNode classNode)
132 {
133 synchronized (loader)
134 {
135 if (!listeners.isEmpty())
136 {
137 fire(toEvent(primaryClassName, classType, classNode));
138 }
139
140 byte[] bytecode = toBytecode(classNode);
141
142 String className = PlasticInternalUtils.toClassName(classNode.name);
143
144 return loader.defineClassWithBytecode(className, bytecode);
145 }
146 }
147
148 private PlasticClassEvent toEvent(final String primaryClassName, final ClassType classType,
149 final ClassNode classNode)
150 {
151 return new PlasticClassEvent()
152 {
153 public ClassType getType()
154 {
155 return classType;
156 }
157
158 public String getPrimaryClassName()
159 {
160 return primaryClassName;
161 }
162
163 public String getDissasembledBytecode()
164 {
165 return PlasticInternalUtils.dissasembleBytecode(classNode);
166 }
167
168 public String getClassName()
169 {
170 return PlasticInternalUtils.toClassName(classNode.name);
171 }
172 };
173 }
174
175 private void fire(PlasticClassEvent event)
176 {
177 for (PlasticClassListener listener : listeners)
178 {
179 listener.classWillLoad(event);
180 }
181 }
182
183 private byte[] toBytecode(ClassNode classNode)
184 {
185 ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
186
187 classNode.accept(writer);
188
189 return writer.toByteArray();
190 }
191
192 public AnnotationAccess createAnnotationAccess(String className)
193 {
194 try
195 {
196 final Class<?> searchClass = loader.loadClass(className);
197
198 return new AnnotationAccess()
199 {
200 public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType)
201 {
202 return getAnnotation(annotationType) != null;
203 }
204
205 public <T extends Annotation> T getAnnotation(Class<T> annotationType)
206 {
207 return searchClass.getAnnotation(annotationType);
208 }
209 };
210 } catch (Exception ex)
211 {
212 throw new RuntimeException(ex);
213 }
214 }
215
216 public AnnotationAccess createAnnotationAccess(List<AnnotationNode> annotationNodes)
217 {
218 if (annotationNodes == null)
219 {
220 return EmptyAnnotationAccess.SINGLETON;
221 }
222
223 final Map<String, Object> cache = PlasticInternalUtils.newMap();
224 final Map<String, AnnotationNode> nameToNode = PlasticInternalUtils.newMap();
225
226 for (AnnotationNode node : annotationNodes)
227 {
228 nameToNode.put(PlasticInternalUtils.objectDescriptorToClassName(node.desc), node);
229 }
230
231 return new AnnotationAccess()
232 {
233 public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType)
234 {
235 return nameToNode.containsKey(annotationType.getName());
236 }
237
238 public <T extends Annotation> T getAnnotation(Class<T> annotationType)
239 {
240 String className = annotationType.getName();
241
242 Object result = cache.get(className);
243
244 if (result == null)
245 {
246 result = buildAnnotation(className);
247
248 if (result != null)
249 cache.put(className, result);
250 }
251
252 return annotationType.cast(result);
253 }
254
255 private Object buildAnnotation(String className)
256 {
257 AnnotationNode node = nameToNode.get(className);
258
259 if (node == null)
260 return null;
261
262 return createAnnotation(className, node);
263 }
264 };
265 }
266
267 Class loadClass(String className)
268 {
269 try
270 {
271 return loader.loadClass(className);
272 } catch (Exception ex)
273 {
274 throw new RuntimeException(String.format("Unable to load class %s: %s", className,
275 PlasticInternalUtils.toMessage(ex)), ex);
276 }
277 }
278
279 protected Object createAnnotation(String className, AnnotationNode node)
280 {
281 AnnotationBuilder builder = new AnnotationBuilder(loadClass(className), this);
282
283 node.accept(builder);
284
285 return builder.createAnnotation();
286 }
287
288 public boolean shouldInterceptClassLoading(String className)
289 {
290 int searchFromIndex = className.length() - 1;
291
292 while (true)
293 {
294 int dotx = className.lastIndexOf('.', searchFromIndex);
295
296 if (dotx < 0)
297 break;
298
299 String packageName = className.substring(0, dotx);
300
301 if (controlledPackages.contains(packageName))
302 return true;
303
304 searchFromIndex = dotx - 1;
305 }
306
307 return false;
308 }
309
310 // Hopefully the synchronized will not cause a deadlock
311
312 public synchronized Class<?> loadAndTransformClass(String className) throws ClassNotFoundException
313 {
314 // Inner classes are not transformed, but they are loaded by the same class loader.
315
316 if (className.contains("$"))
317 {
318 return loadInnerClass(className);
319 }
320
321 // TODO: What about interfaces, enums, annotations, etc. ... they shouldn't be in the package, but
322 // we should generate a reasonable error message.
323
324 if (activeInstrumentClassNames.contains(className))
325 {
326 StringBuilder builder = new StringBuilder("");
327 String sep = "";
328
329 for (String name : activeInstrumentClassNames)
330 {
331 builder.append(sep);
332 builder.append(name);
333
334 sep = ", ";
335 }
336
337 throw new IllegalStateException(String.format("Unable to transform class %s as it is already in the process of being transformed; there is a cycle among the following classes: %s.",
338 className, builder));
339 }
340
341 activeInstrumentClassNames.push(className);
342
343 try
344 {
345
346 InternalPlasticClassTransformation transformation = getPlasticClassTransformation(className);
347
348 delegate.transform(transformation.getPlasticClass());
349
350 ClassInstantiator createInstantiator = transformation.createInstantiator();
351 ClassInstantiator configuredInstantiator = delegate.configureInstantiator(className, createInstantiator);
352
353 instantiators.put(className, configuredInstantiator);
354
355 return transformation.getTransformedClass();
356 } finally
357 {
358 activeInstrumentClassNames.pop();
359 }
360 }
361
362 private Class loadInnerClass(String className)
363 {
364 ClassNode classNode = constructClassNodeFromBytecode(className);
365
366 interceptFieldAccess(classNode);
367
368 return realize(className, ClassType.INNER, classNode);
369 }
370
371 private void interceptFieldAccess(ClassNode classNode)
372 {
373 for (MethodNode method : classNode.methods)
374 {
375 interceptFieldAccess(classNode.name, method);
376 }
377 }
378
379 private void interceptFieldAccess(String classInternalName, MethodNode method)
380 {
381 InsnList insns = method.instructions;
382
383 ListIterator it = insns.iterator();
384
385 while (it.hasNext())
386 {
387 AbstractInsnNode node = (AbstractInsnNode) it.next();
388
389 int opcode = node.getOpcode();
390
391 if (opcode != GETFIELD && opcode != PUTFIELD)
392 {
393 continue;
394 }
395
396 FieldInsnNode fnode = (FieldInsnNode) node;
397
398 String ownerInternalName = fnode.owner;
399
400 if (ownerInternalName.equals(classInternalName))
401 {
402 continue;
403 }
404
405 FieldInstrumentation instrumentation = getFieldInstrumentation(ownerInternalName, fnode.name, opcode == GETFIELD);
406
407 if (instrumentation == null)
408 {
409 continue;
410 }
411
412 // Replace the field access node with the appropriate method invocation.
413
414 insns.insertBefore(fnode, new MethodInsnNode(INVOKEVIRTUAL, ownerInternalName, instrumentation.methodName, instrumentation.methodDescription));
415
416 it.remove();
417 }
418 }
419
420
421 /**
422 * For a fully-qualified class name of an <em>existing</em> class, loads the bytes for the class
423 * and returns a PlasticClass instance.
424 *
425 * @throws ClassNotFoundException
426 */
427 public InternalPlasticClassTransformation getPlasticClassTransformation(String className)
428 throws ClassNotFoundException
429 {
430 assert PlasticInternalUtils.isNonBlank(className);
431
432 ClassNode classNode = constructClassNodeFromBytecode(className);
433
434 String baseClassName = PlasticInternalUtils.toClassName(classNode.superName);
435
436 instrumentations.put(classNode.name, new FieldInstrumentations(classNode.superName));
437
438 return createTransformation(baseClassName, classNode, false);
439 }
440
441 /**
442 * @param baseClassName class from which the transformed class extends
443 * @param classNode node for the class
444 * @param proxy if true, the class is a new empty class; if false an existing class that's being transformed
445 * @return
446 * @throws ClassNotFoundException
447 */
448 private InternalPlasticClassTransformation createTransformation(String baseClassName, ClassNode classNode, boolean proxy)
449 throws ClassNotFoundException
450 {
451 if (shouldInterceptClassLoading(baseClassName))
452 {
453 loader.loadClass(baseClassName);
454
455 BaseClassDef def = baseClassDefs.get(baseClassName);
456
457 assert def != null;
458
459 return new PlasticClassImpl(classNode, this, def.inheritanceData, def.staticContext, proxy);
460 }
461
462 // When the base class is Object, or otherwise not in a transformed package,
463 // then start with the empty
464 return new PlasticClassImpl(classNode, this, emptyInheritanceData, emptyStaticContext, proxy);
465 }
466
467 /**
468 * Constructs a class node by reading the raw bytecode for a class and instantiating a ClassNode
469 * (via {@link ClassReader#accept(org.apache.tapestry5.internal.plastic.asm.ClassVisitor, int)}).
470 *
471 * @param className fully qualified class name
472 * @return corresponding ClassNode
473 */
474 public ClassNode constructClassNodeFromBytecode(String className)
475 {
476 byte[] bytecode = readBytecode(className);
477
478 if (bytecode == null)
479 return null;
480
481 return PlasticInternalUtils.convertBytecodeToClassNode(bytecode);
482 }
483
484 private byte[] readBytecode(String className)
485 {
486 ClassLoader parentClassLoader = loader.getParent();
487
488 return PlasticInternalUtils.readBytecodeForClass(parentClassLoader, className, true);
489 }
490
491 public PlasticClassTransformation createTransformation(String baseClassName, String newClassName)
492 {
493 try
494 {
495 ClassNode newClassNode = new ClassNode();
496
497 newClassNode.visit(V1_5, ACC_PUBLIC, PlasticInternalUtils.toInternalName(newClassName), null,
498 PlasticInternalUtils.toInternalName(baseClassName), null);
499
500 return createTransformation(baseClassName, newClassNode, true);
501 } catch (ClassNotFoundException ex)
502 {
503 throw new RuntimeException(String.format("Unable to create class %s as sub-class of %s: %s", newClassName,
504 baseClassName, PlasticInternalUtils.toMessage(ex)), ex);
505 }
506 }
507
508 public ClassInstantiator getClassInstantiator(String className)
509 {
510 synchronized (loader)
511 {
512 if (!instantiators.containsKey(className))
513 {
514 try
515 {
516 loader.loadClass(className);
517 } catch (ClassNotFoundException ex)
518 {
519 throw new RuntimeException(ex);
520 }
521 }
522
523 ClassInstantiator result = instantiators.get(className);
524
525 if (result == null)
526 {
527 // TODO: Verify that the problem is incorrect package, and not any other failure.
528
529 StringBuilder b = new StringBuilder();
530 b.append("Class '")
531 .append(className)
532 .append("' is not a transformed class. Transformed classes should be in one of the following packages: ");
533
534 String sep = "";
535
536 List<String> names = new ArrayList<String>(controlledPackages);
537 Collections.sort(names);
538
539 for (String name : names)
540 {
541 b.append(sep);
542 b.append(name);
543
544 sep = ", ";
545 }
546
547 String message = b.append(".").toString();
548
549 throw new IllegalArgumentException(message);
550 }
551
552 return result;
553 }
554 }
555
556 TypeCategory getTypeCategory(String typeName)
557 {
558 synchronized (loader)
559 {
560 // TODO: Is this the right place to cache this data?
561
562 return typeName2Category.get(typeName);
563 }
564 }
565
566 public void addPlasticClassListener(PlasticClassListener listener)
567 {
568 assert listener != null;
569
570 listeners.add(listener);
571 }
572
573 public void removePlasticClassListener(PlasticClassListener listener)
574 {
575 assert listener != null;
576
577 listeners.remove(listener);
578 }
579
580 boolean isEnabled(TransformationOption option)
581 {
582 return options.contains(option);
583 }
584
585
586 void setFieldReadInstrumentation(String classInternalName, String fieldName, FieldInstrumentation fi)
587 {
588 instrumentations.get(classInternalName).read.put(fieldName, fi);
589 }
590
591
592 private FieldInstrumentations getFieldInstrumentations(String classInternalName)
593 {
594 FieldInstrumentations result = instrumentations.get(classInternalName);
595
596 if (result != null)
597 {
598 return result;
599 }
600
601 String className = PlasticInternalUtils.toClassName(classInternalName);
602
603 // If it is a top-level (not inner) class in a controlled package, then we
604 // will recursively load the class, to identify any field instrumentations
605 // in it.
606 if (!className.contains("$") && shouldInterceptClassLoading(className))
607 {
608 try
609 {
610 loadAndTransformClass(className);
611
612 // The key is written into the instrumentations map as a side-effect
613 // of loading the class.
614 return instrumentations.get(classInternalName);
615 } catch (Exception ex)
616 {
617 throw new RuntimeException(PlasticInternalUtils.toMessage(ex), ex);
618 }
619 }
620
621 // Either a class outside of controlled packages, or an inner class. Use a placeholder
622 // that contains empty maps.
623
624 result = placeholder;
625 instrumentations.put(classInternalName, result);
626
627 return result;
628 }
629
630 FieldInstrumentation getFieldInstrumentation(String ownerClassInternalName, String fieldName, boolean forRead)
631 {
632 String currentName = ownerClassInternalName;
633
634 while (true)
635 {
636
637 if (currentName == null)
638 {
639 return null;
640 }
641
642 FieldInstrumentations instrumentations = getFieldInstrumentations(currentName);
643
644 FieldInstrumentation instrumentation = instrumentations.get(fieldName, forRead);
645
646 if (instrumentation != null)
647 {
648 return instrumentation;
649 }
650
651 currentName = instrumentations.superClassInternalName;
652 }
653 }
654
655
656 void setFieldWriteInstrumentation(String classInternalName, String fieldName, FieldInstrumentation fi)
657 {
658 instrumentations.get(classInternalName).write.put(fieldName, fi);
659 }
660
661 }