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 349 /** 350 * Returns whether a given class will have it classloading intercepted for 351 * live class reloading. 352 * @since 5.8.3 353 */ 354 public boolean shouldInterceptClassLoading(String className) 355 { 356 return pool.shouldInterceptClassLoading(className); 357 } 358 359 /** 360 * Returns the Plastic class pool used by this instance. 361 * @since 5.8.3 362 */ 363 public PlasticClassPool getPool() 364 { 365 return pool; 366 } 367 368}