/* * Copyright 2003,2004 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.sf.cglib.core; import java.io.*; import java.util.*; import java.lang.ref.*; import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; import java.security.ProtectionDomain; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Type; /** * Abstract class for all code-generating CGLIB utilities. * In addition to caching generated classes for performance, it provides hooks for * customizing the <code>ClassLoader</code>, name of the generated class, and transformations * applied before generation. */ abstract public class AbstractClassGenerator implements ClassGenerator { private static final Object NAME_KEY = new Object(); private static final ThreadLocal CURRENT = new ThreadLocal(); private GeneratorStrategy strategy = DefaultGeneratorStrategy.INSTANCE; private NamingPolicy namingPolicy = DefaultNamingPolicy.INSTANCE; private Source source; private ClassLoader classLoader; private String namePrefix; private Object key; private boolean useCache = true; private String className; protected boolean attemptLoad; protected static class Source { String name; Map cache = new WeakHashMap(); public Source(String name) { this.name = name; } } protected AbstractClassGenerator(Source source) { this.source = source; } protected void setNamePrefix(String namePrefix) { this.namePrefix = namePrefix; } final protected String getClassName() { if (className == null) { className = getClassName(getClassLoader()); } return className; } private String getClassName(final ClassLoader loader) { final Set nameCache = getClassNameCache(loader); return namingPolicy.getClassName( namePrefix, source.name, key, new Predicate() { public boolean evaluate(Object arg) { return nameCache.contains(arg); } } ); } private Set getClassNameCache(ClassLoader loader) { return (Set) ((Map) source.cache.get(loader)).get(NAME_KEY); } /** * Set the <code>ClassLoader</code> in which the class will be generated. * Concrete subclasses of <code>AbstractClassGenerator</code> (such as <code>Enhancer</code>) * will try to choose an appropriate default if this is unset. * <p/> * Classes are cached per-<code>ClassLoader</code> using a <code>WeakHashMap</code>, to allow * the generated classes to be removed when the associated loader is garbage collected. * * @param classLoader the loader to generate the new class with, or null to use the default */ public void setClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } /** * Override the default naming policy. * * @param namingPolicy the custom policy, or null to use the default * @see DefaultNamingPolicy */ public void setNamingPolicy(NamingPolicy namingPolicy) { if (namingPolicy == null) { namingPolicy = DefaultNamingPolicy.INSTANCE; } this.namingPolicy = namingPolicy; } /** * @see #setNamingPolicy */ public NamingPolicy getNamingPolicy() { return namingPolicy; } /** * Whether use and update the static cache of generated classes * for a class with the same properties. Default is <code>true</code>. */ public void setUseCache(boolean useCache) { this.useCache = useCache; } /** * @see #setUseCache */ public boolean getUseCache() { return useCache; } /** * If set, CGLIB will attempt to load classes from the specified * <code>ClassLoader</code> before generating them. Because generated * class names are not guaranteed to be unique, the default is <code>false</code>. */ public void setAttemptLoad(boolean attemptLoad) { this.attemptLoad = attemptLoad; } public boolean getAttemptLoad() { return attemptLoad; } /** * Set the strategy to use to create the bytecode from this generator. * By default an instance of {@see DefaultGeneratorStrategy} is used. */ public void setStrategy(GeneratorStrategy strategy) { if (strategy == null) { strategy = DefaultGeneratorStrategy.INSTANCE; } this.strategy = strategy; } /** * @see #setStrategy */ public GeneratorStrategy getStrategy() { return strategy; } /** * Used internally by CGLIB. Returns the <code>AbstractClassGenerator</code> * that is being used to generate a class in the current thread. */ public static AbstractClassGenerator getCurrent() { return (AbstractClassGenerator) CURRENT.get(); } public ClassLoader getClassLoader() { ClassLoader t = classLoader; if (t == null) { t = getDefaultClassLoader(); } if (t == null) { t = getClass().getClassLoader(); } if (t == null) { t = Thread.currentThread().getContextClassLoader(); } if (t == null) { throw new IllegalStateException("Cannot determine classloader"); } return t; } abstract protected ClassLoader getDefaultClassLoader(); protected Object create(Object key) { try { Object instance = null; synchronized (source) { ClassLoader loader = getClassLoader(); Map cache2 = null; cache2 = (Map) source.cache.get(loader); if (cache2 == null) { cache2 = new HashMap(); cache2.put(NAME_KEY, new HashSet()); source.cache.put(loader, cache2); } else if (useCache) { Reference ref = (Reference) cache2.get(key); instance = (ref == null) ? null : ref.get(); } if (instance == null) { Object save = CURRENT.get(); CURRENT.set(this); try { this.key = key; Class gen = null; if (attemptLoad) { try { gen = loader.loadClass(getClassName()); } catch (ClassNotFoundException e) { // ignore } } if (gen == null) { byte[] b = strategy.generate(this); // --- START - AW additions --- byte[] transformed; Class awPreProcessor = null; try { awPreProcessor = getClassLoader().loadClass( "org.codehaus.aspectwerkz.hook.impl.ClassPreProcessorHelper" ); Method defineClass = awPreProcessor.getMethod( "defineClass0Pre", new Class[]{ ClassLoader.class, String.class, byte[].class, int.class, int.class, ProtectionDomain.class } ); // pipe the bytes through the AW weaver transformed = (byte[]) defineClass.invoke( null, new Object[]{ loader, className, b, new Integer(0), new Integer(b.length), null } ); } catch (ClassNotFoundException e) { // AW not on classpath ignore and go on transformed = b; } catch (InvocationTargetException e) { System.err.println( "WARNING: AspectWerkz preprocessor found on classpath but can not be used due to: " + e.getTargetException().toString() ); // AW not on classpath ignore and go on transformed = b; } catch (Exception e) { System.err.println( "WARNING: AspectWerkz preprocessor found on classpath but can not be used due to: " + e.toString() ); // AW not on classpath ignore and go on transformed = b; } // --- END - AW additions --- String className = ClassNameReader.getClassName(new ClassReader(transformed)); getClassNameCache(loader).add(className); gen = ReflectUtils.defineClass(className, transformed, loader); } instance = firstInstance(gen); if (useCache) { cache2.put(key, new SoftReference(instance)); } return instance; } finally { CURRENT.set(save); } } } return nextInstance(instance); } catch (RuntimeException e) { throw e; } catch (Error e) { throw e; } catch (Exception e) { throw new CodeGenerationException(e); } } abstract protected Object firstInstance(Class type) throws Exception; abstract protected Object nextInstance(Object instance) throws Exception; }