    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