org.apache.juneau.ClassMeta 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.ClassMeta.ClassCategory.*;
import static org.apache.juneau.internal.ClassFlags.*;
import static org.apache.juneau.internal.ClassUtils.*;
import java.io.*;
import java.lang.reflect.*;
import java.lang.reflect.Proxy;
import java.net.*;
import java.net.URI;
import java.util.*;
import java.util.Date;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
import org.apache.juneau.annotation.*;
import org.apache.juneau.http.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.json.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.transform.*;
import org.apache.juneau.utils.*;
/**
* A wrapper class around the {@link Class} object that provides cached information about that class.
*
*
* Instances of this class can be created through the {@link BeanContext#getClassMeta(Class)} method.
*
*
* The {@link BeanContext} class will cache and reuse instances of this class except for the following class types:
*
* - Arrays
*
- Maps with non-Object key/values.
*
- Collections with non-Object key/values.
*
*
*
* This class is tied to the {@link BeanContext} class because it's that class that makes the determination of what is
* a bean.
*
* @param The class type of the wrapped class.
*/
@Bean(properties="innerClass,classCategory,elementType,keyType,valueType,notABeanReason,initException,beanMeta")
public final class ClassMeta implements Type {
/** Class categories. */
enum ClassCategory {
MAP, COLLECTION, CLASS, METHOD, NUMBER, DECIMAL, BOOLEAN, CHAR, DATE, ARRAY, ENUM, OTHER, CHARSEQ, STR, OBJ, URI, BEANMAP, READER, INPUTSTREAM, VOID, ARGS
}
final Class innerClass; // The class being wrapped.
private final Class extends T> implClass; // The implementation class to use if this is an interface.
private final ClassCategory cc; // The class category.
private final Method fromStringMethod; // The static valueOf(String) or fromString(String) or forString(String) method (if it has one).
private final Constructor extends T>
noArgConstructor; // The no-arg constructor for this class (if it has one).
private final Constructor
stringConstructor, // The X(String) constructor (if it has one).
numberConstructor, // The X(Number) constructor (if it has one).
swapConstructor; // The X(Swappable) constructor (if it has one).
private final Class>
swapMethodType, // The class type of the object in the number constructor.
numberConstructorType;
private final Method
swapMethod, // The swap() method (if it has one).
unswapMethod, // The unswap() method (if it has one).
exampleMethod; // The example() or @Example-annotated method (if it has one).
private final Field
exampleField; // The @Example-annotated field (if it has one).
private final Setter
namePropertyMethod, // The method to set the name on an object (if it has one).
parentPropertyMethod; // The method to set the parent on an object (if it has one).
private final boolean
isDelegate, // True if this class extends Delegate.
isAbstract, // True if this class is abstract.
isMemberClass; // True if this is a non-static member class.
private final Object primitiveDefault; // Default value for primitive type classes.
private final Map
publicMethods; // All public methods, including static methods.
private final PojoSwap,?>[] childPojoSwaps; // Any PojoSwaps where the normal type is a subclass of this class.
private final ConcurrentHashMap,PojoSwap,?>>
childSwapMap, // Maps normal subclasses to PojoSwaps.
childUnswapMap; // Maps swap subclasses to PojoSwaps.
private final PojoSwap[] pojoSwaps; // The object POJO swaps associated with this bean (if it has any).
private final BeanFilter beanFilter; // The bean filter associated with this bean (if it has one).
private final BuilderSwap builderSwap; // The builder swap associated with this bean (if it has one).
private final MetadataMap extMeta; // Extended metadata
private final BeanContext beanContext; // The bean context that created this object.
private final ClassMeta>
elementType, // If ARRAY or COLLECTION, the element class type.
keyType, // If MAP, the key class type.
valueType; // If MAP, the value class type.
private final BeanMeta beanMeta; // The bean meta for this bean class (if it's a bean).
private final String
typePropertyName, // The property name of the _type property for this class and subclasses.
notABeanReason, // If this isn't a bean, the reason why.
dictionaryName; // The dictionary name of this class if it has one.
private final Throwable initException; // Any exceptions thrown in the init() method.
private final InvocationHandler invocationHandler; // The invocation handler for this class (if it has one).
private final BeanRegistry beanRegistry; // The bean registry of this class meta (if it has one).
private final ClassMeta>[] args; // Arg types if this is an array of args.
private final Object example; // Example object.
private final Map,Transform,T>> fromTransforms = new ConcurrentHashMap<>();
private final Map,Transform> toTransforms = new ConcurrentHashMap<>();
private final Transform readerTransform;
private final Transform inputStreamTransform;
private final Transform stringTransform;
private ReadWriteLock lock = new ReentrantReadWriteLock(false);
private Lock rLock = lock.readLock(), wLock = lock.writeLock();
/**
* Construct a new {@code ClassMeta} based on the specified {@link Class}.
*
* @param innerClass The class being wrapped.
* @param beanContext The bean context that created this object.
* @param implClass
* For interfaces and abstract classes, this represents the "real" class to instantiate.
* Can be null .
* @param beanFilter
* The {@link BeanFilter} programmatically associated with this class.
* Can be null .
* @param pojoSwap
* The {@link PojoSwap} programmatically associated with this class.
* Can be null .
* @param childPojoSwap
* The child {@link PojoSwap PojoSwaps} programmatically associated with this class.
* These are the PojoSwaps
that have normal classes that are subclasses of this class.
* Can be null .
* @param delayedInit
* Don't call init() in constructor.
* Used for delayed initialization when the possibility of class reference loops exist.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
ClassMeta(Class innerClass, BeanContext beanContext, Class extends T> implClass, BeanFilter beanFilter, PojoSwap[] pojoSwaps, PojoSwap,?>[] childPojoSwaps, Object example) {
this.innerClass = innerClass;
this.beanContext = beanContext;
wLock.lock();
try {
// We always immediately add this class meta to the bean context cache so that we can resolve recursive references.
if (beanContext != null && beanContext.cmCache != null)
beanContext.cmCache.put(innerClass, this);
ClassMetaBuilder builder = new ClassMetaBuilder(innerClass, beanContext, implClass, beanFilter, pojoSwaps, childPojoSwaps, example);
this.cc = builder.cc;
this.isDelegate = builder.isDelegate;
this.fromStringMethod = builder.fromStringMethod;
this.swapMethod = builder.swapMethod;
this.unswapMethod = builder.unswapMethod;
this.swapMethodType = builder.swapMethodType;
this.parentPropertyMethod = builder.parentPropertyMethod;
this.namePropertyMethod = builder.namePropertyMethod;
this.noArgConstructor = builder.noArgConstructor;
this.stringConstructor = builder.stringConstructor;
this.swapConstructor = builder.swapConstructor;
this.numberConstructor = builder.numberConstructor;
this.numberConstructorType = builder.numberConstructorType;
this.primitiveDefault = builder.primitiveDefault;
this.publicMethods = builder.publicMethods;
this.beanFilter = beanFilter;
this.pojoSwaps = builder.pojoSwaps.isEmpty() ? null : builder.pojoSwaps.toArray(new PojoSwap[builder.pojoSwaps.size()]);
this.builderSwap = builder.builderSwap;
this.extMeta = new MetadataMap();
this.keyType = builder.keyType;
this.valueType = builder.valueType;
this.elementType = builder.elementType;
this.notABeanReason = builder.notABeanReason;
this.beanMeta = builder.beanMeta;
this.initException = builder.initException;
this.typePropertyName = builder.typePropertyName;
this.dictionaryName = builder.dictionaryName;
this.invocationHandler = builder.invocationHandler;
this.beanRegistry = builder.beanRegistry;
this.isMemberClass = builder.isMemberClass;
this.isAbstract = builder.isAbstract;
this.implClass = builder.implClass;
this.childUnswapMap = builder.childUnswapMap;
this.childSwapMap = builder.childSwapMap;
this.childPojoSwaps = builder.childPojoSwaps;
this.exampleMethod = builder.exampleMethod;
this.exampleField = builder.exampleField;
this.example = builder.example;
this.args = null;
this.readerTransform = builder.readerTransform;
this.inputStreamTransform = builder.inputStreamTransform;
this.stringTransform = builder.stringTransform;
} finally {
wLock.unlock();
}
}
/**
* Causes thread to wait until constructor has exited.
*/
final void waitForInit() {
rLock.lock();
rLock.unlock();
}
/**
* Copy constructor.
*
*
* Used for creating Map and Collection class metas that shouldn't be cached.
*/
ClassMeta(ClassMeta mainType, ClassMeta> keyType, ClassMeta> valueType, ClassMeta> elementType) {
this.innerClass = mainType.innerClass;
this.implClass = mainType.implClass;
this.childPojoSwaps = mainType.childPojoSwaps;
this.childSwapMap = mainType.childSwapMap;
this.childUnswapMap = mainType.childUnswapMap;
this.cc = mainType.cc;
this.fromStringMethod = mainType.fromStringMethod;
this.noArgConstructor = mainType.noArgConstructor;
this.stringConstructor = mainType.stringConstructor;
this.numberConstructor = mainType.numberConstructor;
this.swapConstructor = mainType.swapConstructor;
this.swapMethodType = mainType.swapMethodType;
this.numberConstructorType = mainType.numberConstructorType;
this.swapMethod = mainType.swapMethod;
this.unswapMethod = mainType.unswapMethod;
this.namePropertyMethod = mainType.namePropertyMethod;
this.parentPropertyMethod = mainType.parentPropertyMethod;
this.isDelegate = mainType.isDelegate;
this.isAbstract = mainType.isAbstract;
this.isMemberClass = mainType.isMemberClass;
this.primitiveDefault = mainType.primitiveDefault;
this.publicMethods = mainType.publicMethods;
this.beanContext = mainType.beanContext;
this.elementType = elementType;
this.keyType = keyType;
this.valueType = valueType;
this.invocationHandler = mainType.invocationHandler;
this.beanMeta = mainType.beanMeta;
this.typePropertyName = mainType.typePropertyName;
this.dictionaryName = mainType.dictionaryName;
this.notABeanReason = mainType.notABeanReason;
this.pojoSwaps = mainType.pojoSwaps;
this.builderSwap = mainType.builderSwap;
this.beanFilter = mainType.beanFilter;
this.extMeta = mainType.extMeta;
this.initException = mainType.initException;
this.beanRegistry = mainType.beanRegistry;
this.exampleMethod = mainType.exampleMethod;
this.exampleField = mainType.exampleField;
this.example = mainType.example;
this.args = null;
this.readerTransform = mainType.readerTransform;
this.inputStreamTransform = mainType.inputStreamTransform;
this.stringTransform = mainType.stringTransform;
}
/**
* Constructor for args-arrays.
*/
@SuppressWarnings("unchecked")
ClassMeta(ClassMeta>[] args) {
this.innerClass = (Class) Object[].class;
this.args = args;
this.implClass = null;
this.childPojoSwaps = null;
this.childSwapMap = null;
this.childUnswapMap = null;
this.cc = ARGS;
this.fromStringMethod = null;
this.noArgConstructor = null;
this.stringConstructor = null;
this.numberConstructor = null;
this.swapConstructor = null;
this.swapMethodType = null;
this.numberConstructorType = null;
this.swapMethod = null;
this.unswapMethod = null;
this.namePropertyMethod = null;
this.parentPropertyMethod = null;
this.isDelegate = false;
this.isAbstract = false;
this.isMemberClass = false;
this.primitiveDefault = null;
this.publicMethods = null;
this.beanContext = null;
this.elementType = null;
this.keyType = null;
this.valueType = null;
this.invocationHandler = null;
this.beanMeta = null;
this.typePropertyName = null;
this.dictionaryName = null;
this.notABeanReason = null;
this.pojoSwaps = null;
this.builderSwap = null;
this.beanFilter = null;
this.extMeta = new MetadataMap();
this.initException = null;
this.beanRegistry = null;
this.exampleMethod = null;
this.exampleField = null;
this.example = null;
this.readerTransform = null;
this.inputStreamTransform = null;
this.stringTransform = null;
}
@SuppressWarnings({"unchecked","rawtypes","hiding"})
private final class ClassMetaBuilder {
Class innerClass;
Class extends T> implClass;
BeanContext beanContext;
ClassCategory cc = ClassCategory.OTHER;
boolean
isDelegate = false,
isMemberClass = false,
isAbstract = false;
Method
fromStringMethod = null,
swapMethod = null,
unswapMethod = null;
Setter
parentPropertyMethod = null,
namePropertyMethod = null;
Constructor
noArgConstructor = null,
stringConstructor = null,
swapConstructor = null,
numberConstructor = null;
Class>
swapMethodType = null,
numberConstructorType = null;
Object primitiveDefault = null;
Map
publicMethods = new LinkedHashMap<>();
ClassMeta>
keyType = null,
valueType = null,
elementType = null;
String
typePropertyName = null,
notABeanReason = null,
dictionaryName = null;
Throwable initException = null;
BeanMeta beanMeta = null;
List pojoSwaps = new ArrayList<>();
BuilderSwap builderSwap;
InvocationHandler invocationHandler = null;
BeanRegistry beanRegistry = null;
PojoSwap,?>[] childPojoSwaps;
ConcurrentHashMap,PojoSwap,?>>
childSwapMap,
childUnswapMap;
Method exampleMethod;
Field exampleField;
Object example;
Transform readerTransform;
Transform inputStreamTransform;
Transform stringTransform;
ClassMetaBuilder(Class innerClass, BeanContext beanContext, Class extends T> implClass, BeanFilter beanFilter, PojoSwap[] pojoSwaps, PojoSwap,?>[] childPojoSwaps, Object example) {
this.innerClass = innerClass;
this.beanContext = beanContext;
this.implClass = implClass;
this.childPojoSwaps = childPojoSwaps;
this.childSwapMap = childPojoSwaps == null ? null : new ConcurrentHashMap,PojoSwap,?>>();
this.childUnswapMap = childPojoSwaps == null ? null : new ConcurrentHashMap,PojoSwap,?>>();
Class c = innerClass;
if (c.isPrimitive()) {
if (c == Boolean.TYPE)
cc = BOOLEAN;
else if (c == Byte.TYPE || c == Short.TYPE || c == Integer.TYPE || c == Long.TYPE || c == Float.TYPE || c == Double.TYPE) {
if (c == Float.TYPE || c == Double.TYPE)
cc = DECIMAL;
else
cc = NUMBER;
}
else if (c == Character.TYPE)
cc = CHAR;
else if (c == void.class || c == Void.class)
cc = VOID;
} else {
if (isParentClass(Delegate.class, c))
isDelegate = true;
if (c == Object.class)
cc = OBJ;
else if (c.isEnum())
cc = ENUM;
else if (c.equals(Class.class))
cc = CLASS;
else if (isParentClass(Method.class, c))
cc = METHOD;
else if (isParentClass(CharSequence.class, c)) {
if (c.equals(String.class))
cc = STR;
else
cc = CHARSEQ;
}
else if (isParentClass(Number.class, c)) {
if (isParentClass(Float.class, c) || isParentClass(Double.class, c))
cc = DECIMAL;
else
cc = NUMBER;
}
else if (isParentClass(Collection.class, c))
cc = COLLECTION;
else if (isParentClass(Map.class, c)) {
if (isParentClass(BeanMap.class, c))
cc = BEANMAP;
else
cc = MAP;
}
else if (c == Character.class)
cc = CHAR;
else if (c == Boolean.class)
cc = BOOLEAN;
else if (isParentClass(Date.class, c) || isParentClass(Calendar.class, c))
cc = DATE;
else if (c.isArray())
cc = ARRAY;
else if (isParentClass(URL.class, c) || isParentClass(URI.class, c) || c.isAnnotationPresent(org.apache.juneau.annotation.URI.class))
cc = URI;
else if (isParentClass(Reader.class, c))
cc = READER;
else if (isParentClass(InputStream.class, c))
cc = INPUTSTREAM;
}
isMemberClass = c.isMemberClass() && ! isStatic(c);
// Find static fromString(String) or equivalent method.
// fromString() must be checked before valueOf() so that Enum classes can create their own
// specialized fromString() methods to override the behavior of Enum.valueOf(String).
// valueOf() is used by enums.
// parse() is used by the java logging Level class.
// forName() is used by Class and Charset
for (String methodName : new String[]{"fromString","fromValue","valueOf","parse","parseString","forName","forString"}) {
if (fromStringMethod == null) {
for (Method m : c.getMethods()) {
if (isAll(m, STATIC, PUBLIC, NOT_DEPRECATED) && hasName(m, methodName) && hasReturnType(m, c) && hasArgs(m, String.class)) {
fromStringMethod = m;
break;
}
}
}
}
// TODO - should use transforms for above code.
// Special cases
try {
if (c == TimeZone.class)
fromStringMethod = c.getMethod("getTimeZone", String.class);
else if (c == Locale.class)
fromStringMethod = LocaleAsString.class.getMethod("fromString", String.class);
} catch (NoSuchMethodException e1) {}
// Find swap() method if present.
for (Method m : c.getMethods()) {
if (isAll(m, PUBLIC, NOT_DEPRECATED, NOT_STATIC) && hasName(m, "swap") && hasFuzzyArgs(m, BeanSession.class)) {
swapMethod = m;
swapMethodType = m.getReturnType();
break;
}
}
// Find unswap() method if present.
if (swapMethod != null) {
for (Method m : c.getMethods()) {
if (isAll(m, PUBLIC, NOT_DEPRECATED, STATIC) &&hasName(m, "unswap") && hasFuzzyArgs(m, BeanSession.class, swapMethodType)) {
unswapMethod = m;
break;
}
}
}
// Find example() method if present.
for (Method m : c.getMethods()) {
if (isAll(m, PUBLIC, NOT_DEPRECATED, STATIC) && hasName(m, "example") && hasFuzzyArgs(m, BeanSession.class)) {
exampleMethod = m;
break;
}
}
for (Field f : getAllFields(c, true)) {
if (f.isAnnotationPresent(ParentProperty.class)) {
if (isStatic(f))
throw new ClassMetaRuntimeException("@ParentProperty used on invalid field ''{0}''", f);
setAccessible(f, false);
parentPropertyMethod = new Setter.FieldSetter(f);
}
if (f.isAnnotationPresent(NameProperty.class)) {
if (isStatic(f))
throw new ClassMetaRuntimeException("@NameProperty used on invalid field ''{0}''", f);
setAccessible(f, false);
namePropertyMethod = new Setter.FieldSetter(f);
}
}
for (Field f : c.getDeclaredFields()) {
if (f.isAnnotationPresent(Example.class)) {
if (! (isStatic(f) && isParentClass(innerClass, f.getType())))
throw new ClassMetaRuntimeException("@Example used on invalid field ''{0}''", f);
setAccessible(f, false);
exampleField = f;
}
}
// Find @NameProperty and @ParentProperty methods if present.
for (Method m : getAllMethods(c, true)) {
if (m.isAnnotationPresent(ParentProperty.class)) {
if (isStatic(m) || ! hasNumArgs(m, 1))
throw new ClassMetaRuntimeException("@ParentProperty used on invalid method ''{0}''", m);
setAccessible(m, false);
parentPropertyMethod = new Setter.MethodSetter(m);
}
if (m.isAnnotationPresent(NameProperty.class)) {
if (isStatic(m) || ! hasNumArgs(m, 1))
throw new ClassMetaRuntimeException("@NameProperty used on invalid method ''{0}''", m);
setAccessible(m, false);
namePropertyMethod = new Setter.MethodSetter(m);
}
}
for (Method m : c.getDeclaredMethods()) {
if (m.isAnnotationPresent(Example.class)) {
if (! (isStatic(m) && hasFuzzyArgs(m, BeanSession.class) && isParentClass(innerClass, m.getReturnType())))
throw new ClassMetaRuntimeException("@Example used on invalid method ''{0}''", m);
setAccessible(m, false);
exampleMethod = m;
}
}
// Note: Primitive types are normally abstract.
isAbstract = ClassUtils.isAbstract(c) && ! c.isPrimitive();
// Find constructor(String) method if present.
for (Constructor cs : c.getConstructors()) {
if (isPublic(cs) && isNotDeprecated(cs)) {
Class>[] pt = cs.getParameterTypes();
if (pt.length == (isMemberClass ? 1 : 0) && c != Object.class && ! isAbstract) {
noArgConstructor = cs;
} else if (pt.length == (isMemberClass ? 2 : 1)) {
Class> arg = pt[(isMemberClass ? 1 : 0)];
if (arg == String.class)
stringConstructor = cs;
else if (swapMethodType != null && swapMethodType.isAssignableFrom(arg))
swapConstructor = cs;
else if (cc != NUMBER && (Number.class.isAssignableFrom(arg) || (arg.isPrimitive() && (arg == int.class || arg == short.class || arg == long.class || arg == float.class || arg == double.class)))) {
numberConstructor = cs;
numberConstructorType = getWrapperIfPrimitive(arg);
}
}
}
}
primitiveDefault = ClassUtils.getPrimitiveDefault(c);
for (Method m : c.getMethods())
if (isAll(m, PUBLIC, NOT_DEPRECATED))
publicMethods.put(getMethodSignature(m), m);
if (innerClass != Object.class) {
noArgConstructor = (Constructor)findNoArgConstructor(implClass == null ? innerClass : implClass, Visibility.PUBLIC);
}
if (beanFilter == null)
beanFilter = findBeanFilter();
if (swapMethod != null) {
final Method fSwapMethod = swapMethod;
final Method fUnswapMethod = unswapMethod;
final Constructor fSwapConstructor = swapConstructor;
this.pojoSwaps.add(
new PojoSwap(c, swapMethod.getReturnType()) {
@Override
public Object swap(BeanSession session, Object o) throws SerializeException {
try {
return fSwapMethod.invoke(o, getMatchingArgs(fSwapMethod.getParameterTypes(), session));
} catch (Exception e) {
throw new SerializeException(e);
}
}
@Override
public T unswap(BeanSession session, Object f, ClassMeta> hint) throws ParseException {
try {
if (fUnswapMethod != null)
return (T)fUnswapMethod.invoke(null, getMatchingArgs(fSwapMethod.getParameterTypes(), session, f));
if (fSwapConstructor != null)
return fSwapConstructor.newInstance(f);
return super.unswap(session, f, hint);
} catch (Exception e) {
throw new ParseException(e);
}
}
}
);
}
if (pojoSwaps != null)
this.pojoSwaps.addAll(Arrays.asList(pojoSwaps));
if (beanContext != null)
this.builderSwap = BuilderSwap.findSwapFromPojoClass(c, beanContext.getBeanConstructorVisibility(), beanContext.getBeanMethodVisibility());
findPojoSwaps(this.pojoSwaps);
try {
// If this is an array, get the element type.
if (cc == ARRAY)
elementType = findClassMeta(innerClass.getComponentType());
// If this is a MAP, see if it's parameterized (e.g. AddressBook extends HashMap)
else if (cc == MAP) {
ClassMeta[] parameters = findParameters();
if (parameters != null && parameters.length == 2) {
keyType = parameters[0];
valueType = parameters[1];
} else {
keyType = findClassMeta(Object.class);
valueType = findClassMeta(Object.class);
}
}
// If this is a COLLECTION, see if it's parameterized (e.g. AddressBook extends LinkedList)
else if (cc == COLLECTION) {
ClassMeta[] parameters = findParameters();
if (parameters != null && parameters.length == 1) {
elementType = parameters[0];
} else {
elementType = findClassMeta(Object.class);
}
}
// If the category is unknown, see if it's a bean.
// Note that this needs to be done after all other initialization has been done.
else if (cc == OTHER) {
BeanMeta newMeta = null;
try {
newMeta = new BeanMeta(ClassMeta.this, beanContext, beanFilter, null);
notABeanReason = newMeta.notABeanReason;
// Always get these even if it's not a bean:
beanRegistry = newMeta.beanRegistry;
typePropertyName = newMeta.typePropertyName;
} catch (RuntimeException e) {
notABeanReason = e.getMessage();
throw e;
}
if (notABeanReason == null)
beanMeta = newMeta;
}
} catch (NoClassDefFoundError e) {
initException = e;
} catch (RuntimeException e) {
initException = e;
throw e;
}
if (beanMeta != null)
dictionaryName = beanMeta.getDictionaryName();
if (beanMeta != null && beanContext != null && beanContext.isUseInterfaceProxies() && innerClass.isInterface())
invocationHandler = new BeanProxyInvocationHandler(beanMeta);
Bean b = c.getAnnotation(Bean.class);
if (b != null) {
if (b.beanDictionary().length != 0)
beanRegistry = new BeanRegistry(beanContext, null, b.beanDictionary());
// This could be a non-bean POJO with a type name.
if (dictionaryName == null && ! b.typeName().isEmpty())
dictionaryName = b.typeName();
}
Example e = c.getAnnotation(Example.class);
if (example == null && e != null && ! e.value().isEmpty())
example = e.value();
if (example == null) {
switch(cc) {
case BOOLEAN:
example = true;
break;
case CHAR:
example = 'a';
break;
case CHARSEQ:
case STR:
example = "foo";
break;
case DECIMAL:
if (isFloat())
example = new Float(1f);
else if (isDouble())
example = new Double(1d);
break;
case ENUM:
Iterator extends Enum> i = EnumSet.allOf((Class extends Enum>)c).iterator();
if (i.hasNext())
example = i.next();
break;
case NUMBER:
if (isShort())
example = new Short((short)1);
else if (isInteger())
example = new Integer(1);
else if (isLong())
example = new Long(1l);
break;
case URI:
case ARGS:
case ARRAY:
case BEANMAP:
case CLASS:
case COLLECTION:
case DATE:
case INPUTSTREAM:
case MAP:
case METHOD:
case OBJ:
case OTHER:
case READER:
case VOID:
break;
}
}
this.example = example;
this.readerTransform = TransformCache.get(Reader.class, c);
this.inputStreamTransform = TransformCache.get(InputStream.class, c);
this.stringTransform = TransformCache.get(String.class, c);
}
private BeanFilter findBeanFilter() {
try {
Map,Bean> ba = getAnnotationsMap(Bean.class, innerClass);
if (! ba.isEmpty())
return new AnnotationBeanFilterBuilder(innerClass, ba).build();
} catch (Exception e) {
throw new RuntimeException(e);
}
return null;
}
private void findPojoSwaps(List l) {
Swap swap = innerClass.getAnnotation(Swap.class);
if (swap != null)
l.add(createPojoSwap(swap));
Swaps swaps = innerClass.getAnnotation(Swaps.class);
if (swaps != null)
for (Swap s : swaps.value())
l.add(createPojoSwap(s));
}
private PojoSwap createPojoSwap(Swap s) {
Class> c = s.value();
if (c == Null.class)
c = s.impl();
if (isParentClass(PojoSwap.class, c)) {
PojoSwap ps = beanContext.newInstance(PojoSwap.class, c);
if (s.mediaTypes().length > 0)
ps.forMediaTypes(MediaType.forStrings(s.mediaTypes()));
if (! s.template().isEmpty())
ps.withTemplate(s.template());
return ps;
}
if (isParentClass(Surrogate.class, c)) {
List> l = SurrogateSwap.findPojoSwaps(c);
if (! l.isEmpty())
return (PojoSwap)l.iterator().next();
}
throw new ClassMetaRuntimeException("Invalid swap class ''{0}'' specified. Must extend from PojoSwap or Surrogate.", c);
}
private ClassMeta> findClassMeta(Class> c) {
return beanContext.getClassMeta(c, false);
}
private ClassMeta>[] findParameters() {
return beanContext.findParameters(innerClass, innerClass);
}
}
/**
* Returns the type property name associated with this class and subclasses.
*
*
* If null , "_type" should be assumed.
*
* @return
* The type property name associated with this bean class, or null if there is no explicit type
* property name defined or this isn't a bean.
*/
public String getBeanTypePropertyName() {
return typePropertyName;
}
/**
* Returns the bean dictionary name associated with this class.
*
*
* The lexical name is defined by {@link Bean#typeName() @Bean(typeName)}.
*
* @return
* The type name associated with this bean class, or null if there is no type name defined or this
* isn't a bean.
*/
public String getDictionaryName() {
return dictionaryName;
}
/**
* Returns the bean registry for this class.
*
*
* This bean registry contains names specified in the {@link Bean#beanDictionary() @Bean(beanDictionary)} annotation
* defined on the class, regardless of whether the class is an actual bean.
* This allows interfaces to define subclasses with type names.
*
* @return The bean registry for this class, or null if no bean registry is associated with it.
*/
public BeanRegistry getBeanRegistry() {
return beanRegistry;
}
/**
* Returns the category of this class.
*
* @return The category of this class.
*/
public ClassCategory getClassCategory() {
return cc;
}
/**
* Returns true if this class is a superclass of or the same as the specified class.
*
* @param c The comparison class.
* @return true if this class is a superclass of or the same as the specified class.
*/
public boolean isAssignableFrom(Class> c) {
return isParentClass(innerClass, c);
}
/**
* Returns true if this class is a subclass of or the same as the specified class.
*
* @param c The comparison class.
* @return true if this class is a subclass of or the same as the specified class.
*/
public boolean isInstanceOf(Class> c) {
return isParentClass(c, innerClass);
}
/**
* Returns true if this class or any child classes has a {@link PojoSwap} associated with it.
*
*
* Used when transforming bean properties to prevent having to look up transforms if we know for certain that no
* transforms are associated with a bean property.
*
* @return true if this class or any child classes has a {@link PojoSwap} associated with it.
*/
protected boolean hasChildPojoSwaps() {
return childPojoSwaps != null;
}
/**
* Returns the {@link PojoSwap} where the specified class is the same/subclass of the normal class of one of the
* child POJO swaps associated with this class.
*
* @param normalClass The normal class being resolved.
* @return The resolved {@link PojoSwap} or null if none were found.
*/
protected PojoSwap,?> getChildPojoSwapForSwap(Class> normalClass) {
if (childSwapMap != null) {
PojoSwap,?> s = childSwapMap.get(normalClass);
if (s == null) {
for (PojoSwap,?> f : childPojoSwaps)
if (s == null && isParentClass(f.getNormalClass(), normalClass))
s = f;
if (s == null)
s = PojoSwap.NULL;
PojoSwap,?> s2 = childSwapMap.putIfAbsent(normalClass, s);
if (s2 != null)
s = s2;
}
if (s == PojoSwap.NULL)
return null;
return s;
}
return null;
}
/**
* Returns the {@link PojoSwap} where the specified class is the same/subclass of the swap class of one of the child
* POJO swaps associated with this class.
*
* @param swapClass The swap class being resolved.
* @return The resolved {@link PojoSwap} or null if none were found.
*/
protected PojoSwap,?> getChildPojoSwapForUnswap(Class> swapClass) {
if (childUnswapMap != null) {
PojoSwap,?> s = childUnswapMap.get(swapClass);
if (s == null) {
for (PojoSwap,?> f : childPojoSwaps)
if (s == null && isParentClass(f.getSwapClass(), swapClass))
s = f;
if (s == null)
s = PojoSwap.NULL;
PojoSwap,?> s2 = childUnswapMap.putIfAbsent(swapClass, s);
if (s2 != null)
s = s2;
}
if (s == PojoSwap.NULL)
return null;
return s;
}
return null;
}
/**
* Locates the no-arg constructor for the specified class.
*
*
* Constructor must match the visibility requirements specified by parameter 'v'.
* If class is abstract, always returns null .
* Note that this also returns the 1-arg constructor for non-static member classes.
*
* @param c The class from which to locate the no-arg constructor.
* @param v The minimum visibility.
* @return The constructor, or null if no no-arg constructor exists with the required visibility.
*/
@SuppressWarnings({"rawtypes","unchecked"})
protected static Constructor extends T> findNoArgConstructor(Class> c, Visibility v) {
if (ClassUtils.isAbstract(c))
return null;
boolean isMemberClass = c.isMemberClass() && ! isStatic(c);
for (Constructor cc : c.getConstructors()) {
if (hasNumArgs(cc, isMemberClass ? 1 : 0) && v.isVisible(cc.getModifiers()) && isNotDeprecated(cc))
return v.transform(cc);
}
return null;
}
/**
* Returns the {@link Class} object that this class type wraps.
*
* @return The wrapped class object.
*/
public Class getInnerClass() {
return innerClass;
}
/**
* Returns the serialized (swapped) form of this class if there is an {@link PojoSwap} associated with it.
*
* @param session
* The bean session.
*
Required because the swap used may depend on the media type being serialized or parsed.
* @return The serialized class type, or this object if no swap is associated with the class.
*/
@BeanIgnore
public ClassMeta> getSerializedClassMeta(BeanSession session) {
PojoSwap ps = getPojoSwap(session);
return (ps == null ? this : ps.getSwapClassMeta(session));
}
/**
* Returns the example of this class.
*
* @param session
* The bean session.
*
Required because the example method may take it in as a parameter.
* @return The serialized class type, or this object if no swap is associated with the class.
*/
@SuppressWarnings({"unchecked","rawtypes"})
@BeanIgnore
public T getExample(BeanSession session) {
try {
if (example != null) {
if (isInstance(example))
return (T)example;
if (example instanceof String) {
if (isCharSequence())
return (T)example;
String s = example.toString();
if (isMapOrBean() && StringUtils.isObjectMap(s, false))
return JsonParser.DEFAULT.parse(s, this);
if (isCollectionOrArray() && StringUtils.isObjectList(s, false))
return JsonParser.DEFAULT.parse(s, this);
}
if (example instanceof Map && isMapOrBean()) {
return JsonParser.DEFAULT.parse(SimpleJsonSerializer.DEFAULT_READABLE.toString(example), this);
}
if (example instanceof Collection && isCollectionOrArray()) {
return JsonParser.DEFAULT.parse(SimpleJsonSerializer.DEFAULT_READABLE.serialize(example), this);
}
}
if (exampleMethod != null)
return (T)invokeMethodFuzzy(exampleMethod, null, session);
if (exampleField != null)
return (T)exampleField.get(null);
if (isCollection()) {
Object etExample = getElementType().getExample(session);
if (etExample != null) {
if (canCreateNewInstance()) {
Collection c = (Collection)newInstance();
c.add(etExample);
return (T)c;
}
return (T)Collections.singleton(etExample);
}
} else if (isArray()) {
Object etExample = getElementType().getExample(session);
if (etExample != null) {
Object o = Array.newInstance(getElementType().innerClass, 1);
Array.set(o, 0, etExample);
return (T)o;
}
} else if (isMap()) {
Object vtExample = getValueType().getExample(session);
Object ktExample = getKeyType().getExample(session);
if (ktExample != null && vtExample != null) {
if (canCreateNewInstance()) {
Map m = (Map)newInstance();
m.put(ktExample, vtExample);
return (T)m;
}
return (T)Collections.singletonMap(ktExample, vtExample);
}
}
return null;
} catch (Exception e) {
throw new ClassMetaRuntimeException(e);
}
}
/**
* For array and {@code Collection} types, returns the class type of the components of the array or
* {@code Collection}.
*
* @return The element class type, or null if this class is not an array or Collection.
*/
public ClassMeta> getElementType() {
return elementType;
}
/**
* For {@code Map} types, returns the class type of the keys of the {@code Map}.
*
* @return The key class type, or null if this class is not a Map.
*/
public ClassMeta> getKeyType() {
return keyType;
}
/**
* For {@code Map} types, returns the class type of the values of the {@code Map}.
*
* @return The value class type, or null if this class is not a Map.
*/
public ClassMeta> getValueType() {
return valueType;
}
/**
* Returns true if this class implements {@link Delegate}, meaning it's a representation of some other
* object.
*
* @return true if this class implements {@link Delegate}.
*/
public boolean isDelegate() {
return isDelegate;
}
/**
* Returns true if this class is a subclass of {@link Map}.
*
* @return true if this class is a subclass of {@link Map}.
*/
public boolean isMap() {
return cc == MAP || cc == BEANMAP;
}
/**
* Returns true if this class is a subclass of {@link Map} or it's a bean.
*
* @return true if this class is a subclass of {@link Map} or it's a bean.
*/
public boolean isMapOrBean() {
return cc == MAP || cc == BEANMAP || beanMeta != null;
}
/**
* Returns true if this class is a subclass of {@link BeanMap}.
*
* @return true if this class is a subclass of {@link BeanMap}.
*/
public boolean isBeanMap() {
return cc == BEANMAP;
}
/**
* Returns true if this class is a subclass of {@link Collection}.
*
* @return true if this class is a subclass of {@link Collection}.
*/
public boolean isCollection() {
return cc == COLLECTION;
}
/**
* Returns true if this class is a subclass of {@link Collection} or is an array.
*
* @return true if this class is a subclass of {@link Collection} or is an array.
*/
public boolean isCollectionOrArray() {
return cc == COLLECTION || cc == ARRAY;
}
/**
* Returns true if this class extends from {@link Set}.
*
* @return true if this class extends from {@link Set}.
*/
public boolean isSet() {
return cc == COLLECTION && isParentClass(Set.class, innerClass);
}
/**
* Returns true if this class extends from {@link List}.
*
* @return true if this class extends from {@link List}.
*/
public boolean isList() {
return cc == COLLECTION && isParentClass(List.class, innerClass);
}
/**
* Returns true if this class is byte []
.
*
* @return true if this class is byte []
.
*/
public boolean isByteArray() {
return cc == ARRAY && this.innerClass == byte[].class;
}
/**
* Returns true if this class is {@link Class}.
*
* @return true if this class is {@link Class}.
*/
public boolean isClass() {
return cc == CLASS;
}
/**
* Returns true if this class is {@link Method}.
*
* @return true if this class is {@link Method}.
*/
public boolean isMethod() {
return cc == METHOD;
}
/**
* Returns true if this class is an {@link Enum}.
*
* @return true if this class is an {@link Enum}.
*/
public boolean isEnum() {
return cc == ENUM;
}
/**
* Returns true if this class is an array.
*
* @return true if this class is an array.
*/
public boolean isArray() {
return cc == ARRAY;
}
/**
* Returns true if this class is a bean.
*
* @return true if this class is a bean.
*/
public boolean isBean() {
return beanMeta != null;
}
/**
* Returns true if this class is {@link Object}.
*
* @return true if this class is {@link Object}.
*/
public boolean isObject() {
return cc == OBJ;
}
/**
* Returns true if this class is not {@link Object}.
*
* @return true if this class is not {@link Object}.
*/
public boolean isNotObject() {
return cc != OBJ;
}
/**
* Returns true if this class is a subclass of {@link Number}.
*
* @return true if this class is a subclass of {@link Number}.
*/
public boolean isNumber() {
return cc == NUMBER || cc == DECIMAL;
}
/**
* Returns true if this class is a subclass of {@link Float} or {@link Double}.
*
* @return true if this class is a subclass of {@link Float} or {@link Double}.
*/
public boolean isDecimal() {
return cc == DECIMAL;
}
/**
* Returns true if this class is either {@link Float} or float .
*
* @return true if this class is either {@link Float} or float .
*/
public boolean isFloat() {
return innerClass == Float.class || innerClass == float.class;
}
/**
* Returns true if this class is either {@link Double} or double .
*
* @return true if this class is either {@link Double} or double .
*/
public boolean isDouble() {
return innerClass == Double.class || innerClass == double.class;
}
/**
* Returns true if this class is either {@link Short} or short .
*
* @return true if this class is either {@link Short} or short .
*/
public boolean isShort() {
return innerClass == Short.class || innerClass == short.class;
}
/**
* Returns true if this class is either {@link Integer} or int .
*
* @return true if this class is either {@link Integer} or int .
*/
public boolean isInteger() {
return innerClass == Integer.class || innerClass == int.class;
}
/**
* Returns true if this class is either {@link Long} or long .
*
* @return true if this class is either {@link Long} or long .
*/
public boolean isLong() {
return innerClass == Long.class || innerClass == long.class;
}
/**
* Returns true if this metadata represents the specified type.
*
* @param c The class to test against.
* @return true if this metadata represents the specified type.
*/
public boolean isType(Class> c) {
return isParentClass(c, innerClass);
}
/**
* Returns true if this class is a {@link Boolean}.
*
* @return true if this class is a {@link Boolean}.
*/
public boolean isBoolean() {
return cc == BOOLEAN;
}
/**
* Returns true if this class is a subclass of {@link CharSequence}.
*
* @return true if this class is a subclass of {@link CharSequence}.
*/
public boolean isCharSequence() {
return cc == STR || cc == CHARSEQ;
}
/**
* Returns true if this class is a {@link String}.
*
* @return true if this class is a {@link String}.
*/
public boolean isString() {
return cc == STR;
}
/**
* Returns true if this class is a {@link Character}.
*
* @return true if this class is a {@link Character}.
*/
public boolean isChar() {
return cc == CHAR;
}
/**
* Returns true if this class is a primitive.
*
* @return true if this class is a primitive.
*/
public boolean isPrimitive() {
return innerClass.isPrimitive();
}
/**
* Returns true if this class is a {@link Date} or {@link Calendar}.
*
* @return true if this class is a {@link Date} or {@link Calendar}.
*/
public boolean isDateOrCalendar() {
return cc == DATE;
}
/**
* Returns true if this class is a {@link Date}.
*
* @return true if this class is a {@link Date}.
*/
public boolean isDate() {
return cc == DATE && ClassUtils.isParentClass(Date.class, innerClass);
}
/**
* Returns true if this class is a {@link Calendar}.
*
* @return true if this class is a {@link Calendar}.
*/
public boolean isCalendar() {
return cc == DATE && ClassUtils.isParentClass(Calendar.class, innerClass);
}
/**
* Returns true if this class is a {@link URI} or {@link URL}.
*
* @return true if this class is a {@link URI} or {@link URL}.
*/
public boolean isUri() {
return cc == URI;
}
/**
* Returns true if this class is a {@link Reader}.
*
* @return true if this class is a {@link Reader}.
*/
public boolean isReader() {
return cc == READER;
}
/**
* Returns true if this class is an {@link InputStream}.
*
* @return true if this class is an {@link InputStream}.
*/
public boolean isInputStream() {
return cc == INPUTSTREAM;
}
/**
* Returns true if this class is {@link Void} or void .
*
* @return true if this class is {@link Void} or void .
*/
public boolean isVoid() {
return cc == VOID;
}
/**
* Returns true if this metadata represents an array of argument types.
*
* @return true if this metadata represents an array of argument types.
*/
public boolean isArgs() {
return cc == ARGS;
}
/**
* Returns the argument types of this meta.
*
* @return The argument types of this meta, or null if this isn't an array of argument types.
*/
public ClassMeta>[] getArgs() {
return args;
}
/**
* Returns the argument metadata at the specified index if this is an args metadata object.
*
* @param index The argument index.
* @return The The argument metadata. Never null .
* @throws BeanRuntimeException If this metadata object is not a list of arguments, or the index is out of range.
*/
public ClassMeta> getArg(int index) {
if (args != null && index >= 0 && index < args.length)
return args[index];
throw new BeanRuntimeException("Invalid argument index specified: {0}. Only {1} arguments are defined.", index, args == null ? 0 : args.length);
}
/**
* Returns true if instance of this object can be null .
*
*
* Objects can be null , but primitives cannot, except for chars which can be represented by
* (char )0
.
*
* @return true if instance of this class can be null.
*/
public boolean isNullable() {
if (innerClass.isPrimitive())
return cc == CHAR;
return true;
}
/**
* Returns true if this class is abstract.
*
* @return true if this class is abstract.
*/
public boolean isAbstract() {
return isAbstract;
}
/**
* Returns true if this class is an inner class.
*
* @return true if this class is an inner class.
*/
public boolean isMemberClass() {
return isMemberClass;
}
/**
* All public methods on this class including static methods.
*
*
* Keys are method signatures.
*
* @return The public methods on this class.
*/
public Map getPublicMethods() {
return publicMethods;
}
/**
* Returns the {@link PojoSwap} associated with this class that's the best match for the specified session.
*
* @param session
* The current bean session.
*
If multiple swaps are associated with a class, only the first one with a matching media type will
* be returned.
* @return
* The {@link PojoSwap} associated with this class, or null if there are no POJO swaps associated with
* this class.
*/
public PojoSwap getPojoSwap(BeanSession session) {
if (pojoSwaps != null) {
int matchQuant = 0, matchIndex = -1;
for (int i = 0; i < pojoSwaps.length; i++) {
int q = pojoSwaps[i].match(session);
if (q > matchQuant) {
matchQuant = q;
matchIndex = i;
}
}
if (matchIndex > -1)
return pojoSwaps[matchIndex];
}
return null;
}
/**
* Returns the builder swap associated with this class.
*
* @param session The current bean session.
* @return The builder swap associated with this class, or null if it doesn't exist.
*/
public BuilderSwap getBuilderSwap(BeanSession session) {
return builderSwap;
}
/**
* Returns the {@link BeanMeta} associated with this class.
*
* @return
* The {@link BeanMeta} associated with this class, or null if there is no bean meta associated with
* this class.
*/
public BeanMeta getBeanMeta() {
return beanMeta;
}
/**
* Returns the no-arg constructor for this class.
*
* @return The no-arg constructor for this class, or null if it does not exist.
*/
public Constructor extends T> getConstructor() {
return noArgConstructor;
}
/**
* Returns the language-specified extended metadata on this class.
*
* @param c The name of the metadata class to create.
* @return Extended metadata on this class. Never null .
*/
public M getExtendedMeta(Class c) {
return extMeta.get(c, this);
}
/**
* Returns the interface proxy invocation handler for this class.
*
* @return The interface proxy invocation handler, or null if it does not exist.
*/
public InvocationHandler getProxyInvocationHandler() {
return invocationHandler;
}
/**
* Returns true if this class has a no-arg constructor or invocation handler.
*
* @return true if a new instance of this class can be constructed.
*/
public boolean canCreateNewInstance() {
if (isMemberClass)
return false;
if (noArgConstructor != null)
return true;
if (getProxyInvocationHandler() != null)
return true;
if (isArray() && elementType.canCreateNewInstance())
return true;
return false;
}
/**
* Returns true if this class has a no-arg constructor or invocation handler.
* Returns false if this is a non-static member class and the outer object does not match the class type of
* the defining class.
*
* @param outer
* The outer class object for non-static member classes. Can be null for non-member or static classes.
* @return
* true if a new instance of this class can be created within the context of the specified outer object.
*/
public boolean canCreateNewInstance(Object outer) {
if (isMemberClass)
return outer != null && noArgConstructor != null && hasArgs(noArgConstructor, outer.getClass());
return canCreateNewInstance();
}
/**
* Returns true if this class can be instantiated as a bean.
* Returns false if this is a non-static member class and the outer object does not match the class type of
* the defining class.
*
* @param outer
* The outer class object for non-static member classes. Can be null for non-member or static classes.
* @return
* true if a new instance of this bean can be created within the context of the specified outer object.
*/
public boolean canCreateNewBean(Object outer) {
if (beanMeta == null)
return false;
if (beanMeta.constructor == null)
return false;
if (isMemberClass)
return outer != null && hasArgs(beanMeta.constructor, outer.getClass());
return true;
}
/**
* Returns true if this class can call the {@link #newInstanceFromString(Object, String)} method.
*
* @param outer
* The outer class object for non-static member classes.
* Can be null for non-member or static classes.
* @return true if this class has a no-arg constructor or invocation handler.
*/
public boolean canCreateNewInstanceFromString(Object outer) {
if (fromStringMethod != null)
return true;
if (stringConstructor != null) {
if (isMemberClass)
return outer != null && hasArgs(stringConstructor, outer.getClass(), String.class);
return true;
}
return false;
}
/**
* Returns true if this class can call the {@link #newInstanceFromString(Object, String)} method.
*
* @param outer
* The outer class object for non-static member classes.
* Can be null for non-member or static classes.
* @return true if this class has a no-arg constructor or invocation handler.
*/
public boolean canCreateNewInstanceFromNumber(Object outer) {
if (numberConstructor != null) {
if (isMemberClass)
return outer != null && hasArgs(numberConstructor, outer.getClass());
return true;
}
return false;
}
/**
* Returns the class type of the parameter of the numeric constructor.
*
* @return The class type of the numeric constructor, or null if no such constructor exists.
*/
@SuppressWarnings("unchecked")
public Class extends Number> getNewInstanceFromNumberClass() {
return (Class extends Number>) numberConstructorType;
}
/**
* Returns the method or field annotated with {@link NameProperty @NameProperty}.
*
* @return
* The method or field annotated with {@link NameProperty @NameProperty} or null if method does not
* exist.
*/
public Setter getNameProperty() {
return namePropertyMethod;
}
/**
* Returns the method or field annotated with {@link ParentProperty @ParentProperty}.
*
* @return
* The method or field annotated with {@link ParentProperty @ParentProperty} or null if method does not
* exist.
*/
public Setter getParentProperty() {
return parentPropertyMethod;
}
/**
* Returns the reason why this class is not a bean, or null if it is a bean.
*
* @return The reason why this class is not a bean, or null if it is a bean.
*/
public synchronized String getNotABeanReason() {
return notABeanReason;
}
/**
* Returns any exception that was throw in the init()
method.
*
* @return The cached exception.
*/
public Throwable getInitException() {
return initException;
}
/**
* Returns the {@link BeanContext} that created this object.
*
* @return The bean context.
*/
public BeanContext getBeanContext() {
return beanContext;
}
/**
* Returns the default value for primitives such as int or Integer .
*
* @return The default value, or null if this class type is not a primitive.
*/
@SuppressWarnings("unchecked")
public T getPrimitiveDefault() {
return (T)primitiveDefault;
}
/**
* Converts the specified object to a string.
*
* @param t The object to convert.
* @return The object converted to a string, or null if the object was null.
*/
public String toString(Object t) {
if (t == null)
return null;
if (isEnum() && beanContext.isUseEnumNames())
return ((Enum>)t).name();
return t.toString();
}
/**
* Create a new instance of the main class of this declared type from a String
input.
*
*
* In order to use this method, the class must have one of the following methods:
*
* public static T valueOf(String in);
* public static T fromString(String in);
* public T(String in);
*
*
* @param outer
* The outer class object for non-static member classes. Can be null for non-member or static classes.
* @param arg The input argument value.
* @return A new instance of the object, or null if there is no string constructor on the object.
* @throws IllegalAccessException
* If the Constructor
object enforces Java language access control and the underlying constructor is
* inaccessible.
* @throws IllegalArgumentException If the parameter type on the method was invalid.
* @throws InstantiationException
* If the class that declares the underlying constructor represents an abstract class, or does not have one of
* the methods described above.
* @throws InvocationTargetException If the underlying constructor throws an exception.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public T newInstanceFromString(Object outer, String arg) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, InstantiationException {
if (isEnum() && beanContext.isUseEnumNames())
return (T)Enum.valueOf((Class extends Enum>)this.innerClass, arg);
Method m = fromStringMethod;
if (m != null)
return (T)m.invoke(null, arg);
Constructor c = stringConstructor;
if (c != null) {
if (isMemberClass)
return c.newInstance(outer, arg);
return c.newInstance(arg);
}
throw new InstantiationError("No string constructor or valueOf(String) method found for class '"+getInnerClass().getName()+"'");
}
/**
* Create a new instance of the main class of this declared type from a Number
input.
*
*
* In order to use this method, the class must have one of the following methods:
*
* public T(Number in);
*
*
* @param session The current bean session.
* @param outer
* The outer class object for non-static member classes.
* Can be null for non-member or static classes.
* @param arg The input argument value.
* @return A new instance of the object, or null if there is no numeric constructor on the object.
* @throws IllegalAccessException
* If the Constructor
object enforces Java language access control and the underlying constructor is
* inaccessible.
* @throws IllegalArgumentException If the parameter type on the method was invalid.
* @throws InstantiationException
* If the class that declares the underlying constructor represents an abstract class, or does not have one of
* the methods described above.
* @throws InvocationTargetException If the underlying constructor throws an exception.
*/
public T newInstanceFromNumber(BeanSession session, Object outer, Number arg) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor c = numberConstructor;
if (c != null) {
Object arg2 = session.convertToType(arg, numberConstructor.getParameterTypes()[0]);
if (isMemberClass)
return c.newInstance(outer, arg2);
return c.newInstance(arg2);
}
throw new InstantiationError("No string constructor or valueOf(Number) method found for class '"+getInnerClass().getName()+"'");
}
/**
* Create a new instance of the main class of this declared type.
*
* @return A new instance of the object, or null if there is no no-arg constructor on the object.
* @throws IllegalAccessException
* If the Constructor
object enforces Java language access control and the underlying constructor is
* inaccessible.
* @throws IllegalArgumentException
* If one of the following occurs:
*
* -
* The number of actual and formal parameters differ.
*
-
* An unwrapping conversion for primitive arguments fails.
*
-
* A parameter value cannot be converted to the corresponding formal parameter type by a method invocation
* conversion.
*
-
* The constructor pertains to an enum type.
*
* @throws InstantiationException If the class that declares the underlying constructor represents an abstract class.
* @throws InvocationTargetException If the underlying constructor throws an exception.
*/
@SuppressWarnings("unchecked")
public T newInstance() throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
if (isArray())
return (T)Array.newInstance(getInnerClass().getComponentType(), 0);
Constructor extends T> c = getConstructor();
if (c != null)
return c.newInstance((Object[])null);
InvocationHandler h = getProxyInvocationHandler();
if (h != null)
return (T)Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] { getInnerClass(), java.io.Serializable.class }, h);
if (isArray())
return (T)Array.newInstance(this.elementType.innerClass,0);
return null;
}
/**
* Same as {@link #newInstance()} except for instantiating non-static member classes.
*
* @param outer
* The instance of the owning object of the member class instance.
* Can be null if instantiating a non-member or static class.
* @return A new instance of the object, or null if there is no no-arg constructor on the object.
* @throws IllegalAccessException
* If the Constructor
object enforces Java language access control and the underlying constructor is
* inaccessible.
* @throws IllegalArgumentException
* If one of the following occurs:
*
* -
* The number of actual and formal parameters differ.
*
-
* An unwrapping conversion for primitive arguments fails.
*
-
* A parameter value cannot be converted to the corresponding formal parameter type by a method invocation
* conversion.
*
-
* The constructor pertains to an enum type.
*
* @throws InstantiationException If the class that declares the underlying constructor represents an abstract class.
* @throws InvocationTargetException If the underlying constructor throws an exception.
*/
public T newInstance(Object outer) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
if (isMemberClass)
return noArgConstructor.newInstance(outer);
return newInstance();
}
/**
* Checks to see if the specified class type is the same as this one.
*
* @param t The specified class type.
* @return true if the specified class type is the same as the class for this type.
*/
@Override /* Object */
public boolean equals(Object t) {
if (t == null || ! (t instanceof ClassMeta))
return false;
ClassMeta> t2 = (ClassMeta>)t;
return t2.getInnerClass() == this.getInnerClass();
}
/**
* Similar to {@link #equals(Object)} except primitive and Object types that are similar are considered the same.
* (e.g. boolean == Boolean
).
*
* @param cm The class meta to compare to.
* @return true if the specified class-meta is equivalent to this one.
*/
public boolean same(ClassMeta> cm) {
if (equals(cm))
return true;
return (isPrimitive() && cc == cm.cc);
}
@Override /* Object */
public String toString() {
return toString(false);
}
/**
* Same as {@link #toString()} except use simple class names.
*
* @param simple Print simple class names only (no package).
* @return A new string.
*/
public String toString(boolean simple) {
return toString(new StringBuilder(), simple).toString();
}
/**
* Appends this object as a readable string to the specified string builder.
*
* @param sb The string builder to append this object to.
* @param simple Print simple class names only (no package).
* @return The same string builder passed in (for method chaining).
*/
protected StringBuilder toString(StringBuilder sb, boolean simple) {
String n = innerClass.getName();
if (simple) {
int i = n.lastIndexOf('.');
n = n.substring(i == -1 ? 0 : i+1).replace('$', '.');
}
if (cc == ARRAY)
return elementType.toString(sb, simple).append('[').append(']');
if (cc == MAP)
return sb.append(n).append(keyType.isObject() && valueType.isObject() ? "" : "<"+keyType.toString(simple)+","+valueType.toString(simple)+">");
if (cc == BEANMAP)
return sb.append(BeanMap.class.getName()).append('<').append(n).append('>');
if (cc == COLLECTION)
return sb.append(n).append(elementType.isObject() ? "" : "<"+elementType.toString(simple)+">");
return sb.append(n);
}
/**
* Returns true if the specified object is an instance of this class.
*
*
* This is a simple comparison on the base class itself and not on any generic parameters.
*
* @param o The object to check.
* @return true if the specified object is an instance of this class.
*/
public boolean isInstance(Object o) {
if (o != null)
return isParentClass(this.innerClass, o.getClass()) || (isPrimitive() && getPrimitiveWrapper(this.innerClass) == o.getClass());
return false;
}
/**
* Returns a readable name for this class (e.g. "java.lang.String" , "boolean[]" ).
*
* @return The readable name for this class.
*/
public String getReadableName() {
return getReadableClassName(this.innerClass);
}
/**
* Shortcut for calling {@link Class#getName()} on the inner class of this metadata.
*
* @return The name of the inner class.
*/
public String getName() {
return innerClass.getName();
}
/**
* Shortcut for calling {@link Class#getSimpleName()} on the inner class of this metadata.
*
* @return The simple name of the inner class.
*/
public String getSimpleName() {
return innerClass.getSimpleName();
}
private static class LocaleAsString {
private static Method forLanguageTagMethod;
static {
try {
forLanguageTagMethod = Locale.class.getMethod("forLanguageTag", String.class);
} catch (NoSuchMethodException e) {}
}
@SuppressWarnings("unused")
public static final Locale fromString(String localeString) {
if (forLanguageTagMethod != null) {
if (localeString.indexOf('_') != -1)
localeString = localeString.replace('_', '-');
try {
return (Locale)forLanguageTagMethod.invoke(null, localeString);
} catch (Exception e) {
throw new BeanRuntimeException(e);
}
}
String[] v = localeString.toString().split("[\\-\\_]");
if (v.length == 1)
return new Locale(v[0]);
else if (v.length == 2)
return new Locale(v[0], v[1]);
else if (v.length == 3)
return new Locale(v[0], v[1], v[2]);
throw new BeanRuntimeException("Could not convert string ''{0}'' to a Locale.", localeString);
}
}
@Override /* Object */
public int hashCode() {
return super.hashCode();
}
/**
* Returns true if this class has a transform associated with it that allows it to be created from a Reader.
*
* @return true if this class has a transform associated with it that allows it to be created from a Reader.
*/
public boolean hasReaderTransform() {
return hasTransformFrom(Reader.class);
}
/**
* Returns the transform for this class for creating instances from a Reader.
*
* @return The transform, or null if no such transform exists.
*/
public Transform getReaderTransform() {
return getFromTransform(Reader.class);
}
/**
* Returns true if this class has a transform associated with it that allows it to be created from an InputStream.
*
* @return true if this class has a transform associated with it that allows it to be created from an InputStream.
*/
public boolean hasInputStreamTransform() {
return hasTransformFrom(InputStream.class);
}
/**
* Returns the transform for this class for creating instances from an InputStream.
*
* @return The transform, or null if no such transform exists.
*/
public Transform getInputStreamTransform() {
return getFromTransform(InputStream.class);
}
/**
* Returns true if this class has a transform associated with it that allows it to be created from a String.
*
* @return true if this class has a transform associated with it that allows it to be created from a String.
*/
public boolean hasStringTransform() {
return stringTransform != null;
}
/**
* Returns the transform for this class for creating instances from a String.
*
* @return The transform, or null if no such transform exists.
*/
public Transform getStringTransform() {
return stringTransform;
}
/**
* Returns true if this class can be instantiated from the specified type.
*
* @param c The class type to convert from.
* @return true if this class can be instantiated from the specified type.
*/
public boolean hasTransformFrom(Class> c) {
return getFromTransform(c) != null;
}
/**
* Returns true if this class can be instantiated from the specified type.
*
* @param c The class type to convert from.
* @return true if this class can be instantiated from the specified type.
*/
public boolean hasTransformFrom(ClassMeta> c) {
return getFromTransform(c.getInnerClass()) != null;
}
/**
* Returns true if this class can be transformed to the specified type.
*
* @param c The class type to convert from.
* @return true if this class can be transformed to the specified type.
*/
public boolean hasTransformTo(Class> c) {
return getToTransform(c) != null;
}
/**
* Returns true if this class can be transformed to the specified type.
*
* @param c The class type to convert from.
* @return true if this class can be transformed to the specified type.
*/
public boolean hasTransformTo(ClassMeta> c) {
return getToTransform(c.getInnerClass()) != null;
}
/**
* Transforms the specified object into an instance of this class.
*
* @param o The object to transform.
* @return The transformed object.
*/
@SuppressWarnings({"unchecked","rawtypes"})
public T transformFrom(Object o) {
Transform t = getFromTransform(o.getClass());
return (T)(t == null ? null : t.transform(o));
}
/**
* Transforms the specified object into an instance of this class.
*
* @param o The object to transform.
* @param c The class
* @return The transformed object.
*/
@SuppressWarnings({"unchecked","rawtypes"})
public O transformTo(Object o, Class c) {
Transform t = getToTransform(c);
return (O)(t == null ? null : t.transform(o));
}
/**
* Transforms the specified object into an instance of this class.
*
* @param o The object to transform.
* @param c The class
* @return The transformed object.
*/
public O transformTo(Object o, ClassMeta c) {
return transformTo(o, c.getInnerClass());
}
/**
* Returns the transform for this class for creating instances from other object types.
*
* @param c The transform-from class.
* @return The transform, or null if no such transform exists.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public Transform getFromTransform(Class c) {
Transform t = fromTransforms.get(c);
if (t == TransformCache.NULL)
return null;
if (t == null) {
t = TransformCache.get(c, innerClass);
if (t == null)
t = TransformCache.NULL;
fromTransforms.put(c, t);
}
return t == TransformCache.NULL ? null : t;
}
/**
* Returns the transform for this class for creating instances from other object types.
*
* @param c The transform-from class.
* @return The transform, or null if no such transform exists.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public Transform getToTransform(Class c) {
Transform t = toTransforms.get(c);
if (t == TransformCache.NULL)
return null;
if (t == null) {
t = TransformCache.get(innerClass, c);
if (t == null)
t = TransformCache.NULL;
toTransforms.put(c, t);
}
return t == TransformCache.NULL ? null : t;
}
}