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 }