blob: b03ca0a137b9bfa84ebb981d53a4874b223bba86 [file] [log] [blame]
package org.codehaus.jackson.mrbean;
import java.lang.reflect.Method;
import java.util.*;
import org.codehaus.jackson.org.objectweb.asm.*;
import static org.codehaus.jackson.org.objectweb.asm.Opcodes.*;
import org.codehaus.jackson.type.JavaType;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.type.TypeFactory;
/**
* Heavy lifter of mr Bean package: class that keeps track of logical POJO properties,
* and figures out how to create an implementation class.
*
* @since 1.6
*/
public class BeanBuilder
{
protected Map<String, Property> _beanProperties = new LinkedHashMap<String,Property>();
protected LinkedHashMap<String,Method> _unsupportedMethods = new LinkedHashMap<String,Method>();
/**
* Abstract class or interface that the bean is created to extend or implement.
*/
protected final Class<?> _implementedType;
protected final TypeFactory _typeFactory;
public BeanBuilder(DeserializationConfig config, Class<?> implType)
{
_implementedType = implType;
_typeFactory = config.getTypeFactory();
}
/*
/**********************************************************
/* Core public API
/**********************************************************
*/
/**
* @param failOnUnrecognized If true, and an unrecognized (non-getter, non-setter)
* method is encountered, will throw {@link IllegalArgumentException}; if false,
* will implement bogus method that will throw {@link UnsupportedOperationException}
* if called.
*/
public BeanBuilder implement(boolean failOnUnrecognized)
{
ArrayList<Class<?>> implTypes = new ArrayList<Class<?>>();
// First: find all supertypes:
implTypes.add(_implementedType);
BeanUtil.findSuperTypes(_implementedType, Object.class, implTypes);
for (Class<?> impl : implTypes) {
// and then find all getters, setters, and other non-concrete methods therein:
for (Method m : impl.getDeclaredMethods()) {
String methodName = m.getName();
int argCount = m.getParameterTypes().length;
if (argCount == 0) { // getter?
if (methodName.startsWith("get") || methodName.startsWith("is") && returnsBoolean(m)) {
addGetter(m);
continue;
}
} else if (argCount == 1 && methodName.startsWith("set")) {
addSetter(m);
continue;
}
// Otherwise, if concrete, or already handled, skip:
// !!! note: won't handle overloaded methods properly
if (BeanUtil.isConcrete(m) || _unsupportedMethods.containsKey(methodName)) {
continue;
}
if (failOnUnrecognized) {
throw new IllegalArgumentException("Unrecognized abstract method '"+methodName
+"' (not a getter or setter) -- to avoid exception, disable AbstractTypeMaterializer.Feature.FAIL_ON_UNMATERIALIZED_METHOD");
}
_unsupportedMethods.put(methodName, m);
}
}
return this;
}
/**
* Method that generates byte code for class that implements abstract
* types requested so far.
*
* @param className Fully-qualified name of the class to generate
* @return Byte code Class instance built by this builder
*/
public byte[] build(String className)
{
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
String internalClass = getInternalClassName(className);
String implName = getInternalClassName(_implementedType.getName());
// muchos important: level at least 1.5 to get generics!!!
// Also: abstract class vs interface...
String superName;
if (_implementedType.isInterface()) {
superName = "java/lang/Object";
cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER, internalClass, null,
superName, new String[] { implName });
} else {
superName = implName;
cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER, internalClass, null,
implName, null);
}
cw.visitSource(className + ".java", null);
BeanBuilder.generateDefaultConstructor(cw, superName);
for (Property prop : _beanProperties.values()) {
// First: determine type to use; preferably setter (usually more explicit); otherwise getter
TypeDescription type = prop.selectType(_typeFactory);
createField(cw, prop, type);
// since some getters and/or setters may be implemented, check:
if (!prop.hasConcreteGetter()) {
createGetter(cw, internalClass, prop, type);
}
if (!prop.hasConcreteSetter()) {
createSetter(cw, internalClass, prop, type);
}
}
for (Method m : _unsupportedMethods.values()) {
createUnimplementedMethod(cw, internalClass, m);
}
cw.visitEnd();
return cw.toByteArray();
}
/*
/**********************************************************
/* Internal methods, property discovery
/**********************************************************
*/
private static String getPropertyName(String methodName)
{
int prefixLen = methodName.startsWith("is") ? 2 : 3;
String body = methodName.substring(prefixLen);
StringBuilder sb = new StringBuilder(body);
sb.setCharAt(0, Character.toLowerCase(body.charAt(0)));
return sb.toString();
}
private static String buildGetterName(String fieldName) {
return "get" + fieldName.substring(0, 1).toUpperCase()+ fieldName.substring(1);
}
private static String buildSetterName(String fieldName) {
return "set" + fieldName.substring(0, 1).toUpperCase()+ fieldName.substring(1);
}
private static String getInternalClassName(String className) {
return className.replace(".", "/");
}
private void addGetter(Method m)
{
Property prop = findProperty(getPropertyName(m.getName()));
// only set if not yet set; we start with super class:
if (prop.getGetter() == null) {
prop.setGetter(m);
}
}
private void addSetter(Method m)
{
Property prop = findProperty(getPropertyName(m.getName()));
if (prop.getSetter() == null) {
prop.setSetter(m);
}
}
private Property findProperty(String propName)
{
Property prop = _beanProperties.get(propName);
if (prop == null) {
prop = new Property(propName);
_beanProperties.put(propName, prop);
}
return prop;
}
private final static boolean returnsBoolean(Method m)
{
Class<?> rt = m.getReturnType();
return (rt == Boolean.class || rt == Boolean.TYPE);
}
/*
/**********************************************************
/* Internal methods, bytecode generation
/**********************************************************
*/
private static void generateDefaultConstructor(ClassWriter cw, String superName)
{
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, superName, "<init>", "()V");
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0); // don't care (real values: 1,1)
mv.visitEnd();
}
private static void createField(ClassWriter cw, Property prop, TypeDescription type)
{
String sig = type.hasGenerics() ? type.genericSignature() : null;
String desc = type.erasedSignature();
FieldVisitor fv = cw.visitField(ACC_PUBLIC, prop.getFieldName(), desc, sig, null);
fv.visitEnd();
}
private static void createSetter(ClassWriter cw, String internalClassName,
Property prop, TypeDescription propertyType)
{
String methodName;
String desc;
Method setter = prop.getSetter();
if (setter != null) { // easy, copy as is
desc = Type.getMethodDescriptor(setter);
methodName = setter.getName();
} else { // otherwise need to explicitly construct from property type (close enough)
desc = "("+ propertyType.erasedSignature() + ")V";
methodName = buildSetterName(prop.getName());
}
String sig = propertyType.hasGenerics() ? ("("+propertyType.genericSignature()+")V") : null;
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, methodName, desc, sig, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0); // this
mv.visitVarInsn(propertyType.getLoadOpcode(), 1);
mv.visitFieldInsn(PUTFIELD, internalClassName, prop.getFieldName(), propertyType.erasedSignature());
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0); // don't care (real values: 2, 2)
mv.visitEnd();
}
private static void createGetter(ClassWriter cw, String internalClassName,
Property prop, TypeDescription propertyType)
{
String methodName;
String desc;
Method getter = prop.getGetter();
if (getter != null) { // easy, copy as is
desc = Type.getMethodDescriptor(getter);
methodName = getter.getName();
} else { // otherwise need to explicitly construct from property type (close enough)
desc = "()"+propertyType.erasedSignature();
methodName = buildGetterName(prop.getName());
}
String sig = propertyType.hasGenerics() ? ("()"+propertyType.genericSignature()) : null;
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, methodName, desc, sig, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0); // load 'this'
mv.visitFieldInsn(GETFIELD, internalClassName, prop.getFieldName(), propertyType.erasedSignature());
mv.visitInsn(propertyType.getReturnOpcode());
mv.visitMaxs(0, 0); // don't care (real values: 1,1)
mv.visitEnd();
}
/**
* Builder for methods that just throw an exception, basically "unsupported
* operation" implementation.
*/
private static void createUnimplementedMethod(ClassWriter cw, String internalClassName,
Method method)
{
String exceptionName = getInternalClassName(UnsupportedOperationException.class.getName());
String sig = Type.getMethodDescriptor(method);
String name = method.getName();
// should we try to pass generic information?
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, name, sig, null, null);
mv.visitTypeInsn(NEW, exceptionName);
mv.visitInsn(DUP);
mv.visitLdcInsn("Unimplemented method '"+name+"' (not a setter/getter, could not materialize)");
mv.visitMethodInsn(INVOKESPECIAL, exceptionName, "<init>", "(Ljava/lang/String;)V");
mv.visitInsn(ATHROW);
mv.visitMaxs(0, 0); // don't care (real values: 3, 1 + method.getParameterTypes().length);
mv.visitEnd();
}
/*
/**********************************************************
/* Helper classes
/**********************************************************
*/
/**
* Bean that contains information about a single logical
* property, which consists of a getter and/or setter,
* and is used to generate getter, setter and matching
* backing field.
*/
private static class Property
{
protected final String _name;
protected final String _fieldName;
protected Method _getter;
protected Method _setter;
public Property(String name)
{
_name = name;
// Let's just prefix field name with single underscore for fun...
_fieldName = "_"+name;
}
public String getName() { return _name; }
public void setGetter(Method m) { _getter = m; }
public void setSetter(Method m) { _setter = m; }
public Method getGetter() { return _getter; }
public Method getSetter() { return _setter; }
public String getFieldName() {
return _fieldName;
}
/*
private static boolean isConcrete(Method m)
{
return m.getModifiers()
}
*/
public boolean hasConcreteGetter() {
return (_getter != null) && BeanUtil.isConcrete(_getter);
}
public boolean hasConcreteSetter() {
return (_setter != null) && BeanUtil.isConcrete(_setter);
}
private TypeDescription getterType(TypeFactory tf)
{
Class<?> context = _getter.getDeclaringClass();
return new TypeDescription(tf.constructType(_getter.getGenericReturnType(), context));
}
private TypeDescription setterType(TypeFactory tf)
{
Class<?> context = _setter.getDeclaringClass();
return new TypeDescription(tf.constructType(_setter.getGenericParameterTypes()[0], context));
}
public TypeDescription selectType(TypeFactory tf)
{
// First: if only know setter, or getter, use that one:
if (_getter == null) {
return setterType(tf);
}
if (_setter == null) {
return getterType(tf);
}
/* Otherwise must ensure they are compatible, choose more specific
* (most often setter - type)
*/
TypeDescription st = setterType(tf);
TypeDescription gt = getterType(tf);
TypeDescription specificType = TypeDescription.moreSpecificType(st, gt);
if (specificType == null) { // incompatible...
throw new IllegalArgumentException("Invalid property '"+getName()
+"': incompatible types for getter/setter ("
+gt+" vs "+st+")");
}
return specificType;
}
}
/**
* Helper bean used to encapsulate most details of type handling
*/
private static class TypeDescription
{
private final Type _asmType;
private JavaType _jacksonType;
/*
/**********************************************************
/* Construction
/**********************************************************
*/
public TypeDescription(JavaType type)
{
_jacksonType = type;
_asmType = Type.getType(type.getRawClass());
}
/*
/**********************************************************
/* Accessors
/**********************************************************
*/
public Class<?> getRawClass() { return _jacksonType.getRawClass(); }
public String erasedSignature() {
return _jacksonType.getErasedSignature();
}
public String genericSignature() {
return _jacksonType.getGenericSignature();
}
/**
* @return True if type has direct generic declaration (which may need
* to be copied)
*/
public boolean hasGenerics() {
return _jacksonType.hasGenericTypes();
}
/*
public boolean isPrimitive() {
return _signature.length() == 1;
}
*/
/*
public int getStoreOpcode() {
return _signatureType.getOpcode(ISTORE);
}
*/
public int getLoadOpcode() {
return _asmType.getOpcode(ILOAD);
}
public int getReturnOpcode() {
return _asmType.getOpcode(IRETURN);
}
@Override
public String toString() {
return _jacksonType.toString();
}
/*
/**********************************************************
/* Other methods
/**********************************************************
*/
public static TypeDescription moreSpecificType(TypeDescription desc1, TypeDescription desc2)
{
Class<?> c1 = desc1.getRawClass();
Class<?> c2 = desc2.getRawClass();
if (c1.isAssignableFrom(c2)) { // c2 more specific than c1
return desc2;
}
if (c2.isAssignableFrom(c1)) { // c1 more specific than c2
return desc1;
}
// not compatible, so:
return null;
}
}
}