blob: 99a8868e1eb6c3c5c7b0c122aabbd57a7a646955 [file] [log] [blame]
package org.codehaus.jackson.mrbean;
import java.lang.reflect.Modifier;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.Versioned;
import org.codehaus.jackson.map.AbstractTypeResolver;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.type.JavaType;
import org.codehaus.jackson.util.VersionUtil;
/**
* Nifty class for pulling implementations of classes out of thin air.
*<p>
* ... friends call him Mister Bean... :-)
*
* @author tatu
* @author sunny
*
* @since 1.6
*/
public class AbstractTypeMaterializer
extends AbstractTypeResolver
implements Versioned
{
/**
* Enumeration that defines togglable features that guide
* the serialization feature.
*/
public enum Feature {
/**
* Feature that determines what happens if an "unrecognized"
* (non-getter, non-setter) abstract method is encountered: if set to
* true, will throw an exception during materialization; if false,
* will materialize method that throws exception only if called.
*/
FAIL_ON_UNMATERIALIZED_METHOD(false),
/**
* Feature that determines what happens when attempt is made to
* generate implementation of non-public class or interface.
* If true, an exception is thrown; if false, will just quietly
* ignore attempts.
*
* @since 1.9
*/
FAIL_ON_NON_PUBLIC_TYPES(true)
;
final boolean _defaultState;
// Method that calculates bit set (flags) of all features that are enabled by default.
protected static int collectDefaults() {
int flags = 0;
for (Feature f : values()) {
if (f.enabledByDefault()) {
flags |= f.getMask();
}
}
return flags;
}
private Feature(boolean defaultState) { _defaultState = defaultState; }
public boolean enabledByDefault() { return _defaultState; }
public int getMask() { return (1 << ordinal()); }
}
/**
* Bitfield (set of flags) of all Features that are enabled
* by default.
*/
protected final static int DEFAULT_FEATURE_FLAGS = Feature.collectDefaults();
/**
* Default package to use for generated classes.
*/
public final static String DEFAULT_PACKAGE_FOR_GENERATED = "org.codehaus.jackson.generated.";
/**
* We will use per-materializer class loader for now; would be nice
* to find a way to reduce number of class loaders (and hence
* number of generated classes!) constructed...
*/
protected final MyClassLoader _classLoader;
/**
* Bit set that contains all enabled features
*/
protected int _featureFlags = DEFAULT_FEATURE_FLAGS;
/**
* Package name to use as prefix for generated classes.
*/
protected String _defaultPackage = DEFAULT_PACKAGE_FOR_GENERATED;
/*
/**********************************************************
/* Construction, configuration
/**********************************************************
*/
public AbstractTypeMaterializer() {
this(null);
}
public AbstractTypeMaterializer(ClassLoader parentClassLoader)
{
if (parentClassLoader == null) {
parentClassLoader = getClass().getClassLoader();
}
_classLoader = new MyClassLoader(parentClassLoader);
}
/**
* Method that will return version information stored in and read from jar
* that contains this class.
*
* @since 1.6
*/
@Override
public Version version() {
return VersionUtil.versionFor(getClass());
}
/**
* Method for checking whether given feature is enabled or not
*/
public final boolean isEnabled(Feature f) {
return (_featureFlags & f.getMask()) != 0;
}
/**
* Method for enabling specified feature.
*/
public void enable(Feature f) {
_featureFlags |= f.getMask();
}
/**
* Method for disabling specified feature.
*/
public void disable(Feature f) {
_featureFlags &= ~f.getMask();
}
/**
* Method for enabling or disabling specified feature.
*/
public void set(Feature f, boolean state)
{
if (state) {
enable(f);
} else {
disable(f);
}
}
public void setDefaultPackage(String defPkg)
{
if (!defPkg.endsWith(".")) {
defPkg = defPkg + ".";
}
_defaultPackage = defPkg;
}
/*
/**********************************************************
/* Public API
/**********************************************************
*/
@Override
public JavaType resolveAbstractType(DeserializationConfig config, JavaType type)
{
/* 19-Feb-2011, tatu: Future plans may include calling of this method for all kinds
* of abstract types. So as simple precaution, let's limit kinds of types we
* will try materializa implementations for.
*/
/* We won't be handling any container types (Collections, Maps and arrays),
* Throwables or enums.
*/
if (type.isContainerType() || type.isPrimitive() || type.isEnumType() || type.isThrowable()) {
return null;
}
Class<?> cls = type.getRawClass();
/* [JACKSON-683] Fail on non-public classes, since we can't easily force
* access to such classes (unless we tried to generate impl classes in that
* package)
*/
if (!Modifier.isPublic(cls.getModifiers())) {
if (isEnabled(Feature.FAIL_ON_NON_PUBLIC_TYPES)) {
throw new IllegalArgumentException("Can not materialize implementation of "+cls+" since it is not public ");
}
return null;
}
// might want to skip proxies, local types too... but let them be for now:
//if (intr.findTypeResolver(beanDesc.getClassInfo(), type) == null) {
return config.constructType(materializeClass(config, cls));
}
/*
/**********************************************************
/* Internal methods
/**********************************************************
*/
protected Class<?> materializeClass(DeserializationConfig config, Class<?> cls)
{
// Need to have proper name mangling in future, but for now...
String newName = _defaultPackage+cls.getName();
BeanBuilder builder = new BeanBuilder(config, cls);
byte[] bytecode = builder.implement(isEnabled(Feature.FAIL_ON_UNMATERIALIZED_METHOD)).build(newName);
Class<?> result = _classLoader.loadAndResolve(newName, bytecode, cls);
return result;
}
/*
/**********************************************************
/* Helper classes
/**********************************************************
*/
/**
* To support actual dynamic loading of bytecode we need a simple
* custom classloader.
*/
private static class MyClassLoader extends ClassLoader
{
public MyClassLoader(ClassLoader parent)
{
super(parent);
}
/**
* @param targetClass Interface or abstract class that class to load should extend or
* implement
*/
public Class<?> loadAndResolve(String className, byte[] byteCode, Class<?> targetClass)
throws IllegalArgumentException
{
// First things first: just to be sure; maybe we have already loaded it?
Class<?> old = findLoadedClass(className);
if (old != null && targetClass.isAssignableFrom(old)) {
return old;
}
Class<?> impl;
try {
impl = defineClass(className, byteCode, 0, byteCode.length);
} catch (LinkageError e) {
throw new IllegalArgumentException("Failed to load class '"+className+"': "+e.getMessage() ,e);
}
// important: must also resolve the class...
resolveClass(impl);
return impl;
}
}
}