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        FieldInstrumentations result = instrumentations.get(classInternalName);
757
758        if (result != null)
759        {
760            return result;
761        }
762
763        String className = PlasticInternalUtils.toClassName(classInternalName);
764
765        // If it is a top-level (not inner) class in a controlled package, then we
766        // will recursively load the class, to identify any field instrumentations
767        // in it.
768        if (!className.contains("$") && shouldInterceptClassLoading(className))
769        {
770            try
771            {
772                loadAndTransformClass(className);
773
774                // The key is written into the instrumentations map as a side-effect
775                // of loading the class.
776                return instrumentations.get(classInternalName);
777            } catch (Exception ex)
778            {
779                throw new RuntimeException(PlasticInternalUtils.toMessage(ex), ex);
780            }
781        }
782
783        // Either a class outside of controlled packages, or an inner class. Use a placeholder
784        // that contains empty maps.
785
786        result = placeholder;
787        instrumentations.put(classInternalName, result);
788
789        return result;
790    }
791
792    FieldInstrumentation getFieldInstrumentation(String ownerClassInternalName, String fieldName, boolean forRead)
793    {
794        String currentName = ownerClassInternalName;
795
796        while (true)
797        {
798
799            if (currentName == null)
800            {
801                return null;
802            }
803
804            FieldInstrumentations instrumentations = getFieldInstrumentations(currentName);
805
806            FieldInstrumentation instrumentation = instrumentations.get(fieldName, forRead);
807
808            if (instrumentation != null)
809            {
810                return instrumentation;
811            }
812
813            currentName = instrumentations.superClassInternalName;
814        }
815    }
816
817
818    void setFieldWriteInstrumentation(String classInternalName, String fieldName, FieldInstrumentation fi)
819    {
820        instrumentations.get(classInternalName).write.put(fieldName, fi);
821    }
822
823    boolean isCheckedException(String exceptionName)
824    {
825        Boolean cached = checkedExceptionCache.get(exceptionName);
826
827        if (cached != null)
828        {
829            return cached;
830        }
831
832        try
833        {
834            Class asClass = getClassLoader().loadClass(exceptionName);
835
836
837            boolean checked = !(Error.class.isAssignableFrom(asClass) ||
838                    RuntimeException.class.isAssignableFrom(asClass));
839
840            checkedExceptionCache.put(exceptionName, checked);
841
842            return checked;
843        } catch (Exception e)
844        {
845            throw new RuntimeException(e);
846        }
847    }
848}
849
850