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 }