001    // Copyright 2011 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.AnnotationNode;
021    import org.apache.tapestry5.internal.plastic.asm.tree.ClassNode;
022    import org.apache.tapestry5.plastic.*;
023    
024    import java.lang.annotation.Annotation;
025    import java.lang.reflect.Modifier;
026    import java.util.*;
027    import java.util.concurrent.CopyOnWriteArrayList;
028    
029    /**
030     * Responsible for managing a class loader that allows ASM {@link ClassNode}s
031     * to be instantiated as runtime classes.
032     */
033    @SuppressWarnings("rawtypes")
034    public class PlasticClassPool implements ClassLoaderDelegate, Opcodes, PlasticClassListenerHub
035    {
036        final PlasticClassLoader loader;
037    
038        private final PlasticManagerDelegate delegate;
039    
040        private final Set<String> controlledPackages;
041    
042        /**
043         * Maps class names to instantiators for that class name.
044         * Synchronized on the loader.
045         */
046        private final Map<String, ClassInstantiator> instantiators = PlasticInternalUtils.newMap();
047    
048        private final InheritanceData emptyInheritanceData = new InheritanceData();
049    
050        private final StaticContext emptyStaticContext = new StaticContext();
051    
052        private final List<PlasticClassListener> listeners = new CopyOnWriteArrayList<PlasticClassListener>();
053    
054        private final Cache<String, TypeCategory> typeName2Category = new Cache<String, TypeCategory>()
055        {
056    
057            protected TypeCategory convert(String typeName)
058            {
059                ClassNode cn = constructClassNode(typeName);
060    
061                return Modifier.isInterface(cn.access) ? TypeCategory.INTERFACE : TypeCategory.CLASS;
062            }
063        };
064    
065        static class BaseClassDef
066        {
067            final InheritanceData inheritanceData;
068    
069            final StaticContext staticContext;
070    
071            public BaseClassDef(InheritanceData inheritanceData, StaticContext staticContext)
072            {
073                this.inheritanceData = inheritanceData;
074                this.staticContext = staticContext;
075            }
076        }
077    
078        /**
079         * Map from FQCN to BaseClassDef. Synchronized on the loader.
080         */
081        private final Map<String, BaseClassDef> baseClassDefs = new HashMap<String, PlasticClassPool.BaseClassDef>();
082    
083        private final Set<TransformationOption> options;
084    
085        /**
086         * Creates the pool with a set of controlled packages; all classes in the controlled packages are loaded by the
087         * pool's class loader, and all top-level classes in the controlled packages are transformed via the delegate.
088         *
089         * @param parentLoader       typically, the Thread's context class loader
090         * @param delegate           responsible for end stages of transforming top-level classes
091         * @param controlledPackages set of package names (note: retained, not copied)
092         * @param options            used when transforming classes
093         */
094        public PlasticClassPool(ClassLoader parentLoader, PlasticManagerDelegate delegate, Set<String> controlledPackages,
095                                Set<TransformationOption> options)
096        {
097            loader = new PlasticClassLoader(parentLoader, this);
098            this.delegate = delegate;
099            this.controlledPackages = controlledPackages;
100            this.options = options;
101        }
102    
103        public ClassLoader getClassLoader()
104        {
105            return loader;
106        }
107    
108        public Class realizeTransformedClass(ClassNode classNode, InheritanceData inheritanceData,
109                                             StaticContext staticContext)
110        {
111            synchronized (loader)
112            {
113                Class result = realize(PlasticInternalUtils.toClassName(classNode.name), ClassType.PRIMARY, classNode);
114    
115                baseClassDefs.put(result.getName(), new BaseClassDef(inheritanceData, staticContext));
116    
117                return result;
118            }
119    
120        }
121    
122        public Class realize(String primaryClassName, ClassType classType, final ClassNode classNode)
123        {
124            synchronized (loader)
125            {
126                if (!listeners.isEmpty())
127                {
128                    fire(toEvent(primaryClassName, classType, classNode));
129                }
130    
131                byte[] bytecode = toBytecode(classNode);
132    
133                String className = PlasticInternalUtils.toClassName(classNode.name);
134    
135                return loader.defineClassWithBytecode(className, bytecode);
136            }
137        }
138    
139        private PlasticClassEvent toEvent(final String primaryClassName, final ClassType classType,
140                                          final ClassNode classNode)
141        {
142            return new PlasticClassEvent()
143            {
144                public ClassType getType()
145                {
146                    return classType;
147                }
148    
149                public String getPrimaryClassName()
150                {
151                    return primaryClassName;
152                }
153    
154                public String getDissasembledBytecode()
155                {
156                    return PlasticInternalUtils.dissasembleBytecode(classNode);
157                }
158    
159                public String getClassName()
160                {
161                    return PlasticInternalUtils.toClassName(classNode.name);
162                }
163            };
164        }
165    
166        private void fire(PlasticClassEvent event)
167        {
168            for (PlasticClassListener listener : listeners)
169            {
170                listener.classWillLoad(event);
171            }
172        }
173    
174        private byte[] toBytecode(ClassNode classNode)
175        {
176            ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
177    
178            classNode.accept(writer);
179    
180            return writer.toByteArray();
181        }
182    
183        public AnnotationAccess createAnnotationAccess(String className)
184        {
185            try
186            {
187                final Class<?> searchClass = loader.loadClass(className);
188    
189                return new AnnotationAccess()
190                {
191                    public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType)
192                    {
193                        return getAnnotation(annotationType) != null;
194                    }
195    
196                    public <T extends Annotation> T getAnnotation(Class<T> annotationType)
197                    {
198                        return searchClass.getAnnotation(annotationType);
199                    }
200                };
201            } catch (Exception ex)
202            {
203                throw new RuntimeException(ex);
204            }
205        }
206    
207        public AnnotationAccess createAnnotationAccess(List<AnnotationNode> annotationNodes)
208        {
209            if (annotationNodes == null)
210            {
211                return EmptyAnnotationAccess.SINGLETON;
212            }
213    
214            final Map<String, Object> cache = PlasticInternalUtils.newMap();
215            final Map<String, AnnotationNode> nameToNode = PlasticInternalUtils.newMap();
216    
217            for (AnnotationNode node : annotationNodes)
218            {
219                nameToNode.put(PlasticInternalUtils.objectDescriptorToClassName(node.desc), node);
220            }
221    
222            return new AnnotationAccess()
223            {
224                public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType)
225                {
226                    return nameToNode.containsKey(annotationType.getName());
227                }
228    
229                public <T extends Annotation> T getAnnotation(Class<T> annotationType)
230                {
231                    String className = annotationType.getName();
232    
233                    Object result = cache.get(className);
234    
235                    if (result == null)
236                    {
237                        result = buildAnnotation(className);
238    
239                        if (result != null)
240                            cache.put(className, result);
241                    }
242    
243                    return annotationType.cast(result);
244                }
245    
246                private Object buildAnnotation(String className)
247                {
248                    AnnotationNode node = nameToNode.get(className);
249    
250                    if (node == null)
251                        return null;
252    
253                    return createAnnotation(className, node);
254                }
255            };
256        }
257    
258        Class loadClass(String className)
259        {
260            try
261            {
262                return loader.loadClass(className);
263            } catch (Exception ex)
264            {
265                throw new RuntimeException(String.format("Unable to load class %s: %s", className,
266                        PlasticInternalUtils.toMessage(ex)), ex);
267            }
268        }
269    
270        protected Object createAnnotation(String className, AnnotationNode node)
271        {
272            AnnotationBuilder builder = new AnnotationBuilder(loadClass(className), this);
273    
274            node.accept(builder);
275    
276            return builder.createAnnotation();
277        }
278    
279        public boolean shouldInterceptClassLoading(String className)
280        {
281            int searchFromIndex = className.length() - 1;
282    
283            while (true)
284            {
285                int dotx = className.lastIndexOf('.', searchFromIndex);
286    
287                if (dotx < 0)
288                    break;
289    
290                String packageName = className.substring(0, dotx);
291    
292                if (controlledPackages.contains(packageName))
293                    return true;
294    
295                searchFromIndex = dotx - 1;
296            }
297    
298            return false;
299        }
300    
301        public Class<?> loadAndTransformClass(String className) throws ClassNotFoundException
302        {
303            // Inner classes are not transformed, but they are loaded by the same class loader.
304    
305            if (className.contains("$"))
306                return loadInnerClass(className);
307    
308            // TODO: What about interfaces, enums, annotations, etc. ... they shouldn't be in the package, but
309            // we should generate a reasonable error message.
310    
311            InternalPlasticClassTransformation transformation = getPlasticClassTransformation(className);
312    
313            delegate.transform(transformation.getPlasticClass());
314    
315            ClassInstantiator createInstantiator = transformation.createInstantiator();
316            ClassInstantiator configuredInstantiator = delegate.configureInstantiator(className, createInstantiator);
317    
318            instantiators.put(className, configuredInstantiator);
319    
320            return transformation.getTransformedClass();
321        }
322    
323        private Class loadInnerClass(String className)
324        {
325            byte[] bytecode = readBytecode(className);
326    
327            return loader.defineClassWithBytecode(className, bytecode);
328        }
329    
330        /**
331         * For a fully-qualified class name of an <em>existing</em> class, loads the bytes for the class
332         * and returns a PlasticClass instance.
333         *
334         * @throws ClassNotFoundException
335         */
336        public InternalPlasticClassTransformation getPlasticClassTransformation(String className)
337                throws ClassNotFoundException
338        {
339            assert PlasticInternalUtils.isNonBlank(className);
340    
341            ClassNode classNode = constructClassNode(className);
342    
343            String baseClassName = PlasticInternalUtils.toClassName(classNode.superName);
344    
345            return createTransformation(baseClassName, classNode);
346        }
347    
348        private InternalPlasticClassTransformation createTransformation(String baseClassName, ClassNode classNode)
349                throws ClassNotFoundException
350        {
351            if (shouldInterceptClassLoading(baseClassName))
352            {
353                loader.loadClass(baseClassName);
354    
355                BaseClassDef def = baseClassDefs.get(baseClassName);
356    
357                assert def != null;
358    
359                return new PlasticClassImpl(classNode, this, def.inheritanceData, def.staticContext);
360            }
361    
362            // When the base class is Object, or otherwise not in a transformed package,
363            // then start with the empty
364            return new PlasticClassImpl(classNode, this, emptyInheritanceData, emptyStaticContext);
365        }
366    
367        /**
368         * Constructs a class node by reading the raw bytecode for a class and instantiating a ClassNode
369         * (via {@link ClassReader#accept(org.apache.tapestry5.internal.plastic.asm.ClassVisitor, int)}).
370         *
371         * @param className fully qualified class name
372         * @return corresponding ClassNode
373         */
374        public ClassNode constructClassNode(String className)
375        {
376            byte[] bytecode = readBytecode(className);
377    
378            if (bytecode == null)
379                return null;
380    
381            return PlasticInternalUtils.convertBytecodeToClassNode(bytecode);
382        }
383    
384        private byte[] readBytecode(String className)
385        {
386            ClassLoader parentClassLoader = loader.getParent();
387    
388            return PlasticInternalUtils.readBytecodeForClass(parentClassLoader, className, true);
389        }
390    
391        public PlasticClassTransformation createTransformation(String baseClassName, String newClassName)
392        {
393            try
394            {
395                ClassNode newClassNode = new ClassNode();
396    
397                newClassNode.visit(V1_5, ACC_PUBLIC, PlasticInternalUtils.toInternalName(newClassName), null,
398                        PlasticInternalUtils.toInternalName(baseClassName), null);
399    
400                return createTransformation(baseClassName, newClassNode);
401            } catch (ClassNotFoundException ex)
402            {
403                throw new RuntimeException(String.format("Unable to create class %s as sub-class of %s: %s", newClassName,
404                        baseClassName, PlasticInternalUtils.toMessage(ex)), ex);
405            }
406        }
407    
408        public ClassInstantiator getClassInstantiator(String className)
409        {
410            synchronized (loader)
411            {
412                if (!instantiators.containsKey(className))
413                {
414                    try
415                    {
416                        loader.loadClass(className);
417                    } catch (ClassNotFoundException ex)
418                    {
419                        throw new RuntimeException(ex);
420                    }
421                }
422    
423                ClassInstantiator result = instantiators.get(className);
424    
425                if (result == null)
426                {
427                    // TODO: Verify that the problem is incorrect package, and not any other failure.
428    
429                    StringBuilder b = new StringBuilder();
430                    b.append("Class '")
431                            .append(className)
432                            .append("' is not a transformed class. Transformed classes should be in one of the following packages: ");
433    
434                    String sep = "";
435    
436                    List<String> names = new ArrayList<String>(controlledPackages);
437                    Collections.sort(names);
438    
439                    for (String name : names)
440                    {
441                        b.append(sep);
442                        b.append(name);
443    
444                        sep = ", ";
445                    }
446    
447                    String message = b.append(".").toString();
448    
449                    throw new IllegalArgumentException(message);
450                }
451    
452                return result;
453            }
454        }
455    
456        TypeCategory getTypeCategory(String typeName)
457        {
458            synchronized (loader)
459            {
460                // TODO: Is this the right place to cache this data?
461    
462                return typeName2Category.get(typeName);
463            }
464        }
465    
466        public void addPlasticClassListener(PlasticClassListener listener)
467        {
468            assert listener != null;
469    
470            listeners.add(listener);
471        }
472    
473        public void removePlasticClassListener(PlasticClassListener listener)
474        {
475            assert listener != null;
476    
477            listeners.remove(listener);
478        }
479    
480        boolean isEnabled(TransformationOption option)
481        {
482            return options.contains(option);
483        }
484    }