    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    }