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.plastic;
016    
017    import java.util.Collection;
018    import java.util.EnumSet;
019    import java.util.Set;
020    
021    import org.apache.tapestry5.internal.plastic.Lockable;
022    import org.apache.tapestry5.internal.plastic.NoopDelegate;
023    import org.apache.tapestry5.internal.plastic.PlasticClassPool;
024    import org.apache.tapestry5.internal.plastic.PlasticInternalUtils;
025    
026    /**
027     * Manages the internal class loaders and other logics necessary to load and transform existing classes,
028     * or to create new classes dynamically at runtime. New instances are instantiates using
029     * {@link #withClassLoader(ClassLoader)} or {@link #withContextClassLoader()}, then configuring
030     * the returned options object before invoking {@link PlasticManagerBuilder#create()}.
031     */
032    @SuppressWarnings("unchecked")
033    public class PlasticManager implements PlasticClassListenerHub
034    {
035        private final PlasticClassPool pool;
036    
037        /**
038         * A builder object for configuring the PlasticManager before instantiating it. Assumes a no-op
039         * {@link PlasticManagerDelegate} and an empty set of controlled packages, which is appropriate
040         * when simply {@linkplain PlasticManager#createProxy(Class, PlasticClassTransformer) creating proxy objects}.
041         * The builder object is internally mutable and uses a fluid API (each method returns the same instance).
042         */
043        public static class PlasticManagerBuilder extends Lockable
044        {
045            private final ClassLoader loader;
046    
047            private PlasticManagerDelegate delegate = new NoopDelegate();
048    
049            private final Set<String> packages = PlasticInternalUtils.newSet();
050    
051            private final Set<TransformationOption> options = EnumSet.noneOf(TransformationOption.class);
052    
053            private PlasticManagerBuilder(ClassLoader loader)
054            {
055                assert loader != null;
056    
057                this.loader = loader;
058            }
059    
060            /**
061             * Sets the {@link PlasticManagerDelegate}, which is ultimately responsible for
062             * transforming classes loaded from controlled packages. The default delegate
063             * does nothing.
064             */
065            public PlasticManagerBuilder delegate(PlasticManagerDelegate delegate)
066            {
067                assert delegate != null;
068    
069                check();
070    
071                this.delegate = delegate;
072    
073                return this;
074            }
075    
076            /**
077             * Adds additional controlled packages, in which classes are loaded and transformed.
078             */
079            public PlasticManagerBuilder packages(Collection<String> packageNames)
080            {
081                check();
082    
083                packages.addAll(packageNames);
084    
085                return this;
086            }
087    
088            public PlasticManagerBuilder enable(TransformationOption option)
089            {
090                check();
091    
092                options.add(option);
093    
094                return this;
095            }
096    
097            /**
098             * Creates the PlasticManager with the current set of options.
099             * 
100             * @return the PlasticManager
101             */
102            public PlasticManager create()
103            {
104                lock();
105    
106                return new PlasticManager(loader, delegate, packages, options);
107            }
108        }
109    
110        /**
111         * Creates a new builder using the thread's context class loader.
112         */
113        public static PlasticManagerBuilder withContextClassLoader()
114        {
115            return withClassLoader(Thread.currentThread().getContextClassLoader());
116        }
117    
118        /** Creates a new builder using the specified class loader. */
119        public static PlasticManagerBuilder withClassLoader(ClassLoader loader)
120        {
121            return new PlasticManagerBuilder(loader);
122        }
123    
124        /**
125         * The standard constructor for PlasticManager, allowing a parent class loader to be specified (this is most often
126         * the thread's contextClassLoader), as well as the delegate and the names of all controlled packages.
127         * 
128         * @param parentClassLoader
129         *            main source for (untransformed) classes
130         * @param delegate
131         *            performs transformations on top-level classes from controlled packages
132         * @param controlledPackageNames
133         *            defines the packages that are to be transformed; top-classes in these packages
134         *            (or sub-packages) will be passed to the delegate for transformation
135         * @param options
136         *            used when transforming classes
137         */
138        private PlasticManager(ClassLoader parentClassLoader, PlasticManagerDelegate delegate,
139                Set<String> controlledPackageNames, Set<TransformationOption> options)
140        {
141            assert parentClassLoader != null;
142            assert delegate != null;
143            assert controlledPackageNames != null;
144    
145            pool = new PlasticClassPool(parentClassLoader, delegate, controlledPackageNames, options);
146        }
147    
148        /**
149         * Returns the ClassLoader that is used to instantiate transformed classes. The parent class loader
150         * of the returned class loader is the context class loader, or the class loader specified by
151         * {@link #withClassLoader(ClassLoader)}.
152         * 
153         * @return class loader used to load classes in controlled packages
154         */
155        public ClassLoader getClassLoader()
156        {
157            return pool.getClassLoader();
158        }
159    
160        /**
161         * This method is used only in testing to get the PlasticClass directly, bypassing the normal code paths. This
162         * is only invoked by Groovy tests which fudges the fact that the same class implements both PlasticClass and
163         * PlasticClassTransformation.
164         * TODO: This may make a kind of callback when we get to proxy creation, rather then pure transformation.
165         * TODO: Clean up this mess!
166         * 
167         * @throws ClassNotFoundException
168         */
169        <T> PlasticClassTransformation<T> getPlasticClass(String className) throws ClassNotFoundException
170        {
171            assert PlasticInternalUtils.isNonBlank(className);
172    
173            return pool.getPlasticClassTransformation(className);
174        }
175    
176        /**
177         * Gets the {@link ClassInstantiator} for the indicated class, which must be in a transformed package.
178         * 
179         * @param className
180         *            fully qualified class name
181         * @return instantiator (configured via the
182         *         {@linkplain PlasticManagerDelegate#configureInstantiator(String, ClassInstantiator) delegate} for the
183         *         class
184         * @throws IllegalArgumentException
185         *             if the class is not a transformed class
186         */
187        public <T> ClassInstantiator<T> getClassInstantiator(String className)
188        {
189            return pool.getClassInstantiator(className);
190        }
191    
192        /**
193         * Creates an entirely new class, extending from the provided base class.
194         * 
195         * @param baseClass
196         *            class to extend from, which must be a class, not an interface
197         * @param callback
198         *            used to configure the new class
199         * @return the instantiator, which allows instances of the new class to be created
200         */
201        public <T> ClassInstantiator<T> createClass(Class<T> baseClass, PlasticClassTransformer callback)
202        {
203            assert baseClass != null;
204            assert callback != null;
205    
206            if (baseClass.isInterface())
207                throw new IllegalArgumentException(String.format("Class %s defines an interface, not a base class.",
208                        baseClass.getName()));
209    
210            String name = String.format("$%s_%s", baseClass.getSimpleName(), PlasticUtils.nextUID());
211    
212            PlasticClassTransformation<T> transformation = pool.createTransformation(baseClass.getName(), name);
213    
214            callback.transform(transformation.getPlasticClass());
215    
216            return transformation.createInstantiator();
217        }
218    
219        /**
220         * Creates an entirely new class. The class extends from Object and implements the provided interface.
221         * 
222         * @param interfaceType
223         *            class to extend from, which must be a class, not an interface
224         * @param callback
225         *            used to configure the new class
226         * @return the instantiator, which allows instances of the new class to be created
227         * @see #createProxyTransformation(Class)
228         */
229        public <T> ClassInstantiator<T> createProxy(Class<T> interfaceType, PlasticClassTransformer callback)
230        {
231            assert callback != null;
232    
233            PlasticClassTransformation<T> transformation = createProxyTransformation(interfaceType);
234    
235            callback.transform(transformation.getPlasticClass());
236    
237            return transformation.createInstantiator();
238        }
239    
240        /**
241         * Creates the underlying {@link PlasticClassTransformation} for an interface proxy. This should only be
242         * used in the cases where encapsulating the PlasticClass construction into a {@linkplain PlasticClassTransformer
243         * callback} is not feasible (which is the case for some of the older APIs inside Tapestry IoC).
244         * 
245         * @param interfaceType
246         *            class proxy will extend from
247         * @return transformation from which an instantiator may be created
248         */
249        public <T> PlasticClassTransformation<T> createProxyTransformation(Class interfaceType)
250        {
251            assert interfaceType != null;
252    
253            if (!interfaceType.isInterface())
254                throw new IllegalArgumentException(String.format(
255                        "Class %s is not an interface; proxies may only be created for interfaces.",
256                        interfaceType.getName()));
257    
258            String name = String.format("$%s_%s", interfaceType.getSimpleName(), PlasticUtils.nextUID());
259    
260            PlasticClassTransformation<T> result = pool.createTransformation("java.lang.Object", name);
261    
262            result.getPlasticClass().introduceInterface(interfaceType);
263    
264            return result;
265        }
266    
267        public void addPlasticClassListener(PlasticClassListener listener)
268        {
269            pool.addPlasticClassListener(listener);
270        }
271    
272        public void removePlasticClassListener(PlasticClassListener listener)
273        {
274            pool.removePlasticClassListener(listener);
275        }
276    }