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.internal.plastic; 016 017 import org.apache.tapestry5.internal.plastic.asm.ClassReader; 018 import org.apache.tapestry5.internal.plastic.asm.ClassWriter; 019 import org.apache.tapestry5.internal.plastic.asm.Opcodes; 020 import org.apache.tapestry5.internal.plastic.asm.tree.AnnotationNode; 021 import org.apache.tapestry5.internal.plastic.asm.tree.ClassNode; 022 import org.apache.tapestry5.plastic.*; 023 024 import java.lang.annotation.Annotation; 025 import java.lang.reflect.Modifier; 026 import java.util.*; 027 import java.util.concurrent.CopyOnWriteArrayList; 028 029 /** 030 * Responsible for managing a class loader that allows ASM {@link ClassNode}s 031 * to be instantiated as runtime classes. 032 */ 033 @SuppressWarnings("rawtypes") 034 public class PlasticClassPool implements ClassLoaderDelegate, Opcodes, PlasticClassListenerHub 035 { 036 final PlasticClassLoader loader; 037 038 private final PlasticManagerDelegate delegate; 039 040 private final Set<String> controlledPackages; 041 042 /** 043 * Maps class names to instantiators for that class name. 044 * Synchronized on the loader. 045 */ 046 private final Map<String, ClassInstantiator> instantiators = PlasticInternalUtils.newMap(); 047 048 private final InheritanceData emptyInheritanceData = new InheritanceData(); 049 050 private final StaticContext emptyStaticContext = new StaticContext(); 051 052 private final List<PlasticClassListener> listeners = new CopyOnWriteArrayList<PlasticClassListener>(); 053 054 private final Cache<String, TypeCategory> typeName2Category = new Cache<String, TypeCategory>() 055 { 056 057 protected TypeCategory convert(String typeName) 058 { 059 ClassNode cn = constructClassNode(typeName); 060 061 return Modifier.isInterface(cn.access) ? TypeCategory.INTERFACE : TypeCategory.CLASS; 062 } 063 }; 064 065 static class BaseClassDef 066 { 067 final InheritanceData inheritanceData; 068 069 final StaticContext staticContext; 070 071 public BaseClassDef(InheritanceData inheritanceData, StaticContext staticContext) 072 { 073 this.inheritanceData = inheritanceData; 074 this.staticContext = staticContext; 075 } 076 } 077 078 /** 079 * Map from FQCN to BaseClassDef. Synchronized on the loader. 080 */ 081 private final Map<String, BaseClassDef> baseClassDefs = new HashMap<String, PlasticClassPool.BaseClassDef>(); 082 083 private final Set<TransformationOption> options; 084 085 /** 086 * Creates the pool with a set of controlled packages; all classes in the controlled packages are loaded by the 087 * pool's class loader, and all top-level classes in the controlled packages are transformed via the delegate. 088 * 089 * @param parentLoader typically, the Thread's context class loader 090 * @param delegate responsible for end stages of transforming top-level classes 091 * @param controlledPackages set of package names (note: retained, not copied) 092 * @param options used when transforming classes 093 */ 094 public PlasticClassPool(ClassLoader parentLoader, PlasticManagerDelegate delegate, Set<String> controlledPackages, 095 Set<TransformationOption> options) 096 { 097 loader = new PlasticClassLoader(parentLoader, this); 098 this.delegate = delegate; 099 this.controlledPackages = controlledPackages; 100 this.options = options; 101 } 102 103 public ClassLoader getClassLoader() 104 { 105 return loader; 106 } 107 108 public Class realizeTransformedClass(ClassNode classNode, InheritanceData inheritanceData, 109 StaticContext staticContext) 110 { 111 synchronized (loader) 112 { 113 Class result = realize(PlasticInternalUtils.toClassName(classNode.name), ClassType.PRIMARY, classNode); 114 115 baseClassDefs.put(result.getName(), new BaseClassDef(inheritanceData, staticContext)); 116 117 return result; 118 } 119 120 } 121 122 public Class realize(String primaryClassName, ClassType classType, final ClassNode classNode) 123 { 124 synchronized (loader) 125 { 126 if (!listeners.isEmpty()) 127 { 128 fire(toEvent(primaryClassName, classType, classNode)); 129 } 130 131 byte[] bytecode = toBytecode(classNode); 132 133 String className = PlasticInternalUtils.toClassName(classNode.name); 134 135 return loader.defineClassWithBytecode(className, bytecode); 136 } 137 } 138 139 private PlasticClassEvent toEvent(final String primaryClassName, final ClassType classType, 140 final ClassNode classNode) 141 { 142 return new PlasticClassEvent() 143 { 144 public ClassType getType() 145 { 146 return classType; 147 } 148 149 public String getPrimaryClassName() 150 { 151 return primaryClassName; 152 } 153 154 public String getDissasembledBytecode() 155 { 156 return PlasticInternalUtils.dissasembleBytecode(classNode); 157 } 158 159 public String getClassName() 160 { 161 return PlasticInternalUtils.toClassName(classNode.name); 162 } 163 }; 164 } 165 166 private void fire(PlasticClassEvent event) 167 { 168 for (PlasticClassListener listener : listeners) 169 { 170 listener.classWillLoad(event); 171 } 172 } 173 174 private byte[] toBytecode(ClassNode classNode) 175 { 176 ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); 177 178 classNode.accept(writer); 179 180 return writer.toByteArray(); 181 } 182 183 public AnnotationAccess createAnnotationAccess(String className) 184 { 185 try 186 { 187 final Class<?> searchClass = loader.loadClass(className); 188 189 return new AnnotationAccess() 190 { 191 public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType) 192 { 193 return getAnnotation(annotationType) != null; 194 } 195 196 public <T extends Annotation> T getAnnotation(Class<T> annotationType) 197 { 198 return searchClass.getAnnotation(annotationType); 199 } 200 }; 201 } catch (Exception ex) 202 { 203 throw new RuntimeException(ex); 204 } 205 } 206 207 public AnnotationAccess createAnnotationAccess(List<AnnotationNode> annotationNodes) 208 { 209 if (annotationNodes == null) 210 { 211 return EmptyAnnotationAccess.SINGLETON; 212 } 213 214 final Map<String, Object> cache = PlasticInternalUtils.newMap(); 215 final Map<String, AnnotationNode> nameToNode = PlasticInternalUtils.newMap(); 216 217 for (AnnotationNode node : annotationNodes) 218 { 219 nameToNode.put(PlasticInternalUtils.objectDescriptorToClassName(node.desc), node); 220 } 221 222 return new AnnotationAccess() 223 { 224 public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType) 225 { 226 return nameToNode.containsKey(annotationType.getName()); 227 } 228 229 public <T extends Annotation> T getAnnotation(Class<T> annotationType) 230 { 231 String className = annotationType.getName(); 232 233 Object result = cache.get(className); 234 235 if (result == null) 236 { 237 result = buildAnnotation(className); 238 239 if (result != null) 240 cache.put(className, result); 241 } 242 243 return annotationType.cast(result); 244 } 245 246 private Object buildAnnotation(String className) 247 { 248 AnnotationNode node = nameToNode.get(className); 249 250 if (node == null) 251 return null; 252 253 return createAnnotation(className, node); 254 } 255 }; 256 } 257 258 Class loadClass(String className) 259 { 260 try 261 { 262 return loader.loadClass(className); 263 } catch (Exception ex) 264 { 265 throw new RuntimeException(String.format("Unable to load class %s: %s", className, 266 PlasticInternalUtils.toMessage(ex)), ex); 267 } 268 } 269 270 protected Object createAnnotation(String className, AnnotationNode node) 271 { 272 AnnotationBuilder builder = new AnnotationBuilder(loadClass(className), this); 273 274 node.accept(builder); 275 276 return builder.createAnnotation(); 277 } 278 279 public boolean shouldInterceptClassLoading(String className) 280 { 281 int searchFromIndex = className.length() - 1; 282 283 while (true) 284 { 285 int dotx = className.lastIndexOf('.', searchFromIndex); 286 287 if (dotx < 0) 288 break; 289 290 String packageName = className.substring(0, dotx); 291 292 if (controlledPackages.contains(packageName)) 293 return true; 294 295 searchFromIndex = dotx - 1; 296 } 297 298 return false; 299 } 300 301 public Class<?> loadAndTransformClass(String className) throws ClassNotFoundException 302 { 303 // Inner classes are not transformed, but they are loaded by the same class loader. 304 305 if (className.contains("$")) 306 return loadInnerClass(className); 307 308 // TODO: What about interfaces, enums, annotations, etc. ... they shouldn't be in the package, but 309 // we should generate a reasonable error message. 310 311 InternalPlasticClassTransformation transformation = getPlasticClassTransformation(className); 312 313 delegate.transform(transformation.getPlasticClass()); 314 315 ClassInstantiator createInstantiator = transformation.createInstantiator(); 316 ClassInstantiator configuredInstantiator = delegate.configureInstantiator(className, createInstantiator); 317 318 instantiators.put(className, configuredInstantiator); 319 320 return transformation.getTransformedClass(); 321 } 322 323 private Class loadInnerClass(String className) 324 { 325 byte[] bytecode = readBytecode(className); 326 327 return loader.defineClassWithBytecode(className, bytecode); 328 } 329 330 /** 331 * For a fully-qualified class name of an <em>existing</em> class, loads the bytes for the class 332 * and returns a PlasticClass instance. 333 * 334 * @throws ClassNotFoundException 335 */ 336 public InternalPlasticClassTransformation getPlasticClassTransformation(String className) 337 throws ClassNotFoundException 338 { 339 assert PlasticInternalUtils.isNonBlank(className); 340 341 ClassNode classNode = constructClassNode(className); 342 343 String baseClassName = PlasticInternalUtils.toClassName(classNode.superName); 344 345 return createTransformation(baseClassName, classNode); 346 } 347 348 private InternalPlasticClassTransformation createTransformation(String baseClassName, ClassNode classNode) 349 throws ClassNotFoundException 350 { 351 if (shouldInterceptClassLoading(baseClassName)) 352 { 353 loader.loadClass(baseClassName); 354 355 BaseClassDef def = baseClassDefs.get(baseClassName); 356 357 assert def != null; 358 359 return new PlasticClassImpl(classNode, this, def.inheritanceData, def.staticContext); 360 } 361 362 // When the base class is Object, or otherwise not in a transformed package, 363 // then start with the empty 364 return new PlasticClassImpl(classNode, this, emptyInheritanceData, emptyStaticContext); 365 } 366 367 /** 368 * Constructs a class node by reading the raw bytecode for a class and instantiating a ClassNode 369 * (via {@link ClassReader#accept(org.apache.tapestry5.internal.plastic.asm.ClassVisitor, int)}). 370 * 371 * @param className fully qualified class name 372 * @return corresponding ClassNode 373 */ 374 public ClassNode constructClassNode(String className) 375 { 376 byte[] bytecode = readBytecode(className); 377 378 if (bytecode == null) 379 return null; 380 381 return PlasticInternalUtils.convertBytecodeToClassNode(bytecode); 382 } 383 384 private byte[] readBytecode(String className) 385 { 386 ClassLoader parentClassLoader = loader.getParent(); 387 388 return PlasticInternalUtils.readBytecodeForClass(parentClassLoader, className, true); 389 } 390 391 public PlasticClassTransformation createTransformation(String baseClassName, String newClassName) 392 { 393 try 394 { 395 ClassNode newClassNode = new ClassNode(); 396 397 newClassNode.visit(V1_5, ACC_PUBLIC, PlasticInternalUtils.toInternalName(newClassName), null, 398 PlasticInternalUtils.toInternalName(baseClassName), null); 399 400 return createTransformation(baseClassName, newClassNode); 401 } catch (ClassNotFoundException ex) 402 { 403 throw new RuntimeException(String.format("Unable to create class %s as sub-class of %s: %s", newClassName, 404 baseClassName, PlasticInternalUtils.toMessage(ex)), ex); 405 } 406 } 407 408 public ClassInstantiator getClassInstantiator(String className) 409 { 410 synchronized (loader) 411 { 412 if (!instantiators.containsKey(className)) 413 { 414 try 415 { 416 loader.loadClass(className); 417 } catch (ClassNotFoundException ex) 418 { 419 throw new RuntimeException(ex); 420 } 421 } 422 423 ClassInstantiator result = instantiators.get(className); 424 425 if (result == null) 426 { 427 // TODO: Verify that the problem is incorrect package, and not any other failure. 428 429 StringBuilder b = new StringBuilder(); 430 b.append("Class '") 431 .append(className) 432 .append("' is not a transformed class. Transformed classes should be in one of the following packages: "); 433 434 String sep = ""; 435 436 List<String> names = new ArrayList<String>(controlledPackages); 437 Collections.sort(names); 438 439 for (String name : names) 440 { 441 b.append(sep); 442 b.append(name); 443 444 sep = ", "; 445 } 446 447 String message = b.append(".").toString(); 448 449 throw new IllegalArgumentException(message); 450 } 451 452 return result; 453 } 454 } 455 456 TypeCategory getTypeCategory(String typeName) 457 { 458 synchronized (loader) 459 { 460 // TODO: Is this the right place to cache this data? 461 462 return typeName2Category.get(typeName); 463 } 464 } 465 466 public void addPlasticClassListener(PlasticClassListener listener) 467 { 468 assert listener != null; 469 470 listeners.add(listener); 471 } 472 473 public void removePlasticClassListener(PlasticClassListener listener) 474 { 475 assert listener != null; 476 477 listeners.remove(listener); 478 } 479 480 boolean isEnabled(TransformationOption option) 481 { 482 return options.contains(option); 483 } 484 }