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 }