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
015package org.apache.tapestry5.plastic;
016
017import java.util.Collection;
018import java.util.EnumSet;
019import java.util.Set;
020
021import org.apache.tapestry5.internal.plastic.Lockable;
022import org.apache.tapestry5.internal.plastic.NoopDelegate;
023import org.apache.tapestry5.internal.plastic.PlasticClassPool;
024import 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")
033public 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        return createProxy(interfaceType, null, callback);
232    }
233
234    /**
235     * Creates an entirely new class. The class extends from Object and implements the provided interface.
236     * 
237     * @param interfaceType
238     *            class to extend from, which must be a class, not an interface
239     * @param implementationType
240     *            class that implements interfaceType. It can be null. 
241     * @param callback
242     *            used to configure the new class
243     * @return the instantiator, which allows instances of the new class to be created
244     * @see #createProxyTransformation(Class, Class)
245     * @since 5.4
246     */
247    public <T> ClassInstantiator<T> createProxy(Class<T> interfaceType, Class<? extends T> implementationType, PlasticClassTransformer callback)
248    {
249        assert callback != null;
250
251        PlasticClassTransformation<T> transformation = createProxyTransformation(interfaceType, implementationType);
252
253        callback.transform(transformation.getPlasticClass());
254
255        return transformation.createInstantiator();
256    }
257
258    /**
259     * Creates the underlying {@link PlasticClassTransformation} for an interface proxy. This should only be
260     * used in the cases where encapsulating the PlasticClass construction into a {@linkplain PlasticClassTransformer
261     * callback} is not feasible (which is the case for some of the older APIs inside Tapestry IoC).
262     * 
263     * @param interfaceType
264     *            class proxy will extend from
265     * @param implementationType
266     *            class that implements interfaceType. It can be null.
267     * @return transformation from which an instantiator may be created
268     */
269    public <T> PlasticClassTransformation<T> createProxyTransformation(Class interfaceType, Class implementationType)
270    {
271        assert interfaceType != null;
272
273        if (!interfaceType.isInterface())
274            throw new IllegalArgumentException(String.format(
275                    "Class %s is not an interface; proxies may only be created for interfaces.",
276                    interfaceType.getName()));
277
278        String name = String.format("$%s_%s", interfaceType.getSimpleName(), PlasticUtils.nextUID());
279
280        final String implementationClassName = implementationType != null ? implementationType.getName() : null;
281        PlasticClassTransformation<T> result = 
282                pool.createTransformation("java.lang.Object", name, implementationClassName);
283
284        result.getPlasticClass().introduceInterface(interfaceType);
285
286        return result;
287    }
288
289    @Override
290    public void addPlasticClassListener(PlasticClassListener listener)
291    {
292        pool.addPlasticClassListener(listener);
293    }
294
295    @Override
296    public void removePlasticClassListener(PlasticClassListener listener)
297    {
298        pool.removePlasticClassListener(listener);
299    }
300}