You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2017/03/10 16:51:23 UTC
[27/34] incubator-juneau git commit: Add builder classes for all
serializers and parsers.
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/95e832e1/juneau-core/src/main/java/org/apache/juneau/ContextFactory.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/ContextFactory.java b/juneau-core/src/main/java/org/apache/juneau/ContextFactory.java
deleted file mode 100644
index 6dcde7b..0000000
--- a/juneau-core/src/main/java/org/apache/juneau/ContextFactory.java
+++ /dev/null
@@ -1,1344 +0,0 @@
-// ***************************************************************************************************************************
-// * 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.BeanContext.*;
-
-import java.lang.reflect.*;
-import java.util.*;
-import java.util.concurrent.*;
-import java.util.concurrent.locks.*;
-
-import org.apache.juneau.internal.*;
-import org.apache.juneau.json.*;
-import org.apache.juneau.parser.*;
-
-/**
- * A factory for instantiating {@link Context} objects.
- * <p>
- * The hierarchy of these objects are...
- * <ul class='spaced-list'>
- * <li>{@link ContextFactory} - A thread-safe, modifiable context property store.<br>
- * Used to create {@link Context} objects.
- * <li>{@link Context} - A reusable, cachable, thread-safe, read-only context with configuration properties copied from the factory.<br>
- * Often used to create {@link Session} objects.
- * <li>{@link Session} - A one-time-use non-thread-safe object.<br>
- * Used by serializers and parsers to retrieve context properties and to be used as scratchpads.
- * </ul>
- *
- * <h6 class='topic'>ContextFactory objects</h6>
- * <p>
- * Context factories can be thought of as consisting of the following:
- * <ul class='spaced-list'>
- * <li>A <code>Map<String,Object></code> of context properties.
- * <li>A <code>Map<Class,Context></code> of context instances.
- * </ul>
- * <p>
- * Context factories are used to create and cache {@link Context} objects using the {@link #getContext(Class)} method.
- * <p>
- * As a general rule, {@link ContextFactory} objects are 'slow'.<br>
- * Setting and retrieving properties on a factory can involve relatively slow data conversion and synchronization.<br>
- * However, the {@link #getContext(Class)} method is fast, and will return cached context objects if the context properties have not changed.
- * <p>
- * Context factories can be used to store context properties for a variety of contexts.<br>
- * For example, a single factory can store context properties for the JSON serializer, XML serializer, HTML serializer
- * etc... and can thus be used to retrieve context objects for those serializers.<br>
- * <p>
- * Other notes:
- * <ul class='spaced-list'>
- * <li>Context factories can be locked using the {@link #lock()} method.<br>
- * This prevents the context properties from being further modified.
- * <li>Context factories can be cloned using the {@link #clone} method.<br>
- * This will return a new unlocked factory with the same context properties.
- * </ul>
- *
- * <h6 class='topic'>Context properties</h6>
- * <p>
- * Context properties are 'settings' for serializers and parsers.<br>
- * For example, the {@link BeanContext#BEAN_sortProperties} context property defines whether
- * bean properties should be serialized in alphabetical order.
- * <p>
- * Each {@link Context} object should contain the context properties that apply to it as static
- * fields (e.g {@link BeanContext#BEAN_sortProperties}).
- * <p>
- * Context properties can be of the following types:
- * <ul class='spaced-list'>
- * <li><l>SIMPLE</l> - A simple property.<br>
- * Examples include: booleans, integers, Strings, Classes, etc...<br>
- * <br>
- * An example of this would be the {@link BeanContext#BEAN_sortProperties} property.<br>
- * It's name is simply <js>"BeanContext.sortProperties"</js>.
- *
- * <li><l>SET</l> - A sorted set of objects.<br>
- * These are denoted by appending <js>".set"</js> to the property name.<br>
- * Objects can be of any type, even complex types.<br>
- * Sorted sets use tree sets to maintain the value in alphabetical order.<br>
- * <br>
- * For example, the {@link BeanContext#BEAN_notBeanClasses} property is used to store classes that should not be treated like beans.<br>
- * It's name is <js>"BeanContext.notBeanClasses.set"</js>.
- *
- * <li><l>LIST</l> - A list of unique objects.<br>
- * These are denoted by appending <js>".list"</js> to the property name.<br>
- * Objects can be of any type, even complex types.<br>
- * Use lists if the ordering of the values in the set is important (similar to how the order of entries in a classpath is important).<br>
- * <br>
- * For example, the {@link BeanContext#BEAN_beanFilters} property is used to store bean filters.<br>
- * It's name is <js>"BeanContext.transforms.list"</js>.
- *
- * <li><l>MAP</l> - A sorted map of key-value pairs.<br>
- * These are denoted by appending <js>".map"</js> to the property name.<br>
- * Keys can be any type directly convertable to and from Strings.
- * Values can be of any type, even complex types.<br>
- * <br>
- * For example, the {@link BeanContext#BEAN_implClasses} property is used to specify the names of implementation classes for interfaces.<br>
- * It's name is <js>"BeanContext.implClasses.map"</js>.<br>
- * </ul>
- * <p>
- * All context properties are set using the {@link #setProperty(String, Object)} method.
- * <p>
- * Default values for context properties can be specified globally as system properties.<br>
- * Example: <code>System.<jsm>setProperty</jsm>(<jsf>BEAN_sortProperties</jsf>, <jk>true</jk>);</code>
- * <p>
- * SET and LIST properties can be added to using the {@link #addToProperty(String, Object)} method and removed from using the {@link #removeFromProperty(String, Object)} method.
- * <p>
- * SET and LIST properties can also be added to and removed from by appending <js>".add"</js> or <js>".remove"</js> to the property name and using the {@link #setProperty(String, Object)} method.
- * <p>
- * The following shows the two different ways to append to a set or list property:
- * <p class='bcode'>
- * Config config = <jk>new</jk> Config().set(<js>"BeanContext.notBeanClasses.set"</js>, Collections.<jsm>emptySet</jsm>());
- *
- * <jc>// Append to set property using addTo().</jc>
- * config.addTo(<js>"BeanContext.notBeanClasses.set"</js>, MyNotBeanClass.<jk>class</jk>);
- *
- * <jc>// Append to set property using set().</jc>
- * config.set(<js>"BeanContext.notBeanClasses.set.add"</js>, MyNotBeanClass.<jk>class</jk>);
- * </p>
- * <p>
- * Lists are appended to the beginning of the set so that behavior can be overridden.<br>
- * <p>
- * For sample, the following code shows the order in which POJO swaps are applied.<br>
- * In this case, we want F3 and F4 to appear at the beginning of the set so that they
- * take precedence over F1 and F2....
- * <p class='bcode'>
- * <jc>// Result will be F3,F4,F1,F2</jc>
- * config.addTo(<js>"BeanContext.transforms.list"</js>, Arrays.<jsm>asList</jsm>(F1.<jk>class</jk>, F2.<jk>class</jk>));
- * config.addTo(<js>"BeanContext.transforms.list"</js>, Arrays.<jsm>asList</jsm>(F3.<jk>class</jk>,F4.<jk>class</jk>));
- * </p>
- * <p>
- * SET and LIST properties can also be set and manipulated using JSON strings.
- * <p class='bcode'>
- * ContextFactory f = ContextFactory.<jsm>create</jsm>();
- *
- * <jc>// Set SET value using JSON array.
- * f.set(<js>"BeanContext.notBeanClasses.set"</js>, <js>"['com.my.MyNotBeanClass1']"</js>);
- *
- * <jc>// Add to SET using simple string.
- * f.addTo(<js>"BeanContext.notBeanClasses.set"</js>, <js>"com.my.MyNotBeanClass2"</js>);
- *
- * <jc>// Add an array of values as a JSON array..
- * f.addTo(<js>"BeanContext.notBeanClasses.set"</js>, <js>"['com.my.MyNotBeanClass3']"</js>);
- *
- * <jc>// Remove an array of values as a JSON array..
- * f.removeFrom(<js>"BeanContext.notBeanClasses.set"</js>, <js>"['com.my.MyNotBeanClass3']"</js>);
- * </p>
- * <p>
- * MAP properties can be added to using the {@link #putToProperty(String, Object, Object)} and {@link #putToProperty(String, Object)} methods.<br>
- * MAP property entries can be removed by setting the value to <jk>null</jk> (e.g. <code>config.putTo(<js>"BEAN_implClasses"</js>, MyNotBeanClass.<jk>class</jk>, <jk>null</jk>);</code>.<br>
- * MAP properties can also be added to by appending <js>".put"</js> to the property name and using the {@link #setProperty(String, Object)} method.<br>
- * <p>
- * The following shows the two different ways to append to a set property:
- * <p class='bcode'>
- * ContextFactory f = ContextFactory.<jsm>create</jsm>().set(<js>"BeanContext.implClasses.map"</js>, Collections.<jsm>emptyMap</jsm>());
- *
- * <jc>// Append to map property using putTo().</jc>
- * f.putTo(<js>"BeanContext.implClasses.map"</js>, MyInterface.<jk>class</jk>, MyInterfaceImpl.<jk>class</jk>);
- *
- * <jc>// Append to map property using set().</jc>
- * Map m = <jk>new</jk> HashMap(){{put(MyInterface.<jk>class</jk>,MyInterfaceImpl.<jk>class</jk>)}};
- * f.set(<js>"BeanContext.implClasses.map.put"</js>, m);
- * </p>
- * <p>
- * MAP properties can also be set and manipulated using JSON strings.
- * <p class='bcode'>
- * ContextFactory f = ContextFactory.<jsm>create</jsm>();
- *
- * <jc>// Set MAP value using JSON object.</jc>
- * f.set(<js>"BeanContext.implClasses.map"</js>, <js>"{'com.my.MyInterface1':'com.my.MyInterfaceImpl1'}"</js>);
- *
- * <jc>// Add to MAP using JSON object.</jc>
- * f.putTo(<js>"BeanContext.implClasses.map"</js>, <js>"{'com.my.MyInterface2':'com.my.MyInterfaceImpl2'}"</js>);
- *
- * <jc>// Remove from MAP using JSON object.</jc>
- * f.putTo(<js>"BeanContext.implClasses.map"</js>, <js>"{'com.my.MyInterface2':null}"</js>);
- * </p>
- * <p>
- * Context properties are retrieved from this factory using the following 3 methods:
- * <ul class='spaced-list'>
- * <li>{@link #getProperty(String, Class, Object)} - Retrieve a SIMPLE or SET property converted to the specified class type.
- * <li>{@link #getMap(String, Class, Class, Map)} - Retrieve a MAP property with keys/values converted to the specified class types.
- * <li>{@link #getPropertyMap(String)} - Retrieve a map of all context properties with the specified prefix (e.g. <js>"BeanContext"</js> for {@link BeanContext} properties).
- * </ul>
- * <p>
- * As a general rule, only {@link Context} objects will use these read methods.
- *
- * <h6 class='topic'>Context objects</h6>
- * <p>
- * A Context object can be thought of as unmodifiable snapshot of a factory.<br>
- * They should be 'fast' by avoiding synchronization by using final fields whenever possible.<br>
- * However, they MUST be thread safe.
- * <p>
- * Context objects are created using the {@link #getContext(Class)} method.<br>
- * As long as the properties on a factory have not been modified, the factory will return a cached copy
- * of a context.
- * <p class='bcode'>
- * ContextFactory f = ContextFactory.<jsm>create</jsm>();
- *
- * <jc>// Get BeanContext with default factory settings.</jc>
- * BeanContext bc = f.getContext(BeanContext.<jk>class</jk>);
- *
- * <jc>// Get another one. This will be the same one.</jc>
- * BeanContext bc2 = f.getContext(BeanContext.<jk>class</jk>);
- * <jsm>assertTrue</jsm>(bc1 == bc2);
- *
- * <jc>// Set a property.</jc>
- * f.set(<jsf>BEAN_sortProperties</jsf>, <jk>true</jk>);
- *
- * <jc>// Get another one. This will be different!</jc>
- * bc2 = f.getContext(BeanContext.<jk>class</jk>);
- * <jsm>assertFalse</jsm>(bc1 == bc2);
- * </p>
- *
- * <h6 class='topic'>Session objects</h6>
- * <p>
- * Session objects are created through {@link Context} objects, typically through a <code>createContext()</code> method.<br>
- * Unlike context objects, they are NOT reusable and NOT thread safe.<br>
- * They are meant to be used one time and then thrown away.<br>
- * They should NEVER need to use synchronization.
- * <p>
- * Session objects are also often used as scratchpads for information such as keeping track of call stack
- * information to detect recursive loops when serializing beans.
- */
-public final class ContextFactory extends Lockable {
-
- // All configuration properties in this object.
- // Keys are property prefixes (e.g. 'BeanContext').
- // Values are maps containing properties for that specific prefix.
- private Map<String,PropertyMap> properties = new ConcurrentSkipListMap<String,PropertyMap>();
-
- // Context cache.
- // This gets cleared every time any properties change on this object.
- private final Map<Class<? extends Context>,Context> contexts = new ConcurrentHashMap<Class<? extends Context>,Context>();
-
- // Global Context cache.
- // Context factories that are the 'same' will use the same maps from this cache.
- // 'same' means the context properties are all the same when converted to strings.
- private static final ConcurrentHashMap<Integer, ConcurrentHashMap<Class<? extends Context>,Context>> globalContextCache = new ConcurrentHashMap<Integer, ConcurrentHashMap<Class<? extends Context>,Context>>();
-
- private ReadWriteLock lock = new ReentrantReadWriteLock();
- private Lock rl = lock.readLock(), wl = lock.writeLock();
-
- // Classloader used to instantiate Class instances.
- ClassLoader classLoader = ClassLoader.getSystemClassLoader();
-
- // Parser to use to convert JSON strings to POJOs
- ReaderParser defaultParser;
-
- // Bean session for converting strings to POJOs.
- private static BeanSession beanSession;
-
- // Used to keep properties in alphabetical order regardless of whether
- // they're not strings.
- private static Comparator<Object> PROPERTY_COMPARATOR = new Comparator<Object>() {
- @Override
- public int compare(Object o1, Object o2) {
- return unswap(o1).toString().compareTo(unswap(o2).toString());
- }
- };
-
- /**
- * Create a new context factory with default settings.
- *
- * @return A new context factory with default settings.
- */
- public static ContextFactory create() {
- ContextFactory f = new ContextFactory();
- BeanContext.loadDefaults(f);
- return f;
- }
-
- /**
- * Create a new context factory with settings copied from the specified factory.
- *
- * @param copyFrom The existing factory to copy properties from.
- * @return A new context factory with default settings.
- */
- public static ContextFactory create(ContextFactory copyFrom) {
- return new ContextFactory().copyFrom(copyFrom);
- }
-
-
- ContextFactory() {}
-
- /**
- * Copy constructor.
- *
- * @param copyFrom The factory to copy properties from.
- */
- public ContextFactory(ContextFactory copyFrom) {
- copyFrom(copyFrom);
- }
-
- /**
- * Copies the properties from the specified factory into this factory.
- *
- * @param cf The factory to copy from.
- * @return This object (for method chaining).
- */
- public ContextFactory copyFrom(ContextFactory cf) {
- for (Map.Entry<String,PropertyMap> e : cf.properties.entrySet())
- this.properties.put(e.getKey(), new PropertyMap(e.getValue()));
- this.classLoader = cf.classLoader;
- this.defaultParser = cf.defaultParser;
- return this;
- }
-
- /**
- * Sets a configuration property value on this object.
- * <p>
- * A typical usage is to set or overwrite configuration values like so...
- * <p class='bcode'>
- * ContextFactory g = ContextFactory.<jsm>create</jsm>();
- * f.setProperty(<jsf>BEAN_sortProperties</jsf>, <jk>true</jk>);
- * </p>
- * <p>
- * The possible class types of the value depend on the property type:
- * <p>
- * <table class='styled'>
- * <tr>
- * <th>Property type</th>
- * <th>Example</th>
- * <th>Allowed value type</th>
- * </tr>
- * <tr>
- * <td>Set <l>SIMPLE</l></td>
- * <td><js>"Foo.x"</js></td>
- * <td>Any object type.</td>
- * </tr>
- * <tr>
- * <td>Set <l>SET/LIST</l></td>
- * <td><js>"Foo.x.set"</js></td>
- * <td>Any collection or array of any objects, or a String containing a JSON array.</td>
- * </tr>
- * <tr>
- * <td>Add/Remove <l>SET/LIST</l></td>
- * <td><js>"Foo.x.set.add"</js></td>
- * <td>If a collection, adds or removes the entries in the collection. Otherwise, adds/removes a single entry.</td>
- * </tr>
- * <tr>
- * <td>Set <l>MAP</l></td>
- * <td><js>"Foo.x.map"</js></td>
- * <td>A map, or a String containing a JSON object. Entries overwrite existing map.</td>
- * </tr>
- * <tr>
- * <td>Put <l>MAP</l></td>
- * <td><js>"Foo.x.map.put"</js></td>
- * <td>A map, or a String containing a JSON object. Entries are added to existing map.</td>
- * </tr>
- * </table>
- *
- * @param name The configuration property name.<br>
- * If name ends with <l>.add</l>, then the specified value is added to the
- * existing property value as an entry in a SET or LIST property.<br>
- * If name ends with <l>.put</l>, then the specified value is added to the
- * existing property value as a key/value pair in a MAP property.<br>
- * If name ends with <l>.remove</l>, then the specified value is removed from the
- * existing property property value in a SET or LIST property.<br>
- *
- * @param value The new value.
- * If <jk>null</jk>, the property value is deleted.<br>
- * In general, the value type can be anything.<br>
- *
- * @return This object (for method chaining).
- */
- public ContextFactory setProperty(String name, Object value) {
- String prefix = prefix(name);
-
- if (name.endsWith(".add"))
- return addToProperty(name.substring(0, name.lastIndexOf('.')), value);
-
- if (name.endsWith(".put"))
- return putToProperty(name.substring(0, name.lastIndexOf('.')), value);
-
- if (name.endsWith(".remove"))
- return removeFromProperty(name.substring(0, name.lastIndexOf('.')), value);
-
- wl.lock();
- try {
- checkLock();
- contexts.clear();
- if (! properties.containsKey(prefix))
- properties.put(prefix, new PropertyMap(prefix));
- properties.get(prefix).set(name, value);
- } finally {
- wl.unlock();
- }
- return this;
- }
-
- /**
- * Convenience method for setting multiple properties in one call.
- * <p>
- * This appends to any previous configuration properties set on this config.
- *
- * @param newProperties The new properties to set.
- * @return This object (for method chaining).
- */
- @SuppressWarnings({ "unchecked", "rawtypes" })
- public ContextFactory setProperties(Map newProperties) {
- wl.lock();
- try {
- checkLock();
- contexts.clear();
- for (Map.Entry e : (Set<Map.Entry>)newProperties.entrySet()) {
- String name = e.getKey().toString();
- Object value = e.getValue();
- String prefix = prefix(name);
- if (name.endsWith(".add"))
- addToProperty(name.substring(0, name.lastIndexOf('.')), value);
- else if (name.endsWith(".remove"))
- removeFromProperty(name.substring(0, name.lastIndexOf('.')), value);
- else {
- if (! properties.containsKey(prefix))
- properties.put(prefix, new PropertyMap(prefix));
- properties.get(prefix).set(name, value);
- }
- }
-
- } finally {
- wl.unlock();
- }
- return this;
- }
-
- /**
- * Adds a value to a SET property.
- *
- * @param name The property name.
- * @param value The new value to add to the SET property.
- * @return This object (for method chaining).
- * @throws ConfigException If property is not a SET property.
- */
- public ContextFactory addToProperty(String name, Object value) {
- String prefix = prefix(name);
- wl.lock();
- try {
- checkLock();
- contexts.clear();
- if (! properties.containsKey(prefix))
- properties.put(prefix, new PropertyMap(prefix));
- properties.get(prefix).addTo(name, value);
- } finally {
- wl.unlock();
- }
- return this;
- }
-
- /**
- * Adds or overwrites a value to a MAP property.
- *
- * @param name The property name.
- * @param key The property value map key.
- * @param value The property value map value.
- * @return This object (for method chaining).
- * @throws ConfigException If property is not a MAP property.
- */
- public ContextFactory putToProperty(String name, Object key, Object value) {
- String prefix = prefix(name);
- wl.lock();
- try {
- checkLock();
- contexts.clear();
- if (! properties.containsKey(prefix))
- properties.put(prefix, new PropertyMap(prefix));
- properties.get(prefix).putTo(name, key, value);
- } finally {
- wl.unlock();
- }
- return this;
- }
-
- /**
- * Adds or overwrites a value to a MAP property.
- *
- * @param name The property value.
- * @param value The property value map value.
- * @return This object (for method chaining).
- * @throws ConfigException If property is not a MAP property.
- */
- public ContextFactory putToProperty(String name, Object value) {
- String prefix = prefix(name);
- wl.lock();
- try {
- checkLock();
- contexts.clear();
- if (! properties.containsKey(prefix))
- properties.put(prefix, new PropertyMap(prefix));
- properties.get(prefix).putTo(name, value);
- } finally {
- wl.unlock();
- }
- return this;
- }
-
- /**
- * Removes a value from a SET property.
- *
- * @param name The property name.
- * @param value The property value in the SET property.
- * @return This object (for method chaining).
- * @throws ConfigException If property is not a SET property.
- */
- public ContextFactory removeFromProperty(String name, Object value) {
- String prefix = prefix(name);
- wl.lock();
- try {
- checkLock();
- contexts.clear();
- if (properties.containsKey(prefix))
- properties.get(prefix).removeFrom(name, value);
- } finally {
- wl.unlock();
- }
- return this;
- }
-
- /**
- * Returns an instance of the specified context initialized with the properties
- * in this config.
- * <p>
- * Multiple calls to this method for the same config class will return the same
- * cached value as long as the config properties on this config are not touched.
- * <p>
- * As soon as any properties are modified on this config, all cached entries
- * are discarded and recreated as needed.
- *
- * @param c The context class to instantiate.
- * @return The context instance.
- */
- @SuppressWarnings("unchecked")
- public <T extends Context> T getContext(Class<T> c) {
- rl.lock();
- try {
- try {
- if (! contexts.containsKey(c)) {
-
- // Try to get it from the global cache.
- Integer key = hashCode();
- if (! globalContextCache.containsKey(key))
- globalContextCache.putIfAbsent(key, new ConcurrentHashMap<Class<? extends Context>,Context>());
- ConcurrentHashMap<Class<? extends Context>, Context> cacheForThisConfig = globalContextCache.get(key);
-
- if (! cacheForThisConfig.containsKey(c))
- cacheForThisConfig.putIfAbsent(c, c.getConstructor(ContextFactory.class).newInstance(this));
-
- contexts.put(c, cacheForThisConfig.get(c));
- }
- return (T)contexts.get(c);
- } catch (Exception e) {
- throw new ConfigException("Could not instantiate config class ''{0}''", className(c)).initCause(e);
- }
- } finally {
- rl.unlock();
- }
- }
-
- /**
- * Returns the configuration properties with the specified prefix.
- * <p>
- * For example, if <l>prefix</l> is <js>"BeanContext"</js>, then retrieves
- * all configuration properties that are prefixed with <js>"BeanContext."</js>.
- *
- * @param prefix The prefix of properties to retrieve.
- * @return The configuration properties with the specified prefix, never <jk>null</jk>.
- */
- public PropertyMap getPropertyMap(String prefix) {
- rl.lock();
- try {
- PropertyMap m = properties.get(prefix);
- return m == null ? new PropertyMap(prefix) : m;
- } finally {
- rl.unlock();
- }
- }
-
- /**
- * Specifies the classloader to use when resolving classes from strings.
- * <p>
- * Can be used for resolving class names when the classes being created are in a different
- * classloader from the Juneau code.
- * <p>
- * If <jk>null</jk>, the system classloader 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 ContextFactory setClassLoader(ClassLoader classLoader) {
- checkLock();
- this.classLoader = (classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader);
- return this;
- }
-
- /**
- * Specifies the parser to use to convert Strings to POJOs.
- * <p>
- * If <jk>null</jk>, {@link JsonParser#DEFAULT} will be used.
- *
- * @param defaultParser The new defaultParser.
- * @throws LockedException If {@link #lock()} was called on this object.
- * @return This object (for method chaining).
- */
- public ContextFactory setDefaultParser(ReaderParser defaultParser) {
- checkLock();
- this.defaultParser = defaultParser == null ? JsonParser.DEFAULT : defaultParser;
- return this;
- }
-
- /**
- * Returns a property value converted to the specified type.
- *
- * @param name The full name of the property (e.g. <js>"BeanContext.sortProperties"</js>)
- * @param type The class type to convert the property value to.
- * @param def The default value if the property is not set.
- *
- * @return The property value.
- * @throws ConfigException If property has a value that cannot be converted to a boolean.
- */
- public <T> T getProperty(String name, Class<T> type, T def) {
- rl.lock();
- try {
- PropertyMap pm = getPropertyMap(prefix(name));
- if (pm != null)
- return pm.get(name, type, def);
- String s = System.getProperty(name);
- if ((! StringUtils.isEmpty(s)) && isBeanSessionAvailable())
- return getBeanSession().convertToType(s, type);
- return def;
- } finally {
- rl.unlock();
- }
- }
-
- /**
- * Returns a property value converted to a {@link LinkedHashMap} with the specified
- * key and value types.
- *
- * @param name The full name of the property (e.g. <js>"BeanContext.sortProperties"</js>)
- * @param keyType The class type of the keys in the map.
- * @param valType The class type of the values in the map.
- * @param def The default value if the property is not set.
- *
- * @return The property value.
- * @throws ConfigException If property has a value that cannot be converted to a boolean.
- */
- public <K,V> Map<K,V> getMap(String name, Class<K> keyType, Class<V> valType, Map<K,V> def) {
- rl.lock();
- try {
- PropertyMap pm = getPropertyMap(prefix(name));
- if (pm != null)
- return pm.getMap(name, keyType, valType, def);
- return def;
- } finally {
- rl.unlock();
- }
- }
-
-
- //-------------------------------------------------------------------------------------
- // Convenience methods.
- //-------------------------------------------------------------------------------------
-
- /**
- * Shortcut for calling <code>getContext(BeanContext.<jk>class</jk>);</code>.
- *
- * @return The bean context instance.
- */
- public BeanContext getBeanContext() {
- return getContext(BeanContext.class);
- }
-
- /**
- * Shortcut for calling <code>addTo(<jsf>BEAN_notBeanClasses</jsf>, <jf>classes</jf>)</code>.
- *
- * @see ContextFactory#addToProperty(String,Object)
- * @param classes The new setting value for the bean context.
- * @throws LockedException If {@link ContextFactory#lock()} was called on this class or the bean context.
- * @return This object (for method chaining).
- * @see ContextFactory#addToProperty(String, Object)
- * @see BeanContext#BEAN_notBeanClasses
- */
- public ContextFactory addNotBeanClasses(Class<?>...classes) throws LockedException {
- checkLock();
- addToProperty(BEAN_notBeanClasses, classes);
- return this;
- }
-
- /**
- * Shortcut for calling <code>addTo(<jsf>BEAN_beanFilters</jsf>, <jf>classes</jf>)</code>.
- *
- * @param classes The new setting value for the bean context.
- * @throws LockedException If {@link ContextFactory#lock()} was called on this class or the bean context.
- * @return This object (for method chaining).
- * @see ContextFactory#addToProperty(String, Object)
- * @see BeanContext#BEAN_beanFilters
- */
- public ContextFactory addBeanFilters(Class<?>...classes) throws LockedException {
- checkLock();
- addToProperty(BEAN_beanFilters, classes);
- return this;
- }
-
- /**
- * Shortcut for calling <code>addTo(<jsf>BEAN_pojoSwaps</jsf>, <jf>classes</jf>)</code>.
- *
- * @param classes The new setting value for the bean context.
- * @throws LockedException If {@link ContextFactory#lock()} was called on this class or the bean context.
- * @return This object (for method chaining).
- * @see ContextFactory#addToProperty(String, Object)
- * @see BeanContext#BEAN_pojoSwaps
- */
- public ContextFactory addPojoSwaps(Class<?>...classes) throws LockedException {
- checkLock();
- addToProperty(BEAN_pojoSwaps, classes);
- return this;
- }
-
- /**
- * Shortcut for calling <code>addTo(<jsf>BEAN_beanDictionary</jsf>, <jf>classes</jf>)</code>.
- *
- * @param classes The new setting value for the bean context.
- * @throws LockedException If {@link ContextFactory#lock()} was called on this class or the bean context.
- * @return This object (for method chaining).
- * @see ContextFactory#addToProperty(String, Object)
- * @see BeanContext#BEAN_beanDictionary
- */
- public ContextFactory addToBeanDictionary(Class<?>...classes) throws LockedException {
- checkLock();
- addToProperty(BEAN_beanDictionary, classes);
- return this;
- }
-
- /**
- * Shortcut for calling <code>putTo(<jsf>BEAN_implCLasses</jsf>, <jf>interfaceClass</jf>, <jf>implClass</jf>)</code>.
- *
- * @param interfaceClass The interface class.
- * @param implClass The implementation class.
- * @throws LockedException If {@link ContextFactory#lock()} was called on this class or the bean context.
- * @param <T> The class type of the interface.
- * @return This object (for method chaining).
- * @see ContextFactory#putToProperty(String, Object, Object)
- * @see BeanContext#BEAN_implClasses
- */
- public <T> ContextFactory addImplClass(Class<T> interfaceClass, Class<? extends T> implClass) throws LockedException {
- checkLock();
- putToProperty(BEAN_implClasses, interfaceClass, implClass);
- return this;
- }
-
-
- //-------------------------------------------------------------------------------------
- // Object methods.
- //-------------------------------------------------------------------------------------
-
- @Override /* Object */
- public int hashCode() {
- HashCode c = new HashCode();
- for (PropertyMap m : properties.values())
- c.add(m);
- return c.get();
- }
-
-
- //--------------------------------------------------------------------------------
- // Utility classes and methods.
- //--------------------------------------------------------------------------------
-
- /**
- * Hashcode generator that treats strings and primitive values the same.
- * (e.g. <code>123</code> and <js>"123"</js> result in the same hashcode.)
- */
- private static class NormalizingHashCode extends HashCode {
- @Override /* HashCode */
- protected Object unswap(Object o) {
- return ContextFactory.unswap(o);
- }
- }
-
- /**
- * Contains all the properties for a particular property prefix (e.g. <js>'BeanContext'</js>)
- * <p>
- * Instances of this map are immutable from outside this class.
- * <p>
- * The {@link PropertyMap#hashCode()} and {@link PropertyMap#equals(Object)} methods
- * can be used to compare with other property maps.
- */
- @SuppressWarnings("hiding")
- public class PropertyMap {
-
- private final Map<String,Property> map = new ConcurrentSkipListMap<String,Property>();
- private volatile int hashCode = 0;
- private final ReadWriteLock lock = new ReentrantReadWriteLock();
- private final Lock rl = lock.readLock(), wl = lock.writeLock();
- private final String prefix;
-
- private PropertyMap(String prefix) {
- this.prefix = prefix;
- prefix = prefix + '.';
- Properties p = System.getProperties();
- for (Map.Entry<Object,Object> e : p.entrySet())
- if (e.getKey().toString().startsWith(prefix))
- set(e.getKey().toString(), e.getValue());
- }
-
- /**
- * Copy constructor.
- */
- private PropertyMap(PropertyMap orig) {
- this.prefix = orig.prefix;
- for (Map.Entry<String,Property> e : orig.map.entrySet())
- this.map.put(e.getKey(), Property.create(e.getValue().name, e.getValue().value()));
- }
-
- /**
- * Returns the specified property as the specified class type.
- *
- * @param name The property name.
- * @param type The type of object to convert the value to.
- * @param def The default value if the specified property is not set.
- *
- * @return The property value.
- */
- public <T> T get(String name, Class<T> type, T def) {
- rl.lock();
- try {
- Property p = map.get(name);
- if (p == null || type == null)
- return def;
- try {
- if (! isBeanSessionAvailable())
- return def;
- return getBeanSession().convertToType(p.value, type);
- } catch (InvalidDataConversionException e) {
- throw new ConfigException("Could not retrieve config property ''{0}''. {1}", p.name, e.getMessage());
- }
- } finally {
- rl.unlock();
- }
- }
-
- /**
- * Returns the specified property as a map with the specified key and value types.
- * <p>
- * The map returned is an instance of {@link LinkedHashMap}.
- *
- * @param name The property name.
- * @param keyType The class type of the keys of the map.
- * @param valueType The class type of the values of the map.
- * @param def The default value if the specified property is not set.
- *
- * @return The property value.
- */
- @SuppressWarnings("unchecked")
- public <K,V> Map<K,V> getMap(String name, Class<K> keyType, Class<V> valueType, Map<K,V> def) {
- rl.lock();
- try {
- Property p = map.get(name);
- if (p == null || keyType == null || valueType == null)
- return def;
- try {
- if (isBeanSessionAvailable()) {
- BeanSession session = getBeanSession();
- return (Map<K,V>)session.convertToType(p.value, session.getClassMeta(LinkedHashMap.class, keyType, valueType));
- }
- return def;
- } catch (InvalidDataConversionException e) {
- throw new ConfigException("Could not retrieve config property ''{0}''. {1}", p.name, e.getMessage());
- }
- } finally {
- rl.unlock();
- }
- }
-
- /**
- * Convenience method for returning all values in this property map as a simple map.
- * <p>
- * Primarily useful for debugging.
- *
- * @return A new {@link LinkedHashMap} with all values in this property map.
- */
- public Map<String,Object> asMap() {
- rl.lock();
- try {
- Map<String,Object> m = new LinkedHashMap<String,Object>();
- for (Property p : map.values())
- m.put(p.name, p.value);
- return m;
- } finally {
- rl.unlock();
- }
- }
-
- private void set(String name, Object value) {
- wl.lock();
- hashCode = 0;
- try {
- if (value == null)
- map.remove(name);
- else
- map.put(name, Property.create(name, value));
- } finally {
- wl.unlock();
- }
- }
-
- private void addTo(String name, Object value) {
- wl.lock();
- hashCode = 0;
- try {
- if (! map.containsKey(name))
- map.put(name, Property.create(name, Collections.emptyList()));
- map.get(name).add(value);
- } finally {
- wl.unlock();
- }
- }
-
- private void putTo(String name, Object key, Object value) {
- wl.lock();
- hashCode = 0;
- try {
- if (! map.containsKey(name))
- map.put(name, Property.create(name, Collections.emptyMap()));
- map.get(name).put(key, value);
- } finally {
- wl.unlock();
- }
- }
-
- private void putTo(String name, Object value) {
- wl.lock();
- hashCode = 0;
- try {
- if (! map.containsKey(name))
- map.put(name, Property.create(name, Collections.emptyMap()));
- map.get(name).put(value);
- } finally {
- wl.unlock();
- }
- }
-
- private void removeFrom(String name, Object value) {
- wl.lock();
- hashCode = 0;
- try {
- if (map.containsKey(name))
- map.get(name).remove(value);
- } finally {
- wl.unlock();
- }
- }
-
- @Override
- public int hashCode() {
- rl.lock();
- try {
- if (hashCode == 0) {
- HashCode c = new HashCode().add(prefix);
- for (Property p : map.values())
- c.add(p);
- this.hashCode = c.get();
- }
- return hashCode;
- } finally {
- rl.unlock();
- }
- }
-
- @Override
- public boolean equals(Object o) {
- rl.lock();
- try {
- if (o instanceof PropertyMap) {
- PropertyMap m = (PropertyMap)o;
- if (m.hashCode() != hashCode())
- return false;
- return this.map.equals(m.map);
- }
- return false;
- } finally {
- rl.unlock();
- }
- }
-
- @Override
- public String toString() {
- return "PropertyMap(id="+System.identityHashCode(this)+")";
- }
- }
-
- private abstract static class Property {
- private final String name, type;
- private final Object value;
-
- private static Property create(String name, Object value) {
- if (name.endsWith(".set"))
- return new SetProperty(name, value);
- else if (name.endsWith(".list"))
- return new ListProperty(name, value);
- else if (name.endsWith(".map"))
- return new MapProperty(name, value);
- return new SimpleProperty(name, value);
- }
-
- Property(String name, String type, Object value) {
- this.name = name;
- this.type = type;
- this.value = value;
- }
-
- void add(Object val) {
- throw new ConfigException("Cannot add value {0} ({1}) to property ''{2}'' ({3}).", JsonSerializer.DEFAULT_LAX.toString(val), ClassUtils.getReadableClassNameForObject(val), name, type);
- }
-
- void remove(Object val) {
- throw new ConfigException("Cannot remove value {0} ({1}) from property ''{2}'' ({3}).", JsonSerializer.DEFAULT_LAX.toString(val), ClassUtils.getReadableClassNameForObject(val), name, type);
- }
-
- void put(Object val) {
- throw new ConfigException("Cannot put value {0} ({1}) to property ''{2}'' ({3}).", JsonSerializer.DEFAULT_LAX.toString(val), ClassUtils.getReadableClassNameForObject(val), name, type);
- }
-
- void put(Object key, Object val) {
- throw new ConfigException("Cannot put value {0}({1})->{2}({3}) to property ''{4}'' ({5}).", JsonSerializer.DEFAULT_LAX.toString(key), ClassUtils.getReadableClassNameForObject(key), JsonSerializer.DEFAULT_LAX.toString(val), ClassUtils.getReadableClassNameForObject(val), name, type);
- }
-
- protected Object value() {
- return value;
- }
-
- @Override /* Object */
- public int hashCode() {
- HashCode c = new NormalizingHashCode().add(name);
- if (value instanceof Map) {
- for (Map.Entry<?,?> e : ((Map<?,?>)value).entrySet())
- c.add(e.getKey()).add(e.getValue());
- } else if (value instanceof Collection) {
- for (Object o : (Collection<?>)value)
- c.add(o);
- } else {
- c.add(value);
- }
- return c.get();
- }
-
- @Override
- public String toString() {
- return "Property(name="+name+",type="+type+")";
- }
- }
-
- private static class SimpleProperty extends Property {
-
- SimpleProperty(String name, Object value) {
- super(name, "SIMPLE", value);
- }
- }
-
- @SuppressWarnings({"unchecked"})
- private static class SetProperty extends Property {
- private final Set<Object> value;
-
- private SetProperty(String name, Object value) {
- super(name, "SET", new ConcurrentSkipListSet<Object>(PROPERTY_COMPARATOR));
- this.value = (Set<Object>)value();
- add(value);
- }
-
- @Override
- void add(Object val) {
- if (val.getClass().isArray())
- for (int i = 0; i < Array.getLength(val); i++)
- add(Array.get(val, i));
- else if (val instanceof Collection)
- for (Object o : (Collection<Object>)val)
- add(o);
- else {
- if (val instanceof String) {
- String s = val.toString();
- if (s.startsWith("[") && s.endsWith("]")) {
- try {
- add(new ObjectList(s));
- return;
- } catch (Exception e) {}
- }
- }
- for (Object o : value)
- if (same(val, o))
- return;
- value.add(val);
- }
- }
-
- @Override
- void remove(Object val) {
- if (val.getClass().isArray())
- for (int i = 0; i < Array.getLength(val); i++)
- remove(Array.get(val, i));
- else if (val instanceof Collection)
- for (Object o : (Collection<Object>)val)
- remove(o);
- else {
- if (val instanceof String) {
- String s = val.toString();
- if (s.startsWith("[") && s.endsWith("]")) {
- try {
- remove(new ObjectList(s));
- return;
- } catch (Exception e) {}
- }
- }
- for (Iterator<Object> i = value.iterator(); i.hasNext();)
- if (same(i.next(), val))
- i.remove();
- }
- }
- }
-
- @SuppressWarnings({"unchecked"})
- private static class ListProperty extends Property {
- private final LinkedList<Object> value;
-
- private ListProperty(String name, Object value) {
- super(name, "LIST", new LinkedList<Object>());
- this.value = (LinkedList<Object>)value();
- add(value);
- }
-
- @Override
- void add(Object val) {
- if (val.getClass().isArray()) {
- for (int i = Array.getLength(val) - 1; i >= 0; i--)
- add(Array.get(val, i));
- } else if (val instanceof List) {
- List<Object> l = (List<Object>)val;
- for (ListIterator<Object> i = l.listIterator(l.size()); i.hasPrevious();)
- add(i.previous());
- } else if (val instanceof Collection) {
- List<Object> l = new ArrayList<Object>((Collection<Object>)val);
- for (ListIterator<Object> i = l.listIterator(l.size()); i.hasPrevious();)
- add(i.previous());
- } else {
- String s = val.toString();
- if (s.startsWith("[") && s.endsWith("]")) {
- try {
- add(new ObjectList(s));
- return;
- } catch (Exception e) {}
- }
- for (Iterator<Object> i = value.iterator(); i.hasNext(); )
- if (same(val, i.next()))
- i.remove();
- value.addFirst(val);
- }
- }
-
- @Override
- void remove(Object val) {
- if (val.getClass().isArray())
- for (int i = 0; i < Array.getLength(val); i++)
- remove(Array.get(val, i));
- else if (val instanceof Collection)
- for (Object o : (Collection<Object>)val)
- remove(o);
- else {
- String s = val.toString();
- if (s.startsWith("[") && s.endsWith("]")) {
- try {
- remove(new ObjectList(s));
- return;
- } catch (Exception e) {}
- }
- for (Iterator<Object> i = value.iterator(); i.hasNext();)
- if (same(i.next(), val))
- i.remove();
- }
- }
- }
-
- @SuppressWarnings({"unchecked","rawtypes"})
- private static class MapProperty extends Property {
- final Map<Object,Object> value;
-
- MapProperty(String name, Object value) {
- // ConcurrentSkipListMap doesn't support Map.Entry.remove(), so use TreeMap instead.
- super(name, "MAP", Collections.synchronizedMap(new TreeMap<Object,Object>(PROPERTY_COMPARATOR)));
- this.value = (Map<Object,Object>)value();
- put(value);
- }
-
- @Override
- void put(Object val) {
- try {
- if (isBeanSessionAvailable() && ! (val instanceof Map))
- val = getBeanSession().convertToType(val, Map.class);
- if (val instanceof Map) {
- Map m = (Map)val;
- for (Map.Entry e : (Set<Map.Entry>)m.entrySet())
- put(e.getKey(), e.getValue());
- return;
- }
- } catch (Exception e) {}
- super.put(val);
- }
-
- @Override
- void put(Object key, Object val) {
- // ConcurrentSkipListMap doesn't support Map.Entry.remove().
- for (Map.Entry<Object,Object> e : value.entrySet()) {
- if (same(e.getKey(), key)) {
- e.setValue(val);
- return;
- }
- }
- value.put(key, val);
- }
- }
-
- /**
- * Converts an object to a normalized form for comparison purposes.
- *
- * @param o The object to normalize.
- * @return The normalized object.
- */
- private static final Object unswap(Object o) {
- if (o instanceof Class)
- return ((Class<?>)o).getName();
- if (o instanceof Number || o instanceof Boolean)
- return o.toString();
- return o;
- }
-
- private static BeanSession getBeanSession() {
- if (beanSession == null && BeanContext.DEFAULT != null)
- beanSession = BeanContext.DEFAULT.createSession();
- return beanSession;
- }
-
- /**
- * Returns true if a bean session is available.
- * Note that a bean session will not be available when constructing the BeanContext.DEFAULT context.
- * (it's a chicken-and-egg thing).
- */
- private static boolean isBeanSessionAvailable() {
- return getBeanSession() != null;
- }
-
- /*
- * Compares two objects for "string"-equality.
- * Basically mean both objects are equal if they're the same when converted to strings.
- */
- @SuppressWarnings({ "rawtypes", "unchecked" })
- private static boolean same(Object o1, Object o2) {
- if (o1 == o2)
- return true;
- if (o1 instanceof Map) {
- if (o2 instanceof Map) {
- Map m1 = (Map)o1, m2 = (Map)o2;
- if (m1.size() == m2.size()) {
- Set<Map.Entry> s1 = m1.entrySet(), s2 = m2.entrySet();
- for (Iterator<Map.Entry> i1 = s1.iterator(), i2 = s2.iterator(); i1.hasNext();) {
- Map.Entry e1 = i1.next(), e2 = i2.next();
- if (! same(e1.getKey(), e2.getKey()))
- return false;
- if (! same(e1.getValue(), e2.getValue()))
- return false;
- }
- return true;
- }
- }
- return false;
- } else if (o1 instanceof Collection) {
- if (o2 instanceof Collection) {
- Collection c1 = (Collection)o1, c2 = (Collection)o2;
- if (c1.size() == c2.size()) {
- for (Iterator i1 = c1.iterator(), i2 = c2.iterator(); i1.hasNext();) {
- if (! same(i1.next(), i2.next()))
- return false;
- }
- return true;
- }
- }
- return false;
- } else {
- return unswap(o1).equals(unswap(o2));
- }
- }
-
- private String prefix(String name) {
- if (name == null)
- throw new ConfigException("Invalid property name specified: 'null'");
- if (name.indexOf('.') == -1)
- return "";
- return name.substring(0, name.indexOf('.'));
- }
-
- private String className(Object o) {
- if (o == null)
- return null;
- if (o instanceof Class)
- return ClassUtils.getReadableClassName((Class<?>)o);
- return ClassUtils.getReadableClassName(o.getClass());
- }
-
- @Override /* Object */
- public String toString() {
- rl.lock();
- try {
- ObjectMap m = new ObjectMap();
- m.put("id", System.identityHashCode(this));
- m.put("hashCode", hashCode());
- m.put("properties.id", System.identityHashCode(properties));
- m.put("contexts.id", System.identityHashCode(contexts));
- m.put("properties", properties);
- m.put("contexts", contexts);
- return m.toString();
- } finally {
- rl.unlock();
- }
- }
-
- /**
- * Creates an unlocked clone of this object.
- *
- * @throws CloneNotSupportedException If class cannot be cloned.
- */
- @Override /* Object */
- public ContextFactory clone() throws CloneNotSupportedException {
- rl.lock();
- try {
- return new ContextFactory(this);
- } finally {
- rl.unlock();
- }
- }
-}