All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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.common.internal.ThrowableUtils.*;
import static org.apache.juneau.internal.CollectionUtils.*;
import static org.apache.juneau.internal.ConsumerUtils.*;
import static org.apache.juneau.internal.ObjectUtils.*;
import static java.util.Arrays.*;

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.time.temporal.*;
import java.util.*;
import java.util.Date;
import java.util.concurrent.*;
import java.util.function.*;

import org.apache.juneau.annotation.*;
import org.apache.juneau.cp.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.json.*;
import org.apache.juneau.reflect.*;
import org.apache.juneau.swap.*;

/**
 * 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. * *

See Also:
    *
* * @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, OPTIONAL } final Class innerClass; // The class being wrapped. final ClassInfo info; private final Class 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 publicMethods; // All public methods, including static methods. private final ObjectSwap[] childSwaps; // Any ObjectSwaps where the normal type is a subclass of this class. private final ConcurrentHashMap,ObjectSwap> childSwapMap, // Maps normal subclasses to ObjectSwaps. childUnswapMap; // Maps swap subclasses to ObjectSwaps. private final ObjectSwap[] swaps; // The object POJO swaps associated with this bean (if it has any). private final BuilderSwap builderSwap; // The builder swap associated with this bean (if it has one). 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 String example; // Example JSON. private final Map,Mutater> fromMutaters = new ConcurrentHashMap<>(); private final Map,Mutater> toMutaters = new ConcurrentHashMap<>(); private final Mutater stringMutater; private final Map,Annotation[]> annotationArrayMap = new ConcurrentHashMap<>(); private final Map,Optional> annotationLastMap = new ConcurrentHashMap<>(); private final Map> properties = new ConcurrentHashMap<>(); private final BiMap enumValues; private final SimpleReadWriteLock lock = new SimpleReadWriteLock(false); /** * 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 swaps * The {@link ObjectSwap} programmatically associated with this class. * Can be null. * @param childSwaps * The child {@link ObjectSwap ObjectSwaps} programmatically associated with this class. * These are the ObjectSwaps 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, ObjectSwap[] swaps, ObjectSwap[] childSwaps) { this.innerClass = innerClass; this.info = ClassInfo.of(innerClass); this.beanContext = beanContext; String notABeanReason = null; try (SimpleLock x = lock.write()) { // 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 && isCacheable(innerClass)) beanContext.cmCache.put(innerClass, this); ClassMetaBuilder builder = new ClassMetaBuilder(innerClass, beanContext, swaps, childSwaps); 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.swaps = builder.swaps.isEmpty() ? null : builder.swaps.toArray(new ObjectSwap[builder.swaps.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.childSwaps = builder.childSwaps; this.exampleMethod = builder.exampleMethod; this.exampleField = builder.exampleField; this.example = builder.example; this.args = null; this.stringMutater = builder.stringMutater; this.enumValues = builder.enumValues == null ? null : builder.enumValues.build(); } catch (ClassMetaRuntimeException e) { notABeanReason = e.getMessage(); throw e; } finally { this.notABeanReason = notABeanReason; } } /** * Generated classes shouldn't be cacheable to prevent needlessly filling up the cache. */ private static boolean isCacheable(Class c) { String n = c.getName(); char x = n.charAt(n.length()-1); // All generated classes appear to end with digits. if (x >= '0' && x <= '9') { if (n.indexOf("$$") != -1 || n.startsWith("sun") || n.startsWith("com.sun") || n.indexOf("$Proxy") != -1) return false; } return true; } /** * Causes thread to wait until constructor has exited. */ void waitForInit() { try (SimpleLock x = lock.read()) {} } /** * 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.info = mainType.info; this.implClass = mainType.implClass; this.childSwaps = mainType.childSwaps; 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.swaps = mainType.swaps; this.builderSwap = mainType.builderSwap; 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; this.enumValues = mainType.enumValues; } /** * Constructor for args-arrays. */ @SuppressWarnings("unchecked") ClassMeta(ClassMeta[] args) { this.innerClass = (Class) Object[].class; this.info = ClassInfo.of(innerClass); this.args = args; this.implClass = null; this.childSwaps = 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.swaps = null; this.builderSwap = null; this.initException = null; this.beanRegistry = null; this.exampleMethod = null; this.exampleField = null; this.example = null; this.stringMutater = null; this.enumValues = null; } @SuppressWarnings({"unchecked","rawtypes","hiding"}) private final class ClassMetaBuilder { Class innerClass; ClassInfo ci; Class 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 publicMethods = map(); ClassMeta keyType = null, valueType = null, elementType = null; String typePropertyName = null, notABeanReason = null, dictionaryName = null; Throwable initException = null; BeanMeta beanMeta = null; List swaps = list(); BuilderSwap builderSwap; InvocationHandler invocationHandler = null; BeanRegistry beanRegistry = null; ObjectSwap[] childSwaps; ConcurrentHashMap,ObjectSwap> childSwapMap, childUnswapMap; Method exampleMethod; Field exampleField; String example; Mutater stringMutater; BiMap.Builder enumValues; ClassMetaBuilder(Class innerClass, BeanContext beanContext, ObjectSwap[] swaps, ObjectSwap[] childSwaps) { this.innerClass = innerClass; this.beanContext = beanContext; BeanContext bc = beanContext; this.childSwaps = childSwaps; if (childSwaps == null) { this.childSwapMap = null; this.childUnswapMap = null; } else { this.childSwapMap = new ConcurrentHashMap<>(); this.childUnswapMap = new ConcurrentHashMap<>(); } Class 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) || ci.hasAnnotation(bc, 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 String[] fromStringMethodNames = {"fromString","fromValue","valueOf","parse","parseString","forName","forString"}; fromStringMethod = optional( ci.getPublicMethod( x -> x.isStatic() && x.isNotDeprecated() && x.hasReturnType(c) && x.hasParamTypes(String.class) && ArrayUtils.contains(x.getName(), fromStringMethodNames)) ).map(MethodInfo::inner) .orElse(null); // Find example() method if present. exampleMethod = optional( ci.getPublicMethod( x -> x.isStatic() && x.isNotDeprecated() && x.hasName("example") && x.hasFuzzyParamTypes(BeanSession.class)) ).map(MethodInfo::inner) .orElse(null); ci.forEachAllField(x -> x.hasAnnotation(bc, ParentProperty.class), x -> { if (x.isStatic()) throw new ClassMetaRuntimeException(c, "@ParentProperty used on invalid field ''{0}''. Must be static.", x); parentPropertyMethod = new Setter.FieldSetter(x.accessible().inner()); }); ci.forEachAllField(x -> x.hasAnnotation(bc, NameProperty.class), x -> { if (x.isStatic()) throw new ClassMetaRuntimeException(c, "@NameProperty used on invalid field ''{0}''. Must be static.", x); namePropertyMethod = new Setter.FieldSetter(x.accessible().inner()); }); ci.forEachDeclaredField(x -> x.hasAnnotation(bc, Example.class), x -> { if (! (x.isStatic() && ci.isParentOf(x.getType().inner()))) throw new ClassMetaRuntimeException(c, "@Example used on invalid field ''{0}''. Must be static and an instance of the type.", x); exampleField = x.accessible().inner(); }); // Find @NameProperty and @ParentProperty methods if present. List methods = ci.getMethods(); for (int i = methods.size()-1; i >=0; i--) { MethodInfo m = methods.get(i); if (m.hasAnnotation(bc, 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(bc, 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()); } } ci.forEachDeclaredMethod(m -> m.hasAnnotation(bc, Example.class), m -> { 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.toString()); m.setAccessible(); exampleMethod = m.inner(); }); // Note: Primitive types are normally abstract. isAbstract = ci.isAbstract() && ci.isNotPrimitive(); // Find constructor(String) method if present. ci.forEachPublicConstructor(cs -> cs.isPublic() && cs.isNotDeprecated(), cs -> { List 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(); ci.forEachPublicMethod( MethodInfo::isNotDeprecated, x -> publicMethods.put(x.getSignature(), x.inner()) ); BeanFilter beanFilter = findBeanFilter(bc); MarshalledFilter marshalledFilter = findMarshalledFilter(bc); addAll(this.swaps, swaps); if (bc != null) this.builderSwap = BuilderSwap.findSwapFromObjectClass(bc, c, bc.getBeanConstructorVisibility(), bc.getBeanMethodVisibility()); findSwaps(this.swaps, bc); if (beanFilter != null) { example = beanFilter.getExample(); implClass = (Class) beanFilter.getImplClass(); } if (marshalledFilter != null) { if (example == null) example = marshalledFilter.getExample(); if (implClass == null) implClass = (Class) marshalledFilter.getImplClass(); } if (innerClass != Object.class) { ClassInfo x = implClass == null ? ci : ClassInfo.of(implClass); noArgConstructor = x.getPublicConstructor(ConstructorInfo::hasNoParams); } 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 || 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, bc, beanFilter, null, implClass == null ? null : noArgConstructor); 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 && bc != null && bc.isUseInterfaceProxies() && innerClass.isInterface()) invocationHandler = new BeanProxyInvocationHandler(beanMeta); if (bc != null) { bc.forEachAnnotation(Bean.class, c, x -> true, x -> { if (x.dictionary().length != 0) beanRegistry = new BeanRegistry(bc, null, x.dictionary()); // This could be a non-bean POJO with a type name. if (dictionaryName == null && ! x.typeName().isEmpty()) dictionaryName = x.typeName(); }); } if (example == null && bc != null) { bc.forEachAnnotation(Example.class, c, x -> ! x.value().isEmpty(), x -> example = x.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 = "1.0"; else if (isDouble()) example = "1.0"; break; case ENUM: Iterator i = EnumSet.allOf((Class)c).iterator(); if (i.hasNext()) example = beanContext.isUseEnumNames() ? i.next().name() : i.next().toString(); break; case NUMBER: if (isShort()) example = "1"; else if (isInteger()) example = "1"; else if (isLong()) example = "1"; 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.stringMutater = Mutaters.get(String.class, c); if (cc == ENUM) { Class ec = (Class>)c; boolean useEnumNames = bc != null && bc.isUseEnumNames(); enumValues = BiMap.create(); enumValues.unmodifiable(); stream(ec.getEnumConstants()).forEach(x -> enumValues.add(x, useEnumNames ? x.name() : x.toString())); } } private BeanFilter findBeanFilter(BeanContext bc) { try { List ba = info.getAnnotations(bc, Bean.class); if (! ba.isEmpty()) return BeanFilter.create(innerClass).applyAnnotations(ba).build(); } catch (Exception e) { throw asRuntimeException(e); } return null; } private MarshalledFilter findMarshalledFilter(BeanContext bc) { try { List ba = info.getAnnotations(bc, Marshalled.class); if (! ba.isEmpty()) return MarshalledFilter.create(innerClass).applyAnnotations(ba).build(); } catch (Exception e) { throw asRuntimeException(e); } return null; } private void findSwaps(List l, BeanContext bc) { if (bc != null) bc.forEachAnnotation(Swap.class, innerClass, x -> true, x -> l.add(createSwap(x))); ObjectSwap defaultSwap = DefaultSwaps.find(ci); if (defaultSwap == null) defaultSwap = AutoObjectSwap.find(bc, ci); if (defaultSwap == null) defaultSwap = AutoNumberSwap.find(bc, ci); if (defaultSwap == null) defaultSwap = AutoMapSwap.find(bc, ci); if (defaultSwap == null) defaultSwap = AutoListSwap.find(bc, ci); if (defaultSwap != null) l.add(defaultSwap); } private ObjectSwap createSwap(Swap s) { Class c = s.value(); if (ClassUtils.isVoid(c)) c = s.impl(); ClassInfo ci = ClassInfo.of(c); if (ci.isChildOf(ObjectSwap.class)) { ObjectSwap ps = BeanCreator.of(ObjectSwap.class).type(c).run(); if (s.mediaTypes().length > 0) ps.forMediaTypes(MediaType.ofAll(s.mediaTypes())); if (! s.template().isEmpty()) ps.withTemplate(s.template()); return ps; } if (ci.isChildOf(Surrogate.class)) { List> l = SurrogateSwap.findObjectSwaps(c, beanContext); if (! l.isEmpty()) return (ObjectSwap)l.iterator().next(); } throw new ClassMetaRuntimeException(c, "Invalid swap class ''{0}'' specified. Must extend from ObjectSwap 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 null. */ public ClassInfo getInfo() { return info; } /** * 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#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 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 info.isChildOf(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 info.isParentOf(c); } /** * Returns true if this class or any child classes has a {@link ObjectSwap} 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 ObjectSwap} associated with it. */ protected boolean hasChildSwaps() { return childSwaps != null; } /** * Returns the {@link ObjectSwap} 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 ObjectSwap} or null if none were found. */ protected ObjectSwap getChildObjectSwapForSwap(Class normalClass) { if (childSwapMap != null) { ObjectSwap s = childSwapMap.get(normalClass); if (s == null) { for (ObjectSwap f : childSwaps) if (s == null && f.getNormalClass().isParentOf(normalClass)) s = f; if (s == null) s = ObjectSwap.NULL; ObjectSwap s2 = childSwapMap.putIfAbsent(normalClass, s); if (s2 != null) s = s2; } if (s == ObjectSwap.NULL) return null; return s; } return null; } /** * Returns the {@link ObjectSwap} 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 ObjectSwap} or null if none were found. */ protected ObjectSwap getChildObjectSwapForUnswap(Class swapClass) { if (childUnswapMap != null) { ObjectSwap s = childUnswapMap.get(swapClass); if (s == null) { for (ObjectSwap f : childSwaps) if (s == null && f.getSwapClass().isParentOf(swapClass)) s = f; if (s == null) s = ObjectSwap.NULL; ObjectSwap s2 = childUnswapMap.putIfAbsent(swapClass, s); if (s2 != null) s = s2; } if (s == ObjectSwap.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 The class from which to locate the no-arg constructor. * @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({"unchecked"}) protected static Constructor findNoArgConstructor(Class c, Visibility v) { ClassInfo ci = ClassInfo.of(c); if (ci.isAbstract()) return null; boolean isMemberClass = ci.isMemberClass() && ci.isNotStatic(); ConstructorInfo cc = ci.getPublicConstructor( x -> x.isVisible(v) && x.isNotDeprecated() && x.hasNumParams(isMemberClass ? 1 : 0) ); if (cc != null) return (Constructor) v.transform(cc.inner()); 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 ObjectSwap} 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. */ public ClassMeta getSerializedClassMeta(BeanSession session) { ObjectSwap ps = getSwap(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. * @param jpSession The JSON parser for parsing examples into POJOs. * @return The serialized class type, or this object if no swap is associated with the class. */ @SuppressWarnings({"unchecked","rawtypes"}) public T getExample(BeanSession session, JsonParserSession jpSession) { try { if (example != null) return jpSession.parse(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, jpSession); 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, jpSession); 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, jpSession); Object ktExample = getKeyType().getExample(session, jpSession); 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 the specified class is an exact match for this metadata. * * @param value The value to check against. * @return true if the specified class is an exact match for this metadata. */ public boolean is(Class value) { return eq(innerClass, value); } /** * 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 isChildOf(Class c) { return info.isChildOf(c); } /** * 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 Optional}. * * @return true if this class is a subclass of {@link Optional}. */ public boolean isOptional() { return cc == OPTIONAL; } /** * 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 is a subclass of {@link Collection} or is an array or {@link Optional}. * * @return true 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 true if this class extends from {@link Set}. * * @return true if this class extends from {@link Set}. */ public boolean isSet() { return cc == COLLECTION && info.isChildOf(Set.class); } /** * Returns true if this class extends from {@link List}. * * @return true if this class extends from {@link List}. */ public boolean isList() { return cc == COLLECTION && info.isChildOf(List.class); } /** * 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 == ClassCategory.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 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} or {@link Calendar} or {@link Temporal}. * * @return true if this class is a {@link Date} or {@link Calendar} or {@link Temporal}. */ public boolean isDateOrCalendarOrTemporal() { return cc == DATE || info.isChildOf(Temporal.class); } /** * Returns true if this class is a {@link Date}. * * @return true if this class is a {@link Date}. */ public boolean isDate() { return cc == DATE && info.isChildOf(Date.class); } /** * Returns true if this class is a {@link Temporal}. * * @return true if this class is a {@link Temporal}. */ public boolean isTemporal() { return info.isChildOf(Temporal.class); } /** * Returns true if this class is a {@link Calendar}. * * @return true if this class is a {@link Calendar}. */ public boolean isCalendar() { return cc == DATE && info.isChildOf(Calendar.class); } /** * 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 ObjectSwap} 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 ObjectSwap} associated with this class, or null if there are no POJO swaps associated with * this class. */ public ObjectSwap getSwap(BeanSession session) { if (swaps != null) { int matchQuant = 0, matchIndex = -1; for (int i = 0; i < swaps.length; i++) { int q = swaps[i].match(session); if (q > matchQuant) { matchQuant = q; matchIndex = i; } } if (matchIndex > -1) return swaps[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 ConstructorInfo getConstructor() { return noArgConstructor; } /** * Returns the no-arg constructor for this class based on the {@link Marshalled#implClass()} value. * * @param conVis The constructor visibility. * @return The no-arg constructor for this class, or null if it does not exist. */ public ConstructorInfo getImplClassConstructor(Visibility conVis) { if (implClass != null) return ClassInfo.of(implClass).getNoArgConstructor(conVis); return null; } /** * 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 || getProxyInvocationHandler() != null || (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 && noArgConstructor.hasParamTypes(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 || beanMeta.constructor == null) return false; if (isMemberClass) return outer != null && beanMeta.constructor.hasParamTypes(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 && 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 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; } /** * If this is an {@link Optional}, returns an empty optional. * *

* Note that if this is a nested optional, will recursively create empty optionals. * * @return An empty optional, or null if this isn't an optional. */ public Optional getOptionalDefault() { if (isOptional()) return optional(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 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 ExecutableException Exception occurred on invoked constructor/method/field. */ @SuppressWarnings({ "unchecked" }) public T newInstanceFromString(Object outer, String arg) throws ExecutableException { if (isEnum()) { T t = (T)enumValues.getKey(arg); if (t == null && ! beanContext.isIgnoreUnknownEnumValues()) throw new ExecutableException("Could not resolve enum value '"+arg+"' on class '"+getInnerClass().getName()+"'"); return t; } 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.invoke(outer, arg); return c.invoke(arg); } throw new ExecutableException("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 null 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.invoke(); 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 ExecutableException Exception occurred on invoked constructor/method/field. */ public T newInstance(Object outer) throws ExecutableException { if (isMemberClass) return noArgConstructor.invoke(outer); return newInstance(); } /** * 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 passed-in string builder. */ 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 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 info.isParentOf(o.getClass()) || (isPrimitive() && info.getPrimitiveWrapper() == 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 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(); } /** * 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 hasReaderMutater() { return hasMutaterFrom(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 Mutater getReaderMutater() { return getFromMutater(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 hasInputStreamMutater() { return hasMutaterFrom(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 Mutater getInputStreamMutater() { return getFromMutater(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 hasStringMutater() { return stringMutater != null; } /** * Returns the transform for this class for creating instances from a String. * * @return The transform, or null if no such transform exists. */ public Mutater getStringMutater() { return stringMutater; } /** * 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 hasMutaterFrom(Class c) { return getFromMutater(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 hasMutaterFrom(ClassMeta c) { return getFromMutater(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 hasMutaterTo(Class c) { return getToMutater(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 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 The transform-to class. * @param o The object to transform. * @param c The class * @return The transformed object. */ @SuppressWarnings({"unchecked","rawtypes"}) public O mutateTo(Object o, Class c) { Mutater t = getToMutater(c); return (O)(t == null ? null : t.mutate(o)); } /** * Transforms the specified object into an instance of this class. * * @param The transform-to class. * @param o The object to transform. * @param c The class * @return The transformed object. */ public O mutateTo(Object o, ClassMeta c) { return mutateTo(o, c.getInnerClass()); } /** * Returns the transform for this class for creating instances from other object types. * * @param The transform-from class. * @param c The transform-from class. * @return The transform, or null if no such transform exists. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public Mutater getFromMutater(Class 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 The transform-to class. * @param c The transform-from class. * @return The transform, or null if no such transform exists. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public Mutater getToMutater(Class 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 getInnerClass().getAnnotation(a) != null. * * @param a The annotation to check for. * @return true if the inner class has the annotation. */ public boolean hasAnnotation(Class a) { return getLastAnnotation(a) != null; } /** * Shortcut for calling getInnerClass().getAnnotation(a). * * @param The annotation type to look for. * @param a The annotation to retrieve. * @return The specified annotation, or null if the class does not have the specified annotation. */ @SuppressWarnings("unchecked") public A getLastAnnotation(Class a) { Optional o = (Optional)annotationLastMap.get(a); if (o == null) { if (beanContext == null) return info.getAnnotation(BeanContext.DEFAULT, a); o = optional(info.getAnnotation(beanContext, a)); annotationLastMap.put(a, o); } return o.orElse(null); } /** * Performs an action on all matching annotations of the specified type defined on this class or parent classes/interfaces in parent-to-child order. * * @param The annotation type to look for. * @param type The annotation to search for. * @param filter A predicate to apply to the entries to determine if action should be performed. Can be null. * @param action An action to perform on the entry. * @return This object. */ public ClassMeta forEachAnnotation(Class type, Predicate filter, Consumer action) { A[] array = annotationArray(type); if (array == null) { if (beanContext == null) info.forEachAnnotation(BeanContext.DEFAULT, type, filter, action); return this; } for (A a : array) consume(filter, action, a); return this; } /** * Returns the first matching annotation on this class or parent classes/interfaces in parent-to-child order. * * @param The annotation type to look for. * @param type The annotation to search for. * @param filter A predicate to apply to the entries to determine if annotation should be used. Can be null. * @return This object. */ public Optional firstAnnotation(Class type, Predicate filter) { A[] array = annotationArray(type); if (array == null) { if (beanContext == null) return Optional.ofNullable(info.firstAnnotation(BeanContext.DEFAULT, type, filter)); return Optional.empty(); } for (A a : array) if (test(filter, a)) return Optional.of(a); return Optional.empty(); } /** * Returns the last matching annotation on this class or parent classes/interfaces in parent-to-child order. * * @param The annotation type to look for. * @param type The annotation to search for. * @param filter A predicate to apply to the entries to determine if annotation should be used. Can be null. * @return This object. */ public Optional lastAnnotation(Class type, Predicate filter) { A[] array = annotationArray(type); if (array == null) { if (beanContext == null) return Optional.ofNullable(info.lastAnnotation(BeanContext.DEFAULT, type, filter)); return Optional.empty(); } for (int i = array.length-1; i >= 0; i--) if (test(filter, array[i])) return Optional.of(array[i]); return Optional.empty(); } @SuppressWarnings("unchecked") private A[] annotationArray(Class type) { A[] array = (A[])annotationArrayMap.get(type); if (array == null && beanContext != null) { List l = list(); info.forEachAnnotation(beanContext, type, x-> true, x -> l.add(x)); array = (A[])Array.newInstance(type, l.size()); for (int i = 0; i < l.size(); i++) Array.set(array, i, l.get(i)); annotationArrayMap.put(type, array); } return array; } /** * Returns a calculated property on this context. * * @param The type to convert the property to. * @param name The name of the property. * @param function The function used to create this property. * @return The property value. Never null. */ @SuppressWarnings("unchecked") public Optional getProperty(String name, Function,T2> function) { Optional t = (Optional) properties.get(name); if (t == null) { t = optional(function.apply(this)); properties.put(name, t); } return t; } @Override /* Object */ public int hashCode() { return innerClass.hashCode(); } @Override /* Object */ public boolean equals(Object o) { return (o instanceof ClassMeta) && eq(this, (ClassMeta)o, (x,y)->eq(x.innerClass, y.innerClass)); } }