http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanContextFactory.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanContextFactory.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanContextFactory.java new file mode 100755 index 0000000..e69a32b --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanContextFactory.java @@ -0,0 +1,454 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2011, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core; + +import static com.ibm.juno.core.BeanContextProperties.*; + +import java.io.*; +import java.util.*; +import java.util.concurrent.locks.*; + +import com.ibm.juno.core.filter.*; +import com.ibm.juno.core.json.*; +import com.ibm.juno.core.parser.*; +import com.ibm.juno.core.serializer.*; +import com.ibm.juno.core.utils.ClassUtils.ClassComparator; + +/** + * Factory class for creating instances of {@link BeanContext}. + * + * @author James Bognar ([email protected]) + */ +public final class BeanContextFactory extends Lockable { + + //-------------------------------------------------------------------------------- + // Static constants + //-------------------------------------------------------------------------------- + + /** + * 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 + }; + + //-------------------------------------------------------------------------------- + // Properties + //-------------------------------------------------------------------------------- + + boolean + beansRequireDefaultConstructor = false, + beansRequireSerializable = false, + beansRequireSettersForGetters = false, + beansRequireSomeProperties = true, + beanMapPutReturnsOldValue = false, + useInterfaceProxies = true, + ignoreUnknownBeanProperties = false, + ignoreUnknownNullBeanProperties = true, + ignorePropertiesWithoutSetters = true, + ignoreInvocationExceptionsOnGetters = false, + ignoreInvocationExceptionsOnSetters = false, + useJavaBeanIntrospector = false; + + Set<String> notBeanPackages = new TreeSet<String>(); + Set<Class<?>> notBeanClasses = newClassTreeSet(); + Map<Class<?>,Class<?>> implClasses = newClassTreeMap(); + Map<String,String> uriVars = new TreeMap<String,String>(); + LinkedList<Class<?>> filters = new LinkedList<Class<?>>(); + ClassLoader classLoader = null; + + Visibility + beanConstructorVisibility = Visibility.PUBLIC, + beanClassVisibility = Visibility.PUBLIC, + beanFieldVisibility = Visibility.PUBLIC, + beanMethodVisibility = Visibility.PUBLIC; + + // Optional default parser set by setDefaultParser(). + ReaderParser defaultParser = null; + + // Read-write lock for preventing acess to the getBeanContext() method while this factory is being modified. + private ReadWriteLock lock = new ReentrantReadWriteLock(); + + // Current BeanContext instance. + private BeanContext beanContext; + + //-------------------------------------------------------------------------------- + // Methods + //-------------------------------------------------------------------------------- + + /** + * Default constructor. + */ + public BeanContextFactory() { + addNotBeanClasses(DEFAULT_NOTBEAN_CLASSES); + setProperty(BEAN_addNotBeanPackages, DEFAULT_NOTBEAN_PACKAGES); + } + + /** + * Creates and returns a {@link BeanContext} with settings currently specified on this factory class. + * This method will return the same object until the factory settings are modified at which point + * a new {@link BeanContext} will be constructed. + * + * @return The bean context object. + */ + public BeanContext getBeanContext() { + readLock(); + try { + if (beanContext == null) + beanContext = new BeanContext(this); + return beanContext; + } finally { + readUnlock(); + } + } + + /** + * Sets the default parser for this bean context. + * <p> + * The default parser is used in the following methods: + * <ul> + * <code>beanContext.newBeanMap(Bean.<jk>class</jk>).load(String)</code> - Used for parsing init properties. + * <li>{@link BeanContext#convertToType(Object, ClassMeta)} - Used for converting strings to beans. + * </ul> + * + * @param defaultParser The new default parser. + * @return This object (for method chaining). + */ + public BeanContextFactory setDefaultParser(ReaderParser defaultParser) { + writeLock(); + try { + this.defaultParser = defaultParser; + return this; + } finally { + writeUnlock(); + } + } + + + //-------------------------------------------------------------------------------- + // Configuration property methods + //-------------------------------------------------------------------------------- + + /** + * Sets a property on this context. + * <p> + * Refer to {@link BeanContextProperties} for a description of available properties. + * + * @param property The property whose value is getting changed. + * @param value The new value. + * @throws LockedException If {@link #lock()} was called on this object. + * @return This object (for method chaining). + */ + public BeanContextFactory setProperty(String property, Object value) throws LockedException { + writeLock(); + try { + // Note: Have to use the default bean context to set these properties since calling + // convertToType will cause the cache object to be initialized. + BeanContext bc = BeanContext.DEFAULT; + + if (property.equals(BEAN_beansRequireDefaultConstructor)) + beansRequireDefaultConstructor = bc.convertToType(value, Boolean.class); + else if (property.equals(BEAN_beansRequireSerializable)) + beansRequireSerializable = bc.convertToType(value, Boolean.class); + else if (property.equals(BEAN_beansRequireSettersForGetters)) + beansRequireSettersForGetters = bc.convertToType(value, Boolean.class); + else if (property.equals(BEAN_beansRequireSomeProperties)) + beansRequireSomeProperties = bc.convertToType(value, Boolean.class); + else if (property.equals(BEAN_beanMapPutReturnsOldValue)) + beanMapPutReturnsOldValue = bc.convertToType(value, Boolean.class); + else if (property.equals(BEAN_beanConstructorVisibility)) + beanConstructorVisibility = Visibility.valueOf(value.toString()); + else if (property.equals(BEAN_beanClassVisibility)) + beanClassVisibility = Visibility.valueOf(value.toString()); + else if (property.equals(BEAN_beanFieldVisibility)) + beanFieldVisibility = Visibility.valueOf(value.toString()); + else if (property.equals(BEAN_methodVisibility)) + beanMethodVisibility = Visibility.valueOf(value.toString()); + else if (property.equals(BEAN_useJavaBeanIntrospector)) + useJavaBeanIntrospector = bc.convertToType(value, Boolean.class); + else if (property.equals(BEAN_useInterfaceProxies)) + useInterfaceProxies = bc.convertToType(value, Boolean.class); + else if (property.equals(BEAN_ignoreUnknownBeanProperties)) + ignoreUnknownBeanProperties = bc.convertToType(value, Boolean.class); + else if (property.equals(BEAN_ignoreUnknownNullBeanProperties)) + ignoreUnknownNullBeanProperties = bc.convertToType(value, Boolean.class); + else if (property.equals(BEAN_ignorePropertiesWithoutSetters)) + ignorePropertiesWithoutSetters = bc.convertToType(value, Boolean.class); + else if (property.equals(BEAN_ignoreInvocationExceptionsOnGetters)) + ignoreInvocationExceptionsOnGetters = bc.convertToType(value, Boolean.class); + else if (property.equals(BEAN_ignoreInvocationExceptionsOnSetters)) + ignoreInvocationExceptionsOnSetters = bc.convertToType(value, Boolean.class); + else if (property.equals(BEAN_addNotBeanPackages)) { + Set<String> set = new TreeSet<String>(notBeanPackages); + for (String s : value.toString().split(",")) + set.add(s.trim()); + notBeanPackages = set; + } else if (property.equals(BEAN_removeNotBeanPackages)) { + Set<String> set = new TreeSet<String>(notBeanPackages); + for (String s : value.toString().split(",")) + set.remove(s.trim()); + notBeanPackages = set; + } + } finally { + writeUnlock(); + } + return this; + } + + /** + * Sets multiple properties on this context. + * <p> + * Refer to {@link BeanContextProperties} for a description of available properties. + * + * @param properties The properties to set. Ignored if <jk>null</jk>. + * @throws LockedException If {@link #lock()} was called on this object. + * @return This object (for method chaining). + */ + public BeanContextFactory setProperties(ObjectMap properties) throws LockedException { + writeLock(); + try { + if (properties != null) + for (Map.Entry<String,Object> e : properties.entrySet()) + setProperty(e.getKey(), e.getValue()); + return this; + } finally { + writeUnlock(); + } + } + + /** + * Adds an explicit list of Java classes to be excluded from consideration as being beans. + * + * @param classes One or more fully-qualified Java class names. + * @return This object (for method chaining). + */ + public BeanContextFactory addNotBeanClasses(Class<?>...classes) { + writeLock(); + try { + this.notBeanClasses.addAll(Arrays.asList(classes)); + return this; + } finally { + writeUnlock(); + } + } + + /** + * Add filters to this context. + * <p> + * There are two category of classes that can be passed in through this method: + * <ul> + * <li>Subclasses of {@link PojoFilter} and {@link BeanFilter}. + * <li>Any other class. + * </ul> + * <p> + * When <code>IFilter</code> classes are specified, they identify objects that need to be + * transformed into some other type during serialization (and optionally the reverse during parsing). + * <p> + * When non-<code>IFilter</code> classes are specified, they are wrapped inside {@link BeanFilter BeanFilters}. + * For example, if you have an interface <code>IFoo</code> and a subclass <code>Foo</code>, and you + * only want properties defined on <code>IFoo</code> to be visible as bean properties for <code>Foo</code> objects, + * you can simply pass in <code>IFoo.<jk>class</jk></code> to this method. + * <p> + * The following code shows the order in which filters are applied: + * <p class='bcode'> + * <jc>// F3,F4,F1,F2</jc> + * beanContext.addFilters(F1.<jk>class</jk>,F2.<jk>class</jk>); + * beanContext.addFilters(F3.<jk>class</jk>,F4.<jk>class</jk>); + * </p> + * + * @param classes One or more classes to add as filters to this context. + * @throws LockedException If {@link #lock()} was called on this object. + * @return This object (for method chaining). + */ + public BeanContextFactory addFilters(Class<?>...classes) throws LockedException { + writeLock(); + try { + classes = Arrays.copyOf(classes, classes.length); // Copy array to prevent modification! + Collections.reverse(Arrays.asList(classes)); + for (Class<?> c : classes) + filters.addFirst(c); + return this; + } finally { + writeUnlock(); + } + } + + /** + * Specifies an implementation class for an interface or abstract class. + * <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). + * + * @param <T> The interface class. + * @param interfaceClass The interface class. + * @param implClass The implementation of the interface class. + * @throws LockedException If {@link #lock()} was called on this object. + * @return This object (for method chaining). + */ + public <T> BeanContextFactory addImplClass(Class<T> interfaceClass, Class<? extends T> implClass) throws LockedException { + writeLock(); + try { + this.implClasses.put(interfaceClass, implClass); + return this; + } finally { + writeUnlock(); + } + } + + /** + * Specifies the classloader to use when resolving classes, usually <js>"_class"</js> attributes. + * <p> + * Can be used for resolving class names when the classes being created are in a different + * classloader from the Juno code. + * <p> + * If <jk>null</jk>, <code>Class.forName(String)</code> will be used to resolve classes. + * + * @param classLoader The new classloader. + * @throws LockedException If {@link #lock()} was called on this object. + * @return This object (for method chaining). + */ + public BeanContextFactory setClassLoader(ClassLoader classLoader) throws LockedException { + writeLock(); + try { + this.classLoader = classLoader; + return this; + } finally { + writeUnlock(); + } + } + + //-------------------------------------------------------------------------------- + // Overridden methods on Lockable + //-------------------------------------------------------------------------------- + + @Override /* Lockable */ + public BeanContextFactory lock() { + if (! isLocked()) { + writeLock(); + super.lock(); + try { + notBeanPackages = Collections.unmodifiableSet(notBeanPackages); + notBeanClasses = Collections.unmodifiableSet(notBeanClasses); + implClasses = Collections.unmodifiableMap(implClasses); + uriVars = Collections.unmodifiableMap(uriVars); + } finally { + writeUnlock(); + } + } + return this; + } + + private void writeLock() { + checkLock(); + lock.writeLock().lock(); + beanContext = null; + } + + private void writeUnlock() { + lock.writeLock().unlock(); + } + + private void readLock() { + lock.readLock().lock(); + } + + private void readUnlock() { + lock.readLock().unlock(); + } + + @Override /* Lockable */ + public synchronized BeanContextFactory clone() { + try { + return (BeanContextFactory)super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); // Shouldn't happen. + } + } + + @Override /* Lockable */ + public void onUnclone() { + readLock(); + try { + notBeanPackages = new LinkedHashSet<String>(notBeanPackages); + filters = new LinkedList<Class<?>>(filters); + notBeanClasses = newClassTreeSet(notBeanClasses); + implClasses = newClassTreeMap(implClasses); + uriVars = new TreeMap<String,String>(uriVars); + beanContext = null; + } finally { + readUnlock(); + } + } + + @Override /* Object */ + public String toString() { + readLock(); + try { + ObjectMap m = new ObjectMap() + .append("id", System.identityHashCode(this)) + .append("beansRequireDefaultConstructor", beansRequireDefaultConstructor) + .append("beansRequireSerializable", beansRequireSerializable) + .append("beansRequireSettersForGetters", beansRequireSettersForGetters) + .append("beansRequireSomeProperties", beansRequireSomeProperties) + .append("beanMapPutReturnsOldValue", beanMapPutReturnsOldValue) + .append("useInterfaceProxies", useInterfaceProxies) + .append("ignoreUnknownBeanProperties", ignoreUnknownBeanProperties) + .append("ignoreUnknownNullBeanProperties", ignoreUnknownNullBeanProperties) + .append("ignorePropertiesWithoutSetters", ignorePropertiesWithoutSetters) + .append("ignoreInvocationExceptionsOnGetters", ignoreInvocationExceptionsOnGetters) + .append("ignoreInvocationExceptionsOnSetters", ignoreInvocationExceptionsOnSetters) + .append("useJavaBeanIntrospector", useJavaBeanIntrospector) + .append("filters", filters) + .append("notBeanPackages", notBeanPackages) + .append("notBeanClasses", notBeanClasses) + .append("implClasses", implClasses) + .append("uriVars", uriVars); + return m.toString(JsonSerializer.DEFAULT_LAX_READABLE); + } catch (SerializeException e) { + return e.getLocalizedMessage(); + } finally { + readUnlock(); + } + } + + private TreeMap<Class<?>,Class<?>> newClassTreeMap(Map<Class<?>,Class<?>> m) { + TreeMap<Class<?>,Class<?>> tm = newClassTreeMap(); + tm.putAll(m); + return tm; + } + + private TreeMap<Class<?>,Class<?>> newClassTreeMap() { + return new TreeMap<Class<?>,Class<?>>(new ClassComparator()); + } + + private TreeSet<Class<?>> newClassTreeSet(Set<Class<?>> s) { + TreeSet<Class<?>> ts = newClassTreeSet(); + ts.addAll(s); + return ts; + } + + private TreeSet<Class<?>> newClassTreeSet() { + return new TreeSet<Class<?>>(new ClassComparator()); + } +}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanContextProperties.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanContextProperties.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanContextProperties.class new file mode 100755 index 0000000..652c6b4 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanContextProperties.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanContextProperties.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanContextProperties.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanContextProperties.java new file mode 100755 index 0000000..4edaf17 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanContextProperties.java @@ -0,0 +1,188 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2014. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core; + +import java.beans.*; +import java.io.*; +import java.lang.reflect.*; + +import com.ibm.juno.core.annotation.*; + +/** + * Configurable properties on the {@link BeanContextFactory} class. + * <p> + * Use the {@link BeanContextFactory#setProperty(String, Object)} method to set properties on + * bean contexts. + * + * @author James Bognar ([email protected]) + */ +public final class BeanContextProperties { + + /** + * Require no-arg constructor ({@link Boolean}, default=<jk>false</jk>). + * <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"; + + /** + * Require {@link Serializable} interface ({@link Boolean}, default=<jk>false</jk>). + * <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"; + + /** + * Require setters for getters ({@link Boolean}, default=<jk>false</jk>). + * <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"; + + /** + * Require some properties ({@link Boolean}, default=<jk>true</jk>). + * <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"; + + /** + * Put returns old value ({@link Boolean}, default=<jk>false</jk>). + * <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"; + + /** + * Look for bean constructors with the specified minimum visibility ({@link Visibility}, default={@link Visibility#PUBLIC}). + */ + public static final String BEAN_beanConstructorVisibility = "BeanContext.beanConstructorVisibility"; + + /** + * Look for bean classes with the specified minimum visibility ({@link Visibility}, default={@link Visibility#PUBLIC}). + * <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"; + + /** + * Look for bean fields with the specified minimum visibility ({@link Visibility}, default={@link Visibility#PUBLIC}). + * <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"; + + /** + * Look for bean methods with the specified minimum visibility ({@link Visibility}, default={@link Visibility#PUBLIC}). + * <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"; + + /** + * Use Java {@link Introspector} for determining bean properties ({@link Boolean}, default=<jk>false</jk>). + * <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"; + + /** + * Use interface proxies ({@link Boolean}, default=<jk>true</jk>). + * <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"; + + /** + * Ignore unknown properties ({@link Boolean}, default=<jk>false</jk>). + * <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"; + + /** + * Ignore unknown properties with null values ({@link Boolean}, default=<jk>true</jk>). + * <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"; + + /** + * Ignore properties without setters ({@link Boolean}, default=<jk>true</jk>). + * <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"; + + /** + * Ignore invocation errors on getters ({@link Boolean}, default=<jk>false</jk>). + * <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"; + + /** + * Ignore invocation errors on setters ({@link Boolean}, default=<jk>false</jk>). + * <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"; + + /** + * Add to the list of packages whose classes should not be considered beans ({@link String}, comma-delimited list). + * <p> + * When specified, the current list of ignore packages are appended to. + * The default list of ignore packages are as follows: + * <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> + * 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_addNotBeanPackages = "BeanContext.addNotBeanPackages"; + + /** + * Remove from the list of packages whose classes should not be considered beans ({@link String}, comma-delimited list). + * <p> + * Essentially the opposite of {@link #BEAN_addNotBeanPackages}. + */ + public static final String BEAN_removeNotBeanPackages = "BeanContext.removeNotBeanPackages"; +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMap$1$1.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMap$1$1.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMap$1$1.class new file mode 100755 index 0000000..a16cbe6 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMap$1$1.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMap$1.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMap$1.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMap$1.class new file mode 100755 index 0000000..f6be25f Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMap$1.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMap.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMap.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMap.class new file mode 100755 index 0000000..61512ad Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMap.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMap.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMap.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMap.java new file mode 100755 index 0000000..8aa1fb5 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMap.java @@ -0,0 +1,461 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2011, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core; + +import java.io.*; +import java.lang.reflect.*; +import java.util.*; + +import com.ibm.juno.core.annotation.*; +import com.ibm.juno.core.filter.*; +import com.ibm.juno.core.parser.*; +import com.ibm.juno.core.xml.annotation.*; + +/** + * Java bean wrapper class. + * + * + * <h6 class='topic'>Description</h6> + * <p> + * A wrapper that wraps Java bean instances inside of a {@link Map} interface that allows + * properties on the wrapped object can be accessed using the {@link Map#get(Object) get()} and {@link Map#put(Object,Object) put()} methods. + * <p> + * Use the {@link BeanContext} class to create instances of this class. + * + * + * <h6 class='topic'>Bean property order</h6> + * <p> + * The order of the properties returned by the {@link Map#keySet() keySet()} and {@link Map#entrySet() entrySet()} methods are as follows: + * <ul> + * <li>If {@link Bean @Bean} annotation is specified on class, then the order is the same as the list of properties in the annotation. + * <li>If {@link Bean @Bean} annotation is not specified on the class, then the order is the same as that returned + * by the {@link java.beans.BeanInfo} class (i.e. ordered by definition in the class). + * </ul> + * <br> + * The order can also be overridden through the use of a {@link BeanFilter}. + * + * + * <h6 class='topic'>POJO filters</h6> + * <p> + * If {@link PojoFilter PojoFilters} are defined on the class types of the properties of this bean or the bean properties themselves, the + * {@link #get(Object)} and {@link #put(Object, Object)} methods will automatically + * transform the property value to and from the serialized form. + * + * @author Barry M. Caceres + * @author James Bognar ([email protected]) + * @param <T> Specifies the type of object that this map encapsulates. + */ +public class BeanMap<T> extends AbstractMap<String,Object> implements Delegate<T> { + + /** The wrapped object. */ + protected T bean; + + /** Temporary holding cache for beans with read-only properties. Normally null. */ + protected Map<String,Object> propertyCache; + + /** Temporary holding cache for bean properties of array types when the add() method is being used. */ + protected Map<String,List<?>> arrayPropertyCache; + + /** The BeanMeta associated with the class of the object. */ + protected BeanMeta<T> meta; + + /** + * Instance of this class are instantiated through the BeanContext class. + * + * @param bean The bean to wrap inside this map. + * @param meta The metadata associated with the bean class. + */ + protected BeanMap(T bean, BeanMeta<T> meta) { + this.bean = bean; + this.meta = meta; + if (meta.constructorArgs.length > 0) + propertyCache = new TreeMap<String,Object>(); + } + + /** + * Returns the metadata associated with this bean map. + * + * @return The metadata associated with this bean map. + */ + public BeanMeta<T> getMeta() { + return meta; + } + + /** + * Returns the wrapped bean object. + * Triggers bean creation if bean has read-only properties set through a constructor + * defined by the {@link BeanConstructor} annotation. + * + * @return The inner bean object. + */ + public T getBean() { + T b = getBean(true); + + // If we have any arrays that need to be constructed, do it now. + if (arrayPropertyCache != null) { + for (Map.Entry<String,List<?>> e : arrayPropertyCache.entrySet()) { + String key = e.getKey(); + List<?> value = e.getValue(); + BeanPropertyMeta<T> bpm = getPropertyMeta(key); + try { + bpm.setArray(b, value); + } catch (Exception e1) { + throw new RuntimeException(e1); + } + } + arrayPropertyCache = null; + } + return b; + } + + /** + * Returns the wrapped bean object. + * <p> + * If <code>create</code> is <jk>false</jk>, then this method may return <jk>null</jk> + * if the bean has read-only properties set through a constructor + * defined by the {@link BeanConstructor} annotation. + * <p> + * This method does NOT always return the bean in it's final state. + * Array properties temporary stored as ArrayLists are not finalized + * until the {@link #getBean()} method is called. + * + * @param create If bean hasn't been instantiated yet, then instantiate it. + * @return The inner bean object. + */ + public T getBean(boolean create) { + /** If this is a read-only bean, then we need to create it. */ + if (bean == null && create && meta.constructorArgs.length > 0) { + String[] props = meta.constructorArgs; + Constructor<T> c = meta.constructor; + Object[] args = new Object[props.length]; + for (int i = 0; i < props.length; i++) + args[i] = propertyCache.remove(props[i]); + try { + bean = c.newInstance(args); + for (Map.Entry<String,Object> e : propertyCache.entrySet()) + put(e.getKey(), e.getValue()); + propertyCache = null; + } catch (Exception e) { + throw new BeanRuntimeException(e); + } + } + return bean; + } + + /** + * Returns the value of the property identified as the URI property (annotated with {@link BeanProperty#beanUri()} as <jk>true</jk>). + * + * @return The URI value, or <jk>null</jk> if no URI property exists on this bean. + */ + public Object getBeanUri() { + BeanMeta<T> bm = getMeta(); + return bm.hasBeanUriProperty() ? bm.getBeanUriProperty().get(this) : null; + } + + /** + * Sets the bean URI property if the bean has a URI property. + * Ignored otherwise. + * + * @param o The bean URI object. + * @return If the bean context setting {@code beanMapPutReturnsOldValue} is <jk>true</jk>, then the old value of the property is returned. + * Otherwise, this method always returns <jk>null</jk>. + */ + public Object putBeanUri(Object o) { + BeanMeta<T> bm = getMeta(); + return bm.hasBeanUriProperty() ? bm.getBeanUriProperty().set(this, o) : null; + } + + /** + * Sets a property on the bean. + * <p> + * If there is a {@link PojoFilter} associated with this bean property or bean property type class, then + * you must pass in a filtered value. + * For example, if the bean property type class is a {@link Date} and the bean property has the + * {@link com.ibm.juno.core.filters.DateFilter.ISO8601DT} filter associated with it through the + * {@link BeanProperty#filter() @BeanProperty.filter()} annotation, the value being passed in must be + * a String containing an ISO8601 date-time string value. + * + * <dl> + * <dt>Example:</dt> + * <dd> + * <p class='bcode'> + * <jc>// Construct a bean with a 'birthDate' Date field</jc> + * Person p = <jk>new</jk> Person(); + * + * <jc>// Create a bean context and add the ISO8601 date-time filter</jc> + * BeanContext beanContext = <jk>new</jk> BeanContext().addFilter(DateFilter.ISO8601DT.<jk>class</jk>); + * + * <jc>// Wrap our bean in a bean map</jc> + * BeanMap<Person> b = beanContext.forBean(p); + * + * <jc>// Set the field</jc> + * myBeanMap.put(<js>"birthDate"</js>, <js>"'1901-03-03T04:05:06-5000'"</js>); + * </p> + * </dd> + * </dl> + * + * @param property The name of the property to set. + * @param value The value to set the property to. + * @return If the bean context setting {@code beanMapPutReturnsOldValue} is <jk>true</jk>, then the old value of the property is returned. + * Otherwise, this method always returns <jk>null</jk>. + * @throws RuntimeException if any of the following occur. + * <ul> + * <li>BeanMapEntry does not exist on the underlying object. + * <li>Security settings prevent access to the underlying object setter method. + * <li>An exception occurred inside the setter method. + * </ul> + */ + @Override /* Map */ + public Object put(String property, Object value) { + BeanPropertyMeta<T> p = meta.properties.get(property); + if (p == null) { + if (meta.ctx.ignoreUnknownBeanProperties) + return null; + if (property.equals("<uri>") && meta.uriProperty != null) + return meta.uriProperty.set(this, value); + + // If this bean has subtypes, and we haven't set the subtype yet, + // store the property in a temporary cache until the bean can be instantiated. + // This eliminates the need for requiring that the sub type attribute be provided first. + if (meta.subTypeIdProperty != null) { + if (propertyCache == null) + propertyCache = new TreeMap<String,Object>(); + return propertyCache.put(property, value); + } + + throw new BeanRuntimeException(meta.c, "Bean property ''{0}'' not found.", property); + } + if (meta.filter != null) + if (meta.filter.writeProperty(this.bean, property, value)) + return null; + return p.set(this, value); + } + + /** + * Add a value to a collection or array property. + * <p> + * As a general rule, adding to arrays is not recommended since the array must be recreate each time + * this method is called. + * + * @param property Property name or child-element name (if {@link Xml#childName()} is specified). + * @param value The value to add to the collection or array. + */ + public void add(String property, Object value) { + BeanPropertyMeta<T> p = meta.properties.get(property); + if (p == null) { + if (meta.ctx.ignoreUnknownBeanProperties) + return; + throw new BeanRuntimeException(meta.c, "Bean property ''{0}'' not found.", property); + } + p.add(this, value); + } + + + /** + * Gets a property on the bean. + * <p> + * If there is a {@link PojoFilter} associated with this bean property or bean property type class, then + * this method will return the filtered value. + * For example, if the bean property type class is a {@link Date} and the bean property has the + * {@link com.ibm.juno.core.filters.DateFilter.ISO8601DT} filter associated with it through the + * {@link BeanProperty#filter() @BeanProperty.filter()} annotation, this method will return a String + * containing an ISO8601 date-time string value. + * + * <dl> + * <dt>Example:</dt> + * <dd> + * <p class='bcode'> + * <jc>// Construct a bean with a 'birthDate' Date field</jc> + * Person p = <jk>new</jk> Person(); + * p.setBirthDate(<jk>new</jk> Date(1, 2, 3, 4, 5, 6)); + * + * <jc>// Create a bean context and add the ISO8601 date-time filter</jc> + * BeanContext beanContext = <jk>new</jk> BeanContext().addFilter(DateFilter.ISO8601DT.<jk>class</jk>); + * + * <jc>// Wrap our bean in a bean map</jc> + * BeanMap<Person> b = beanContext.forBean(p); + * + * <jc>// Get the field as a string (i.e. "'1901-03-03T04:05:06-5000'")</jc> + * String s = myBeanMap.get(<js>"birthDate"</js>); + * </p> + * </dd> + * </dl> + * + * @param property The name of the property to get. + * @throws RuntimeException if any of the following occur. + * <ol> + * <li>BeanMapEntry does not exist on the underlying object. + * <li>Security settings prevent access to the underlying object getter method. + * <li>An exception occurred inside the getter method. + * </ol> + */ + @Override /* Map */ + public Object get(Object property) { + BeanPropertyMeta<T> p = meta.properties.get(property); + if (p == null) + return null; + if (meta.filter != null && property != null) + return meta.filter.readProperty(this.bean, property.toString(), p.get(this)); + return p.get(this); + } + + /** + * Convenience method for setting multiple property values by passing in JSON (or other) text. + * <p> + * Typically the input is going to be JSON, although the actual data type + * depends on the default parser specified by the {@link BeanContextFactory#setDefaultParser(ReaderParser)} method + * on the bean context that created this map. + * + * <dl> + * <dt>Example:</dt> + * <dd> + * <p class='bcode'> + * aPersonBean.load(<js>"{name:'John Smith',age:21}"</js>) + * </p> + * </dd> + * </dl> + * + * @param input The text that will get parsed into a map and then added to this map. + * @return This object (for method chaining). + * @throws ParseException If the input contains a syntax error or is malformed. + */ + public BeanMap<T> load(String input) throws ParseException { + putAll(new ObjectMap(input, this.meta.ctx.defaultParser)); + return this; + } + + /** + * Convenience method for setting multiple property values by passing in a reader. + * + * @param r The text that will get parsed into a map and then added to this map. + * @param p The parser to use to parse the text. + * @return This object (for method chaining). + * @throws ParseException If the input contains a syntax error or is malformed. + * @throws IOException Thrown by <code>Reader</code>. + */ + public BeanMap<T> load(Reader r, ReaderParser p) throws ParseException, IOException { + putAll(new ObjectMap(r, p)); + return this; + } + + /** + * Convenience method for loading this map with the contents of the specified map. + * <p> + * Identical to {@link #putAll(Map)} except as a fluent-style method. + * + * @param entries The map containing the entries to add to this map. + * @return This object (for method chaining). + */ + @SuppressWarnings({"unchecked","rawtypes"}) + public BeanMap<T> load(Map entries) { + putAll(entries); + return this; + } + + /** + * Returns the names of all properties associated with the bean. + * <p> + * The returned set is unmodifiable. + */ + @Override /* Map */ + public Set<String> keySet() { + return meta.properties.keySet(); + } + + /** + * Returns the specified property on this bean map. + * <p> + * Allows you to get and set an individual property on a bean without having a + * handle to the bean itself by using the {@link BeanMapEntry#getValue()} + * and {@link BeanMapEntry#setValue(Object)} methods. + * <p> + * This method can also be used to get metadata on a property by + * calling the {@link BeanMapEntry#getMeta()} method. + * + * @param propertyName The name of the property to look up. + * @return The bean property, or null if the bean has no such property. + */ + public BeanMapEntry<T> getProperty(String propertyName) { + BeanPropertyMeta<T> p = meta.properties.get(propertyName); + if (p == null) + return null; + return new BeanMapEntry<T>(this, p); + } + + /** + * Returns the metadata on the specified property. + * + * @param propertyName The name of the bean property. + * @return Metadata on the specified property, or <jk>null</jk> if that property does not exist. + */ + public BeanPropertyMeta<T> getPropertyMeta(String propertyName) { + return meta.properties.get(propertyName); + } + + /** + * Returns the {@link ClassMeta} of the wrapped bean. + * + * @return The class type of the wrapped bean. + */ + @Override /* Delagate */ + public ClassMeta<T> getClassMeta() { + return this.meta.getClassMeta(); + } + + /** + * Returns all the properties associated with the bean. + */ + @Override /* Map */ + public Set<Entry<String,Object>> entrySet() { + + // Construct our own anonymous set to implement this function. + Set<Entry<String,Object>> s = new AbstractSet<Entry<String,Object>>() { + + // Get the list of properties from the meta object. + // Note that the HashMap.values() method caches results, so this collection + // will really only be constructed once per bean type since the underlying + // map never changes. + final Collection<BeanPropertyMeta<T>> pSet = meta.properties.values(); + + @Override /* Set */ + public Iterator<java.util.Map.Entry<String, Object>> iterator() { + + // Construct our own anonymous iterator that uses iterators against the meta.properties + // map to maintain position. This prevents us from having to construct any of our own + // collection objects. + return new Iterator<Entry<String,Object>>() { + + final Iterator<BeanPropertyMeta<T>> pIterator = pSet.iterator(); + + @Override /* Iterator */ + public boolean hasNext() { + return pIterator.hasNext(); + } + + @Override /* Iterator */ + public Map.Entry<String, Object> next() { + return new BeanMapEntry<T>(BeanMap.this, pIterator.next()); + } + + @Override /* Iterator */ + public void remove() { + throw new UnsupportedOperationException("Cannot remove item from iterator."); + } + }; + } + + @Override /* Set */ + public int size() { + return pSet.size(); + } + }; + + return s; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMapEntry.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMapEntry.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMapEntry.class new file mode 100755 index 0000000..eb1afcc Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMapEntry.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMapEntry.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMapEntry.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMapEntry.java new file mode 100755 index 0000000..c502776 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMapEntry.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core; + +import java.util.*; + +import com.ibm.juno.core.annotation.*; +import com.ibm.juno.core.filter.*; + +/** + * Represents a single entry in a bean map. + * <p> + * This class can be used to get and set property values on a bean, or to get metadata on a property. + * + * <h6 class='topic'>Examples</h6> + * <p class='bcode'> + * <jc>// Construct a new bean</jc> + * Person p = <jk>new</jk> Person(); + * + * <jc>// Wrap it in a bean map</jc> + * BeanMap<Person> b = BeanContext.<jsf>DEFAULT</jsf>.forBean(p); + * + * <jc>// Get a reference to the birthDate property</jc> + * BeanMapEntry birthDate = b.getProperty(<js>"birthDate"</js>); + * + * <jc>// Set the property value</jc> + * birthDate.setValue(<jk>new</jk> Date(1, 2, 3, 4, 5, 6)); + * + * <jc>// Or if the DateFilter.DEFAULT_ISO8601DT is registered with the bean context, set a filtered value</jc> + * birthDate.setFilteredValue(<js>"'1901-03-03T04:05:06-5000'"</js>); + * </p> + * + * @author James Bognar ([email protected]) + * + * @param <T> The bean type. + */ +public class BeanMapEntry<T> implements Map.Entry<String,Object> { + private final BeanMap<T> beanMap; + private final BeanPropertyMeta<T> meta; + + /** + * Constructor. + * + * @param beanMap The bean map that this entry belongs to. + * @param property The bean property. + */ + protected BeanMapEntry(BeanMap<T> beanMap, BeanPropertyMeta<T> property) { + this.beanMap = beanMap; + this.meta = property; + } + + @Override /* Map.Entry */ + public String getKey() { + return meta.getName(); + } + + /** + * Returns the value of this property. + * <p> + * If there is a {@link PojoFilter} associated with this bean property or bean property type class, then + * this method will return the filtered value. + * For example, if the bean property type class is a {@link Date} and the bean property has the + * {@link com.ibm.juno.core.filters.DateFilter.ISO8601DT} filter associated with it through the + * {@link BeanProperty#filter() @BeanProperty.filter()} annotation, this method will return a String + * containing an ISO8601 date-time string value. + */ + @Override /* Map.Entry */ + public Object getValue() { + return meta.get(this.beanMap); + } + + /** + * Sets the value of this property. + * <p> + * If the property is an array of type {@code X}, then the value can be a {@code Collection<X>} or {@code X[]} or {@code Object[]}. + * <p> + * If the property is a bean type {@code X}, then the value can either be an {@code X} or a {@code Map}. + * <p> + * If there is a {@link PojoFilter} associated with this bean property or bean property type class, then + * you must pass in a filtered value. + * For example, if the bean property type class is a {@link Date} and the bean property has the + * {@link com.ibm.juno.core.filters.DateFilter.ISO8601DT} filter associated with it through the + * {@link BeanProperty#filter() @BeanProperty.filter()} annotation, the value being passed in must be + * a String containing an ISO8601 date-time string value. + * + * @return The set value after it's been converted. + */ + @Override /* Map.Entry */ + public Object setValue(Object value) { + return meta.set(this.beanMap, value); + } + + /** + * Returns the bean map that contains this property. + * + * @return The bean map that contains this property. + */ + public BeanMap<T> getBeanMap() { + return this.beanMap; + } + + /** + * Returns the metadata about this bean property. + * + * @return Metadata about this bean property. + */ + public BeanPropertyMeta<T> getMeta() { + return this.meta; + } + + @Override /* Object */ + public String toString() { + return this.getKey() + "=" + this.getValue(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMeta$BeanMethod.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMeta$BeanMethod.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMeta$BeanMethod.class new file mode 100755 index 0000000..a4cbf3a Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMeta$BeanMethod.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMeta$SubTypePropertyMeta.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMeta$SubTypePropertyMeta.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMeta$SubTypePropertyMeta.class new file mode 100755 index 0000000..2a98356 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMeta$SubTypePropertyMeta.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMeta.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMeta.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMeta.class new file mode 100755 index 0000000..c6c554a Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMeta.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMeta.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMeta.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMeta.java new file mode 100755 index 0000000..d5ac436 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMeta.java @@ -0,0 +1,749 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2011, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core; + +import static com.ibm.juno.core.Visibility.*; +import static com.ibm.juno.core.utils.ClassUtils.*; + +import java.beans.*; +import java.io.*; +import java.lang.reflect.*; +import java.util.*; +import java.util.Map.Entry; + +import com.ibm.juno.core.annotation.*; +import com.ibm.juno.core.filter.*; +import com.ibm.juno.core.html.*; +import com.ibm.juno.core.jena.*; +import com.ibm.juno.core.utils.*; +import com.ibm.juno.core.xml.*; + + +/** + * Encapsulates all access to the properties of a bean class (like a souped-up {@link java.beans.BeanInfo}). + * + * + * <h6 class='topic'>Description</h6> + * <p> + * Uses introspection to find all the properties associated with this class. If the {@link Bean @Bean} annotation + * is present on the class, or the class has a {@link BeanFilter} registered with it in the bean context, + * then that information is used to determine the properties on the class. + * Otherwise, the {@code BeanInfo} functionality in Java is used to determine the properties on the class. + * + * + * <h6 class='topic'>Bean property ordering</h6> + * <p> + * The order of the properties are as follows: + * <ul> + * <li>If {@link Bean @Bean} annotation is specified on class, then the order is the same as the list of properties in the annotation. + * <li>If {@link Bean @Bean} annotation is not specified on the class, then the order is based on the following. + * <ul> + * <li>Public fields (same order as {@code Class.getFields()}). + * <li>Properties returned by {@code BeanInfo.getPropertyDescriptors()}. + * <li>Non-standard getters/setters with {@link BeanProperty @BeanProperty} annotation defined on them. + * </ul> + * </ul> + * <br> + * The order can also be overridden through the use of an {@link BeanFilter}. + * + * + * @param <T> The class type that this metadata applies to. + * @author Barry M. Caceres + * @author James Bognar ([email protected]) + */ +public class BeanMeta<T> { + + /** The target class type that this meta object describes. */ + protected ClassMeta<T> classMeta; + + /** The target class that this meta object describes. */ + protected Class<T> c; + + /** The properties on the target class. */ + protected Map<String,BeanPropertyMeta<T>> properties; + + /** The getter properties on the target class. */ + protected Map<Method,String> getterProps = new HashMap<Method,String>(); + + /** The setter properties on the target class. */ + protected Map<Method,String> setterProps = new HashMap<Method,String>(); + + /** The bean context that created this metadata object. */ + protected BeanContext ctx; + + /** Optional bean filter associated with the target class. */ + protected BeanFilter<? extends T> filter; + + /** Type variables implemented by this bean. */ + protected Map<Class<?>,Class<?>[]> typeVarImpls; + + /** The constructor for this bean. */ + protected Constructor<T> constructor; + + /** For beans with constructors with BeanConstructor annotation, this is the list of constructor arg properties. */ + protected String[] constructorArgs = new String[0]; + + /** XML-related metadata */ + protected XmlBeanMeta<T> xmlMeta; + + // Other fields + BeanPropertyMeta<T> uriProperty; // The property identified as the URI for this bean (annotated with @BeanProperty.beanUri). + BeanPropertyMeta<T> subTypeIdProperty; // The property indentified as the sub type differentiator property (identified by @Bean.subTypeProperty annotation). + PropertyNamer propertyNamer; // Class used for calculating bean property names. + + BeanMeta() {} + + /** + * Constructor. + * + * @param classMeta The target class. + * @param ctx The bean context that created this object. + * @param filter Optional bean filter associated with the target class. Can be <jk>null</jk>. + */ + protected BeanMeta(ClassMeta<T> classMeta, BeanContext ctx, com.ibm.juno.core.filter.BeanFilter<? extends T> filter) { + this.classMeta = classMeta; + this.ctx = ctx; + this.filter = filter; + this.c = classMeta.getInnerClass(); + } + + /** + * Returns the {@link ClassMeta} of this bean. + * + * @return The {@link ClassMeta} of this bean. + */ + @BeanIgnore + public ClassMeta<T> getClassMeta() { + return classMeta; + } + + /** + * Initializes this bean meta, and returns an error message if the specified class is not + * a bean for any reason. + * + * @return Reason why this class isn't a bean, or <jk>null</jk> if no problems detected. + * @throws BeanRuntimeException If unexpected error occurs such as invalid annotations on the bean class. + */ + @SuppressWarnings("unchecked") + protected String init() throws BeanRuntimeException { + + try { + Visibility + conVis = ctx.beanConstructorVisibility, + cVis = ctx.beanClassVisibility, + mVis = ctx.beanMethodVisibility, + fVis = ctx.beanFieldVisibility; + + // If @Bean.interfaceClass is specified on the parent class, then we want + // to use the properties defined on that class, not the subclass. + Class<?> c2 = (filter != null && filter.getInterfaceClass() != null ? filter.getInterfaceClass() : c); + + Class<?> stopClass = (filter != null ? filter.getStopClass() : Object.class); + if (stopClass == null) + stopClass = Object.class; + + Map<String,BeanPropertyMeta<T>> normalProps = new LinkedHashMap<String,BeanPropertyMeta<T>>(); + + /// See if this class matches one the patterns in the exclude-class list. + if (ctx.isNotABean(c)) + return "Class matches exclude-class list"; + + if (! cVis.isVisible(c.getModifiers())) + return "Class is not public"; + + if (c.isAnnotationPresent(BeanIgnore.class)) + return "Class is annotated with @BeanIgnore"; + + // Make sure it's serializable. + if (filter == null && ctx.beansRequireSerializable && ! isParentClass(Serializable.class, c)) + return "Class is not serializable"; + + // Look for @BeanConstructor constructor. + for (Constructor<?> x : c.getConstructors()) { + if (x.isAnnotationPresent(BeanConstructor.class)) { + if (constructor != null) + throw new BeanRuntimeException(c, "Multiple instances of '@BeanConstructor' found."); + constructor = (Constructor<T>)x; + constructorArgs = x.getAnnotation(BeanConstructor.class).properties(); + if (constructorArgs.length != x.getParameterTypes().length) + throw new BeanRuntimeException(c, "Number of properties defined in '@BeanConstructor' annotation does not match number of parameters in constructor."); + if (! setAccessible(constructor)) + throw new BeanRuntimeException(c, "Could not set accessibility to true on method with @BeanConstructor annotation. Method=''{0}''", constructor.getName()); + } + } + + // If this is an interface, look for impl classes defined in the context. + if (constructor == null) + constructor = (Constructor<T>)ctx.getImplClassConstructor(c, conVis); + + if (constructor == null) + constructor = (Constructor<T>)ClassMeta.findNoArgConstructor(c, conVis); + + if (constructor == null && filter == null && ctx.beansRequireDefaultConstructor) + return "Class does not have the required no-arg constructor"; + + if (! setAccessible(constructor)) + throw new BeanRuntimeException(c, "Could not set accessibility to true on no-arg constructor"); + + // Explicitly defined property names in @Bean annotation. + Set<String> fixedBeanProps = new LinkedHashSet<String>(); + + if (filter != null) { + + // Get the 'properties' attribute if specified. + if (filter.getProperties() != null) + for (String p : filter.getProperties()) + fixedBeanProps.add(p); + + if (filter.getPropertyNamer() != null) + propertyNamer = filter.getPropertyNamer().newInstance(); + } + + if (propertyNamer == null) + propertyNamer = new PropertyNamerDefault(); + + // First populate the properties with those specified in the bean annotation to + // ensure that ordering first. + for (String name : fixedBeanProps) + normalProps.put(name, new BeanPropertyMeta<T>(this, name)); + + if (ctx.useJavaBeanIntrospector) { + BeanInfo bi = null; + if (! c2.isInterface()) + bi = Introspector.getBeanInfo(c2, stopClass); + else + bi = Introspector.getBeanInfo(c2, null); + if (bi != null) { + for (PropertyDescriptor pd : bi.getPropertyDescriptors()) { + String name = pd.getName(); + if (! normalProps.containsKey(name)) + normalProps.put(name, new BeanPropertyMeta<T>(this, name)); + normalProps.get(name).setGetter(pd.getReadMethod()).setSetter(pd.getWriteMethod()); + } + } + + } else /* Use 'better' introspection */ { + + for (Field f : findBeanFields(c2, stopClass, fVis)) { + String name = findPropertyName(f, fixedBeanProps); + if (name != null) { + if (! normalProps.containsKey(name)) + normalProps.put(name, new BeanPropertyMeta<T>(this, name)); + normalProps.get(name).setField(f); + } + } + + List<BeanMethod> bms = findBeanMethods(c2, stopClass, mVis, fixedBeanProps, propertyNamer); + + // Iterate through all the getters. + for (BeanMethod bm : bms) { + String pn = bm.propertyName; + Method m = bm.method; + if (! normalProps.containsKey(pn)) + normalProps.put(pn, new BeanPropertyMeta<T>(this, pn)); + BeanPropertyMeta<?> bpm = normalProps.get(pn); + if (! bm.isSetter) + bpm.setGetter(m); + } + + // Now iterate through all the setters. + for (BeanMethod bm : bms) { + if (bm.isSetter) { + BeanPropertyMeta<?> bpm = normalProps.get(bm.propertyName); + if (bm.matchesPropertyType(bpm)) + bpm.setSetter(bm.method); + } + } + } + + typeVarImpls = new HashMap<Class<?>,Class<?>[]>(); + findTypeVarImpls(c, typeVarImpls); + if (typeVarImpls.isEmpty()) + typeVarImpls = null; + + // Eliminate invalid properties, and set the contents of getterProps and setterProps. + for (Iterator<BeanPropertyMeta<T>> i = normalProps.values().iterator(); i.hasNext();) { + BeanPropertyMeta<T> p = i.next(); + try { + if (p.validate()) { + + if (p.getGetter() != null) + getterProps.put(p.getGetter(), p.getName()); + + if (p.getSetter() != null) + setterProps.put(p.getSetter(), p.getName()); + + if (p.isBeanUri()) + uriProperty = p; + + } else { + i.remove(); + } + } catch (ClassNotFoundException e) { + throw new BeanRuntimeException(c, e.getLocalizedMessage()); + } + } + + // Check for missing properties. + for (String fp : fixedBeanProps) + if (! normalProps.containsKey(fp)) + throw new BeanRuntimeException(c, "The property ''{0}'' was defined on the @Bean(properties=X) annotation but was not found on the class definition.", fp); + + // Mark constructor arg properties. + for (String fp : constructorArgs) { + BeanPropertyMeta<T> m = normalProps.get(fp); + if (m == null) + throw new BeanRuntimeException(c, "The property ''{0}'' was defined on the @BeanConstructor(properties=X) annotation but was not found on the class definition.", fp); + m.setAsConstructorArg(); + } + + // Make sure at least one property was found. + if (filter == null && ctx.beansRequireSomeProperties && normalProps.size() == 0) + return "No properties detected on bean class"; + + properties = new LinkedHashMap<String,BeanPropertyMeta<T>>(); + + if (filter != null && filter.getSubTypeProperty() != null) { + String subTypeProperty = filter.getSubTypeProperty(); + this.subTypeIdProperty = new SubTypePropertyMeta(subTypeProperty, filter.getSubTypes(), normalProps.remove(subTypeProperty)); + properties.put(subTypeProperty, this.subTypeIdProperty); + } + + properties.putAll(normalProps); + + // If a filter is defined, look for inclusion and exclusion lists. + if (filter != null) { + + // Eliminated excluded properties if BeanFilter.excludeKeys is specified. + String[] includeKeys = filter.getProperties(); + String[] excludeKeys = filter.getExcludeProperties(); + if (excludeKeys != null) { + for (String k : excludeKeys) + properties.remove(k); + + // Only include specified properties if BeanFilter.includeKeys is specified. + // Note that the order must match includeKeys. + } else if (includeKeys != null) { + Map<String,BeanPropertyMeta<T>> properties2 = new LinkedHashMap<String,BeanPropertyMeta<T>>(); + for (String k : includeKeys) { + if (properties.containsKey(k)) + properties2.put(k, properties.get(k)); + } + properties = properties2; + } + } + + xmlMeta = new XmlBeanMeta<T>(this, null); + + // We return this through the Bean.keySet() interface, so make sure it's not modifiable. + properties = Collections.unmodifiableMap(properties); + + } catch (BeanRuntimeException e) { + throw e; + } catch (Exception e) { + return "Exception: " + StringUtils.getStackTrace(e); + } + + return null; + } + + /** + * Returns the subtype ID property of this bean if it has one. + * <p> + * The subtype id is specified using the {@link Bean#subTypeProperty()} annotation. + * + * @return The meta property for the sub type property, or <jk>null</jk> if no subtype is defined for this bean. + */ + public BeanPropertyMeta<T> getSubTypeIdProperty() { + return subTypeIdProperty; + } + + /** + * Returns <jk>true</jk> if this bean has subtypes associated with it. + * Subtypes are defined using the {@link Bean#subTypes()} annotation. + * + * @return <jk>true</jk> if this bean has subtypes associated with it. + */ + public boolean isSubTyped() { + return subTypeIdProperty != null; + } + + /** + * Returns <jk>true</jk> if one of the properties on this bean is annotated with {@link BeanProperty#beanUri()} as <jk>true</jk> + * + * @return <jk>true</jk> if this bean has subtypes associated with it. <jk>true</jk> if there is a URI property associated with this bean. + */ + public boolean hasBeanUriProperty() { + return uriProperty != null; + } + + /** + * Returns the bean property marked as the URI for the bean (annotated with {@link BeanProperty#beanUri()} as <jk>true</jk>). + * + * @return The URI property, or <jk>null</jk> if no URI property exists on this bean. + */ + public BeanPropertyMeta<T> getBeanUriProperty() { + return uriProperty; + } + + /* + * Temporary getter/setter method struct. + */ + private static class BeanMethod { + String propertyName; + boolean isSetter; + Method method; + Class<?> type; + + BeanMethod(String propertyName, boolean isSetter, Method method) { + this.propertyName = propertyName; + this.isSetter = isSetter; + this.method = method; + if (isSetter) + this.type = method.getParameterTypes()[0]; + else + this.type = method.getReturnType(); + } + + /* + * Returns true if this method matches the class type of the specified property. + * Only meant to be used for setters. + */ + boolean matchesPropertyType(BeanPropertyMeta<?> b) { + if (b == null) + return false; + + // Get the bean property type from the getter/field. + Class<?> pt = null; + if (b.getGetter() != null) + pt = b.getGetter().getReturnType(); + else if (b.getField() != null) + pt = b.getField().getType(); + + // Doesn't match if no getter/field defined. + if (pt == null) + return false; + + // Doesn't match if not same type or super type as getter/field. + if (! isParentClass(type, pt)) + return false; + + // If a setter was previously set, only use this setter if it's a closer + // match (e.g. prev type is a superclass of this type). + if (b.getSetter() == null) + return true; + + Class<?> prevType = b.getSetter().getParameterTypes()[0]; + return isParentClass(prevType, type, true); + } + + @Override /* Object */ + public String toString() { + return method.toString(); + } + } + + /* + * Find all the bean methods on this class. + * + * @param c The filtered class. + * @param stopClass Don't look above this class in the hierarchy. + * @param v The minimum method visibility. + * @param fixedBeanProps Only include methods whose properties are in this list. + * @param pn Use this property namer to determine property names from the method names. + */ + private static List<BeanMethod> findBeanMethods(Class<?> c, Class<?> stopClass, Visibility v, Set<String> fixedBeanProps, PropertyNamer pn) { + List<BeanMethod> l = new LinkedList<BeanMethod>(); + + for (Class<?> c2 : findClasses(c, stopClass)) { + for (Method m : c2.getDeclaredMethods()) { + int mod = m.getModifiers(); + if (Modifier.isStatic(mod) || Modifier.isTransient(mod)) + continue; + if (m.isAnnotationPresent(BeanIgnore.class)) + continue; + if (m.isBridge()) // This eliminates methods with covariant return types from parent classes on child classes. + continue; + if (! (v.isVisible(m) || m.isAnnotationPresent(BeanProperty.class))) + continue; + String n = m.getName(); + Class<?>[] pt = m.getParameterTypes(); + Class<?> rt = m.getReturnType(); + boolean isGetter = false, isSetter = false; + if (pt.length == 1 && n.startsWith("set") && (isParentClass(rt, c) || rt.equals(Void.TYPE))) { + isSetter = true; + n = n.substring(3); + } else if (pt.length == 0 && n.startsWith("get") && (! rt.equals(Void.TYPE))) { + isGetter = true; + n = n.substring(3); + } else if (pt.length == 0 && n.startsWith("is") && (rt.equals(Boolean.TYPE) || rt.equals(Boolean.class))) { + isGetter = true; + n = n.substring(2); + } + n = pn.getPropertyName(n); + if (isGetter || isSetter) { + BeanProperty bp = m.getAnnotation(BeanProperty.class); + if (bp != null && ! bp.name().equals("")) { + n = bp.name(); + if (! fixedBeanProps.isEmpty()) + if (! fixedBeanProps.contains(n)) + throw new BeanRuntimeException(c, "Method property ''{0}'' identified in @BeanProperty, but missing from @Bean", n); + } + l.add(new BeanMethod(n, isSetter, m)); + } + } + } + return l; + } + + private static Collection<Field> findBeanFields(Class<?> c, Class<?> stopClass, Visibility v) { + List<Field> l = new LinkedList<Field>(); + for (Class<?> c2 : findClasses(c, stopClass)) { + for (Field f : c2.getDeclaredFields()) { + int m = f.getModifiers(); + if (Modifier.isStatic(m) || Modifier.isTransient(m)) + continue; + if (f.isAnnotationPresent(BeanIgnore.class)) + continue; + if (! (v.isVisible(f) || f.isAnnotationPresent(BeanProperty.class))) + continue; + l.add(f); + } + } + return l; + } + + private static List<Class<?>> findClasses(Class<?> c, Class<?> stopClass) { + LinkedList<Class<?>> l = new LinkedList<Class<?>>(); + findClasses(c, l, stopClass); + return l; + } + + private static void findClasses(Class<?> c, LinkedList<Class<?>> l, Class<?> stopClass) { + while (c != null && stopClass != c) { + l.addFirst(c); + for (Class<?> ci : c.getInterfaces()) + findClasses(ci, l, stopClass); + c = c.getSuperclass(); + } + } + + /** + * Returns the metadata on all properties associated with this bean. + * + * @return Metadata on all properties associated with this bean. + */ + public Collection<BeanPropertyMeta<T>> getPropertyMetas() { + return this.properties.values(); + } + + /** + * Returns the metadata on the specified list of properties. + * + * @param pNames The list of properties to retrieve. If <jk>null</jk>, returns all properties. + * @return The metadata on the specified list of properties. + */ + public Collection<BeanPropertyMeta<T>> getPropertyMetas(final String...pNames) { + if (pNames == null) + return getPropertyMetas(); + List<BeanPropertyMeta<T>> l = new ArrayList<BeanPropertyMeta<T>>(pNames.length); + for (int i = 0; i < pNames.length; i++) + l.add(getPropertyMeta(pNames[i])); + return l; + } + + /** + * Returns XML related metadata for this bean type. + * + * @return The XML metadata for this bean type. + */ + public XmlBeanMeta<T> getXmlMeta() { + return xmlMeta; + } + + /** + * Returns metadata about the specified property. + * + * @param name The name of the property on this bean. + * @return The metadata about the property, or <jk>null</jk> if no such property exists + * on this bean. + */ + public BeanPropertyMeta<T> getPropertyMeta(String name) { + return this.properties.get(name); + } + + /** + * Creates a new instance of this bean. + * + * @param outer The outer object if bean class is a non-static inner member class. + * @return A new instance of this bean if possible, or <jk>null</jk> if not. + * @throws IllegalArgumentException Thrown by constructor. + * @throws InstantiationException Thrown by constructor. + * @throws IllegalAccessException Thrown by constructor. + * @throws InvocationTargetException Thrown by constructor. + */ + @SuppressWarnings("unchecked") + protected T newBean(Object outer) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { + if (classMeta.isMemberClass) { + if (constructor != null) + return constructor.newInstance(outer); + } else { + if (constructor != null) + return constructor.newInstance((Object[])null); + InvocationHandler h = classMeta.getProxyInvocationHandler(); + if (h != null) { + ClassLoader cl = classMeta.beanContext.classLoader; + if (cl == null) + cl = this.getClass().getClassLoader(); + return (T)Proxy.newProxyInstance(cl, new Class[] { classMeta.innerClass, java.io.Serializable.class }, h); + } + } + return null; + } + + /* + * Returns the property name of the specified field if it's a valid property. + * Returns null if the field isn't a valid property. + */ + private String findPropertyName(Field f, Set<String> fixedBeanProps) { + BeanProperty bp = f.getAnnotation(BeanProperty.class); + if (bp != null && ! bp.name().equals("")) { + String name = bp.name(); + if (fixedBeanProps.isEmpty() || fixedBeanProps.contains(name)) + return name; + throw new BeanRuntimeException(c, "Method property ''{0}'' identified in @BeanProperty, but missing from @Bean", name); + } + String name = propertyNamer.getPropertyName(f.getName()); + if (fixedBeanProps.isEmpty() || fixedBeanProps.contains(name)) + return name; + return null; + } + + /** + * Recursively determines the classes represented by parameterized types in the class hierarchy of + * the specified type, and puts the results in the specified map.<br> + * <p> + * For example, given the following classes... + * <p class='bcode'> + * public static class BeanA<T> { + * public T x; + * } + * public static class BeanB extends BeanA<Integer>} {...} + * <p> + * ...calling this method on {@code BeanB.class} will load the following data into {@code m} indicating + * that the {@code T} parameter on the BeanA class is implemented with an {@code Integer}: + * <p class='bcode'> + * {BeanA.class:[Integer.class]} + * <p> + * TODO: This code doesn't currently properly handle the following situation: + * <p class='bcode'> + * public static class BeanB<T extends Number> extends BeanA<T>; + * public static class BeanC extends BeanB<Integer>; + * <p> + * When called on {@code BeanC}, the variable will be detected as a {@code Number}, not an {@code Integer}.<br> + * If anyone can figure out a better way of doing this, please do so! + * + * @param t The type we're recursing. + * @param m Where the results are loaded. + */ + private static void findTypeVarImpls(Type t, Map<Class<?>,Class<?>[]> m) { + if (t instanceof Class) { + Class<?> c = (Class<?>)t; + findTypeVarImpls(c.getGenericSuperclass(), m); + for (Type ci : c.getGenericInterfaces()) + findTypeVarImpls(ci, m); + } else if (t instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType)t; + Type rt = pt.getRawType(); + if (rt instanceof Class) { + Type[] gImpls = pt.getActualTypeArguments(); + Class<?>[] gTypes = new Class[gImpls.length]; + for (int i = 0; i < gImpls.length; i++) { + Type gt = gImpls[i]; + if (gt instanceof Class) + gTypes[i] = (Class<?>)gt; + else if (gt instanceof TypeVariable) { + TypeVariable<?> tv = (TypeVariable<?>)gt; + for (Type upperBound : tv.getBounds()) + if (upperBound instanceof Class) + gTypes[i] = (Class<?>)upperBound; + } + } + m.put((Class<?>)rt, gTypes); + findTypeVarImpls(pt.getRawType(), m); + } + } + } + + /* + * Bean property for getting and setting bean subtype. + */ + @SuppressWarnings({"rawtypes","unchecked"}) + private class SubTypePropertyMeta extends BeanPropertyMeta<T> { + + private Map<Class<?>,String> subTypes; + private BeanPropertyMeta<T> realProperty; // Bean property if bean actually has a real subtype field. + + SubTypePropertyMeta(String subTypeAttr, Map<Class<?>,String> subTypes, BeanPropertyMeta<T> realProperty) { + super(BeanMeta.this, subTypeAttr, ctx.string()); + this.subTypes = subTypes; + this.realProperty = realProperty; + this.htmlMeta = new HtmlBeanPropertyMeta<T>(this); + this.xmlMeta = new XmlBeanPropertyMeta<T>(this); + this.rdfMeta = new RdfBeanPropertyMeta<T>(this); + } + + /* + * Setting this bean property causes the inner bean to be set to the subtype implementation. + */ + @Override /* BeanPropertyMeta */ + public Object set(BeanMap<T> m, Object value) throws BeanRuntimeException { + if (value == null) + throw new BeanRuntimeException("Attempting to set bean subtype property to null."); + String subTypeId = value.toString(); + for (Entry<Class<?>,String> e : subTypes.entrySet()) { + if (e.getValue().equals(subTypeId)) { + Class subTypeClass = e.getKey(); + m.meta = ctx.getBeanMeta(subTypeClass); + try { + m.bean = (T)subTypeClass.newInstance(); + if (realProperty != null) + realProperty.set(m, value); + // If subtype attribute wasn't specified first, set them again from the temporary cache. + if (m.propertyCache != null) + for (Map.Entry<String,Object> me : m.propertyCache.entrySet()) + m.put(me.getKey(), me.getValue()); + } catch (Exception e1) { + throw new BeanRuntimeException(e1); + } + return null; + } + } + throw new BeanRuntimeException(c, "Unknown subtype ID ''{0}''", subTypeId); + } + + @Override /* BeanPropertyMeta */ + public Object get(BeanMap<T> m) throws BeanRuntimeException { + String subTypeId = filter.getSubTypes().get(c); + if (subTypeId == null) + throw new BeanRuntimeException(c, "Unmapped sub type class"); + return subTypeId; + } + } + + @Override /* Object */ + public String toString() { + StringBuilder sb = new StringBuilder(c.getName()); + sb.append(" {\n"); + for (BeanPropertyMeta<?> pm : this.properties.values()) + sb.append("\t").append(pm.toString()).append(",\n"); + sb.append("}"); + return sb.toString(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMetaFiltered.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMetaFiltered.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMetaFiltered.class new file mode 100755 index 0000000..6f63839 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMetaFiltered.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMetaFiltered.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMetaFiltered.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMetaFiltered.java new file mode 100755 index 0000000..9effae3 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanMetaFiltered.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core; + +import java.util.*; + +import com.ibm.juno.core.annotation.*; +import com.ibm.juno.core.xml.*; + +/** + * Sames as {@link BeanMeta}, except the list of bean properties are limited + * by a {@link BeanProperty#properties()} annotation. + * + * @param <T> The class type that this metadata applies to. + * @author James Bognar ([email protected]) + */ +public final class BeanMetaFiltered<T> extends BeanMeta<T> { + + private final BeanMeta<T> innerMeta; + + /** + * Wrapper constructor. + * + * @param innerMeta The unfiltered bean meta of the bean property. + * @param pNames The list of filtered property names. + */ + public BeanMetaFiltered(BeanMeta<T> innerMeta, String[] pNames) { + this.innerMeta = innerMeta; + this.properties = new LinkedHashMap<String,BeanPropertyMeta<T>>(); + for (String p : pNames) + properties.put(p, innerMeta.getPropertyMeta(p)); + this.xmlMeta = new XmlBeanMeta<T>(innerMeta, pNames); + } + + /** + * Wrapper constructor. + * + * @param innerMeta The unfiltered bean meta of the bean property. + * @param pNames The list of filtered property names. + */ + public BeanMetaFiltered(BeanMeta<T> innerMeta, Collection<String> pNames) { + this(innerMeta, pNames.toArray(new String[pNames.size()])); + } + + @Override /* Delagate */ + public ClassMeta<T> getClassMeta() { + return innerMeta.classMeta; + } + + @Override /* BeanMeta */ + public Collection<BeanPropertyMeta<T>> getPropertyMetas() { + return properties.values(); + } + + @Override /* BeanMeta */ + public BeanPropertyMeta<T> getPropertyMeta(String name) { + return properties.get(name); + } + + @Override /* Object */ + public String toString() { + return innerMeta.c.getName(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanPropertyMeta.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanPropertyMeta.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanPropertyMeta.class new file mode 100755 index 0000000..d55611b Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/BeanPropertyMeta.class differ
