blob: 42425eef7e71c5818cce9617a94318b4365a81e1 [file] [log] [blame]
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you 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 org.apache.juneau;
import static org.apache.juneau.Visibility.*;
import static org.apache.juneau.internal.ClassUtils.*;
import java.beans.*;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.Map.*;
import org.apache.juneau.annotation.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.transform.*;
import org.apache.juneau.utils.*;
/**
* Encapsulates all access to the properties of a bean class (like a souped-up {@link java.beans.BeanInfo}).
*
* <h5 class='section'>Description:</h5>
* <p>
* Uses introspection to find all the properties associated with this class. If the {@link Bean @Bean} annotation
* is present on the class, or the class has a {@link BeanFilter} registered with it in the bean context,
* then that information is used to determine the properties on the class.
* Otherwise, the {@code BeanInfo} functionality in Java is used to determine the properties on the class.
*
* <h6 class='topic'>Bean property ordering</h6>
* <p>
* The order of the properties are as follows:
* <ul class='spaced-list'>
* <li>If {@link Bean @Bean} annotation is specified on class, then the order is the same as the list of properties in the annotation.
* <li>If {@link Bean @Bean} annotation is not specified on the class, then the order is based on the following.
* <ul>
* <li>Public fields (same order as {@code Class.getFields()}).
* <li>Properties returned by {@code BeanInfo.getPropertyDescriptors()}.
* <li>Non-standard getters/setters with {@link BeanProperty @BeanProperty} annotation defined on them.
* </ul>
* </ul>
* <br>
* The order can also be overridden through the use of an {@link BeanFilter}.
*
* @param <T> The class type that this metadata applies to.
*/
public class BeanMeta<T> {
/** The target class type that this meta object describes. */
protected final ClassMeta<T> classMeta;
/** The target class that this meta object describes. */
protected final Class<T> c;
/** The properties on the target class. */
protected final Map<String,BeanPropertyMeta> properties;
/** The getter properties on the target class. */
protected final Map<Method,String> getterProps;
/** The setter properties on the target class. */
protected final Map<Method,String> setterProps;
/** The bean context that created this metadata object. */
protected final BeanContext ctx;
/** Optional bean filter associated with the target class. */
protected final BeanFilter beanFilter;
/** Type variables implemented by this bean. */
protected final Map<Class<?>,Class<?>[]> typeVarImpls;
/** The constructor for this bean. */
protected final Constructor<T> constructor;
/** For beans with constructors with BeanConstructor annotation, this is the list of constructor arg properties. */
protected final String[] constructorArgs;
private final MetadataMap extMeta; // Extended metadata
// Other fields
final BeanPropertyMeta subTypeProperty; // The property indentified as the sub type differentiator property (identified by @Bean.subTypeProperty annotation).
private final BeanPropertyMeta typeProperty; // "_type" mock bean property.
private final String dictionaryName; // The @Bean.typeName() annotation defined on this bean class.
final String notABeanReason; // Readable string explaining why this class wasn't a bean.
final BeanRegistry beanRegistry;
/**
* Constructor.
*
* @param classMeta The target class.
* @param ctx The bean context that created this object.
* @param beanFilter Optional bean filter associated with the target class. Can be <jk>null</jk>.
* @param pNames Explicit list of property names and order of properties. If <jk>null</jk>, determine automatically.
*/
protected BeanMeta(final ClassMeta<T> classMeta, BeanContext ctx, BeanFilter beanFilter, String[] pNames) {
this.classMeta = classMeta;
this.ctx = ctx;
this.c = classMeta.getInnerClass();
Builder<T> b = new Builder<T>(classMeta, ctx, beanFilter, pNames);
this.notABeanReason = b.init(this);
this.beanFilter = beanFilter;
this.dictionaryName = (beanFilter == null ? null : beanFilter.getTypeName());
this.properties = b.properties;
this.getterProps = Collections.unmodifiableMap(b.getterProps);
this.setterProps = Collections.unmodifiableMap(b.setterProps);
this.typeVarImpls = b.typeVarImpls;
this.constructor = b.constructor;
this.constructorArgs = b.constructorArgs;
this.extMeta = b.extMeta;
this.subTypeProperty = b.subTypeIdProperty;
this.beanRegistry = b.beanRegistry;
this.typeProperty = new BeanPropertyMeta(this, ctx.getBeanTypePropertyName(), ctx.string(), beanRegistry);
}
private static final class Builder<T> {
ClassMeta<T> classMeta;
BeanContext ctx;
BeanFilter beanFilter;
String[] pNames;
Map<String,BeanPropertyMeta> properties;
Map<Method,String> getterProps = new HashMap<Method,String>();
Map<Method,String> setterProps = new HashMap<Method,String>();
Map<Class<?>,Class<?>[]> typeVarImpls;
Constructor<T> constructor;
String[] constructorArgs = new String[0];
MetadataMap extMeta = new MetadataMap();
BeanPropertyMeta subTypeIdProperty;
PropertyNamer propertyNamer;
BeanRegistry beanRegistry;
private Builder(ClassMeta<T> classMeta, BeanContext ctx, BeanFilter beanFilter, String[] pNames) {
this.classMeta = classMeta;
this.ctx = ctx;
this.beanFilter = beanFilter;
this.pNames = pNames;
}
@SuppressWarnings("unchecked")
private String init(BeanMeta<T> beanMeta) {
Class<?> c = classMeta.getInnerClass();
try {
Visibility
conVis = ctx.beanConstructorVisibility,
cVis = ctx.beanClassVisibility,
mVis = ctx.beanMethodVisibility,
fVis = ctx.beanFieldVisibility;
List<Class<?>> bdClasses = new ArrayList<Class<?>>();
Bean bean = classMeta.innerClass.getAnnotation(Bean.class);
if (bean != null) {
bdClasses.addAll(Arrays.asList(bean.beanDictionary()));
if (! bean.typeName().isEmpty())
bdClasses.add(classMeta.innerClass);
}
this.beanRegistry = new BeanRegistry(ctx, null, bdClasses.toArray(new Class<?>[bdClasses.size()]));
// If @Bean.interfaceClass is specified on the parent class, then we want
// to use the properties defined on that class, not the subclass.
Class<?> c2 = (beanFilter != null && beanFilter.getInterfaceClass() != null ? beanFilter.getInterfaceClass() : c);
Class<?> stopClass = (beanFilter != null ? beanFilter.getStopClass() : Object.class);
if (stopClass == null)
stopClass = Object.class;
Map<String,BeanPropertyMeta> normalProps = new LinkedHashMap<String,BeanPropertyMeta>();
/// See if this class matches one the patterns in the exclude-class list.
if (ctx.isNotABean(c))
return "Class matches exclude-class list";
if (! cVis.isVisible(c.getModifiers()))
return "Class is not public";
if (c.isAnnotationPresent(BeanIgnore.class))
return "Class is annotated with @BeanIgnore";
// Make sure it's serializable.
if (beanFilter == null && ctx.beansRequireSerializable && ! isParentClass(Serializable.class, c))
return "Class is not serializable";
// Look for @BeanConstructor constructor.
for (Constructor<?> x : c.getConstructors()) {
if (x.isAnnotationPresent(BeanConstructor.class)) {
if (constructor != null)
throw new BeanRuntimeException(c, "Multiple instances of '@BeanConstructor' found.");
constructor = (Constructor<T>)x;
constructorArgs = StringUtils.split(x.getAnnotation(BeanConstructor.class).properties(), ',');
if (constructorArgs.length != x.getParameterTypes().length)
throw new BeanRuntimeException(c, "Number of properties defined in '@BeanConstructor' annotation does not match number of parameters in constructor.");
if (! setAccessible(constructor))
throw new BeanRuntimeException(c, "Could not set accessibility to true on method with @BeanConstructor annotation. Method=''{0}''", constructor.getName());
}
}
// If this is an interface, look for impl classes defined in the context.
if (constructor == null)
constructor = (Constructor<T>)ctx.getImplClassConstructor(c, conVis);
if (constructor == null)
constructor = (Constructor<T>)findNoArgConstructor(c, conVis);
if (constructor == null && beanFilter == null && ctx.beansRequireDefaultConstructor)
return "Class does not have the required no-arg constructor";
if (! setAccessible(constructor))
throw new BeanRuntimeException(c, "Could not set accessibility to true on no-arg constructor");
// Explicitly defined property names in @Bean annotation.
Set<String> fixedBeanProps = new LinkedHashSet<String>();
if (beanFilter != null) {
// Get the 'properties' attribute if specified.
if (beanFilter.getProperties() != null)
for (String p : beanFilter.getProperties())
fixedBeanProps.add(p);
if (beanFilter.getPropertyNamer() != null)
propertyNamer = beanFilter.getPropertyNamer();
}
if (propertyNamer == null)
propertyNamer = new PropertyNamerDefault();
// First populate the properties with those specified in the bean annotation to
// ensure that ordering first.
for (String name : fixedBeanProps)
normalProps.put(name, new BeanPropertyMeta(beanMeta, name));
if (ctx.useJavaBeanIntrospector) {
BeanInfo bi = null;
if (! c2.isInterface())
bi = Introspector.getBeanInfo(c2, stopClass);
else
bi = Introspector.getBeanInfo(c2, null);
if (bi != null) {
for (PropertyDescriptor pd : bi.getPropertyDescriptors()) {
String name = pd.getName();
if (! normalProps.containsKey(name))
normalProps.put(name, new BeanPropertyMeta(beanMeta, name));
normalProps.get(name).setGetter(pd.getReadMethod()).setSetter(pd.getWriteMethod());
}
}
} else /* Use 'better' introspection */ {
for (Field f : findBeanFields(c2, stopClass, fVis)) {
String name = findPropertyName(f, fixedBeanProps);
if (name != null) {
if (! normalProps.containsKey(name))
normalProps.put(name, new BeanPropertyMeta(beanMeta, name));
normalProps.get(name).setField(f);
}
}
List<BeanMethod> bms = findBeanMethods(c2, stopClass, mVis, fixedBeanProps, propertyNamer);
// Iterate through all the getters.
for (BeanMethod bm : bms) {
String pn = bm.propertyName;
Method m = bm.method;
if (! normalProps.containsKey(pn))
normalProps.put(pn, new BeanPropertyMeta(beanMeta, pn));
BeanPropertyMeta bpm = normalProps.get(pn);
if (! bm.isSetter)
bpm.setGetter(m);
}
// Now iterate through all the setters.
for (BeanMethod bm : bms) {
if (bm.isSetter) {
BeanPropertyMeta bpm = normalProps.get(bm.propertyName);
if (bm.matchesPropertyType(bpm))
bpm.setSetter(bm.method);
}
}
}
typeVarImpls = new HashMap<Class<?>,Class<?>[]>();
findTypeVarImpls(c, typeVarImpls);
if (typeVarImpls.isEmpty())
typeVarImpls = null;
// Eliminate invalid properties, and set the contents of getterProps and setterProps.
for (Iterator<BeanPropertyMeta> i = normalProps.values().iterator(); i.hasNext();) {
BeanPropertyMeta p = i.next();
try {
if (p.validate(ctx, beanRegistry, typeVarImpls)) {
if (p.getGetter() != null)
getterProps.put(p.getGetter(), p.getName());
if (p.getSetter() != null)
setterProps.put(p.getSetter(), p.getName());
} else {
i.remove();
}
} catch (ClassNotFoundException e) {
throw new BeanRuntimeException(c, e.getLocalizedMessage());
}
}
// Check for missing properties.
for (String fp : fixedBeanProps)
if (! normalProps.containsKey(fp))
throw new BeanRuntimeException(c, "The property ''{0}'' was defined on the @Bean(properties=X) annotation but was not found on the class definition.", fp);
// Mark constructor arg properties.
for (String fp : constructorArgs) {
BeanPropertyMeta m = normalProps.get(fp);
if (m == null)
throw new BeanRuntimeException(c, "The property ''{0}'' was defined on the @BeanConstructor(properties=X) annotation but was not found on the class definition.", fp);
m.setAsConstructorArg();
}
// Make sure at least one property was found.
if (beanFilter == null && ctx.beansRequireSomeProperties && normalProps.size() == 0)
return "No properties detected on bean class";
boolean sortProperties = (ctx.sortProperties || (beanFilter != null && beanFilter.isSortProperties())) && fixedBeanProps.isEmpty();
properties = sortProperties ? new TreeMap<String,BeanPropertyMeta>() : new LinkedHashMap<String,BeanPropertyMeta>();
if (beanFilter != null && beanFilter.getSubTypeProperty() != null) {
String subTypeProperty = beanFilter.getSubTypeProperty();
this.subTypeIdProperty = new SubTypePropertyMeta(beanMeta, subTypeProperty, beanFilter.getSubTypes(), normalProps.remove(subTypeProperty), beanRegistry);
properties.put(subTypeProperty, this.subTypeIdProperty);
}
properties.putAll(normalProps);
// If a beanFilter is defined, look for inclusion and exclusion lists.
if (beanFilter != null) {
// Eliminated excluded properties if BeanFilter.excludeKeys is specified.
String[] includeKeys = beanFilter.getProperties();
String[] excludeKeys = beanFilter.getExcludeProperties();
if (excludeKeys != null) {
for (String k : excludeKeys)
properties.remove(k);
// Only include specified properties if BeanFilter.includeKeys is specified.
// Note that the order must match includeKeys.
} else if (includeKeys != null) {
Map<String,BeanPropertyMeta> properties2 = new LinkedHashMap<String,BeanPropertyMeta>();
for (String k : includeKeys) {
if (properties.containsKey(k))
properties2.put(k, properties.get(k));
}
properties = properties2;
}
}
if (pNames != null) {
Map<String,BeanPropertyMeta> properties2 = new LinkedHashMap<String,BeanPropertyMeta>();
for (String k : pNames) {
if (properties.containsKey(k))
properties2.put(k, properties.get(k));
}
properties = properties2;
}
// We return this through the Bean.keySet() interface, so make sure it's not modifiable.
properties = Collections.unmodifiableMap(properties);
} catch (BeanRuntimeException e) {
throw e;
} catch (Exception e) {
return "Exception: " + StringUtils.getStackTrace(e);
}
return null;
}
/*
* Returns the property name of the specified field if it's a valid property.
* Returns null if the field isn't a valid property.
*/
private String findPropertyName(Field f, Set<String> fixedBeanProps) {
BeanProperty bp = f.getAnnotation(BeanProperty.class);
if (bp != null && ! bp.name().equals("")) {
String name = bp.name();
if (fixedBeanProps.isEmpty() || fixedBeanProps.contains(name))
return name;
throw new BeanRuntimeException(classMeta.getInnerClass(), "Method property ''{0}'' identified in @BeanProperty, but missing from @Bean", name);
}
String name = propertyNamer.getPropertyName(f.getName());
if (fixedBeanProps.isEmpty() || fixedBeanProps.contains(name))
return name;
return null;
}
}
/**
* Returns the {@link ClassMeta} of this bean.
*
* @return The {@link ClassMeta} of this bean.
*/
@BeanIgnore
public ClassMeta<T> getClassMeta() {
return classMeta;
}
/**
* Returns the dictionary name for this bean as defined through the {@link Bean#typeName()} annotation.
*
* @return The dictioanry name for this bean, or <jk>null</jk> if it has no dictionary name defined.
*/
public String getDictionaryName() {
return dictionaryName;
}
/**
* Returns the subtype property of this bean if it has one.
* <p>
* The subtype is specified using the {@link Bean#subTypeProperty()} annotation.
*
* @return The meta property for the sub type property, or <jk>null</jk> if no subtype is defined for this bean.
*/
public BeanPropertyMeta getSubTypeProperty() {
return subTypeProperty;
}
/**
* Returns <jk>true</jk> if this bean has subtypes associated with it.
* Subtypes are defined using the {@link Bean#subTypes()} annotation.
*
* @return <jk>true</jk> if this bean has subtypes associated with it.
*/
public boolean isSubTyped() {
return subTypeProperty != null;
}
/**
* Returns a mock bean property that resolves to the name <js>"_type"</js> and whose value always resolves
* to the dictionary name of the bean.
*
* @return The type name property.
*/
public BeanPropertyMeta getTypeProperty() {
return typeProperty;
}
/*
* Temporary getter/setter method struct.
*/
private static class BeanMethod {
String propertyName;
boolean isSetter;
Method method;
Class<?> type;
BeanMethod(String propertyName, boolean isSetter, Method method) {
this.propertyName = propertyName;
this.isSetter = isSetter;
this.method = method;
if (isSetter)
this.type = method.getParameterTypes()[0];
else
this.type = method.getReturnType();
}
/*
* Returns true if this method matches the class type of the specified property.
* Only meant to be used for setters.
*/
boolean matchesPropertyType(BeanPropertyMeta b) {
if (b == null)
return false;
// Get the bean property type from the getter/field.
Class<?> pt = null;
if (b.getGetter() != null)
pt = b.getGetter().getReturnType();
else if (b.getField() != null)
pt = b.getField().getType();
// Doesn't match if no getter/field defined.
if (pt == null)
return false;
// Doesn't match if not same type or super type as getter/field.
if (! isParentClass(type, pt))
return false;
// If a setter was previously set, only use this setter if it's a closer
// match (e.g. prev type is a superclass of this type).
if (b.getSetter() == null)
return true;
Class<?> prevType = b.getSetter().getParameterTypes()[0];
return isParentClass(prevType, type, true);
}
@Override /* Object */
public String toString() {
return method.toString();
}
}
/*
* Find all the bean methods on this class.
*
* @param c The transformed class.
* @param stopClass Don't look above this class in the hierarchy.
* @param v The minimum method visibility.
* @param fixedBeanProps Only include methods whose properties are in this list.
* @param pn Use this property namer to determine property names from the method names.
*/
private static List<BeanMethod> findBeanMethods(Class<?> c, Class<?> stopClass, Visibility v, Set<String> fixedBeanProps, PropertyNamer pn) {
List<BeanMethod> l = new LinkedList<BeanMethod>();
for (Class<?> c2 : findClasses(c, stopClass)) {
for (Method m : c2.getDeclaredMethods()) {
int mod = m.getModifiers();
if (Modifier.isStatic(mod))
continue;
if (m.isAnnotationPresent(BeanIgnore.class))
continue;
if (m.isBridge()) // This eliminates methods with covariant return types from parent classes on child classes.
continue;
if (! (v.isVisible(m) || m.isAnnotationPresent(BeanProperty.class)))
continue;
String n = m.getName();
Class<?>[] pt = m.getParameterTypes();
Class<?> rt = m.getReturnType();
boolean isGetter = false, isSetter = false;
BeanProperty bp = m.getAnnotation(BeanProperty.class);
if (pt.length == 0) {
if (n.startsWith("get") && (! rt.equals(Void.TYPE))) {
isGetter = true;
n = n.substring(3);
} else if (n.startsWith("is") && (rt.equals(Boolean.TYPE) || rt.equals(Boolean.class))) {
isGetter = true;
n = n.substring(2);
} else if (bp != null && ! bp.name().isEmpty()) {
isGetter = true;
n = bp.name();
}
} else if (pt.length == 1) {
if (n.startsWith("set") && (isParentClass(rt, c) || rt.equals(Void.TYPE))) {
isSetter = true;
n = n.substring(3);
} else if (bp != null && ! bp.name().isEmpty()) {
isSetter = true;
n = bp.name();
}
}
n = pn.getPropertyName(n);
if (isGetter || isSetter) {
if (bp != null && ! bp.name().equals("")) {
n = bp.name();
if (! fixedBeanProps.isEmpty())
if (! fixedBeanProps.contains(n))
throw new BeanRuntimeException(c, "Method property ''{0}'' identified in @BeanProperty, but missing from @Bean", n);
}
l.add(new BeanMethod(n, isSetter, m));
}
}
}
return l;
}
private static Collection<Field> findBeanFields(Class<?> c, Class<?> stopClass, Visibility v) {
List<Field> l = new LinkedList<Field>();
for (Class<?> c2 : findClasses(c, stopClass)) {
for (Field f : c2.getDeclaredFields()) {
int m = f.getModifiers();
if (Modifier.isStatic(m) || Modifier.isTransient(m))
continue;
if (f.isAnnotationPresent(BeanIgnore.class))
continue;
if (! (v.isVisible(f) || f.isAnnotationPresent(BeanProperty.class)))
continue;
l.add(f);
}
}
return l;
}
private static List<Class<?>> findClasses(Class<?> c, Class<?> stopClass) {
LinkedList<Class<?>> l = new LinkedList<Class<?>>();
findClasses(c, l, stopClass);
return l;
}
private static void findClasses(Class<?> c, LinkedList<Class<?>> l, Class<?> stopClass) {
while (c != null && stopClass != c) {
l.addFirst(c);
for (Class<?> ci : c.getInterfaces())
findClasses(ci, l, stopClass);
c = c.getSuperclass();
}
}
/**
* Returns the metadata on all properties associated with this bean.
*
* @return Metadata on all properties associated with this bean.
*/
public Collection<BeanPropertyMeta> getPropertyMetas() {
return this.properties.values();
}
/**
* Returns the metadata on the specified list of properties.
*
* @param pNames The list of properties to retrieve. If <jk>null</jk>, returns all properties.
* @return The metadata on the specified list of properties.
*/
public Collection<BeanPropertyMeta> getPropertyMetas(final String...pNames) {
if (pNames == null)
return getPropertyMetas();
List<BeanPropertyMeta> l = new ArrayList<BeanPropertyMeta>(pNames.length);
for (int i = 0; i < pNames.length; i++)
l.add(getPropertyMeta(pNames[i]));
return l;
}
/**
* Returns the language-specified extended metadata on this bean class.
*
* @param metaDataClass The name of the metadata class to create.
* @return Extended metadata on this bean class. Never <jk>null</jk>.
*/
public <M extends BeanMetaExtended> M getExtendedMeta(Class<M> metaDataClass) {
return extMeta.get(metaDataClass, this);
}
/**
* Returns metadata about the specified property.
*
* @param name The name of the property on this bean.
* @return The metadata about the property, or <jk>null</jk> if no such property exists
* on this bean.
*/
public BeanPropertyMeta getPropertyMeta(String name) {
return this.properties.get(name);
}
/**
* Creates a new instance of this bean.
*
* @param outer The outer object if bean class is a non-static inner member class.
* @return A new instance of this bean if possible, or <jk>null</jk> if not.
* @throws IllegalArgumentException Thrown by constructor.
* @throws InstantiationException Thrown by constructor.
* @throws IllegalAccessException Thrown by constructor.
* @throws InvocationTargetException Thrown by constructor.
*/
@SuppressWarnings("unchecked")
protected T newBean(Object outer) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
if (classMeta.isMemberClass()) {
if (constructor != null)
return constructor.newInstance(outer);
} else {
if (constructor != null)
return constructor.newInstance((Object[])null);
InvocationHandler h = classMeta.getProxyInvocationHandler();
if (h != null) {
ClassLoader cl = classMeta.getBeanContext().classLoader;
if (cl == null)
cl = this.getClass().getClassLoader();
return (T)Proxy.newProxyInstance(cl, new Class[] { classMeta.innerClass, java.io.Serializable.class }, h);
}
}
return null;
}
/**
* Recursively determines the classes represented by parameterized types in the class hierarchy of
* the specified type, and puts the results in the specified map.<br>
* <p>
* For example, given the following classes...
* <p class='bcode'>
* public static class BeanA&lt;T> {
* public T x;
* }
* public static class BeanB extends BeanA&lt;Integer>} {...}
* <p>
* ...calling this method on {@code BeanB.class} will load the following data into {@code m} indicating
* that the {@code T} parameter on the BeanA class is implemented with an {@code Integer}:
* <p class='bcode'>
* {BeanA.class:[Integer.class]}
* <p>
* TODO: This code doesn't currently properly handle the following situation:
* <p class='bcode'>
* public static class BeanB&ltT extends Number> extends BeanA&ltT>;
* public static class BeanC extends BeanB&ltInteger>;
* <p>
* When called on {@code BeanC}, the variable will be detected as a {@code Number}, not an {@code Integer}.<br>
* If anyone can figure out a better way of doing this, please do so!
*
* @param t The type we're recursing.
* @param m Where the results are loaded.
*/
private static void findTypeVarImpls(Type t, Map<Class<?>,Class<?>[]> m) {
if (t instanceof Class) {
Class<?> c = (Class<?>)t;
findTypeVarImpls(c.getGenericSuperclass(), m);
for (Type ci : c.getGenericInterfaces())
findTypeVarImpls(ci, m);
} else if (t instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType)t;
Type rt = pt.getRawType();
if (rt instanceof Class) {
Type[] gImpls = pt.getActualTypeArguments();
Class<?>[] gTypes = new Class[gImpls.length];
for (int i = 0; i < gImpls.length; i++) {
Type gt = gImpls[i];
if (gt instanceof Class)
gTypes[i] = (Class<?>)gt;
else if (gt instanceof TypeVariable) {
TypeVariable<?> tv = (TypeVariable<?>)gt;
for (Type upperBound : tv.getBounds())
if (upperBound instanceof Class)
gTypes[i] = (Class<?>)upperBound;
}
}
m.put((Class<?>)rt, gTypes);
findTypeVarImpls(pt.getRawType(), m);
}
}
}
/*
* Bean property for getting and setting bean subtype.
*/
@SuppressWarnings({"rawtypes","unchecked"})
private static class SubTypePropertyMeta extends BeanPropertyMeta {
private Map<Class<?>,String> subTypes;
private BeanPropertyMeta realProperty; // Bean property if bean actually has a real subtype field.
private BeanMeta<?> beanMeta;
SubTypePropertyMeta(BeanMeta beanMeta, String subTypeAttr, Map<Class<?>,String> subTypes, BeanPropertyMeta realProperty, BeanRegistry beanRegistry) {
super(beanMeta, subTypeAttr, beanMeta.ctx.string(), beanRegistry);
this.subTypes = subTypes;
this.realProperty = realProperty;
this.beanMeta = beanMeta;
}
/*
* Setting this bean property causes the inner bean to be set to the subtype implementation.
*/
@Override /* BeanPropertyMeta */
public Object set(BeanMap<?> m, Object value) throws BeanRuntimeException {
if (value == null)
throw new BeanRuntimeException("Attempting to set bean subtype property to null.");
String subTypeId = value.toString();
for (Entry<Class<?>,String> e : subTypes.entrySet()) {
if (e.getValue().equals(subTypeId)) {
Class subTypeClass = e.getKey();
m.meta = beanMeta.ctx.getBeanMeta(subTypeClass);
try {
m.setBean(subTypeClass.newInstance());
if (realProperty != null)
realProperty.set(m, value);
// If subtype attribute wasn't specified first, set them again from the temporary cache.
if (m.propertyCache != null)
for (Map.Entry<String,Object> me : m.propertyCache.entrySet())
m.put(me.getKey(), me.getValue());
} catch (Exception e1) {
throw new BeanRuntimeException(e1);
}
return null;
}
}
throw new BeanRuntimeException(beanMeta.c, "Unknown subtype ID ''{0}''", subTypeId);
}
@Override /* BeanPropertyMeta */
public Object get(BeanMap<?> m) throws BeanRuntimeException {
String subTypeId = beanMeta.beanFilter.getSubTypes().get(beanMeta.c);
if (subTypeId == null)
throw new BeanRuntimeException(beanMeta.c, "Unmapped sub type class");
return subTypeId;
}
}
@Override /* Object */
public String toString() {
StringBuilder sb = new StringBuilder(c.getName());
sb.append(" {\n");
for (BeanPropertyMeta pm : this.properties.values())
sb.append('\t').append(pm.toString()).append(",\n");
sb.append('}');
return sb.toString();
}
}