blob: 385b703d3d3b4942f06c63a26f00fa240e65b2f0 [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.ClassMeta.ClassCategory.*;
import static org.apache.juneau.internal.ClassUtils.*;
import static org.apache.juneau.reflect.ReflectFlags.*;
import java.io.*;
import java.lang.annotation.*;
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.reflect.*;
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.
*
* <p>
* Instances of this class can be created through the {@link BeanContext#getClassMeta(Class)} method.
*
* <p>
* The {@link BeanContext} class will cache and reuse instances of this class except for the following class types:
* <ul>
* <li>Arrays
* <li>Maps with non-Object key/values.
* <li>Collections with non-Object key/values.
* </ul>
*
* <p>
* This class is tied to the {@link BeanContext} class because it's that class that makes the determination of what is
* a bean.
*
* @param <T> The class type of the wrapped class.
*/
@Bean(bpi="innerClass,classCategory,elementType,keyType,valueType,notABeanReason,initException,beanMeta")
public final class ClassMeta<T> 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, OPTIONAL
}
final Class<T> innerClass; // The class being wrapped.
final ClassInfo info;
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 ConstructorInfo
noArgConstructor, // The no-arg constructor for this class (if it has one).
stringConstructor; // The X(String) constructor (if it has one).
private final Method
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<String,Method>
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<Class<?>,PojoSwap<?,?>>
childSwapMap, // Maps normal subclasses to PojoSwaps.
childUnswapMap; // Maps swap subclasses to PojoSwaps.
private final PojoSwap<T,?>[] 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<T,?> 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<T> 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<Class<?>,Mutater<?,T>> fromMutaters = new ConcurrentHashMap<>();
private final Map<Class<?>,Mutater<T,?>> toMutaters = new ConcurrentHashMap<>();
private final Mutater<String,T> stringMutater;
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 <jk>null</jk>.
* @param beanFilter
* The {@link BeanFilter} programmatically associated with this class.
* Can be <jk>null</jk>.
* @param pojoSwap
* The {@link PojoSwap} programmatically associated with this class.
* Can be <jk>null</jk>.
* @param childPojoSwap
* The child {@link PojoSwap PojoSwaps} programmatically associated with this class.
* These are the <c>PojoSwaps</c> that have normal classes that are subclasses of this class.
* Can be <jk>null</jk>.
* @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<T> innerClass, BeanContext beanContext, Class<? extends T> implClass, BeanFilter beanFilter, PojoSwap<T,?>[] pojoSwaps, PojoSwap<?,?>[] childPojoSwaps, Object example) {
this.innerClass = innerClass;
this.info = ClassInfo.of(innerClass);
this.beanContext = beanContext;
this.extMeta = new MetadataMap();
String notABeanReason = null;
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<T> builder = new ClassMetaBuilder(innerClass, beanContext, implClass, beanFilter, pojoSwaps, childPojoSwaps, example);
this.cc = builder.cc;
this.isDelegate = builder.isDelegate;
this.fromStringMethod = builder.fromStringMethod;
this.parentPropertyMethod = builder.parentPropertyMethod;
this.namePropertyMethod = builder.namePropertyMethod;
this.noArgConstructor = builder.noArgConstructor;
this.stringConstructor = builder.stringConstructor;
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.keyType = builder.keyType;
this.valueType = builder.valueType;
this.elementType = builder.elementType;
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.stringMutater = builder.stringMutater;
} catch (ClassMetaRuntimeException e) {
notABeanReason = e.getMessage();
throw e;
} finally {
this.notABeanReason = notABeanReason;
wLock.unlock();
}
}
/**
* Causes thread to wait until constructor has exited.
*/
final void waitForInit() {
rLock.lock();
rLock.unlock();
}
/**
* Copy constructor.
*
* <p>
* Used for creating Map and Collection class metas that shouldn't be cached.
*/
ClassMeta(ClassMeta<T> mainType, ClassMeta<?> keyType, ClassMeta<?> valueType, ClassMeta<?> elementType) {
this.innerClass = mainType.innerClass;
this.info = mainType.info;
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.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.stringMutater = mainType.stringMutater;
}
/**
* Constructor for args-arrays.
*/
@SuppressWarnings("unchecked")
ClassMeta(ClassMeta<?>[] args) {
this.innerClass = (Class<T>) Object[].class;
this.info = ClassInfo.of(innerClass);
this.extMeta = new MetadataMap();
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.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.initException = null;
this.beanRegistry = null;
this.exampleMethod = null;
this.exampleField = null;
this.example = null;
this.stringMutater = null;
}
@SuppressWarnings({"unchecked","rawtypes","hiding"})
private final class ClassMetaBuilder<T> {
Class<T> innerClass;
ClassInfo ci;
Class<? extends T> implClass;
BeanContext beanContext;
ClassCategory cc = ClassCategory.OTHER;
boolean
isDelegate = false,
isMemberClass = false,
isAbstract = false;
Method
fromStringMethod = null;
Setter
parentPropertyMethod = null,
namePropertyMethod = null;
ConstructorInfo
noArgConstructor = null,
stringConstructor = null;
Object primitiveDefault = null;
Map<String,Method>
publicMethods = new LinkedHashMap<>();
ClassMeta<?>
keyType = null,
valueType = null,
elementType = null;
String
typePropertyName = null,
notABeanReason = null,
dictionaryName = null;
Throwable initException = null;
BeanMeta beanMeta = null;
List<PojoSwap> pojoSwaps = new ArrayList<>();
BuilderSwap builderSwap;
InvocationHandler invocationHandler = null;
BeanRegistry beanRegistry = null;
PojoSwap<?,?>[] childPojoSwaps;
ConcurrentHashMap<Class<?>,PojoSwap<?,?>>
childSwapMap,
childUnswapMap;
Method exampleMethod;
Field exampleField;
Object example;
Mutater<String,T> stringMutater;
@SuppressWarnings("deprecation")
ClassMetaBuilder(Class<T> innerClass, BeanContext beanContext, Class<? extends T> implClass, BeanFilter beanFilter, PojoSwap<T,?>[] pojoSwaps, PojoSwap<?,?>[] childPojoSwaps, Object example) {
this.innerClass = innerClass;
this.beanContext = beanContext;
this.implClass = implClass;
ClassInfo ici = ClassInfo.of(implClass);
this.childPojoSwaps = childPojoSwaps;
if (childPojoSwaps == null) {
this.childSwapMap = null;
this.childUnswapMap = null;
} else {
this.childSwapMap = new ConcurrentHashMap<>();
this.childUnswapMap = new ConcurrentHashMap<>();
}
Class<T> c = innerClass;
ci = ClassInfo.of(c);
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 (ci.isChildOf(Delegate.class))
isDelegate = true;
if (c == Object.class)
cc = OBJ;
else if (c.isEnum())
cc = ENUM;
else if (c.equals(Class.class))
cc = ClassCategory.CLASS;
else if (ci.isChildOf(Method.class))
cc = METHOD;
else if (ci.isChildOf(CharSequence.class)) {
if (c.equals(String.class))
cc = STR;
else
cc = CHARSEQ;
}
else if (ci.isChildOf(Number.class)) {
if (ci.isChildOfAny(Float.class, Double.class))
cc = DECIMAL;
else
cc = NUMBER;
}
else if (ci.isChildOf(Collection.class))
cc = COLLECTION;
else if (ci.isChildOf(Map.class)) {
if (ci.isChildOf(BeanMap.class))
cc = BEANMAP;
else
cc = MAP;
}
else if (c == Character.class)
cc = CHAR;
else if (c == Boolean.class)
cc = BOOLEAN;
else if (ci.isChildOfAny(Date.class, Calendar.class))
cc = DATE;
else if (c.isArray())
cc = ARRAY;
else if (ci.isChildOfAny(URL.class, URI.class) || c.isAnnotationPresent(org.apache.juneau.annotation.URI.class))
cc = URI;
else if (ci.isChildOf(Reader.class))
cc = READER;
else if (ci.isChildOf(InputStream.class))
cc = INPUTSTREAM;
else if (ci.is(Optional.class))
cc = OPTIONAL;
}
isMemberClass = ci.isMemberClass() && ci.isNotStatic();
// 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 (MethodInfo m : ci.getPublicMethods()) {
if (m.isAll(STATIC, PUBLIC, NOT_DEPRECATED) && m.hasName(methodName) && m.hasReturnType(c) && m.hasParamTypes(String.class)) {
fromStringMethod = m.inner();
break;
}
}
}
}
// Find example() method if present.
for (MethodInfo m : ci.getPublicMethods()) {
if (m.isAll(PUBLIC, NOT_DEPRECATED, STATIC) && m.hasName("example") && m.hasFuzzyParamTypes(BeanSession.class)) {
exampleMethod = m.inner();
break;
}
}
for (FieldInfo f : ci.getAllFieldsParentFirst()) {
if (f.hasAnnotation(ParentProperty.class)) {
if (f.isStatic())
throw new ClassMetaRuntimeException(c, "@ParentProperty used on invalid field ''{0}''. Must be static.", f);
f.setAccessible();
parentPropertyMethod = new Setter.FieldSetter(f.inner());
}
if (f.hasAnnotation(NameProperty.class)) {
if (f.isStatic())
throw new ClassMetaRuntimeException(c, "@NameProperty used on invalid field ''{0}''. Must be static.", f);
f.setAccessible();
namePropertyMethod = new Setter.FieldSetter(f.inner());
}
}
for (FieldInfo f : ci.getDeclaredFields()) {
if (f.hasAnnotation(Example.class)) {
if (! (f.isStatic() && ci.isParentOf(f.getType().inner())))
throw new ClassMetaRuntimeException(c, "@Example used on invalid field ''{0}''. Must be static and an instance of the type.", f);
f.setAccessible();
exampleField = f.inner();
}
}
// Find @NameProperty and @ParentProperty methods if present.
for (MethodInfo m : ci.getAllMethodsParentFirst()) {
if (m.hasAnnotation(ParentProperty.class)) {
if (m.isStatic() || ! m.hasNumParams(1))
throw new ClassMetaRuntimeException(c, "@ParentProperty used on invalid method ''{0}''. Must not be static and have one argument.", m);
m.setAccessible();
parentPropertyMethod = new Setter.MethodSetter(m.inner());
}
if (m.hasAnnotation(NameProperty.class)) {
if (m.isStatic() || ! m.hasNumParams(1))
throw new ClassMetaRuntimeException(c, "@NameProperty used on invalid method ''{0}''. Must not be static and have one argument.", m);
m.setAccessible();
namePropertyMethod = new Setter.MethodSetter(m.inner());
}
}
for (MethodInfo m : ci.getDeclaredMethods()) {
if (m.hasAnnotation(Example.class)) {
if (! (m.isStatic() && m.hasFuzzyParamTypes(BeanSession.class) && ci.isParentOf(m.getReturnType().inner())))
throw new ClassMetaRuntimeException(c, "@Example used on invalid method ''{0}''. Must be static and return an instance of the declaring class.", m);
m.setAccessible();
exampleMethod = m.inner();
}
}
// Note: Primitive types are normally abstract.
isAbstract = ci.isAbstract() && ci.isNotPrimitive();
// Find constructor(String) method if present.
for (ConstructorInfo cs : ci.getPublicConstructors()) {
if (cs.isPublic() && cs.isNotDeprecated()) {
List<ClassInfo> pt = cs.getParamTypes();
if (pt.size() == (isMemberClass ? 1 : 0) && c != Object.class && ! isAbstract) {
noArgConstructor = cs;
} else if (pt.size() == (isMemberClass ? 2 : 1)) {
ClassInfo arg = pt.get(isMemberClass ? 1 : 0);
if (arg.is(String.class))
stringConstructor = cs;
}
}
}
primitiveDefault = ci.getPrimitiveDefault();
for (MethodInfo m : ci.getPublicMethods())
if (m.isAll(PUBLIC, NOT_DEPRECATED))
publicMethods.put(m.getSignature(), m.inner());
if (innerClass != Object.class) {
ClassInfo x = implClass == null ? ci : ici;
noArgConstructor = x.getPublicConstructor();
}
if (beanFilter == null)
beanFilter = findBeanFilter();
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<String,Person>)
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<Person>)
else if (cc == COLLECTION || cc == OPTIONAL) {
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<T>(beanMeta);
Bean b = c.getAnnotation(Bean.class);
if (b != null) {
if (b.beanDictionary().length != 0)
beanRegistry = new BeanRegistry(beanContext, null, b.beanDictionary());
if (b.dictionary().length != 0)
beanRegistry = new BeanRegistry(beanContext, null, b.dictionary());
// 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 OPTIONAL:
case VOID:
break;
}
}
this.example = example;
this.stringMutater = Mutaters.get(String.class, c);
}
private BeanFilter findBeanFilter() {
try {
List<Bean> ba = info.getAnnotations(Bean.class);
if (! ba.isEmpty())
return new AnnotationBeanFilterBuilder(innerClass, ba).build();
} catch (Exception e) {
throw new RuntimeException(e);
}
return null;
}
private void findPojoSwaps(List<PojoSwap> 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));
PojoSwap defaultSwap = DefaultSwaps.find(ci);
if (defaultSwap == null)
defaultSwap = AutoObjectSwap.find(ci);
if (defaultSwap == null)
defaultSwap = AutoNumberSwap.find(ci);
if (defaultSwap == null)
defaultSwap = AutoMapSwap.find(ci);
if (defaultSwap == null)
defaultSwap = AutoListSwap.find(ci);
if (defaultSwap != null)
l.add(defaultSwap);
}
private PojoSwap<T,?> createPojoSwap(Swap s) {
Class<?> c = s.value();
if (c == Null.class)
c = s.impl();
ClassInfo ci = ClassInfo.of(c);
if (ci.isChildOf(PojoSwap.class)) {
PojoSwap ps = castOrCreate(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 (ci.isChildOf(Surrogate.class)) {
List<SurrogateSwap<?,?>> l = SurrogateSwap.findPojoSwaps(c);
if (! l.isEmpty())
return (PojoSwap<T,?>)l.iterator().next();
}
throw new ClassMetaRuntimeException(c, "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 {@link ClassInfo} wrapper for the underlying class.
*
* @return The {@link ClassInfo} wrapper for the underlying class, never <jk>null</jk>.
*/
public ClassInfo getInfo() {
return info;
}
/**
* Returns the type property name associated with this class and subclasses.
*
* <p>
* If <jk>null</jk>, <js>"_type"</js> should be assumed.
*
* @return
* The type property name associated with this bean class, or <jk>null</jk> 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.
*
* <p>
* The lexical name is defined by {@link Bean#typeName() @Bean(typeName)}.
*
* @return
* The type name associated with this bean class, or <jk>null</jk> 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.
*
* <p>
* This bean registry contains names specified in the {@link Bean#dictionary() @Bean(dictionary)} 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 <jk>null</jk> 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 <jk>true</jk> if this class is a superclass of or the same as the specified class.
*
* @param c The comparison class.
* @return <jk>true</jk> if this class is a superclass of or the same as the specified class.
*/
public boolean isAssignableFrom(Class<?> c) {
return info.isChildOf(c);
}
/**
* Returns <jk>true</jk> if this class is a subclass of or the same as the specified class.
*
* @param c The comparison class.
* @return <jk>true</jk> if this class is a subclass of or the same as the specified class.
*/
public boolean isInstanceOf(Class<?> c) {
return info.isParentOf(c);
}
/**
* Returns <jk>true</jk> if this class or any child classes has a {@link PojoSwap} associated with it.
*
* <p>
* 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 <jk>true</jk> 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 <jk>null</jk> 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 && f.getNormalClass().isParentOf(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 <jk>null</jk> 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 && f.getSwapClass().isParentOf(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.
*
* <p>
* Constructor must match the visibility requirements specified by parameter 'v'.
* If class is abstract, always returns <jk>null</jk>.
* 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 <jk>null</jk> if no no-arg constructor exists with the required visibility.
*/
@SuppressWarnings({"unchecked"})
protected static <T> Constructor<? extends T> findNoArgConstructor(Class<?> c, Visibility v) {
ClassInfo ci = ClassInfo.of(c);
if (ci.isAbstract())
return null;
boolean isMemberClass = ci.isMemberClass() && ci.isNotStatic();
for (ConstructorInfo cc : ci.getPublicConstructors()) {
if (cc.hasNumParams(isMemberClass ? 1 : 0) && cc.isVisible(v) && cc.isNotDeprecated())
return (Constructor<? extends T>) v.transform(cc.inner());
}
return null;
}
/**
* Returns the {@link Class} object that this class type wraps.
*
* @return The wrapped class object.
*/
public Class<T> 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.
* <br>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<T,?> ps = getPojoSwap(session);
return (ps == null ? this : ps.getSwapClassMeta(session));
}
/**
* Returns the example of this class.
*
* @param session
* The bean session.
* <br>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)MethodInfo.of(exampleMethod).invokeFuzzy(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 <jk>null</jk> 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 <jk>null</jk> 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 <jk>null</jk> if this class is not a Map.
*/
public ClassMeta<?> getValueType() {
return valueType;
}
/**
* Returns <jk>true</jk> if this class implements {@link Delegate}, meaning it's a representation of some other
* object.
*
* @return <jk>true</jk> if this class implements {@link Delegate}.
*/
public boolean isDelegate() {
return isDelegate;
}
/**
* Returns <jk>true</jk> if this class is a subclass of {@link Map}.
*
* @return <jk>true</jk> if this class is a subclass of {@link Map}.
*/
public boolean isMap() {
return cc == MAP || cc == BEANMAP;
}
/**
* Returns <jk>true</jk> if this class is a subclass of {@link Map} or it's a bean.
*
* @return <jk>true</jk> 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 <jk>true</jk> if this class is a subclass of {@link BeanMap}.
*
* @return <jk>true</jk> if this class is a subclass of {@link BeanMap}.
*/
public boolean isBeanMap() {
return cc == BEANMAP;
}
/**
* Returns <jk>true</jk> if this class is a subclass of {@link Collection}.
*
* @return <jk>true</jk> if this class is a subclass of {@link Collection}.
*/
public boolean isCollection() {
return cc == COLLECTION;
}
/**
* Returns <jk>true</jk> if this class is a subclass of {@link Optional}.
*
* @return <jk>true</jk> if this class is a subclass of {@link Optional}.
*/
public boolean isOptional() {
return cc == OPTIONAL;
}
/**
* Returns <jk>true</jk> if this class is a subclass of {@link Collection} or is an array.
*
* @return <jk>true</jk> if this class is a subclass of {@link Collection} or is an array.
*/
public boolean isCollectionOrArray() {
return cc == COLLECTION || cc == ARRAY;
}
/**
* Returns <jk>true</jk> if this class is a subclass of {@link Collection} or is an array or {@link Optional}.
*
* @return <jk>true</jk> if this class is a subclass of {@link Collection} or is an array or {@link Optional}.
*/
public boolean isCollectionOrArrayOrOptional() {
return cc == COLLECTION || cc == ARRAY || cc == OPTIONAL;
}
/**
* Returns <jk>true</jk> if this class extends from {@link Set}.
*
* @return <jk>true</jk> if this class extends from {@link Set}.
*/
public boolean isSet() {
return cc == COLLECTION && info.isChildOf(Set.class);
}
/**
* Returns <jk>true</jk> if this class extends from {@link List}.
*
* @return <jk>true</jk> if this class extends from {@link List}.
*/
public boolean isList() {
return cc == COLLECTION && info.isChildOf(List.class);
}
/**
* Returns <jk>true</jk> if this class is <code><jk>byte</jk>[]</code>.
*
* @return <jk>true</jk> if this class is <code><jk>byte</jk>[]</code>.
*/
public boolean isByteArray() {
return cc == ARRAY && this.innerClass == byte[].class;
}
/**
* Returns <jk>true</jk> if this class is {@link Class}.
*
* @return <jk>true</jk> if this class is {@link Class}.
*/
public boolean isClass() {
return cc == ClassCategory.CLASS;
}
/**
* Returns <jk>true</jk> if this class is {@link Method}.
*
* @return <jk>true</jk> if this class is {@link Method}.
*/
public boolean isMethod() {
return cc == METHOD;
}
/**
* Returns <jk>true</jk> if this class is an {@link Enum}.
*
* @return <jk>true</jk> if this class is an {@link Enum}.
*/
public boolean isEnum() {
return cc == ENUM;
}
/**
* Returns <jk>true</jk> if this class is an array.
*
* @return <jk>true</jk> if this class is an array.
*/
public boolean isArray() {
return cc == ARRAY;
}
/**
* Returns <jk>true</jk> if this class is a bean.
*
* @return <jk>true</jk> if this class is a bean.
*/
public boolean isBean() {
return beanMeta != null;
}
/**
* Returns <jk>true</jk> if this class is {@link Object}.
*
* @return <jk>true</jk> if this class is {@link Object}.
*/
public boolean isObject() {
return cc == OBJ;
}
/**
* Returns <jk>true</jk> if this class is not {@link Object}.
*
* @return <jk>true</jk> if this class is not {@link Object}.
*/
public boolean isNotObject() {
return cc != OBJ;
}
/**
* Returns <jk>true</jk> if this class is a subclass of {@link Number}.
*
* @return <jk>true</jk> if this class is a subclass of {@link Number}.
*/
public boolean isNumber() {
return cc == NUMBER || cc == DECIMAL;
}
/**
* Returns <jk>true</jk> if this class is a subclass of {@link Float} or {@link Double}.
*
* @return <jk>true</jk> if this class is a subclass of {@link Float} or {@link Double}.
*/
public boolean isDecimal() {
return cc == DECIMAL;
}
/**
* Returns <jk>true</jk> if this class is either {@link Float} or <jk>float</jk>.
*
* @return <jk>true</jk> if this class is either {@link Float} or <jk>float</jk>.
*/
public boolean isFloat() {
return innerClass == Float.class || innerClass == float.class;
}
/**
* Returns <jk>true</jk> if this class is either {@link Double} or <jk>double</jk>.
*
* @return <jk>true</jk> if this class is either {@link Double} or <jk>double</jk>.
*/
public boolean isDouble() {
return innerClass == Double.class || innerClass == double.class;
}
/**
* Returns <jk>true</jk> if this class is either {@link Short} or <jk>short</jk>.
*
* @return <jk>true</jk> if this class is either {@link Short} or <jk>short</jk>.
*/
public boolean isShort() {
return innerClass == Short.class || innerClass == short.class;
}
/**
* Returns <jk>true</jk> if this class is either {@link Integer} or <jk>int</jk>.
*
* @return <jk>true</jk> if this class is either {@link Integer} or <jk>int</jk>.
*/
public boolean isInteger() {
return innerClass == Integer.class || innerClass == int.class;
}
/**
* Returns <jk>true</jk> if this class is either {@link Long} or <jk>long</jk>.
*
* @return <jk>true</jk> if this class is either {@link Long} or <jk>long</jk>.
*/
public boolean isLong() {
return innerClass == Long.class || innerClass == long.class;
}
/**
* Returns <jk>true</jk> if this metadata represents the specified type.
*
* @param c The class to test against.
* @return <jk>true</jk> if this metadata represents the specified type.
*/
public boolean isType(Class<?> c) {
return info.isChildOf(c);
}
/**
* Returns <jk>true</jk> if this class is a {@link Boolean}.
*
* @return <jk>true</jk> if this class is a {@link Boolean}.
*/
public boolean isBoolean() {
return cc == BOOLEAN;
}
/**
* Returns <jk>true</jk> if this class is a subclass of {@link CharSequence}.
*
* @return <jk>true</jk> if this class is a subclass of {@link CharSequence}.
*/
public boolean isCharSequence() {
return cc == STR || cc == CHARSEQ;
}
/**
* Returns <jk>true</jk> if this class is a {@link String}.
*
* @return <jk>true</jk> if this class is a {@link String}.
*/
public boolean isString() {
return cc == STR;
}
/**
* Returns <jk>true</jk> if this class is a {@link Character}.
*
* @return <jk>true</jk> if this class is a {@link Character}.
*/
public boolean isChar() {
return cc == CHAR;
}
/**
* Returns <jk>true</jk> if this class is a primitive.
*
* @return <jk>true</jk> if this class is a primitive.
*/
public boolean isPrimitive() {
return innerClass.isPrimitive();
}
/**
* Returns <jk>true</jk> if this class is a {@link Date} or {@link Calendar}.
*
* @return <jk>true</jk> if this class is a {@link Date} or {@link Calendar}.
*/
public boolean isDateOrCalendar() {
return cc == DATE;
}
/**
* Returns <jk>true</jk> if this class is a {@link Date}.
*
* @return <jk>true</jk> if this class is a {@link Date}.
*/
public boolean isDate() {
return cc == DATE && info.isChildOf(Date.class);
}
/**
* Returns <jk>true</jk> if this class is a {@link Calendar}.
*
* @return <jk>true</jk> if this class is a {@link Calendar}.
*/
public boolean isCalendar() {
return cc == DATE && info.isChildOf(Calendar.class);
}
/**
* Returns <jk>true</jk> if this class is a {@link URI} or {@link URL}.
*
* @return <jk>true</jk> if this class is a {@link URI} or {@link URL}.
*/
public boolean isUri() {
return cc == URI;
}
/**
* Returns <jk>true</jk> if this class is a {@link Reader}.
*
* @return <jk>true</jk> if this class is a {@link Reader}.
*/
public boolean isReader() {
return cc == READER;
}
/**
* Returns <jk>true</jk> if this class is an {@link InputStream}.
*
* @return <jk>true</jk> if this class is an {@link InputStream}.
*/
public boolean isInputStream() {
return cc == INPUTSTREAM;
}
/**
* Returns <jk>true</jk> if this class is {@link Void} or <jk>void</jk>.
*
* @return <jk>true</jk> if this class is {@link Void} or <jk>void</jk>.
*/
public boolean isVoid() {
return cc == VOID;
}
/**
* Returns <jk>true</jk> if this metadata represents an array of argument types.
*
* @return <jk>true</jk> 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 <jk>null</jk> 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 <jk>null</jk>.
* @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 <jk>true</jk> if instance of this object can be <jk>null</jk>.
*
* <p>
* Objects can be <jk>null</jk>, but primitives cannot, except for chars which can be represented by
* <code>(<jk>char</jk>)0</code>.
*
* @return <jk>true</jk> if instance of this class can be null.
*/
public boolean isNullable() {
if (innerClass.isPrimitive())
return cc == CHAR;
return true;
}
/**
* Returns <jk>true</jk> if this class is abstract.
*
* @return <jk>true</jk> if this class is abstract.
*/
public boolean isAbstract() {
return isAbstract;
}
/**
* Returns <jk>true</jk> if this class is an inner class.
*
* @return <jk>true</jk> if this class is an inner class.
*/
public boolean isMemberClass() {
return isMemberClass;
}
/**
* All public methods on this class including static methods.
*
* <p>
* Keys are method signatures.
*
* @return The public methods on this class.
*/
public Map<String,Method> 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.
* <br>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 <jk>null</jk> if there are no POJO swaps associated with
* this class.
*/
public PojoSwap<T,?> 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 <jk>null</jk> if it doesn't exist.
*/
public BuilderSwap<T,?> getBuilderSwap(BeanSession session) {
return builderSwap;
}
/**
* Returns the {@link BeanMeta} associated with this class.
*
* @return
* The {@link BeanMeta} associated with this class, or <jk>null</jk> if there is no bean meta associated with
* this class.
*/
public BeanMeta<T> getBeanMeta() {
return beanMeta;
}
/**
* Returns the no-arg constructor for this class.
*
* @return The no-arg constructor for this class, or <jk>null</jk> if it does not exist.
*/
public ConstructorInfo 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 <jk>null</jk>.
*/
public <M extends ClassMetaExtended> M getExtendedMeta(Class<M> c) {
return extMeta.get(c, this);
}
/**
* Returns the interface proxy invocation handler for this class.
*
* @return The interface proxy invocation handler, or <jk>null</jk> if it does not exist.
*/
public InvocationHandler getProxyInvocationHandler() {
return invocationHandler;
}
/**
* Returns <jk>true</jk> if this class has a no-arg constructor or invocation handler.
*
* @return <jk>true</jk> 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 <jk>true</jk> if this class has a no-arg constructor or invocation handler.
* Returns <jk>false</jk> 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 <jk>null</jk> for non-member or static classes.
* @return
* <jk>true</jk> 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 && noArgConstructor.hasParamTypes(outer.getClass());
return canCreateNewInstance();
}
/**
* Returns <jk>true</jk> if this class can be instantiated as a bean.
* Returns <jk>false</jk> 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 <jk>null</jk> for non-member or static classes.
* @return
* <jk>true</jk> 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 && beanMeta.constructor.hasParamTypes(outer.getClass());
return true;
}
/**
* Returns <jk>true</jk> if this class can call the {@link #newInstanceFromString(Object, String)} method.
*
* @param outer
* The outer class object for non-static member classes.
* Can be <jk>null</jk> for non-member or static classes.
* @return <jk>true</jk> 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 && stringConstructor.hasParamTypes(outer.getClass(), String.class);
return true;
}
return false;
}
/**
* Returns the method or field annotated with {@link NameProperty @NameProperty}.
*
* @return
* The method or field annotated with {@link NameProperty @NameProperty} or <jk>null</jk> 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 <jk>null</jk> if method does not
* exist.
*/
public Setter getParentProperty() {
return parentPropertyMethod;
}
/**
* Returns the reason why this class is not a bean, or <jk>null</jk> if it is a bean.
*
* @return The reason why this class is not a bean, or <jk>null</jk> if it is a bean.
*/
public synchronized String getNotABeanReason() {
return notABeanReason;
}
/**
* Returns any exception that was throw in the <c>init()</c> 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 <jk>int</jk> or <jk>Integer</jk>.
*
* @return The default value, or <jk>null</jk> if this class type is not a primitive.
*/
@SuppressWarnings("unchecked")
public T getPrimitiveDefault() {
return (T)primitiveDefault;
}
/**
* If this is an {@link Optional}, returns an empty optional.
*
* <p>
* Note that if this is a nested optional, will recursively create empty optionals.
*
* @return An empty optional, or <jk>null</jk> if this isn't an optional.
*/
public Optional<?> getOptionalDefault() {
if (isOptional())
return Optional.ofNullable(getElementType().getOptionalDefault());
return null;
}
/**
* Converts the specified object to a string.
*
* @param t The object to convert.
* @return The object converted to a string, or <jk>null</jk> 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 <c>String</c> input.
*
* <p>
* In order to use this method, the class must have one of the following methods:
* <ul>
* <li><code><jk>public static</jk> T valueOf(String in);</code>
* <li><code><jk>public static</jk> T fromString(String in);</code>
* <li><code><jk>public</jk> T(String in);</code>
* </ul>
*
* @param outer
* The outer class object for non-static member classes. Can be <jk>null</jk> for non-member or static classes.
* @param arg The input argument value.
* @return A new instance of the object, or <jk>null</jk> if there is no string constructor on the object.
* @throws ExecutableException Exception occurred on invoked constructor/method/field.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public T newInstanceFromString(Object outer, String arg) throws ExecutableException {
if (isEnum() && beanContext.isUseEnumNames() && fromStringMethod != null)
return (T)Enum.valueOf((Class<? extends Enum>)this.innerClass, arg);
Method m = fromStringMethod;
if (m != null) {
try {
return (T)m.invoke(null, arg);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new ExecutableException(e);
}
}
ConstructorInfo c = stringConstructor;
if (c != null) {
if (isMemberClass)
return c.<T>invoke(outer, arg);
return c.<T>invoke(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.
*
* @return A new instance of the object, or <jk>null</jk> if there is no no-arg constructor on the object.
* @throws ExecutableException Exception occurred on invoked constructor/method/field.
*/
@SuppressWarnings("unchecked")
public T newInstance() throws ExecutableException {
if (isArray())
return (T)Array.newInstance(getInnerClass().getComponentType(), 0);
ConstructorInfo c = getConstructor();
if (c != null)
return c.<T>invoke((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 <jk>null</jk> if instantiating a non-member or static class.
* @return A new instance of the object, or <jk>null</jk> if there is no no-arg constructor on the object.
* @throws ExecutableException Exception occurred on invoked constructor/method/field.
*/
public T newInstance(Object outer) throws ExecutableException {
if (isMemberClass)
return noArgConstructor.<T>invoke(outer);
return newInstance();
}
/**
* Checks to see if the specified class type is the same as this one.
*
* @param t The specified class type.
* @return <jk>true</jk> 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. <jk>boolean</jk> == <c>Boolean</c>).
*
* @param cm The class meta to compare to.
* @return <jk>true</jk> 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 || cc == OPTIONAL)
return sb.append(n).append(elementType.isObject() ? "" : "<"+elementType.toString(simple)+">");
return sb.append(n);
}
/**
* Returns <jk>true</jk> if the specified object is an instance of this class.
*
* <p>
* This is a simple comparison on the base class itself and not on any generic parameters.
*
* @param o The object to check.
* @return <jk>true</jk> if the specified object is an instance of this class.
*/
public boolean isInstance(Object o) {
if (o != null)
return info.isParentOf(o.getClass()) || (isPrimitive() && info.getPrimitiveWrapper() == o.getClass());
return false;
}
/**
* Returns a readable name for this class (e.g. <js>"java.lang.String"</js>, <js>"boolean[]"</js>).
*
* @return The readable name for this class.
*/
public String getFullName() {
return info.getFullName();
}
/**
* 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();
}
@Override /* Object */
public int hashCode() {
return super.hashCode();
}
/**
* Returns <jk>true</jk> if this class has a transform associated with it that allows it to be created from a Reader.
*
* @return <jk>true</jk> if this class has a transform associated with it that allows it to be created from a Reader.
*/
public boolean hasReaderMutater() {
return hasMutaterFrom(Reader.class);
}
/**
* Returns the transform for this class for creating instances from a Reader.
*
* @return The transform, or <jk>null</jk> if no such transform exists.
*/
public Mutater<Reader,T> getReaderMutater() {
return getFromMutater(Reader.class);
}
/**
* Returns <jk>true</jk> if this class has a transform associated with it that allows it to be created from an InputStream.
*
* @return <jk>true</jk> if this class has a transform associated with it that allows it to be created from an InputStream.
*/
public boolean hasInputStreamMutater() {
return hasMutaterFrom(InputStream.class);
}
/**
* Returns the transform for this class for creating instances from an InputStream.
*
* @return The transform, or <jk>null</jk> if no such transform exists.
*/
public Mutater<InputStream,T> getInputStreamMutater() {
return getFromMutater(InputStream.class);
}
/**
* Returns <jk>true</jk> if this class has a transform associated with it that allows it to be created from a String.
*
* @return <jk>true</jk> if this class has a transform associated with it that allows it to be created from a String.
*/
public boolean hasStringMutater() {
return stringMutater != null;
}
/**
* Returns the transform for this class for creating instances from a String.
*
* @return The transform, or <jk>null</jk> if no such transform exists.
*/
public Mutater<String,T> getStringMutater() {
return stringMutater;
}
/**
* Returns <jk>true</jk> if this class can be instantiated from the specified type.
*
* @param c The class type to convert from.
* @return <jk>true</jk> if this class can be instantiated from the specified type.
*/
public boolean hasMutaterFrom(Class<?> c) {
return getFromMutater(c) != null;
}
/**
* Returns <jk>true</jk> if this class can be instantiated from the specified type.
*
* @param c The class type to convert from.
* @return <jk>true</jk> if this class can be instantiated from the specified type.
*/
public boolean hasMutaterFrom(ClassMeta<?> c) {
return getFromMutater(c.getInnerClass()) != null;
}
/**
* Returns <jk>true</jk> if this class can be transformed to the specified type.
*
* @param c The class type to convert from.
* @return <jk>true</jk> if this class can be transformed to the specified type.
*/
public boolean hasMutaterTo(Class<?> c) {
return getToMutater(c) != null;
}
/**
* Returns <jk>true</jk> if this class can be transformed to the specified type.
*
* @param c The class type to convert from.
* @return <jk>true</jk> if this class can be transformed to the specified type.
*/
public boolean hasMutaterTo(ClassMeta<?> c) {
return getToMutater(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 mutateFrom(Object o) {
Mutater t = getFromMutater(o.getClass());
return (T)(t == null ? null : t.mutate(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> O mutateTo(Object o, Class<O> c) {
Mutater t = getToMutater(c);
return (O)(t == null ? null : t.mutate(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> O mutateTo(Object o, ClassMeta<O> c) {
return mutateTo(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 <jk>null</jk> if no such transform exists.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public <I> Mutater<I,T> getFromMutater(Class<I> c) {
Mutater t = fromMutaters.get(c);
if (t == Mutaters.NULL)
return null;
if (t == null) {
t = Mutaters.get(c, innerClass);
if (t == null)
t = Mutaters.NULL;
fromMutaters.put(c, t);
}
return t == Mutaters.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 <jk>null</jk> if no such transform exists.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public <O> Mutater<T,O> getToMutater(Class<O> c) {
Mutater t = toMutaters.get(c);
if (t == Mutaters.NULL)
return null;
if (t == null) {
t = Mutaters.get(innerClass, c);
if (t == null)
t = Mutaters.NULL;
toMutaters.put(c, t);
}
return t == Mutaters.NULL ? null : t;
}
/**
* Shortcut for calling <code>getInnerClass().getAnnotation(a) != <jk>null</jk></code>.
*
* @param a The annotation to check for.
* @return <jk>true</jk> if the inner class has the annotation.
*/
public boolean hasAnnotation(Class<? extends Annotation> a) {
return getAnnotation(a) != null;
}
/**
* Shortcut for calling <c>getInnerClass().getAnnotation(a)</c>.
*
* @param a The annotation to retrieve.
* @return The specified annotation, or <jk>null</jk> if the class does not have the specified annotation.
*/
public <A extends Annotation> A getAnnotation(Class<A> a) {
return this.innerClass.getAnnotation(a);
}
}