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