    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}