Repository: incubator-juneau Updated Branches: refs/heads/master b37d99bac -> 75b0d8ee6
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/75b0d8ee/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java ---------------------------------------------------------------------- diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java new file mode 100644 index 0000000..70ffaf3 --- /dev/null +++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java @@ -0,0 +1,1776 @@ +// *************************************************************************************************************************** +// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * +// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * +// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * +// * with the License. You may obtain a copy of the License at * +// * * +// * http://www.apache.org/licenses/LICENSE-2.0 * +// * * +// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * +// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * +// * specific language governing permissions and limitations under the License. * +// *************************************************************************************************************************** +package org.apache.juneau; + +import static org.apache.juneau.Visibility.*; +import static org.apache.juneau.internal.ClassUtils.*; +import static org.apache.juneau.internal.StringUtils.*; + +import java.beans.*; +import java.io.*; +import java.lang.reflect.*; +import java.util.*; +import java.util.concurrent.*; + +import org.apache.juneau.annotation.*; +import org.apache.juneau.http.*; +import org.apache.juneau.internal.*; +import org.apache.juneau.json.*; +import org.apache.juneau.parser.*; +import org.apache.juneau.serializer.*; +import org.apache.juneau.transform.*; + +/** + * Core class of the Juneau architecture. + * + * <p> + * This class servers multiple purposes: + * <ul class='spaced-list'> + * <li> + * Provides the ability to wrap beans inside {@link Map} interfaces. + * <li> + * Serves as a repository for metadata on POJOs, such as associated {@link BeanFilter beanFilters}, + * {@link PropertyNamer property namers}, etc... which are used to tailor how POJOs are serialized and parsed. + * <li> + * Serves as a common utility class for all {@link Serializer Serializers} and {@link Parser Parsers} + * for serializing and parsing Java beans. + * </ul> + * + * <p> + * All serializer and parser contexts extend from this context. + * + * <h5 class='topic'>Bean Contexts</h5> + * + * Bean contexts are created through the {@link PropertyStore#getContext(Class)} method. + * These context objects are read-only, reusable, and thread-safe. + * The {@link PropertyStore} class will typically cache copies of <code>Context</code> objects based on + * the current settings on the factory. + * + * <p> + * Each bean context maintains a cache of {@link ClassMeta} objects that describe information about classes encountered. + * These <code>ClassMeta</code> objects are time-consuming to construct. + * Therefore, instances of {@link BeanContext} that share the same <js>"BeanContext.*"</js> property values share + * the same cache. This allows for efficient reuse of <code>ClassMeta</code> objects so that the information about + * classes only needs to be calculated once. + * Because of this, many of the properties defined on the {@link BeanContext} class cannot be overridden on the session. + * + * <h5 class='topic'>Bean Sessions</h5> + * + * Whereas <code>BeanContext</code> objects are permanent, unchangeable, cached, and thread-safe, + * {@link BeanSession} objects are ephemeral and not thread-safe. + * They are meant to be used as quickly-constructed scratchpads for creating bean maps. + * {@link BeanMap} objects can only be created through the session. + * + * <h5 class='topic'>BeanContext configuration properties</h5> + * + * <code>BeanContexts</code> have several configuration properties that can be used to tweak behavior on how beans are + * handled. These are denoted as the static <jsf>BEAN_*</jsf> fields on this class. + * + * <p> + * Some settings (e.g. {@link BeanContext#BEAN_beansRequireDefaultConstructor}) are used to differentiate between bean + * and non-bean classes. + * Attempting to create a bean map around one of these objects will throw a {@link BeanRuntimeException}. + * The purpose for this behavior is so that the serializers can identify these non-bean classes and convert them to + * plain strings using the {@link Object#toString()} method. + * + * <p> + * Some settings (e.g. {@link BeanContext#BEAN_beanFieldVisibility}) are used to determine what kinds of properties are + * detected on beans. + * + * <p> + * Some settings (e.g. {@link BeanContext#BEAN_beanMapPutReturnsOldValue}) change the runtime behavior of bean maps. + * + * <p> + * Settings are specified using the {@link PropertyStore#setProperty(String, Object)} method and related convenience + * methods. + * + * <h5 class='section'>Example:</h5> + * + * <p class='bcode'> + * <jc>// Construct a context from scratch.</jc> + * BeanContext beanContext = PropertyStore.<jsm>create</jsm>() + * .property(BeanContext.<jsf>BEAN_beansRequireDefaultConstructor</jsf>, <jk>true</jk>) + * .notBeanClasses(Foo.<jk>class</jk>) + * .getBeanContext(); + * + * <jc>// Clone an existing property store.</jc> + * BeanContext beanContext = PropertyStore.<jsm>create</jsm>(otherConfig) + * .property(BeanContext.<jsf>BEAN_beansRequireDefaultConstructor</jsf>, <jk>true</jk>) + * .notBeanClasses(Foo.<jk>class</jk>) + * .getBeanContext(); + * </p> + * + * <h5 class='topic'>Bean Maps</h5> + * + * {@link BeanMap BeanMaps} are wrappers around Java beans that allow properties to be retrieved and + * set using the common {@link Map#put(Object,Object)} and {@link Map#get(Object)} methods. + * + * <p> + * Bean maps are created in two ways... + * <ol> + * <li>{@link BeanSession#toBeanMap(Object) BeanSession.toBeanMap()} - Wraps an existing bean inside a {@code Map} + * wrapper. + * <li>{@link BeanSession#newBeanMap(Class) BeanSession.newBeanMap()} - Create a new bean instance wrapped in a + * {@code Map} wrapper. + * </ol> + * + * <h5 class='section'>Example:</h5> + * + * <p class='bcode'> + * <jc>// A sample bean class</jc> + * <jk>public class</jk> Person { + * <jk>public</jk> String getName(); + * <jk>public void</jk> setName(String name); + * <jk>public int</jk> getAge(); + * <jk>public void</jk> setAge(<jk>int</jk> age); + * } + * + * <jc>// Create a new bean session</jc> + * BeanSession session = BeanContext.<jsf>DEFAULT</jsf>.createSession(); + * + * <jc>// Wrap an existing bean in a new bean map</jc> + * BeanMap<Person> m1 = session.toBeanMap(<jk>new</jk> Person()); + * m1.put(<js>"name"</js>, <js>"John Smith"</js>); + * m1.put(<js>"age"</js>, 45); + * + * <jc>// Create a new bean instance wrapped in a new bean map</jc> + * BeanMap<Person> m2 = session.newBeanMap(Person.<jk>class</jk>); + * m2.put(<js>"name"</js>, <js>"John Smith"</js>); + * m2.put(<js>"age"</js>, 45); + * Person p = m2.getBean(); <jc>// Get the bean instance that was created.</jc> + * </p> + * + * <h5 class='topic'>Bean Annotations</h5> + * + * This package contains annotations that can be applied to class definitions to override what properties are detected + * on a bean. + * + * <h5 class='section'>Example:</h5> + * + * <p class='bcode'> + * <jc>// Bean class definition where only property 'name' is detected.</jc> + * <ja>@Bean</ja>(properties=<js>"name"</js>) + * <jk>public class</jk> Person { + * <jk>public</jk> String getName(); + * <jk>public void</jk> setName(String name); + * <jk>public int</jk> getAge(); + * <jk>public void</jk> setAge(<jk>int</jk> age); + * } + * </p> + * + * <p> + * See {@link Bean @Bean} and {@link BeanProperty @BeanProperty} for more information. + * + * <h5 class='topic'>Beans with read-only properties</h5> + * + * Bean maps can also be defined on top of beans with read-only properties by adding a + * {@link BeanConstructor @BeanConstructor} annotation to one of the constructors on the + * bean class. This will allow read-only properties to be set through constructor arguments. + * + * <p> + * When the <code>@BeanConstructor</code> annotation is present, bean instantiation is delayed until the call to + * {@link BeanMap#getBean()}. + * Until then, bean property values are stored in a local cache until <code>getBean()</code> is called. + * Because of this additional caching step, parsing into read-only beans tends to be slower and use more memory than + * parsing into beans with writable properties. + * + * <p> + * Attempting to call {@link BeanMap#put(String,Object)} on a read-only property after calling {@link BeanMap#getBean()} + * will result in a {@link BeanRuntimeException} being thrown. + * Multiple calls to {@link BeanMap#getBean()} will return the same bean instance. + * + * <p> + * Beans can be defined with a combination of read-only and read-write properties. + * + * <p> + * See {@link BeanConstructor @BeanConstructor} for more information. + * + * <h5 class='topic'>BeanFilters and PojoSwaps</h5> + * + * {@link BeanFilter BeanFilters} and {@link PojoSwap PojoSwaps} are used to tailor how beans and POJOs are handled. + * <ol class='spaced-list'> + * <li> + * {@link BeanFilter} - Allows you to tailor handling of bean classes. + * This class can be considered a programmatic equivalent to the {@link Bean} annotation when + * annotating classes are not possible (e.g. you don't have access to the source). + * This includes specifying which properties are visible and the ability to programmatically override the + * execution of properties. + * <li> + * {@link PojoSwap} - Allows you to swap out non-serializable objects with serializable replacements. + * </ol> + * + * <p> + * See <a class='doclink' href='transform/package-summary.html#TOC'>org.apache.juneau.transform</a> for more + * information. + * + * <h5 class='topic'>ClassMetas</h5> + * + * The {@link ClassMeta} class is a wrapper around {@link Class} object that provides cached information about that + * class (e.g. whether it's a {@link Map} or {@link Collection} or bean). + * + * <p> + * As a general rule, it's best to reuse bean contexts (and therefore serializers and parsers too) whenever possible + * since it takes some time to populate the internal {@code ClassMeta} object cache. + * By reusing bean contexts, the class type metadata only needs to be calculated once which significantly improves + * performance. + * + * <p> + * See {@link ClassMeta} for more information. + */ +@SuppressWarnings({"unchecked","rawtypes"}) +public class BeanContext extends Context { + + /** + * <b>Configuration property:</b> Beans require no-arg constructors. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.beansRequireDefaultConstructor"</js> + * <li><b>Data type:</b> <code>Boolean</code> + * <li><b>Default:</b> <jk>false</jk> + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * If <jk>true</jk>, a Java class must implement a default no-arg constructor to be considered a bean. + * + * <p> + * The {@link Bean @Bean} annotation can be used on a class to override this setting when <jk>true</jk>. + */ + public static final String BEAN_beansRequireDefaultConstructor = "BeanContext.beansRequireDefaultConstructor"; + + /** + * <b>Configuration property:</b> Beans require {@link Serializable} interface. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.beansRequireSerializable"</js> + * <li><b>Data type:</b> <code>Boolean</code> + * <li><b>Default:</b> <jk>false</jk> + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * If <jk>true</jk>, a Java class must implement the {@link Serializable} interface to be considered a bean. + * + * <p> + * The {@link Bean @Bean} annotation can be used on a class to override this setting when <jk>true</jk>. + */ + public static final String BEAN_beansRequireSerializable = "BeanContext.beansRequireSerializable"; + + /** + * <b>Configuration property:</b> Beans require setters for getters. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.beansRequireSettersForGetters"</js> + * <li><b>Data type:</b> <code>Boolean</code> + * <li><b>Default:</b> <jk>false</jk> + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * If <jk>true</jk>, only getters that have equivalent setters will be considered as properties on a bean. + * Otherwise, they will be ignored. + */ + public static final String BEAN_beansRequireSettersForGetters = "BeanContext.beansRequireSettersForGetters"; + + /** + * <b>Configuration property:</b> Beans require at least one property. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.beansRequireSomeProperties"</js> + * <li><b>Data type:</b> <code>Boolean</code> + * <li><b>Default:</b> <jk>true</jk> + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * If <jk>true</jk>, then a Java class must contain at least 1 property to be considered a bean. + * + * <p> + * The {@link Bean @Bean} annotation can be used on a class to override this setting when <jk>true</jk>. + */ + public static final String BEAN_beansRequireSomeProperties = "BeanContext.beansRequireSomeProperties"; + + /** + * <b>Configuration property:</b> {@link BeanMap#put(String,Object) BeanMap.put()} method will return old property + * value. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.beanMapPutReturnsOldValue"</js> + * <li><b>Data type:</b> <code>Boolean</code> + * <li><b>Default:</b> <jk>false</jk> + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * If <jk>true</jk>, then the {@link BeanMap#put(String,Object) BeanMap.put()} method will return old property + * values. + * + * <p> + * Disabled by default because it introduces a slight performance penalty. + */ + public static final String BEAN_beanMapPutReturnsOldValue = "BeanContext.beanMapPutReturnsOldValue"; + + /** + * <b>Configuration property:</b> Look for bean constructors with the specified minimum visibility. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.beanConstructorVisibility"</js> + * <li><b>Data type:</b> {@link Visibility} + * <li><b>Default:</b> {@link Visibility#PUBLIC} + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + */ + public static final String BEAN_beanConstructorVisibility = "BeanContext.beanConstructorVisibility"; + + /** + * <b>Configuration property:</b> Look for bean classes with the specified minimum visibility. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.beanClassVisibility"</js> + * <li><b>Data type:</b> {@link Visibility} + * <li><b>Default:</b> {@link Visibility#PUBLIC} + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * Classes are not considered beans unless they meet the minimum visibility requirements. + * For example, if the visibility is <code>PUBLIC</code> and the bean class is <jk>protected</jk>, then the class + * will not be interpreted as a bean class. + */ + public static final String BEAN_beanClassVisibility = "BeanContext.beanClassVisibility"; + + /** + * <b>Configuration property:</b> Look for bean fields with the specified minimum visibility. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.beanFieldVisibility"</js> + * <li><b>Data type:</b> {@link Visibility} + * <li><b>Default:</b> {@link Visibility#PUBLIC} + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * Fields are not considered bean properties unless they meet the minimum visibility requirements. + * For example, if the visibility is <code>PUBLIC</code> and the bean field is <jk>protected</jk>, then the field + * will not be interpreted as a bean property. + * + * <p> + * Use {@link Visibility#NONE} to prevent bean fields from being interpreted as bean properties altogether. + */ + public static final String BEAN_beanFieldVisibility = "BeanContext.beanFieldVisibility"; + + /** + * <b>Configuration property:</b> Look for bean methods with the specified minimum visibility. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.methodVisibility"</js> + * <li><b>Data type:</b> {@link Visibility} + * <li><b>Default:</b> {@link Visibility#PUBLIC} + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * Methods are not considered bean getters/setters unless they meet the minimum visibility requirements. + * For example, if the visibility is <code>PUBLIC</code> and the bean method is <jk>protected</jk>, then the method + * will not be interpreted as a bean getter or setter. + */ + public static final String BEAN_methodVisibility = "BeanContext.methodVisibility"; + + /** + * <b>Configuration property:</b> Use Java {@link Introspector} for determining bean properties. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.useJavaBeanIntrospector"</js> + * <li><b>Data type:</b> <code>Boolean</code> + * <li><b>Default:</b> <jk>false</jk> + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * Using the built-in Java bean introspector will not pick up fields or non-standard getters/setters. + * Most {@link Bean @Bean} annotations will be ignored. + */ + public static final String BEAN_useJavaBeanIntrospector = "BeanContext.useJavaBeanIntrospector"; + + /** + * <b>Configuration property:</b> Use interface proxies. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.useInterfaceProxies"</js> + * <li><b>Data type:</b> <code>Boolean</code> + * <li><b>Default:</b> <jk>true</jk> + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * If <jk>true</jk>, then interfaces will be instantiated as proxy classes through the use of an + * {@link InvocationHandler} if there is no other way of instantiating them. + */ + public static final String BEAN_useInterfaceProxies = "BeanContext.useInterfaceProxies"; + + /** + * <b>Configuration property:</b> Ignore unknown properties. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.ignoreUnknownBeanProperties"</js> + * <li><b>Data type:</b> <code>Boolean</code> + * <li><b>Default:</b> <jk>false</jk> + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * If <jk>true</jk>, trying to set a value on a non-existent bean property will silently be ignored. + * Otherwise, a {@code RuntimeException} is thrown. + */ + public static final String BEAN_ignoreUnknownBeanProperties = "BeanContext.ignoreUnknownBeanProperties"; + + /** + * <b>Configuration property:</b> Ignore unknown properties with null values. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.ignoreUnknownNullBeanProperties"</js> + * <li><b>Data type:</b> <code>Boolean</code> + * <li><b>Default:</b> <jk>true</jk> + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * If <jk>true</jk>, trying to set a <jk>null</jk> value on a non-existent bean property will silently be ignored. + * Otherwise, a {@code RuntimeException} is thrown. + */ + public static final String BEAN_ignoreUnknownNullBeanProperties = "BeanContext.ignoreUnknownNullBeanProperties"; + + /** + * <b>Configuration property:</b> Ignore properties without setters. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.ignorePropertiesWithoutSetters"</js> + * <li><b>Data type:</b> <code>Boolean</code> + * <li><b>Default:</b> <jk>true</jk> + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * If <jk>true</jk>, trying to set a value on a bean property without a setter will silently be ignored. + * Otherwise, a {@code RuntimeException} is thrown. + */ + public static final String BEAN_ignorePropertiesWithoutSetters = "BeanContext.ignorePropertiesWithoutSetters"; + + /** + * <b>Configuration property:</b> Ignore invocation errors on getters. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.ignoreInvocationExceptionsOnGetters"</js> + * <li><b>Data type:</b> <code>Boolean</code> + * <li><b>Default:</b> <jk>false</jk> + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * If <jk>true</jk>, errors thrown when calling bean getter methods will silently be ignored. + * Otherwise, a {@code BeanRuntimeException} is thrown. + */ + public static final String BEAN_ignoreInvocationExceptionsOnGetters = "BeanContext.ignoreInvocationExceptionsOnGetters"; + + /** + * <b>Configuration property:</b> Ignore invocation errors on setters. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.ignoreInvocationExceptionsOnSetters"</js> + * <li><b>Data type:</b> <code>Boolean</code> + * <li><b>Default:</b> <jk>false</jk> + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * If <jk>true</jk>, errors thrown when calling bean setter methods will silently be ignored. + * Otherwise, a {@code BeanRuntimeException} is thrown. + */ + public static final String BEAN_ignoreInvocationExceptionsOnSetters = "BeanContext.ignoreInvocationExceptionsOnSetters"; + + /** + * <b>Configuration property:</b> Sort bean properties in alphabetical order. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.sortProperties"</js> + * <li><b>Data type:</b> <code>Boolean</code> + * <li><b>Default:</b> <jk>false</jk> + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * When <jk>true</jk>, all bean properties will be serialized and access in alphabetical order. + * Otherwise, the natural order of the bean properties is used which is dependent on the JVM vendor. + * On IBM JVMs, the bean properties are ordered based on their ordering in the Java file. + * On Oracle JVMs, the bean properties are not ordered (which follows the official JVM specs). + * + * <p> + * This property is disabled by default so that IBM JVM users don't have to use {@link Bean @Bean} annotations + * to force bean properties to be in a particular order and can just alter the order of the fields/methods + * in the Java file. + */ + public static final String BEAN_sortProperties = "BeanContext.sortProperties"; + + /** + * <b>Configuration property:</b> Packages whose classes should not be considered beans. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.notBeanPackages.set"</js> + * <li><b>Data type:</b> <code>Set<String></code> + * <li><b>Default:</b> + * <ul> + * <li><code>java.lang</code> + * <li><code>java.lang.annotation</code> + * <li><code>java.lang.ref</code> + * <li><code>java.lang.reflect</code> + * <li><code>java.io</code> + * <li><code>java.net</code> + * <li><code>java.nio.*</code> + * <li><code>java.util.*</code> + * </ul> + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * When specified, the current list of ignore packages are appended to. + * + * <p> + * Any classes within these packages will be serialized to strings using {@link Object#toString()}. + * + * <p> + * Note that you can specify prefix patterns to include all subpackages. + */ + public static final String BEAN_notBeanPackages = "BeanContext.notBeanPackages.set"; + + /** + * <b>Configuration property:</b> Add to packages whose classes should not be considered beans. + */ + public static final String BEAN_notBeanPackages_add = "BeanContext.notBeanPackages.set.add"; + + /** + * <b>Configuration property:</b> Remove from packages whose classes should not be considered beans. + */ + public static final String BEAN_notBeanPackages_remove = "BeanContext.notBeanPackages.set.remove"; + + /** + * <b>Configuration property:</b> Classes to be excluded from consideration as being beans. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.notBeanClasses.set"</js> + * <li><b>Data type:</b> <code>Set<Class></code> + * <li><b>Default:</b> empty set + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * Not-bean classes are typically converted to <code>Strings</code> during serialization even if they appear to be + * bean-like. + */ + public static final String BEAN_notBeanClasses = "BeanContext.notBeanClasses.set"; + + /** + * <b>Configuration property:</b> Add to classes that should not be considered beans. + */ + public static final String BEAN_notBeanClasses_add = "BeanContext.notBeanClasses.set.add"; + + /** + * <b>Configuration property:</b> Remove from classes that should not be considered beans. + */ + public static final String BEAN_notBeanClasses_remove = "BeanContext.notBeanClasses.set.remove"; + + /** + * <b>Configuration property:</b> Bean filters to apply to beans. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.beanFilters.list"</js> + * <li><b>Data type:</b> <code>List<Class></code> + * <li><b>Default:</b> empty list + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * This is a programmatic equivalent to the {@link Bean @Bean} annotation. + * It's useful when you want to use the Bean annotation functionality, but you don't have the ability to alter the + * bean classes. + * + * <p> + * There are two category of classes that can be passed in through this method: + * <ul class='spaced-list'> + * <li> + * Subclasses of {@link BeanFilterBuilder}. + * These must have a public no-arg constructor. + * <li> + * Bean interface classes. + * A shortcut for defining a {@link InterfaceBeanFilterBuilder}. + * Any subclasses of an interface class will only have properties defined on the interface. + * All other bean properties will be ignored. + * </ul> + */ + public static final String BEAN_beanFilters = "BeanContext.beanFilters.list"; + + /** + * <b>Configuration property:</b> Add to bean filters. + */ + public static final String BEAN_beanFilters_add = "BeanContext.beanFilters.list.add"; + + /** + * <b>Configuration property:</b> Remove from bean filters. + */ + public static final String BEAN_beanFilters_remove = "BeanContext.beanFilters.list.remove"; + + /** + * <b>Configuration property:</b> POJO swaps to apply to Java objects. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.pojoSwaps.list"</js> + * <li><b>Data type:</b> <code>List<Class></code> + * <li><b>Default:</b> empty list + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * There are two category of classes that can be passed in through this method: + * <ul> + * <li>Subclasses of {@link PojoSwap}. + * <li>Surrogate classes. A shortcut for defining a {@link SurrogateSwap}. + * </ul> + */ + public static final String BEAN_pojoSwaps = "BeanContext.pojoSwaps.list"; + + /** + * <b>Configuration property:</b> Add to POJO swap classes. + */ + public static final String BEAN_pojoSwaps_add = "BeanContext.pojoSwaps.list.add"; + + /** + * <b>Configuration property:</b> Remove from POJO swap classes. + */ + public static final String BEAN_pojoSwaps_remove = "BeanContext.pojoSwaps.list.remove"; + + /** + * <b>Configuration property:</b> Implementation classes for interfaces and abstract classes. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.implClasses.map"</js> + * <li><b>Data type:</b> <code>Map<Class,Class></code> + * <li><b>Default:</b> empty map + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * For interfaces and abstract classes this method can be used to specify an implementation class for the + * interface/abstract class so that instances of the implementation class are used when instantiated (e.g. during a + * parse). + */ + public static final String BEAN_implClasses = "BeanContext.implClasses.map"; + + /** + * <b>Configuration property:</b> Add an implementation class. + */ + public static final String BEAN_implClasses_put = "BeanContext.implClasses.map.put"; + + /** + * <b>Configuration property:</b> Explicitly specify visible bean properties. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.includeProperties"</js> + * <li><b>Data type:</b> <code>Map<String,String></code> + * <li><b>Default:</b> <code>{}</code> + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * Specifies to only include the specified list of properties for the specified bean classes. + * + * <p> + * The keys are either fully-qualified or simple class names, and the values are comma-delimited lists of property + * names. + * The key <js>"*"</js> means all bean classes. + * + * <p> + * For example, <code>{Bean1:<js>'foo,bar'</js>}</code> means only serialize the <code>foo</code> and + * <code>bar</code> properties on the specified bean. + * + * <p> + * Setting applies to specified class and all subclasses. + */ + public static final String BEAN_includeProperties = "BeanContext.includeProperties.map"; + + /** + * <b>Configuration property:</b> Explicitly specify visible bean properties. + */ + public static final String BEAN_includeProperties_put = "BeanContext.includeProperties.map.put"; + + /** + * <b>Configuration property:</b> Exclude specified properties from beans. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.excludeProperties"</js> + * <li><b>Data type:</b> <code>Map<String,String></code> + * <li><b>Default:</b> <code>{}</code> + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * Specifies to exclude the specified list of properties for the specified bean class. + * + * <p> + * The keys are either fully-qualified or simple class names, and the values are comma-delimited lists of property + * names. + * The key <js>"*"</js> means all bean classes. + * + * <p> + * For example, <code>{Bean1:<js>'foo,bar'</js>}</code> means don't serialize the <code>foo</code> and + * <code>bar</code> properties on the specified bean. + * + * <p> + * Setting applies to specified class and all subclasses. + */ + public static final String BEAN_excludeProperties = "BeanContext.excludeProperties.map"; + + /** + * <b>Configuration property:</b> Exclude specified properties from beans. + */ + public static final String BEAN_excludeProperties_put = "BeanContext.excludeProperties.map.put"; + + /** + * <b>Configuration property:</b> Bean lookup dictionary. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.beanDictionary.list"</js> + * <li><b>Data type:</b> <code>List<Class></code> + * <li><b>Default:</b> empty list + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * This list can consist of the following class types: + * <ul> + * <li>Any bean class that specifies a value for {@link Bean#typeName() @Bean.typeName()}. + * <li>Any subclass of {@link BeanDictionaryList} containing a collection of bean classes with type name + * annotations. + * <li>Any subclass of {@link BeanDictionaryMap} containing a mapping of type names to classes without type name + * annotations. + * </ul> + */ + public static final String BEAN_beanDictionary = "BeanContext.beanDictionary.list"; + + /** + * <b>Configuration property:</b> Add to bean dictionary. + */ + public static final String BEAN_beanDictionary_add = "BeanContext.beanDictionary.list.add"; + + /** + * <b>Configuration property:</b> Remove from bean dictionary. + */ + public static final String BEAN_beanDictionary_remove = "BeanContext.beanDictionary.list.remove"; + + /** + * <b>Configuration property:</b> Name to use for the bean type properties used to represent a bean type. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.beanTypePropertyName"</js> + * <li><b>Data type:</b> <code>String</code> + * <li><b>Default:</b> <js>"_type"</js> + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + */ + public static final String BEAN_beanTypePropertyName = "BeanContext.beanTypePropertyName"; + + /** + * <b>Configuration property:</b> Default parser to use when converting <code>Strings</code> to POJOs. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.defaultParser"</js> + * <li><b>Data type:</b> <code>Class</code> + * <li><b>Default:</b> {@link JsonSerializer} + * <li><b>Session-overridable:</b> <jk>false</jk> + * </ul> + * + * <p> + * Used in the in the {@link BeanSession#convertToType(Object, Class)} method. + */ + public static final String BEAN_defaultParser = "BeanContext.defaultParser"; + + /** + * <b>Configuration property:</b> Locale. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.locale"</js> + * <li><b>Data type:</b> <code>Locale</code> + * <li><b>Default:</b> <code>Locale.getDefault()</code> + * <li><b>Session-overridable:</b> <jk>true</jk> + * </ul> + * + * <p> + * Used in the in the {@link BeanSession#convertToType(Object, Class)} method. + */ + public static final String BEAN_locale = "BeanContext.locale"; + + /** + * <b>Configuration property:</b> TimeZone. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.timeZone"</js> + * <li><b>Data type:</b> <code>TimeZone</code> + * <li><b>Default:</b> <jk>null</jk> + * <li><b>Session-overridable:</b> <jk>true</jk> + * </ul> + * + * <p> + * Used in the in the {@link BeanSession#convertToType(Object, Class)} method. + */ + public static final String BEAN_timeZone = "BeanContext.timeZone"; + + /** + * <b>Configuration property:</b> Media type. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.mediaType"</js> + * <li><b>Data type:</b> <code>MediaType</code> + * <li><b>Default:</b> <jk>null</jk> + * <li><b>Session-overridable:</b> <jk>true</jk> + * </ul> + * + * <p> + * Specifies a default media type value for serializer and parser sessions. + */ + public static final String BEAN_mediaType = "BeanContext.mediaType"; + + /** + * <b>Configuration property:</b> Debug mode. + * + * <ul> + * <li><b>Name:</b> <js>"BeanContext.debug"</js> + * <li><b>Data type:</b> <code>Boolean</code> + * <li><b>Default:</b> <jk>false</jk> + * <li><b>Session-overridable:</b> <jk>true</jk> + * </ul> + * + * <p> + * Enables the following additional information during serialization: + * <ul class='spaced-list'> + * <li> + * When bean getters throws exceptions, the exception includes the object stack information + * in order to determine how that method was invoked. + * <li> + * Enables {@link SerializerContext#SERIALIZER_detectRecursions}. + * </ul> + * + * <p> + * Enables the following additional information during parsing: + * <ul class='spaced-list'> + * <li> + * When bean setters throws exceptions, the exception includes the object stack information + * in order to determine how that method was invoked. + * </ul> + */ + public static final String BEAN_debug = "BeanContext.debug"; + + /* + * The default package pattern exclusion list. + * Any beans in packages in this list will not be considered beans. + */ + private static final String[] DEFAULT_NOTBEAN_PACKAGES = { + "java.lang", + "java.lang.annotation", + "java.lang.ref", + "java.lang.reflect", + "java.io", + "java.net", + "java.nio.*", + "java.util.*" + }; + + /* + * The default bean class exclusion list. + * Anything in this list will not be considered beans. + */ + private static final Class<?>[] DEFAULT_NOTBEAN_CLASSES = { + Map.class, + Collection.class, + Reader.class, + Writer.class, + InputStream.class, + OutputStream.class, + Throwable.class + }; + + + static final void loadDefaults(PropertyStore config) { + config.setProperty(BEAN_notBeanPackages, DEFAULT_NOTBEAN_PACKAGES); + config.setProperty(BEAN_notBeanClasses, DEFAULT_NOTBEAN_CLASSES); + } + + + // This map is important! + // We may have many ConfigFactory objects that have identical BeanContext properties. + // This map ensures that if the BeanContext properties in the ConfigFactory are the same, + // then we reuse the same Class->ClassMeta cache map. + // This significantly reduces the number of times we need to construct ClassMeta objects which can be expensive. + private static final ConcurrentHashMap<Integer,Map<Class,ClassMeta>> cmCacheCache + = new ConcurrentHashMap<Integer,Map<Class,ClassMeta>>(); + + /** Default config. All default settings. */ + public static final BeanContext DEFAULT = PropertyStore.create().getContext(BeanContext.class); + + /** Default config. All default settings except sort bean properties. */ + public static final BeanContext DEFAULT_SORTED = PropertyStore.create().setProperty(BEAN_sortProperties, true).getContext(BeanContext.class); + + final boolean + beansRequireDefaultConstructor, + beansRequireSerializable, + beansRequireSettersForGetters, + beansRequireSomeProperties, + beanMapPutReturnsOldValue, + useInterfaceProxies, + ignoreUnknownBeanProperties, + ignoreUnknownNullBeanProperties, + ignorePropertiesWithoutSetters, + ignoreInvocationExceptionsOnGetters, + ignoreInvocationExceptionsOnSetters, + useJavaBeanIntrospector, + sortProperties, + debug; + + final Visibility + beanConstructorVisibility, + beanClassVisibility, + beanMethodVisibility, + beanFieldVisibility; + + final Class<?>[] notBeanClasses, beanDictionaryClasses; + final String[] notBeanPackageNames, notBeanPackagePrefixes; + final BeanFilter[] beanFilters; + final PojoSwap<?,?>[] pojoSwaps; + final BeanRegistry beanRegistry; + final Map<Class<?>,Class<?>> implClasses; + final Class<?>[] implKeyClasses, implValueClasses; + final ClassLoader classLoader; + final Locale locale; + final TimeZone timeZone; + final MediaType mediaType; + final Map<String,String[]> includeProperties, excludeProperties; + + final Map<Class,ClassMeta> cmCache; + final ClassMeta<Object> cmObject; // Reusable ClassMeta that represents general Objects. + final ClassMeta<String> cmString; // Reusable ClassMeta that represents general Strings. + final ClassMeta<Class> cmClass; // Reusable ClassMeta that represents general Classes. + + // Optional default parser set by setDefaultParser(). + final ReaderParser defaultParser; + + final String beanTypePropertyName; + + final int hashCode; + + /** + * Constructor. + * + * <p> + * Typically only called from {@link PropertyStore#getContext(Class)} or {@link PropertyStore#getBeanContext()}. + * + * @param ps The property store that created this context. + */ + public BeanContext(PropertyStore ps) { + super(ps); + + PropertyStore.PropertyMap pm = ps.getPropertyMap("BeanContext"); + hashCode = pm.hashCode(); + classLoader = ps.classLoader; + defaultParser = ps.defaultParser; + + beansRequireDefaultConstructor = pm.get(BEAN_beansRequireDefaultConstructor, boolean.class, false); + beansRequireSerializable = pm.get(BEAN_beansRequireSerializable, boolean.class, false); + beansRequireSettersForGetters = pm.get(BEAN_beansRequireSettersForGetters, boolean.class, false); + beansRequireSomeProperties = pm.get(BEAN_beansRequireSomeProperties, boolean.class, true); + beanMapPutReturnsOldValue = pm.get(BEAN_beanMapPutReturnsOldValue, boolean.class, false); + useInterfaceProxies = pm.get(BEAN_useInterfaceProxies, boolean.class, true); + ignoreUnknownBeanProperties = pm.get(BEAN_ignoreUnknownBeanProperties, boolean.class, false); + ignoreUnknownNullBeanProperties = pm.get(BEAN_ignoreUnknownNullBeanProperties, boolean.class, true); + ignorePropertiesWithoutSetters = pm.get(BEAN_ignorePropertiesWithoutSetters, boolean.class, true); + ignoreInvocationExceptionsOnGetters = pm.get(BEAN_ignoreInvocationExceptionsOnGetters, boolean.class, false); + ignoreInvocationExceptionsOnSetters = pm.get(BEAN_ignoreInvocationExceptionsOnSetters, boolean.class, false); + useJavaBeanIntrospector = pm.get(BEAN_useJavaBeanIntrospector, boolean.class, false); + sortProperties = pm.get(BEAN_sortProperties, boolean.class, false); + beanTypePropertyName = pm.get(BEAN_beanTypePropertyName, String.class, "_type"); + debug = ps.getProperty(BEAN_debug, boolean.class, false); + + beanConstructorVisibility = pm.get(BEAN_beanConstructorVisibility, Visibility.class, PUBLIC); + beanClassVisibility = pm.get(BEAN_beanClassVisibility, Visibility.class, PUBLIC); + beanMethodVisibility = pm.get(BEAN_methodVisibility, Visibility.class, PUBLIC); + beanFieldVisibility = pm.get(BEAN_beanFieldVisibility, Visibility.class, PUBLIC); + + notBeanClasses = pm.get(BEAN_notBeanClasses, Class[].class, new Class[0]); + + List<String> l1 = new LinkedList<String>(); + List<String> l2 = new LinkedList<String>(); + for (String s : pm.get(BEAN_notBeanPackages, String[].class, new String[0])) { + if (s.endsWith(".*")) + l2.add(s.substring(0, s.length()-2)); + else + l1.add(s); + } + notBeanPackageNames = l1.toArray(new String[l1.size()]); + notBeanPackagePrefixes = l2.toArray(new String[l2.size()]); + + LinkedList<BeanFilter> lbf = new LinkedList<BeanFilter>(); + for (Class<?> c : pm.get(BEAN_beanFilters, Class[].class, new Class[0])) { + if (isParentClass(BeanFilter.class, c)) + lbf.add(newInstance(BeanFilter.class, c)); + else if (isParentClass(BeanFilterBuilder.class, c)) + lbf.add(newInstance(BeanFilterBuilder.class, c).build()); + else + lbf.add(new InterfaceBeanFilterBuilder(c).build()); + } + beanFilters = lbf.toArray(new BeanFilter[0]); + + LinkedList<PojoSwap<?,?>> lpf = new LinkedList<PojoSwap<?,?>>(); + for (Class<?> c : pm.get(BEAN_pojoSwaps, Class[].class, new Class[0])) { + if (isParentClass(PojoSwap.class, c)) + lpf.add(newInstance(PojoSwap.class, c)); + else + lpf.addAll(SurrogateSwap.findPojoSwaps(c)); + } + pojoSwaps = lpf.toArray(new PojoSwap[0]); + + implClasses = new TreeMap<Class<?>,Class<?>>(new ClassComparator()); + Map<Class,Class> m = pm.getMap(BEAN_implClasses, Class.class, Class.class, null); + if (m != null) + for (Map.Entry<Class,Class> e : m.entrySet()) + implClasses.put(e.getKey(), e.getValue()); + implKeyClasses = implClasses.keySet().toArray(new Class[0]); + implValueClasses = implClasses.values().toArray(new Class[0]); + + Map<String,String[]> m2 = pm.getMap(BEAN_includeProperties, String.class, String[].class, null); + includeProperties = m2 == null ? Collections.EMPTY_MAP : Collections.unmodifiableMap(m2); + m2 = pm.getMap(BEAN_excludeProperties, String.class, String[].class, null); + excludeProperties = m2 == null ? Collections.EMPTY_MAP : Collections.unmodifiableMap(m2); + + locale = pm.get(BEAN_locale, Locale.class, null); + timeZone = pm.get(BEAN_timeZone, TimeZone.class, null); + mediaType = pm.get(BEAN_mediaType, MediaType.class, null); + + if (! cmCacheCache.containsKey(hashCode)) { + ConcurrentHashMap<Class,ClassMeta> cm = new ConcurrentHashMap<Class,ClassMeta>(); + cm.putIfAbsent(String.class, new ClassMeta(String.class, this, null, null, findPojoSwap(String.class), findChildPojoSwaps(String.class))); + cm.putIfAbsent(Object.class, new ClassMeta(Object.class, this, null, null, findPojoSwap(Object.class), findChildPojoSwaps(Object.class))); + cmCacheCache.putIfAbsent(hashCode, cm); + } + this.cmCache = cmCacheCache.get(hashCode); + this.cmString = cmCache.get(String.class); + this.cmObject = cmCache.get(Object.class); + this.cmClass = cmCache.get(Class.class); + + this.beanDictionaryClasses = pm.get(BEAN_beanDictionary, Class[].class, new Class[0]); + this.beanRegistry = new BeanRegistry(this, null); + } + + /** + * Create a new bean session based on the properties defined on this context. + * + * @param args + * The session arguments. + * @return A new session object. + */ + public BeanSession createSession(BeanSessionArgs args) { + return new BeanSession(this, args); + } + + /** + * Create a new bean session based on the properties defined on this context. + * + * <p> + * Use this method for creating sessions if you don't need to override any + * properties or locale/timezone currently set on this context. + * + * @return A new session object. + */ + public BeanSession createSession() { + return new BeanSession(this, new BeanSessionArgs(null, this.locale, this.timeZone, this.mediaType)); + } + + /** + * Returns <jk>true</jk> if the specified bean context shares the same cache as this bean context. + * + * <p> + * Useful for testing purposes. + * + * @param bc The bean context to compare to. + * @return <jk>true</jk> if the bean contexts have equivalent settings and thus share caches. + */ + public final boolean hasSameCache(BeanContext bc) { + return bc.cmCache == this.cmCache; + } + + /** + * Determines whether the specified class is ignored as a bean class based on the various exclusion parameters + * specified on this context class. + * + * @param c The class type being tested. + * @return <jk>true</jk> if the specified class matches any of the exclusion parameters. + */ + protected final boolean isNotABean(Class<?> c) { + if (c.isArray() || c.isPrimitive() || c.isEnum() || c.isAnnotation()) + return true; + Package p = c.getPackage(); + if (p != null) { + for (String p2 : notBeanPackageNames) + if (p.getName().equals(p2)) + return true; + for (String p2 : notBeanPackagePrefixes) + if (p.getName().startsWith(p2)) + return true; + } + for (Class exclude : notBeanClasses) + if (isParentClass(exclude, c)) + return true; + return false; + } + + /** + * Returns <jk>true</jk> if the specified object is a bean. + * + * @param o The object to test. + * @return <jk>true</jk> if the specified object is a bean. <jk>false</jk> if the bean is <jk>null</jk>. + */ + public boolean isBean(Object o) { + if (o == null) + return false; + return getClassMetaForObject(o).isBean(); + } + + /** + * Prints meta cache statistics to <code>System.out</code>. + */ + protected static void dumpCacheStats() { + try { + int ctCount = 0; + for (Map<Class,ClassMeta> cm : cmCacheCache.values()) + ctCount += cm.size(); + System.out.println(format("ClassMeta cache: {0} instances in {1} caches", ctCount, cmCacheCache.size())); // NOT DEBUG + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Returns the {@link BeanMeta} class for the specified class. + * + * @param <T> The class type to get the meta-data on. + * @param c The class to get the meta-data on. + * @return + * The {@link BeanMeta} for the specified class, or <jk>null</jk> if the class is not a bean per the settings on + * this context. + */ + public final <T> BeanMeta<T> getBeanMeta(Class<T> c) { + if (c == null) + return null; + return getClassMeta(c).getBeanMeta(); + } + + /** + * Construct a {@code ClassMeta} wrapper around a {@link Class} object. + * + * @param <T> The class type being wrapped. + * @param type The class to resolve. + * @return + * If the class is not an array, returns a cached {@link ClassMeta} object. + * Otherwise, returns a new {@link ClassMeta} object every time. + */ + public final <T> ClassMeta<T> getClassMeta(Class<T> type) { + return getClassMeta(type, true); + } + + /** + * Construct a {@code ClassMeta} wrapper around a {@link Class} object. + * + * @param <T> The class type being wrapped. + * @param type The class to resolve. + * @param waitForInit + * If <jk>true</jk>, wait for the ClassMeta constructor to finish before returning. + * @return + * If the class is not an array, returns a cached {@link ClassMeta} object. + * Otherwise, returns a new {@link ClassMeta} object every time. + */ + final <T> ClassMeta<T> getClassMeta(Class<T> type, boolean waitForInit) { + + // If this is an array, then we want it wrapped in an uncached ClassMeta object. + // Note that if it has a pojo swap, we still want to cache it so that + // we can cache something like byte[] with ByteArrayBase64Swap. + if (type.isArray() && findPojoSwap(type) == null) + return new ClassMeta(type, this, findImplClass(type), findBeanFilter(type), findPojoSwap(type), findChildPojoSwaps(type)); + + // This can happen if we have transforms defined against String or Object. + if (cmCache == null) + return null; + + ClassMeta<T> cm = cmCache.get(type); + if (cm == null) { + + synchronized (this) { + // Make sure someone didn't already set it while this thread was blocked. + cm = cmCache.get(type); + if (cm == null) + cm = new ClassMeta<T>(type, this, findImplClass(type), findBeanFilter(type), findPojoSwap(type), findChildPojoSwaps(type)); + } + } + if (waitForInit) + cm.waitForInit(); + return cm; + } + + /** + * Used to resolve <code>ClassMetas</code> of type <code>Collection</code> and <code>Map</code> that have + * <code>ClassMeta</code> values that themselves could be collections or maps. + * + * <p> + * <code>Collection</code> meta objects are assumed to be followed by zero or one meta objects indicating the element type. + * + * <p> + * <code>Map</code> meta objects are assumed to be followed by zero or two meta objects indicating the key and value types. + * + * <p> + * The array can be arbitrarily long to indicate arbitrarily complex data structures. + * + * <h5 class='section'>Examples:</h5> + * <ul> + * <li><code>getClassMeta(String.<jk>class</jk>);</code> - A normal type. + * <li><code>getClassMeta(List.<jk>class</jk>);</code> - A list containing objects. + * <li><code>getClassMeta(List.<jk>class</jk>, String.<jk>class</jk>);</code> - A list containing strings. + * <li><code>getClassMeta(LinkedList.<jk>class</jk>, String.<jk>class</jk>);</code> - A linked-list containing + * strings. + * <li><code>getClassMeta(LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>);</code> - + * A linked-list containing linked-lists of strings. + * <li><code>getClassMeta(Map.<jk>class</jk>);</code> - A map containing object keys/values. + * <li><code>getClassMeta(Map.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>);</code> - A map + * containing string keys/values. + * <li><code>getClassMeta(Map.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>);</code> - + * A map containing string keys and values of lists containing beans. + * </ul> + * + * @param type + * The class to resolve. + * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, + * {@link GenericArrayType} + * @param args + * The type arguments of the class if it's a collection or map. + * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, + * {@link GenericArrayType} + * <br>Ignored if the main type is not a map or collection. + * @return The resolved class meta. + */ + public final <T> ClassMeta<T> getClassMeta(Type type, Type...args) { + if (type == null) + return null; + ClassMeta<T> cm = type instanceof Class ? getClassMeta((Class)type) : resolveClassMeta(type, null); + if (args.length == 0) + return cm; + ClassMeta<?>[] cma = new ClassMeta[args.length+1]; + cma[0] = cm; + for (int i = 0; i < Array.getLength(args); i++) { + Type arg = (Type)Array.get(args, i); + cma[i+1] = arg instanceof Class ? getClassMeta((Class)arg) : resolveClassMeta(arg, null); + } + return (ClassMeta<T>) getTypedClassMeta(cma, 0); + } + + /* + * Resolves the 'genericized' class meta at the specified position in the ClassMeta array. + */ + private ClassMeta<?> getTypedClassMeta(ClassMeta<?>[] c, int pos) { + ClassMeta<?> cm = c[pos++]; + if (cm.isCollection()) { + ClassMeta<?> ce = c.length == pos ? object() : getTypedClassMeta(c, pos); + return (ce.isObject() ? cm : new ClassMeta(cm, null, null, ce)); + } else if (cm.isMap()) { + ClassMeta<?> ck = c.length == pos ? object() : c[pos++]; + ClassMeta<?> cv = c.length == pos ? object() : getTypedClassMeta(c, pos); + return (ck.isObject() && cv.isObject() ? cm : new ClassMeta(cm, ck, cv, null)); + } + return cm; + } + + final ClassMeta resolveClassMeta(Type o, Map<Class<?>,Class<?>[]> typeVarImpls) { + if (o == null) + return null; + + if (o instanceof ClassMeta) { + ClassMeta<?> cm = (ClassMeta)o; + + // This classmeta could have been created by a different context. + // Need to re-resolve it to pick up PojoSwaps and stuff on this context. + if (cm.getBeanContext() == this) + return cm; + if (cm.isMap()) + return getClassMeta(cm.innerClass, cm.getKeyType(), cm.getValueType()); + if (cm.isCollection()) + return getClassMeta(cm.innerClass, cm.getElementType()); + return getClassMeta(cm.innerClass); + } + + Class c = resolve(o, typeVarImpls); + + // This can happen when trying to resolve the "E getFirst()" method on LinkedList, whose type is a TypeVariable + // These should just resolve to Object. + if (c == null) + return object(); + + ClassMeta rawType = getClassMeta(c); + + // If this is a Map or Collection, and the parameter types aren't part + // of the class definition itself (e.g. class AddressBook extends List<Person>), + // then we need to figure out the parameters. + if (rawType.isMap() || rawType.isCollection()) { + ClassMeta[] params = findParameters(o, c); + if (params == null) + return rawType; + if (rawType.isMap()) { + if (params.length != 2) + return rawType; + if (params[0].isObject() && params[1].isObject()) + return rawType; + return new ClassMeta(rawType, params[0], params[1], null); + } + if (rawType.isCollection()) { + if (params.length != 1) + return rawType; + if (params[0].isObject()) + return rawType; + return new ClassMeta(rawType, null, null, params[0]); + } + } + + return rawType; + } + + /** + * Convert a Type to a Class if possible. + * Return null if not possible. + */ + final Class resolve(Type t, Map<Class<?>,Class<?>[]> typeVarImpls) { + + if (t instanceof Class) + return (Class)t; + + if (t instanceof ParameterizedType) + // A parameter (e.g. <String>. + return (Class)((ParameterizedType)t).getRawType(); + + if (t instanceof GenericArrayType) { + // An array parameter (e.g. <byte[]>). + Type gatct = ((GenericArrayType)t).getGenericComponentType(); + + if (gatct instanceof Class) + return Array.newInstance((Class)gatct, 0).getClass(); + + if (gatct instanceof ParameterizedType) + return Array.newInstance((Class)((ParameterizedType)gatct).getRawType(), 0).getClass(); + + if (gatct instanceof GenericArrayType) + return Array.newInstance(resolve(gatct, typeVarImpls), 0).getClass(); + + return null; + + } else if (t instanceof TypeVariable) { + if (typeVarImpls != null) { + TypeVariable tv = (TypeVariable)t; + String varName = tv.getName(); + int varIndex = -1; + Class gc = (Class)tv.getGenericDeclaration(); + TypeVariable[] tvv = gc.getTypeParameters(); + for (int i = 0; i < tvv.length; i++) { + if (tvv[i].getName().equals(varName)) { + varIndex = i; + } + } + if (varIndex != -1) { + + // If we couldn't find a type variable implementation, that means + // the type was defined at runtime (e.g. Bean b = new Bean<Foo>();) + // in which case the type is lost through erasure. + // Assume java.lang.Object as the type. + if (! typeVarImpls.containsKey(gc)) + return null; + + return typeVarImpls.get(gc)[varIndex]; + } + } + } + return null; + } + + final ClassMeta[] findParameters(Type o, Class c) { + if (o == null) + o = c; + + // Loop until we find a ParameterizedType + if (! (o instanceof ParameterizedType)) { + loop: do { + o = c.getGenericSuperclass(); + if (o instanceof ParameterizedType) + break loop; + for (Type t : c.getGenericInterfaces()) { + o = t; + if (o instanceof ParameterizedType) + break loop; + } + c = c.getSuperclass(); + } while (c != null); + } + + if (o instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType)o; + if (! pt.getRawType().equals(Enum.class)) { + List<ClassMeta<?>> l = new LinkedList<ClassMeta<?>>(); + for (Type pt2 : pt.getActualTypeArguments()) { + if (pt2 instanceof WildcardType || pt2 instanceof TypeVariable) + return null; + l.add(resolveClassMeta(pt2, null)); + } + if (l.isEmpty()) + return null; + return l.toArray(new ClassMeta[l.size()]); + } + } + + return null; + } + + /** + * Shortcut for calling {@code getClassMeta(o.getClass())}. + * + * @param <T> The class of the object being passed in. + * @param o The class to find the class type for. + * @return The ClassMeta object, or <jk>null</jk> if {@code o} is <jk>null</jk>. + */ + public final <T> ClassMeta<T> getClassMetaForObject(T o) { + if (o == null) + return null; + return (ClassMeta<T>)getClassMeta(o.getClass()); + } + + + /** + * Used for determining the class type on a method or field where a {@code @BeanProperty} annotation may be present. + * + * @param <T> The class type we're wrapping. + * @param p The property annotation on the type if there is one. + * @param t The type. + * @param typeVarImpls + * Contains known resolved type parameters on the specified class so that we can result + * {@code ParameterizedTypes} and {@code TypeVariables}. + * Can be <jk>null</jk> if the information is not known. + * @return The new {@code ClassMeta} object wrapped around the {@code Type} object. + */ + protected final <T> ClassMeta<T> resolveClassMeta(BeanProperty p, Type t, Map<Class<?>,Class<?>[]> typeVarImpls) { + ClassMeta<T> cm = resolveClassMeta(t, typeVarImpls); + ClassMeta<T> cm2 = cm; + if (p != null) { + + if (p.type() != Object.class) + cm2 = resolveClassMeta(p.type(), typeVarImpls); + + if (cm2.isMap()) { + Class<?>[] pParams = (p.params().length == 0 ? new Class[]{Object.class, Object.class} : p.params()); + if (pParams.length != 2) + throw new FormattedRuntimeException("Invalid number of parameters specified for Map (must be 2): {0}", pParams.length); + ClassMeta<?> keyType = resolveType(pParams[0], cm2.getKeyType(), cm.getKeyType()); + ClassMeta<?> valueType = resolveType(pParams[1], cm2.getValueType(), cm.getValueType()); + if (keyType.isObject() && valueType.isObject()) + return cm2; + return new ClassMeta<T>(cm2, keyType, valueType, null); + } + + if (cm2.isCollection()) { + Class<?>[] pParams = (p.params().length == 0 ? new Class[]{Object.class} : p.params()); + if (pParams.length != 1) + throw new FormattedRuntimeException("Invalid number of parameters specified for Collection (must be 1): {0}", pParams.length); + ClassMeta<?> elementType = resolveType(pParams[0], cm2.getElementType(), cm.getElementType()); + if (elementType.isObject()) + return cm2; + return new ClassMeta<T>(cm2, null, null, elementType); + } + + return cm2; + } + + return cm; + } + + private ClassMeta<?> resolveType(Type...t) { + for (Type tt : t) { + if (tt != null) { + ClassMeta<?> cm = getClassMeta(tt); + if (tt != cmObject) + return cm; + } + } + return cmObject; + } + + /** + * Returns the {@link PojoSwap} associated with the specified class, or <jk>null</jk> if there is no POJO swap + * associated with the class. + * + * @param <T> The class associated with the swap. + * @param c The class associated with the swap. + * @return The swap associated with the class, or null if there is no association. + */ + private final <T> PojoSwap findPojoSwap(Class<T> c) { + // Note: On first + if (c != null) + for (PojoSwap f : pojoSwaps) + if (isParentClass(f.getNormalClass(), c)) + return f; + return null; + } + + /** + * Checks whether a class has a {@link PojoSwap} associated with it in this bean context. + * + * @param c The class to check. + * @return <jk>true</jk> if the specified class or one of its subclasses has a {@link PojoSwap} associated with it. + */ + private final PojoSwap[] findChildPojoSwaps(Class<?> c) { + if (c == null || pojoSwaps.length == 0) + return null; + List<PojoSwap> l = null; + for (PojoSwap f : pojoSwaps) { + if (isParentClass(c, f.getNormalClass())) { + if (l == null) + l = new ArrayList<PojoSwap>(); + l.add(f); + } + } + return l == null ? null : l.toArray(new PojoSwap[l.size()]); + } + + /** + * Returns the {@link BeanFilter} associated with the specified class, or <jk>null</jk> if there is no bean filter + * associated with the class. + * + * @param <T> The class associated with the bean filter. + * @param c The class associated with the bean filter. + * @return The bean filter associated with the class, or null if there is no association. + */ + private final <T> BeanFilter findBeanFilter(Class<T> c) { + if (c != null) + for (BeanFilter f : beanFilters) + if (isParentClass(f.getBeanClass(), c)) + return f; + return null; + } + + /** + * Returns the type property name as defined by {@link BeanContext#BEAN_beanTypePropertyName}. + * + * @return The type property name. Never <jk>null</jk>. + */ + protected final String getBeanTypePropertyName() { + return beanTypePropertyName; + } + + /** + * Returns the bean registry defined in this bean context defined by {@link BeanContext#BEAN_beanDictionary}. + * + * @return The bean registry defined in this bean context. Never <jk>null</jk>. + */ + protected final BeanRegistry getBeanRegistry() { + return beanRegistry; + } + + /** + * Gets the no-arg constructor for the specified class. + * + * @param <T> The class to check. + * @param c The class to check. + * @param v The minimum visibility for the constructor. + * @return The no arg constructor, or <jk>null</jk> if the class has no no-arg constructor. + */ + protected final <T> Constructor<? extends T> getImplClassConstructor(Class<T> c, Visibility v) { + if (implClasses.isEmpty()) + return null; + Class cc = c; + while (cc != null) { + Class implClass = implClasses.get(cc); + if (implClass != null) + return findNoArgConstructor(implClass, v); + for (Class ic : cc.getInterfaces()) { + implClass = implClasses.get(ic); + if (implClass != null) + return findNoArgConstructor(implClass, v); + } + cc = cc.getSuperclass(); + } + return null; + } + + private final <T> Class<? extends T> findImplClass(Class<T> c) { + if (implClasses.isEmpty()) + return null; + Class cc = c; + while (cc != null) { + Class implClass = implClasses.get(cc); + if (implClass != null) + return implClass; + for (Class ic : cc.getInterfaces()) { + implClass = implClasses.get(ic); + if (implClass != null) + return implClass; + } + cc = cc.getSuperclass(); + } + return null; + } + + /** + * Returns the {@link #BEAN_includeProperties} setting for the specified class. + * + * @param c The class. + * @return The properties to include for the specified class, or <jk>null</jk> if it's not defined for the class. + */ + public String[] getIncludeProperties(Class<?> c) { + if (includeProperties.isEmpty()) + return null; + String[] s = null; + for (Iterator<Class<?>> i = ClassUtils.getParentClasses(c, false, true); i.hasNext();) { + Class<?> c2 = i.next(); + s = includeProperties.get(c2.getName()); + if (s != null) + return s; + s = includeProperties.get(c2.getSimpleName()); + if (s != null) + return s; + } + return includeProperties.get("*"); + } + + /** + * Returns the {@link #BEAN_excludeProperties} setting for the specified class. + * + * @param c The class. + * @return The properties to exclude for the specified class, or <jk>null</jk> if it's not defined for the class. + */ + public String[] getExcludeProperties(Class<?> c) { + if (excludeProperties.isEmpty()) + return null; + String[] s = null; + for (Iterator<Class<?>> i = ClassUtils.getParentClasses(c, false, true); i.hasNext();) { + Class<?> c2 = i.next(); + s = excludeProperties.get(c2.getName()); + if (s != null) + return s; + s = excludeProperties.get(c2.getSimpleName()); + if (s != null) + return s; + } + return excludeProperties.get("*"); + } + + /** + * Returns a reusable {@link ClassMeta} representation for the class <code>Object</code>. + * + * <p> + * This <code>ClassMeta</code> is often used to represent "any object type" when an object type is not known. + * + * <p> + * This method is identical to calling <code>getClassMeta(Object.<jk>class</jk>)</code> but uses a cached copy to + * avoid a hashmap lookup. + * + * @return The {@link ClassMeta} object associated with the <code>Object</code> class. + */ + protected final ClassMeta<Object> object() { + return cmObject; + } + + /** + * Returns a reusable {@link ClassMeta} representation for the class <code>String</code>. + * + * <p> + * This <code>ClassMeta</code> is often used to represent key types in maps. + * + * <p> + * This method is identical to calling <code>getClassMeta(String.<jk>class</jk>)</code> but uses a cached copy to + * avoid a hashmap lookup. + * + * @return The {@link ClassMeta} object associated with the <code>String</code> class. + */ + protected final ClassMeta<String> string() { + return cmString; + } + + /** + * Returns a reusable {@link ClassMeta} representation for the class <code>Class</code>. + * + * <p> + * This <code>ClassMeta</code> is often used to represent key types in maps. + * + * <p> + * This method is identical to calling <code>getClassMeta(Class.<jk>class</jk>)</code> but uses a cached copy to + * avoid a hashmap lookup. + * + * @return The {@link ClassMeta} object associated with the <code>String</code> class. + */ + protected final ClassMeta<Class> _class() { + return cmClass; + } + + @Override /* Object */ + public int hashCode() { + return hashCode; + } + + @Override /* Object */ + public boolean equals(Object o) { + if (this == o) + return true; + if (o instanceof BeanContext) + return ((BeanContext)o).hashCode == hashCode; + return false; + } + + @Override /* Context */ + public ObjectMap asMap() { + return super.asMap() + .append("BeanContext", new ObjectMap() + .append("id", System.identityHashCode(this)) + .append("beansRequireDefaultConstructor", beansRequireDefaultConstructor) + .append("beansRequireSerializable", beansRequireSerializable) + .append("beansRequireSettersForGetters", beansRequireSettersForGetters) + .append("beansRequireSomeProperties", beansRequireSomeProperties) + .append("beanMapPutReturnsOldValue", beanMapPutReturnsOldValue) + .append("beanConstructorVisibility", beanConstructorVisibility) + .append("beanClassVisibility", beanClassVisibility) + .append("beanMethodVisibility", beanMethodVisibility) + .append("beanFieldVisibility", beanFieldVisibility) + .append("useInterfaceProxies", useInterfaceProxies) + .append("ignoreUnknownBeanProperties", ignoreUnknownBeanProperties) + .append("ignoreUnknownNullBeanProperties", ignoreUnknownNullBeanProperties) + .append("ignorePropertiesWithoutSetters", ignorePropertiesWithoutSetters) + .append("ignoreInvocationExceptionsOnGetters", ignoreInvocationExceptionsOnGetters) + .append("ignoreInvocationExceptionsOnSetters", ignoreInvocationExceptionsOnSetters) + .append("useJavaBeanIntrospector", useJavaBeanIntrospector) + .append("beanFilters", beanFilters) + .append("pojoSwaps", pojoSwaps) + .append("notBeanClasses", notBeanClasses) + .append("implClasses", implClasses) + .append("sortProperties", sortProperties) + .append("locale", locale) + .append("timeZone", timeZone) + .append("mediaType", mediaType) + .append("includeProperties", includeProperties) + .append("excludeProperties", excludeProperties) + ); + } +} \ No newline at end of file
