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 implementationType 238 * class that implements interfaceType. It can be null. 239 * @param callback 240 * used to configure the new class 241 * @return the instantiator, which allows instances of the new class to be created 242 * @see #createProxyTransformation(Class, Class) 243 * @since 5.4 244 */ 245 public <T> ClassInstantiator<T> createProxy(Class<T> interfaceType, Class<? extends T> implementationType, PlasticClassTransformer callback) 246 { 247 assert callback != null; 248 249 PlasticClassTransformation<T> transformation = createProxyTransformation(interfaceType, implementationType); 250 251 callback.transform(transformation.getPlasticClass()); 252 253 return transformation.createInstantiator(); 254 } 255 256 /** 257 * Creates the underlying {@link PlasticClassTransformation} for an interface proxy. This should only be 258 * used in the cases where encapsulating the PlasticClass construction into a {@linkplain PlasticClassTransformer 259 * callback} is not feasible (which is the case for some of the older APIs inside Tapestry IoC). 260 * 261 * @param interfaceType 262 * class proxy will extend from 263 * @param implementationType 264 * class that implements interfaceType. It can be null. 265 * @return transformation from which an instantiator may be created 266 */ 267 public <T> PlasticClassTransformation<T> createProxyTransformation(Class interfaceType, Class implementationType) 268 { 269 assert interfaceType != null; 270 271 if (!interfaceType.isInterface()) 272 throw new IllegalArgumentException(String.format( 273 "Class %s is not an interface; proxies may only be created for interfaces.", 274 interfaceType.getName())); 275 276 String name = String.format("$%s_%s", interfaceType.getSimpleName(), PlasticUtils.nextUID()); 277 278 final String implementationClassName = implementationType != null ? implementationType.getName() : null; 279 PlasticClassTransformation<T> result = 280 pool.createTransformation("java.lang.Object", name, implementationClassName); 281 282 result.getPlasticClass().introduceInterface(interfaceType); 283 284 return result; 285 } 286 287 @Override 288 public void addPlasticClassListener(PlasticClassListener listener) 289 { 290 pool.addPlasticClassListener(listener); 291 } 292 293 @Override 294 public void removePlasticClassListener(PlasticClassListener listener) 295 { 296 pool.removePlasticClassListener(listener); 297 } 298}