org.apache.juneau.BeanMeta Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * 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 static org.apache.juneau.internal.ReflectionUtils.*;
import static org.apache.juneau.internal.StringUtils.*;
import java.beans.*;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import org.apache.juneau.annotation.*;
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}).
*
* Description:
*
* 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.
*
* Bean property ordering
*
* The order of the properties are as follows:
*
* -
* If {@link Bean @Bean} annotation is specified on class, then the order is the same as the list of properties
* in the annotation.
*
-
* If {@link Bean @Bean} annotation is not specified on the class, then the order is based on the following.
*
* - Public fields (same order as {@code Class.getFields()}).
*
- Properties returned by {@code BeanInfo.getPropertyDescriptors()}.
*
- Non-standard getters/setters with {@link BeanProperty @BeanProperty} annotation defined on them.
*
*
*
*
* The order can also be overridden through the use of an {@link BeanFilter}.
*
* @param The class type that this metadata applies to.
*/
public class BeanMeta {
/** The target class type that this meta object describes. */
protected final ClassMeta classMeta;
/** The target class that this meta object describes. */
protected final Class c;
/** The properties on the target class. */
protected final Map properties;
/** The getter properties on the target class. */
protected final Map getterProps;
/** The setter properties on the target class. */
protected final Map 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>[]> typeVarImpls;
/** The constructor for this bean. */
protected final Constructor 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 String typePropertyName; // "_type" property actual name.
private final BeanPropertyMeta typeProperty; // "_type" mock bean property.
final BeanPropertyMeta dynaProperty; // "extras" 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;
final boolean sortProperties;
/**
* 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 null .
* @param pNames Explicit list of property names and order of properties. If null , determine automatically.
*/
protected BeanMeta(final ClassMeta classMeta, BeanContext ctx, BeanFilter beanFilter, String[] pNames) {
this.classMeta = classMeta;
this.ctx = ctx;
this.c = classMeta.getInnerClass();
Builder b = new Builder(classMeta, ctx, beanFilter, pNames);
this.notABeanReason = b.init(this);
this.beanFilter = beanFilter;
this.dictionaryName = b.dictionaryName;
this.properties = b.properties == null ? null : Collections.unmodifiableMap(b.properties);
this.getterProps = Collections.unmodifiableMap(b.getterProps);
this.setterProps = Collections.unmodifiableMap(b.setterProps);
this.dynaProperty = b.dynaProperty;
this.typeVarImpls = b.typeVarImpls == null ? null : Collections.unmodifiableMap(b.typeVarImpls);
this.constructor = b.constructor;
this.constructorArgs = b.constructorArgs;
this.extMeta = b.extMeta;
this.beanRegistry = b.beanRegistry;
this.typePropertyName = b.typePropertyName;
this.typeProperty = new BeanPropertyMeta.Builder(this, typePropertyName, ctx.string(), beanRegistry).build();
this.sortProperties = b.sortProperties;
}
private static final class Builder {
ClassMeta classMeta;
BeanContext ctx;
BeanFilter beanFilter;
String[] pNames;
Map properties;
Map getterProps = new HashMap();
Map setterProps = new HashMap();
BeanPropertyMeta dynaProperty;
Map,Class>[]> typeVarImpls;
Constructor constructor;
String[] constructorArgs = new String[0];
MetadataMap extMeta = new MetadataMap();
PropertyNamer propertyNamer;
BeanRegistry beanRegistry;
String dictionaryName, typePropertyName;
boolean sortProperties;
private Builder(ClassMeta 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 beanMeta) {
Class> c = classMeta.getInnerClass();
try {
Visibility
conVis = ctx.beanConstructorVisibility,
cVis = ctx.beanClassVisibility,
mVis = ctx.beanMethodVisibility,
fVis = ctx.beanFieldVisibility;
List> bdClasses = new ArrayList>();
if (beanFilter != null && beanFilter.getBeanDictionary() != null)
bdClasses.addAll(Arrays.asList(beanFilter.getBeanDictionary()));
Bean bean = classMeta.innerClass.getAnnotation(Bean.class);
if (bean != null) {
if (! bean.typeName().isEmpty())
bdClasses.add(classMeta.innerClass);
}
this.beanRegistry = new BeanRegistry(ctx, null, bdClasses.toArray(new Class>[bdClasses.size()]));
for (Bean b : findAnnotationsParentFirst(Bean.class, classMeta.innerClass))
if (! b.typePropertyName().isEmpty())
typePropertyName = b.typePropertyName();
if (typePropertyName == null)
typePropertyName = ctx.getBeanTypePropertyName();
// 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 normalProps = new LinkedHashMap();
/// 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()) || c.isAnonymousClass()))
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)x;
constructorArgs = 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)ctx.getImplClassConstructor(c, conVis);
if (constructor == null)
constructor = (Constructor)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 fixedBeanProps = new LinkedHashSet();
String[] includeProperties = ctx.getIncludeProperties(c);
String[] excludeProperties = ctx.getExcludeProperties(c);
if (beanFilter != null) {
// Get the 'properties' attribute if specified.
if (beanFilter.getProperties() != null && includeProperties == null)
for (String p : beanFilter.getProperties())
fixedBeanProps.add(p);
if (beanFilter.getPropertyNamer() != null)
propertyNamer = beanFilter.getPropertyNamer();
}
if (includeProperties != null)
fixedBeanProps.addAll(Arrays.asList(includeProperties));
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.Builder(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.Builder(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.Builder(beanMeta, name));
normalProps.get(name).setField(f);
}
}
List 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.Builder(beanMeta, pn));
BeanPropertyMeta.Builder bpm = normalProps.get(pn);
if (! bm.isSetter)
bpm.setGetter(m);
}
// Now iterate through all the setters.
for (BeanMethod bm : bms) {
if (bm.isSetter) {
BeanPropertyMeta.Builder bpm = normalProps.get(bm.propertyName);
if (bm.matchesPropertyType(bpm))
bpm.setSetter(bm.method);
}
}
}
typeVarImpls = new HashMap,Class>[]>();
findTypeVarImpls(c, typeVarImpls);
if (typeVarImpls.isEmpty())
typeVarImpls = null;
// Eliminate invalid properties, and set the contents of getterProps and setterProps.
for (Iterator i = normalProps.values().iterator(); i.hasNext();) {
BeanPropertyMeta.Builder p = i.next();
try {
if (p.validate(ctx, beanRegistry, typeVarImpls)) {
if (p.getter != null)
getterProps.put(p.getter, p.name);
if (p.setter != null)
setterProps.put(p.setter, p.name);
} 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.Builder 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";
sortProperties = (ctx.sortProperties || (beanFilter != null && beanFilter.isSortProperties())) && fixedBeanProps.isEmpty();
properties = sortProperties ? new TreeMap() : new LinkedHashMap();
if (beanFilter != null && beanFilter.getTypeName() != null)
dictionaryName = beanFilter.getTypeName();
if (dictionaryName == null)
dictionaryName = findDictionaryName(this.classMeta);
for (Map.Entry e : normalProps.entrySet()) {
BeanPropertyMeta pMeta = e.getValue().build();
if (pMeta.isDyna())
dynaProperty = pMeta;
properties.put(e.getKey(), pMeta);
}
// 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 && excludeProperties == 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 properties2 = new LinkedHashMap();
for (String k : includeKeys) {
if (properties.containsKey(k))
properties2.put(k, properties.get(k));
}
properties = properties2;
}
}
if (excludeProperties != null)
for (String ep : excludeProperties)
properties.remove(ep);
if (pNames != null) {
Map properties2 = new LinkedHashMap();
for (String k : pNames) {
if (properties.containsKey(k))
properties2.put(k, properties.get(k));
}
properties = properties2;
}
} catch (BeanRuntimeException e) {
throw e;
} catch (Exception e) {
return "Exception: " + getStackTrace(e);
}
return null;
}
private String findDictionaryName(ClassMeta> cm) {
BeanRegistry br = cm.getBeanRegistry();
if (br != null) {
String s = br.getTypeName(this.classMeta);
if (s != null)
return s;
}
Class> pcm = cm.innerClass.getSuperclass();
if (pcm != null) {
String s = findDictionaryName(ctx.getClassMeta(pcm));
if (s != null)
return s;
}
for (Class> icm : cm.innerClass.getInterfaces()) {
String s = findDictionaryName(ctx.getClassMeta(icm));
if (s != null)
return s;
}
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 fixedBeanProps) {
BeanProperty bp = f.getAnnotation(BeanProperty.class);
String name = bpName(bp);
if (! name.isEmpty()) {
if (fixedBeanProps.isEmpty() || fixedBeanProps.contains(name))
return name;
return null; // Could happen if filtered via BEAN_includeProperties/BEAN_excludeProperties.
}
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 getClassMeta() {
return classMeta;
}
/**
* Returns the dictionary name for this bean as defined through the {@link Bean#typeName()} annotation.
*
* @return The dictionary name for this bean, or null if it has no dictionary name defined.
*/
public String getDictionaryName() {
return dictionaryName;
}
/**
* Returns a mock bean property that resolves to the name "_type" 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.Builder b) {
if (b == null)
return false;
// Don't do further validation if this is the "*" bean property.
if ("*".equals(b.name))
return true;
// Get the bean property type from the getter/field.
Class> pt = null;
if (b.getter != null)
pt = b.getter.getReturnType();
else if (b.field != null)
pt = b.field.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.setter == null)
return true;
Class> prevType = b.setter.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 findBeanMethods(Class> c, Class> stopClass, Visibility v, Set fixedBeanProps, PropertyNamer pn) {
List l = new LinkedList();
for (Class> c2 : findClasses(c, stopClass)) {
for (Method m : c2.getDeclaredMethods()) {
int mod = m.getModifiers();
if (Modifier.isStatic(mod))
continue;
if (m.isBridge()) // This eliminates methods with covariant return types from parent classes on child classes.
continue;
BeanIgnore bi = getMethodAnnotation(BeanIgnore.class, c, m);
if (bi != null)
continue;
BeanProperty bp = getMethodAnnotation(BeanProperty.class, c, m);
if (! (v.isVisible(m) || bp != null))
continue;
String n = m.getName();
Class>[] pt = m.getParameterTypes();
Class> rt = m.getReturnType();
boolean isGetter = false, isSetter = false;
String bpName = bpName(bp);
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 (! bpName.isEmpty()) {
isGetter = true;
n = bpName;
}
} else if (pt.length == 1) {
if (n.startsWith("set") && (isParentClass(rt, c) || rt.equals(Void.TYPE))) {
isSetter = true;
n = n.substring(3);
} else if (! bpName.isEmpty()) {
isSetter = true;
n = bpName;
}
} else if (pt.length == 2) {
if ("*".equals(bpName)) {
isSetter = true;
n = bpName;
}
}
n = pn.getPropertyName(n);
if (isGetter || isSetter) {
if (! bpName.isEmpty()) {
n = bpName;
if (! fixedBeanProps.isEmpty())
if (! fixedBeanProps.contains(n))
n = null; // Could happen if filtered via BEAN_includeProperties/BEAN_excludeProperties
}
if (n != null)
l.add(new BeanMethod(n, isSetter, m));
}
}
}
return l;
}
private static Collection findBeanFields(Class> c, Class> stopClass, Visibility v) {
List l = new LinkedList();
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> findClasses(Class> c, Class> stopClass) {
LinkedList> l = new LinkedList>();
findClasses(c, l, stopClass);
return l;
}
private static void findClasses(Class> c, LinkedList> 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 getPropertyMetas() {
return this.properties.values();
}
/**
* Returns the metadata on the specified list of properties.
*
* @param pNames The list of properties to retrieve. If null , returns all properties.
* @return The metadata on the specified list of properties.
*/
public Collection getPropertyMetas(final String...pNames) {
if (pNames == null)
return getPropertyMetas();
List l = new ArrayList(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 null .
*/
public M getExtendedMeta(Class 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 null if no such property exists on this bean.
*/
public BeanPropertyMeta getPropertyMeta(String name) {
BeanPropertyMeta bpm = properties.get(name);
if (bpm == null)
bpm = dynaProperty;
return bpm;
}
/**
* 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 null 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();
ClassLoader cl = classMeta.innerClass.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.
*
*
* For example, given the following classes...
*
* public static class BeanA<T> {
* public T x;
* }
* public static class BeanB extends BeanA<Integer>} {...}
*
*
* ...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}:
*
* {BeanA.class:[Integer.class]}
*
*
*
* TODO: This code doesn't currently properly handle the following situation:
*
* public static class BeanB<T extends Number> extends BeanA<T>;
* public static class BeanC extends BeanB<Integer>;
*
*
*
* When called on {@code BeanC}, the variable will be detected as a {@code Number}, not an {@code Integer}.
* 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>[]> 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);
}
}
}
private static String bpName(BeanProperty bp) {
if (bp == null)
return "";
if (! bp.name().isEmpty())
return bp.name();
return bp.value();
}
@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();
}
}