net.sf.cglib.core.AbstractClassGenerator Maven / Gradle / Ivy
/*
* 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 net.sf.cglib.core.internal.Function;
import net.sf.cglib.core.internal.LoadingCache;
import org.objectweb.asm.ClassReader;
import java.lang.ref.WeakReference;
import java.security.ProtectionDomain;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.util.WeakHashMap;
/**
* Abstract class for all code-generating CGLIB utilities.
* In addition to caching generated classes for performance, it provides hooks for
* customizing the ClassLoader
, name of the generated class, and transformations
* applied before generation.
*/
abstract public class AbstractClassGenerator
implements ClassGenerator
{
private static final ThreadLocal CURRENT = new ThreadLocal();
private static volatile Map CACHE = new WeakHashMap();
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;
private boolean attemptLoad;
protected static class ClassLoaderData {
private final Set reservedClassNames = new HashSet();
/**
* {@link AbstractClassGenerator} here holds "cache key" (e.g. {@link net.sf.cglib.proxy.Enhancer}
* configuration), and the value is the generated class plus some additional values
* (see {@link #unwrapCachedValue(Object)}.
* The generated classes can be reused as long as their classloader is reachable.
* Note: the only way to access a class is to find it through generatedClasses cache, thus
* the key should not expire as long as the class itself is alive (its classloader is alive).
*/
private final LoadingCache generatedClasses;
/**
* Note: ClassLoaderData object is stored as a value of {@code WeakHashMap} thus
* this classLoader reference should be weak otherwise it would make classLoader strongly reachable
* and alive forever.
* Reference queue is not required since the cleanup is handled by {@link WeakHashMap}.
*/
private final WeakReference classLoader;
private final Predicate uniqueNamePredicate = new Predicate() {
public boolean evaluate(Object name) {
return reservedClassNames.contains(name);
}
};
private static final Function GET_KEY = new Function() {
public Object apply(AbstractClassGenerator gen) {
return gen.key;
}
};
public ClassLoaderData(ClassLoader classLoader) {
if (classLoader == null) {
throw new IllegalArgumentException("classLoader == null is not yet supported");
}
this.classLoader = new WeakReference(classLoader);
Function load =
new Function() {
public Object apply(AbstractClassGenerator gen) {
Class klass = gen.generate(ClassLoaderData.this);
return gen.wrapCachedClass(klass);
}
};
generatedClasses = new LoadingCache(GET_KEY, load);
}
public ClassLoader getClassLoader() {
return classLoader.get();
}
public void reserveName(String name) {
reservedClassNames.add(name);
}
public Predicate getUniqueNamePredicate() {
return uniqueNamePredicate;
}
public Object get(AbstractClassGenerator gen, boolean useCache) {
if (!useCache) {
return gen.generate(ClassLoaderData.this);
} else {
Object cachedValue = generatedClasses.get(gen);
return gen.unwrapCachedValue(cachedValue);
}
}
}
protected T wrapCachedClass(Class klass) {
return (T) new WeakReference(klass);
}
protected Object unwrapCachedValue(T cached) {
return ((WeakReference) cached).get();
}
protected static class Source {
String name;
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() {
return className;
}
private void setClassName(String className) {
this.className = className;
}
private String generateClassName(Predicate nameTestPredicate) {
return namingPolicy.getClassName(namePrefix, source.name, key, nameTestPredicate);
}
/**
* Set the ClassLoader
in which the class will be generated.
* Concrete subclasses of AbstractClassGenerator
(such as Enhancer
)
* will try to choose an appropriate default if this is unset.
*
* Classes are cached per-ClassLoader
using a WeakHashMap
, 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.
* @see DefaultNamingPolicy
* @param namingPolicy the custom policy, or null to use the default
*/
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 true
.
*/
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
* ClassLoader
before generating them. Because generated
* class names are not guaranteed to be unique, the default is false
.
*/
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 AbstractClassGenerator
* 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();
/**
* Returns the protection domain to use when defining the class.
*
* Default implementation returns null
for using a default protection domain. Sub-classes may
* override to use a more specific protection domain.
*
*
* @return the protection domain (null
for using a default)
*/
protected ProtectionDomain getProtectionDomain() {
return null;
}
protected Object create(Object key) {
try {
ClassLoader loader = getClassLoader();
Map cache = CACHE;
ClassLoaderData data = cache.get(loader);
if (data == null) {
synchronized (AbstractClassGenerator.class) {
cache = CACHE;
data = cache.get(loader);
if (data == null) {
Map newCache = new WeakHashMap(cache);
data = new ClassLoaderData(loader);
newCache.put(loader, data);
CACHE = newCache;
}
}
}
this.key = key;
Object obj = data.get(this, getUseCache());
if (obj instanceof Class) {
return firstInstance((Class) obj);
}
return nextInstance(obj);
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Exception e) {
throw new CodeGenerationException(e);
}
}
protected Class generate(ClassLoaderData data) {
Class gen;
Object save = CURRENT.get();
CURRENT.set(this);
try {
ClassLoader classLoader = data.getClassLoader();
if (classLoader == null) {
throw new IllegalStateException("ClassLoader is null while trying to define class " +
getClassName() + ". It seems that the loader has been expired from a weak reference somehow. " +
"Please file an issue at cglib's issue tracker.");
}
synchronized (classLoader) {
String name = generateClassName(data.getUniqueNamePredicate());
data.reserveName(name);
this.setClassName(name);
}
if (attemptLoad) {
try {
gen = classLoader.loadClass(getClassName());
return gen;
} catch (ClassNotFoundException e) {
// ignore
}
}
byte[] b = strategy.generate(this);
String className = ClassNameReader.getClassName(new ClassReader(b));
ProtectionDomain protectionDomain = getProtectionDomain();
synchronized (classLoader) { // just in case
if (protectionDomain == null) {
gen = ReflectUtils.defineClass(className, b, classLoader);
} else {
gen = ReflectUtils.defineClass(className, b, classLoader, protectionDomain);
}
}
return gen;
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Exception e) {
throw new CodeGenerationException(e);
} finally {
CURRENT.set(save);
}
}
abstract protected Object firstInstance(Class type) throws Exception;
abstract protected Object nextInstance(Object instance) throws Exception;
}