    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 callback
    238     *            used to configure the new class
    239     * @return the instantiator, which allows instances of the new class to be created
    240     * @see #createProxyTransformation(Class, Class)
    241     */
    242    public <T> ClassInstantiator<T> createProxy(Class<T> interfaceType, PlasticClassTransformer callback, boolean introduceInterface)
    243    {
    244        return createProxy(interfaceType, null, callback, introduceInterface);
    245    }
    246
    247    /**
    248     * Creates an entirely new class. The class extends from Object and implements the provided interface.
    249     *
    250     * @param interfaceType
    251     *            class to extend from, which must be a class, not an interface
    252     * @param implementationType
    253     *            class that implements interfaceType. It can be null.
    254     * @param callback
    255     *            used to configure the new class
    256     * @return the instantiator, which allows instances of the new class to be created
    257     * @see #createProxyTransformation(Class, Class)
    258     * @since 5.4
    259     */
    260    public <T> ClassInstantiator<T> createProxy(Class<T> interfaceType, Class<? extends T> implementationType, PlasticClassTransformer callback)
    261    {
    262        return createProxy(interfaceType, implementationType, callback, true);
    263    }
    264
    265    /**
    266     * Creates an entirely new class. The class extends from Object and implements the provided interface.
    267     *
    268     * @param interfaceType
    269     *            class to extend from, which must be a class, not an interface
    270     * @param implementationType
    271     *            class that implements interfaceType. It can be null.
    272     * @param callback
    273     *            used to configure the new class
    274     * @param introduceInterface
    275     *            whether to introduce the interface to the Plastic class or not.
    276     * @return the instantiator, which allows instances of the new class to be created
    277     * @see #createProxyTransformation(Class, Class)
    278     * @since 5.4.5
    279     */
    280    public <T> ClassInstantiator<T> createProxy(Class<T> interfaceType, Class<? extends T> implementationType, PlasticClassTransformer callback,
    281            boolean introduceInterface)
    282    {
    283        assert callback != null;
    284
    285        PlasticClassTransformation<T> transformation = createProxyTransformation(interfaceType, implementationType, introduceInterface);
    286
    287        callback.transform(transformation.getPlasticClass());
    288
    289        return transformation.createInstantiator();
    290    }
    291
    292    /**
    293     * Returns <code>createProxyTransformation(interfaceType, implementationType, true)</code>
    294     */
    295    public <T> PlasticClassTransformation<T> createProxyTransformation(Class interfaceType, Class implementationType)
    296    {
    297        return createProxyTransformation(interfaceType, implementationType, true);
    298    }
    299
    300    /**
    301     * Creates the underlying {@link PlasticClassTransformation} for an interface proxy. This should only be
    302     * used in the cases where encapsulating the PlasticClass construction into a {@linkplain PlasticClassTransformer
    303     * callback} is not feasible (which is the case for some of the older APIs inside Tapestry IoC).
    304     *
    305     * @param interfaceType
    306     *            class proxy will extend from
    307     * @param implementationType
    308     *            class that implements interfaceType. It can be null.
    309     * @param introduceInterface
    310     *            whether <code>result.getPlasticClass().introduceInterface(interfaceType);</code> should
    311     *            be called or not.
    312     * @return transformation from which an instantiator may be created
    313     */
    314    public <T> PlasticClassTransformation<T> createProxyTransformation(Class interfaceType, Class implementationType, boolean introduceInterface)
    315    {
    316        assert interfaceType != null;
    317
    318        if (!interfaceType.isInterface())
    319            throw new IllegalArgumentException(String.format(
    320                    "Class %s is not an interface; proxies may only be created for interfaces.",
    321                    interfaceType.getName()));
    322
    323        String name = String.format("$%s_%s", interfaceType.getSimpleName(), PlasticUtils.nextUID());
    324
    325        final String implementationClassName = implementationType != null ? implementationType.getName() : null;
    326        PlasticClassTransformation<T> result =
    327                pool.createTransformation("java.lang.Object", name, implementationClassName);
    328
    329        if (introduceInterface)
    330        {
    331            result.getPlasticClass().introduceInterface(interfaceType);
    332        }
    333
    334        return result;
    335    }
    336
    337    @Override
    338    public void addPlasticClassListener(PlasticClassListener listener)
    339    {
    340        pool.addPlasticClassListener(listener);
    341    }
    342
    343    @Override
    344    public void removePlasticClassListener(PlasticClassListener listener)
    345    {
    346        pool.removePlasticClassListener(listener);
    347    }
    348}