001// Copyright 2011-2013 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 015package org.apache.tapestry5.internal.plastic; 016 017import java.io.BufferedInputStream; 018import java.io.IOException; 019import java.io.InputStream; 020import java.lang.annotation.Annotation; 021import java.lang.reflect.Modifier; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.HashMap; 026import java.util.List; 027import java.util.ListIterator; 028import java.util.Map; 029import java.util.Set; 030import java.util.Stack; 031import java.util.concurrent.CopyOnWriteArrayList; 032 033import org.apache.tapestry5.internal.plastic.asm.ClassReader; 034import org.apache.tapestry5.internal.plastic.asm.ClassWriter; 035import org.apache.tapestry5.internal.plastic.asm.Opcodes; 036import org.apache.tapestry5.internal.plastic.asm.tree.AbstractInsnNode; 037import org.apache.tapestry5.internal.plastic.asm.tree.AnnotationNode; 038import org.apache.tapestry5.internal.plastic.asm.tree.ClassNode; 039import org.apache.tapestry5.internal.plastic.asm.tree.FieldInsnNode; 040import org.apache.tapestry5.internal.plastic.asm.tree.InsnList; 041import org.apache.tapestry5.internal.plastic.asm.tree.MethodInsnNode; 042import org.apache.tapestry5.internal.plastic.asm.tree.MethodNode; 043import org.apache.tapestry5.plastic.AnnotationAccess; 044import org.apache.tapestry5.plastic.ClassInstantiator; 045import org.apache.tapestry5.plastic.ClassType; 046import org.apache.tapestry5.plastic.PlasticClassEvent; 047import org.apache.tapestry5.plastic.PlasticClassListener; 048import org.apache.tapestry5.plastic.PlasticClassListenerHub; 049import org.apache.tapestry5.plastic.PlasticClassTransformation; 050import org.apache.tapestry5.plastic.PlasticConstants; 051import org.apache.tapestry5.plastic.PlasticManagerDelegate; 052import org.apache.tapestry5.plastic.TransformationOption; 053import org.slf4j.Logger; 054import org.slf4j.LoggerFactory; 055 056/** 057 * Responsible for managing a class loader that allows ASM {@link ClassNode}s 058 * to be instantiated as runtime classes. 059 */ 060@SuppressWarnings("rawtypes") 061public class PlasticClassPool implements ClassLoaderDelegate, Opcodes, PlasticClassListenerHub 062{ 063 private static final Logger LOGGER = LoggerFactory.getLogger(PlasticClassPool.class); 064 065 final PlasticClassLoader loader; 066 067 private final PlasticManagerDelegate delegate; 068 069 private final Set<String> controlledPackages; 070 071 private final Map<String, Boolean> checkedExceptionCache = new HashMap<String, Boolean>(); 072 073 074 // Would use Deque, but that's added in 1.6 and we're still striving for 1.5 code compatibility. 075 076 private final Stack<String> activeInstrumentClassNames = new Stack<String>(); 077 078 /** 079 * Maps class names to instantiators for that class name. 080 * Synchronized on the loader. 081 */ 082 private final Map<String, ClassInstantiator> instantiators = PlasticInternalUtils.newConcurrentMap(); 083 084 private final InheritanceData emptyInheritanceData = new InheritanceData(null); 085 086 private final StaticContext emptyStaticContext = new StaticContext(); 087 088 private final List<PlasticClassListener> listeners = new CopyOnWriteArrayList<PlasticClassListener>(); 089 090 private PlasticClassPool parent; 091 092 private Collection<PlasticClassPool> children = new ArrayList<>(); 093 094 private final Cache<String, TypeCategory> typeName2Category = new Cache<String, TypeCategory>() 095 { 096 @Override 097 protected TypeCategory convert(String typeName) 098 { 099 ClassNode cn = constructClassNodeFromBytecode(typeName); 100 101 return Modifier.isInterface(cn.access) ? TypeCategory.INTERFACE : TypeCategory.CLASS; 102 } 103 }; 104 105 static class BaseClassDef 106 { 107 final InheritanceData inheritanceData; 108 109 final StaticContext staticContext; 110 111 public BaseClassDef(InheritanceData inheritanceData, StaticContext staticContext) 112 { 113 this.inheritanceData = inheritanceData; 114 this.staticContext = staticContext; 115 } 116 } 117 118 /** 119 * Map from FQCN to BaseClassDef. Synchronized on the loader. 120 */ 121 private final Map<String, BaseClassDef> baseClassDefs = PlasticInternalUtils.newMap(); 122 123 124 private final Map<String, FieldInstrumentations> instrumentations = PlasticInternalUtils.newMap(); 125 126 private final Map<String, String> transformedClassNameToImplementationClassName = PlasticInternalUtils.newMap(); 127 128 129 private final FieldInstrumentations placeholder = new FieldInstrumentations(null); 130 131 132 private final Set<TransformationOption> options; 133 134 /** 135 * Creates the pool with a set of controlled packages; all classes in the controlled packages are loaded by the 136 * pool's class loader, and all top-level classes in the controlled packages are transformed via the delegate. 137 * 138 * @param parentLoader 139 * typically, the Thread's context class loader 140 * @param delegate 141 * responsible for end stages of transforming top-level classes 142 * @param controlledPackages 143 * set of package names (note: retained, not copied) 144 * @param options 145 * used when transforming classes 146 */ 147 public PlasticClassPool(ClassLoader parentLoader, PlasticManagerDelegate delegate, Set<String> controlledPackages, 148 Set<TransformationOption> options) 149 { 150 loader = new PlasticClassLoader(parentLoader, this); 151 this.delegate = delegate; 152 this.controlledPackages = controlledPackages; 153 this.options = options; 154 } 155 156 public ClassLoader getClassLoader() 157 { 158 return loader; 159 } 160 161 public Class realizeTransformedClass(ClassNode classNode, InheritanceData inheritanceData, 162 StaticContext staticContext) 163 { 164 synchronized (loader) 165 { 166 Class result = realize(PlasticInternalUtils.toClassName(classNode.name), ClassType.PRIMARY, classNode); 167 baseClassDefs.put(result.getName(), new BaseClassDef(inheritanceData, staticContext)); 168 169 return result; 170 } 171 172 } 173 174 public Class realize(String primaryClassName, ClassType classType, ClassNode classNode) 175 { 176 synchronized (loader) 177 { 178 if (!listeners.isEmpty()) 179 { 180 fire(toEvent(primaryClassName, classType, classNode)); 181 } 182 183 byte[] bytecode = toBytecode(classNode); 184 185 String className = PlasticInternalUtils.toClassName(classNode.name); 186 187 return loader.defineClassWithBytecode(className, bytecode); 188 } 189 } 190 191 private PlasticClassEvent toEvent(final String primaryClassName, final ClassType classType, 192 final ClassNode classNode) 193 { 194 return new PlasticClassEvent() 195 { 196 @Override 197 public ClassType getType() 198 { 199 return classType; 200 } 201 202 @Override 203 public String getPrimaryClassName() 204 { 205 return primaryClassName; 206 } 207 208 @Override 209 public String getDissasembledBytecode() 210 { 211 return PlasticInternalUtils.dissasembleBytecode(classNode); 212 } 213 214 @Override 215 public String getClassName() 216 { 217 return PlasticInternalUtils.toClassName(classNode.name); 218 } 219 }; 220 } 221 222 private void fire(PlasticClassEvent event) 223 { 224 for (PlasticClassListener listener : listeners) 225 { 226 listener.classWillLoad(event); 227 } 228 } 229 230 private byte[] toBytecode(ClassNode classNode) 231 { 232 ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); 233 234 classNode.accept(writer); 235 236 return writer.toByteArray(); 237 } 238 239 public AnnotationAccess createAnnotationAccess(String className) 240 { 241 try 242 { 243 final Class<?> searchClass = loader.loadClass(className); 244 245 return new AnnotationAccess() 246 { 247 @Override 248 public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType) 249 { 250 return getAnnotation(annotationType) != null; 251 } 252 253 @Override 254 public <T extends Annotation> T getAnnotation(Class<T> annotationType) 255 { 256 return searchClass.getAnnotation(annotationType); 257 } 258 }; 259 } catch (Exception ex) 260 { 261 throw new RuntimeException(ex); 262 } 263 } 264 265 public AnnotationAccess createAnnotationAccess(List<AnnotationNode> annotationNodes) 266 { 267 if (annotationNodes == null) 268 { 269 return EmptyAnnotationAccess.SINGLETON; 270 } 271 272 final Map<String, Object> cache = PlasticInternalUtils.newMap(); 273 final Map<String, AnnotationNode> nameToNode = PlasticInternalUtils.newMap(); 274 275 for (AnnotationNode node : annotationNodes) 276 { 277 nameToNode.put(PlasticInternalUtils.objectDescriptorToClassName(node.desc), node); 278 } 279 280 return new AnnotationAccess() 281 { 282 @Override 283 public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType) 284 { 285 return nameToNode.containsKey(annotationType.getName()); 286 } 287 288 @Override 289 public <T extends Annotation> T getAnnotation(Class<T> annotationType) 290 { 291 String className = annotationType.getName(); 292 293 Object result = cache.get(className); 294 295 if (result == null) 296 { 297 result = buildAnnotation(className); 298 299 if (result != null) 300 cache.put(className, result); 301 } 302 303 return annotationType.cast(result); 304 } 305 306 private Object buildAnnotation(String className) 307 { 308 AnnotationNode node = nameToNode.get(className); 309 310 if (node == null) 311 return null; 312 313 return createAnnotation(className, node); 314 } 315 }; 316 } 317 318 Class loadClass(String className) 319 { 320 try 321 { 322 return loader.loadClass(className); 323 } catch (Exception ex) 324 { 325 throw new RuntimeException(String.format("Unable to load class %s: %s", className, 326 PlasticInternalUtils.toMessage(ex)), ex); 327 } 328 } 329 330 protected Object createAnnotation(String className, AnnotationNode node) 331 { 332 AnnotationBuilder builder = new AnnotationBuilder(loadClass(className), this); 333 334 node.accept(builder); 335 336 return builder.createAnnotation(); 337 } 338 339 @Override 340 public boolean shouldInterceptClassLoading(String className) 341 { 342 int searchFromIndex = className.length() - 1; 343 344 while (true) 345 { 346 int dotx = className.lastIndexOf('.', searchFromIndex); 347 348 if (dotx < 0) 349 break; 350 351 String packageName = className.substring(0, dotx); 352 353 if (controlledPackages.contains(packageName)) 354 return true; 355 356 searchFromIndex = dotx - 1; 357 } 358 359 return false; 360 } 361 362 // Hopefully the synchronized will not cause a deadlock 363 364 @Override 365 public synchronized Class<?> loadAndTransformClass(String className) throws ClassNotFoundException 366 { 367 // Inner classes are not transformed, but they are loaded by the same class loader. 368 369 if (className.contains("$")) 370 { 371 return loadInnerClass(className); 372 } 373 374 // TODO: What about interfaces, enums, annotations, etc. ... they shouldn't be in the package, but 375 // we should generate a reasonable error message. 376 377 if (activeInstrumentClassNames.contains(className)) 378 { 379 StringBuilder builder = new StringBuilder(""); 380 String sep = ""; 381 382 for (String name : activeInstrumentClassNames) 383 { 384 builder.append(sep); 385 builder.append(name); 386 387 sep = ", "; 388 } 389 390 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.", 391 className, builder)); 392 } 393 394 activeInstrumentClassNames.push(className); 395 396 try 397 { 398 399 InternalPlasticClassTransformation transformation = getPlasticClassTransformation(className); 400 401 delegate.transform(transformation.getPlasticClass()); 402 403 ClassInstantiator createInstantiator = transformation.createInstantiator(); 404 ClassInstantiator configuredInstantiator = delegate.configureInstantiator(className, createInstantiator); 405 406 instantiators.put(className, configuredInstantiator); 407 408 return transformation.getTransformedClass(); 409 } finally 410 { 411 activeInstrumentClassNames.pop(); 412 } 413 } 414 415 private Class loadInnerClass(String className) 416 { 417 ClassNode classNode = constructClassNodeFromBytecode(className); 418 419 interceptFieldAccess(classNode); 420 421 return realize(className, ClassType.INNER, classNode); 422 } 423 424 private void interceptFieldAccess(ClassNode classNode) 425 { 426 for (MethodNode method : classNode.methods) 427 { 428 interceptFieldAccess(classNode.name, method); 429 } 430 } 431 432 private void interceptFieldAccess(String classInternalName, MethodNode method) 433 { 434 InsnList insns = method.instructions; 435 436 ListIterator it = insns.iterator(); 437 438 while (it.hasNext()) 439 { 440 AbstractInsnNode node = (AbstractInsnNode) it.next(); 441 442 int opcode = node.getOpcode(); 443 444 if (opcode != GETFIELD && opcode != PUTFIELD) 445 { 446 continue; 447 } 448 449 FieldInsnNode fnode = (FieldInsnNode) node; 450 451 String ownerInternalName = fnode.owner; 452 453 if (ownerInternalName.equals(classInternalName)) 454 { 455 continue; 456 } 457 458 FieldInstrumentation instrumentation = getFieldInstrumentation(ownerInternalName, fnode.name, opcode == GETFIELD); 459 460 if (instrumentation == null) 461 { 462 continue; 463 } 464 465 // Replace the field access node with the appropriate method invocation. 466 467 insns.insertBefore(fnode, new MethodInsnNode(INVOKEVIRTUAL, ownerInternalName, instrumentation.methodName, instrumentation.methodDescription, false)); 468 469 it.remove(); 470 } 471 } 472 473 474 /** 475 * For a fully-qualified class name of an <em>existing</em> class, loads the bytes for the class 476 * and returns a PlasticClass instance. 477 * 478 * @throws ClassNotFoundException 479 */ 480 public InternalPlasticClassTransformation getPlasticClassTransformation(String className) 481 throws ClassNotFoundException 482 { 483 assert PlasticInternalUtils.isNonBlank(className); 484 485 ClassNode classNode = constructClassNodeFromBytecode(className); 486 487 String baseClassName = PlasticInternalUtils.toClassName(classNode.superName); 488 489 instrumentations.put(classNode.name, new FieldInstrumentations(classNode.superName)); 490 491 // TODO: check whether second parameter should really be null 492 return createTransformation(baseClassName, classNode, null, false); 493 } 494 495 /** 496 * @param baseClassName 497 * class from which the transformed class extends 498 * @param classNode 499 * node for the class 500 * @param implementationClassNode 501 * node for the implementation class. May be null. 502 * @param proxy 503 * if true, the class is a new empty class; if false an existing class that's being transformed 504 * @throws ClassNotFoundException 505 */ 506 private InternalPlasticClassTransformation createTransformation(String baseClassName, ClassNode classNode, ClassNode implementationClassNode, boolean proxy) 507 throws ClassNotFoundException 508 { 509 if (shouldInterceptClassLoading(baseClassName)) 510 { 511 loader.loadClass(baseClassName); 512 513 PlasticClassPool current = this; 514 515 BaseClassDef def = current.baseClassDefs.get(baseClassName); 516 517 while (def == null && current.parent != null) 518 { 519 current = current.parent; 520 def = current.baseClassDefs.get(baseClassName); 521 } 522 523 // Usually, when df is still null, that's because the superclass 524 // is a page class too 525 if (def == null) 526 { 527 def = findBaseClassDef(baseClassName, current); 528 } 529 530 assert def != null; 531 532 return new PlasticClassImpl(classNode, implementationClassNode, this, def.inheritanceData, def.staticContext, proxy); 533 } 534 535 // When the base class is Object, or otherwise not in a transformed package, 536 // then start with the empty 537 return new PlasticClassImpl(classNode, implementationClassNode, this, emptyInheritanceData, emptyStaticContext, proxy); 538 } 539 540 private BaseClassDef findBaseClassDef(String baseClassName, PlasticClassPool plasticClassPool) 541 { 542 BaseClassDef def = plasticClassPool.baseClassDefs.get(baseClassName); 543 if (def == null) 544 { 545 for (PlasticClassPool child : plasticClassPool.children) 546 { 547 def = child.findBaseClassDef(baseClassName, child); 548 if (def != null) 549 { 550 break; 551 } 552 } 553 } 554 return def; 555 } 556 557 /** 558 * Constructs a class node by reading the raw bytecode for a class and instantiating a ClassNode 559 * (via {@link ClassReader#accept(org.apache.tapestry5.internal.plastic.asm.ClassVisitor, int)}). 560 * 561 * @param className 562 * fully qualified class name 563 * @return corresponding ClassNode 564 */ 565 public ClassNode constructClassNodeFromBytecode(String className) 566 { 567 byte[] bytecode = readBytecode(className); 568 569 if (bytecode == null) 570 return null; 571 572 return PlasticInternalUtils.convertBytecodeToClassNode(bytecode); 573 } 574 575 private byte[] readBytecode(String className) 576 { 577 ClassLoader parentClassLoader = loader.getParent(); 578 579 return PlasticInternalUtils.readBytecodeForClass(parentClassLoader, className, true); 580 } 581 582 public PlasticClassTransformation createTransformation(String baseClassName, String newClassName) 583 { 584 return createTransformation(baseClassName, newClassName, null); 585 } 586 587 public PlasticClassTransformation createTransformation(String baseClassName, String newClassName, String implementationClassName) 588 { 589 try 590 { 591 ClassNode newClassNode = new ClassNode(); 592 593 final String internalNewClassNameinternalName = PlasticInternalUtils.toInternalName(newClassName); 594 final String internalBaseClassName = PlasticInternalUtils.toInternalName(baseClassName); 595 newClassNode.visit(PlasticConstants.DEFAULT_VERSION_OPCODE, ACC_PUBLIC, internalNewClassNameinternalName, null, internalBaseClassName, null); 596 597 ClassNode implementationClassNode = null; 598 599 if (implementationClassName != null) 600 { 601 // When decorating or advising a service, implementationClassName is the name 602 // of a proxy class already, such as "$ServiceName_[random string]", 603 // which doesn't exist as a file in the classpath, just in memory. 604 // So we need to keep what's the original implementation class name 605 // for each proxy, even a proxy around a proxy. 606 if (transformedClassNameToImplementationClassName.containsKey(implementationClassName)) 607 { 608 implementationClassName = 609 transformedClassNameToImplementationClassName.get(implementationClassName); 610 } 611 612 if (!implementationClassName.startsWith("com.sun.proxy")) 613 { 614 615 try 616 { 617 implementationClassNode = readClassNode(implementationClassName); 618 } catch (IOException e) 619 { 620 LOGGER.warn(String.format("Unable to load class %s as the implementation of service %s", 621 implementationClassName, baseClassName)); 622 // Go on. Probably a proxy class 623 } 624 625 } 626 627 transformedClassNameToImplementationClassName.put(newClassName, implementationClassName); 628 629 } 630 631 return createTransformation(baseClassName, newClassNode, implementationClassNode, true); 632 } catch (ClassNotFoundException ex) 633 { 634 throw new RuntimeException(String.format("Unable to create class %s as sub-class of %s: %s", newClassName, 635 baseClassName, PlasticInternalUtils.toMessage(ex)), ex); 636 } 637 638 } 639 640 private ClassNode readClassNode(String className) throws IOException 641 { 642 return readClassNode(className, getClassLoader()); 643 } 644 645 static ClassNode readClassNode(String className, ClassLoader classLoader) throws IOException 646 { 647 ClassNode classNode = new ClassNode(); 648 final String location = PlasticInternalUtils.toInternalName(className) + ".class"; 649 InputStream inputStream = classLoader.getResourceAsStream(location); 650 BufferedInputStream bis = new BufferedInputStream(inputStream); 651 ClassReader classReader = new ClassReader(inputStream); 652 inputStream.close(); 653 bis.close(); 654 classReader.accept(classNode, 0); 655 return classNode; 656 657 } 658 659 public ClassInstantiator getClassInstantiator(String className) 660 { 661 ClassInstantiator result = instantiators.get(className); 662 663 if (result == null) 664 { 665 try 666 { 667 loader.loadClass(className); 668 result = instantiators.get(className); 669 } catch (ClassNotFoundException ex) 670 { 671 throw new RuntimeException(ex); 672 } 673 } 674 675 676 if (result != null) 677 { 678 return result; 679 } 680 681 // TODO: Verify that the problem is incorrect package, and not any other failure. 682 683 StringBuilder b = new StringBuilder(); 684 b.append("Class '") 685 .append(className) 686 .append("' is not a transformed class. Transformed classes should be in one of the following packages: "); 687 688 String sep = ""; 689 690 List<String> names = new ArrayList<String>(controlledPackages); 691 Collections.sort(names); 692 693 for (String name : names) 694 { 695 b.append(sep); 696 b.append(name); 697 698 sep = ", "; 699 } 700 701 String message = b.append('.').toString(); 702 703 throw new IllegalArgumentException(message); 704 } 705 706 TypeCategory getTypeCategory(String typeName) 707 { 708 synchronized (loader) 709 { 710 // TODO: Is this the right place to cache this data? 711 712 return typeName2Category.get(typeName); 713 } 714 } 715 716 @Override 717 public void addPlasticClassListener(PlasticClassListener listener) 718 { 719 assert listener != null; 720 721 listeners.add(listener); 722 } 723 724 @Override 725 public void removePlasticClassListener(PlasticClassListener listener) 726 { 727 assert listener != null; 728 729 listeners.remove(listener); 730 } 731 732 /** 733 * Sets the parent of this instance. Only used to look up baseClassDefs. 734 * @since 5.8.3 735 */ 736 public void setParent(PlasticClassPool parent) 737 { 738 this.parent = parent; 739 parent.children.add(this); 740 } 741 742 boolean isEnabled(TransformationOption option) 743 { 744 return options.contains(option); 745 } 746 747 748 void setFieldReadInstrumentation(String classInternalName, String fieldName, FieldInstrumentation fi) 749 { 750 instrumentations.get(classInternalName).read.put(fieldName, fi); 751 } 752 753 754 private FieldInstrumentations getFieldInstrumentations(String classInternalName) 755 { 756 757 // Check whether parent pool already has instrumentations for 758 // that class to avoid a duplicated class definition attempt 759 // when running tapestry-core in multiple classloader mode. 760 PlasticClassPool current = this; 761 FieldInstrumentations result; 762 do 763 { 764 result = current.instrumentations.get(classInternalName); 765 current = current.parent; 766 } 767 while (current != null && result == null); 768 769 if (result != null) 770 { 771 return result; 772 } 773 774 String className = PlasticInternalUtils.toClassName(classInternalName); 775 776 // If it is a top-level (not inner) class in a controlled package, then we 777 // will recursively load the class, to identify any field instrumentations 778 // in it. 779 if (!className.contains("$") && shouldInterceptClassLoading(className)) 780 { 781 try 782 { 783 loadAndTransformClass(className); 784 785 // The key is written into the instrumentations map as a side-effect 786 // of loading the class. 787 return instrumentations.get(classInternalName); 788 } catch (Exception ex) 789 { 790 throw new RuntimeException(PlasticInternalUtils.toMessage(ex), ex); 791 } 792 } 793 794 // Either a class outside of controlled packages, or an inner class. Use a placeholder 795 // that contains empty maps. 796 797 result = placeholder; 798 instrumentations.put(classInternalName, result); 799 800 return result; 801 } 802 803 FieldInstrumentation getFieldInstrumentation(String ownerClassInternalName, String fieldName, boolean forRead) 804 { 805 String currentName = ownerClassInternalName; 806 807 while (true) 808 { 809 810 if (currentName == null) 811 { 812 return null; 813 } 814 815 FieldInstrumentations instrumentations = getFieldInstrumentations(currentName); 816 817 FieldInstrumentation instrumentation = instrumentations.get(fieldName, forRead); 818 819 if (instrumentation != null) 820 { 821 return instrumentation; 822 } 823 824 currentName = instrumentations.superClassInternalName; 825 } 826 } 827 828 829 void setFieldWriteInstrumentation(String classInternalName, String fieldName, FieldInstrumentation fi) 830 { 831 instrumentations.get(classInternalName).write.put(fieldName, fi); 832 } 833 834 boolean isCheckedException(String exceptionName) 835 { 836 Boolean cached = checkedExceptionCache.get(exceptionName); 837 838 if (cached != null) 839 { 840 return cached; 841 } 842 843 try 844 { 845 Class asClass = getClassLoader().loadClass(exceptionName); 846 847 848 boolean checked = !(Error.class.isAssignableFrom(asClass) || 849 RuntimeException.class.isAssignableFrom(asClass)); 850 851 checkedExceptionCache.put(exceptionName, checked); 852 853 return checked; 854 } catch (Exception e) 855 { 856 throw new RuntimeException(e); 857 } 858 } 859 860 @Override 861 public String toString() 862 { 863 return "PlasticClassPool [loader=" + loader + "]"; 864 } 865 866} 867 868