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 2016/08/09 19:54:15 UTC
[42/51] [partial] incubator-juneau git commit: Rename project
directories.
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/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
new file mode 100644
index 0000000..bc957ca
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/ContextFactory.java
@@ -0,0 +1,1301 @@
+/***************************************************************************************************************************
+ * 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_transforms} property is used to store transform classes.<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 transforms 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.
+ *
+ *
+ * @author James Bognar (james.bognar@salesforce.com)
+ */
+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;
+
+ // 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 normalize(o1).toString().compareTo(normalize(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))
+ return BeanContext.DEFAULT.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_transforms</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_transforms
+ */
+ public ContextFactory addTransforms(Class<?>...classes) throws LockedException {
+ checkLock();
+ addToProperty(BEAN_transforms, 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.)
+ */
+ protected static class NormalizingHashCode extends HashCode {
+ @Override /* HashCode */
+ protected Object normalize(Object o) {
+ return ContextFactory.normalize(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.
+ *
+ * @author James Bognar (james.bognar@salesforce.com)
+ */
+ @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 (BeanContext.DEFAULT == null)
+ return def;
+ return BeanContext.DEFAULT.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 {
+ BeanContext bc = BeanContext.DEFAULT;
+ if (bc != null)
+ return (Map<K,V>)bc.convertToType(p.value, bc.getMapClassMeta(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 (BeanContext.DEFAULT != null && ! (val instanceof Map))
+ val = BeanContext.DEFAULT.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 normalize(Object o) {
+ if (o instanceof Class)
+ return ((Class<?>)o).getName();
+ if (o instanceof Number || o instanceof Boolean)
+ return o.toString();
+ return o;
+ }
+
+ /*
+ * 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 normalize(o1).equals(normalize(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();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/CoreApi.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/CoreApi.java b/juneau-core/src/main/java/org/apache/juneau/CoreApi.java
new file mode 100644
index 0000000..34e6429
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/CoreApi.java
@@ -0,0 +1,213 @@
+/***************************************************************************************************************************
+ * 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;
+
+/**
+ * Common super class for all core-API serializers, parsers, and serializer/parser groups.
+ *
+ * <h6 class='topic'>Description</h6>
+ * <p>
+ * Maintains an inner {@link ContextFactory} instance that can be used by serializer and parser subclasses
+ * to work with beans in a consistent way.
+ * <p>
+ * Provides several duplicate convenience methods from the {@link ContextFactory} class to set properties on that class from this class.
+ * <p>
+ * Also implements the {@link Lockable} interface to allow for easy locking and cloning.
+ *
+ * @author James Bognar (james.bognar@salesforce.com)
+ */
+public abstract class CoreApi extends Lockable {
+
+ private ContextFactory contextFactory = ContextFactory.create();
+ private BeanContext beanContext;
+
+ /**
+ * Returns the {@link ContextFactory} object associated with this class.
+ * <p>
+ * The context factory stores all configuration properties for this class.
+ * Adding/modifying properties on this factory will alter the behavior of this object.
+ * <p>
+ * Calling the {@link ContextFactory#lock()} method on the returned object will prevent
+ * any further modifications to the configuration for this object
+ * ANY ANY OTHERS THAT SHARE THE SAME FACTORY!.
+ * Note that calling the {@link #lock()} method on this class will only
+ * lock the configuration for this particular instance of the class.
+ *
+ * @return The context factory associated with this object.
+ */
+ public ContextFactory getContextFactory() {
+ return contextFactory;
+ }
+
+ /**
+ * Returns the bean context to use for this class.
+ *
+ * @return The bean context object.
+ */
+ public BeanContext getBeanContext() {
+ if (beanContext == null)
+ return contextFactory.getContext(BeanContext.class);
+ return beanContext;
+ }
+
+ /**
+ * Creates a {@link Context} class instance of the specified type.
+ *
+ * @param contextClass The class instance to create.
+ * @return A context class instance of the specified type.
+ */
+ protected final <T extends Context> T getContext(Class<T> contextClass) {
+ return contextFactory.getContext(contextClass);
+ }
+
+ /**
+ * Shortcut for calling <code>getContextFactory().setProperty(<jf>property</jf>, <jf>value</jf>);</code>.
+ *
+ * @param property The property name.
+ * @param value The property value.
+ * @return This class (for method chaining).
+ * @throws LockedException If {@link #lock()} has been called on this object or {@link ContextFactory} object.
+ * @see ContextFactory#setProperty(String, Object)
+ */
+ public CoreApi setProperty(String property, Object value) throws LockedException {
+ checkLock();
+ contextFactory.setProperty(property, value);
+ return this;
+ }
+
+ /**
+ * Shortcut for calling <code>getContextFactory().setProperties(<jf>properties</jf>);</code>.
+ *
+ * @param properties The properties to set on this class.
+ * @return This class (for method chaining).
+ * @throws LockedException If {@link #lock()} has been called on this object.
+ * @see ContextFactory#setProperties(java.util.Map)
+ */
+ public CoreApi setProperties(ObjectMap properties) throws LockedException {
+ checkLock();
+ contextFactory.setProperties(properties);
+ return this;
+ }
+
+ /**
+ * Shortcut for calling <code>getContextFactory().addNotBeanClasses(<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 CoreApi addNotBeanClasses(Class<?>...classes) throws LockedException {
+ checkLock();
+ contextFactory.addNotBeanClasses(classes);
+ return this;
+ }
+
+ /**
+ * Shortcut for calling <code>getContextFactory().addTransforms(<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_transforms
+ */
+ public CoreApi addTransforms(Class<?>...classes) throws LockedException {
+ checkLock();
+ contextFactory.addTransforms(classes);
+ return this;
+ }
+
+ /**
+ * Shortcut for calling <code>getContextFactory().addImplClass(<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> CoreApi addImplClass(Class<T> interfaceClass, Class<? extends T> implClass) throws LockedException {
+ checkLock();
+ contextFactory.addImplClass(interfaceClass, implClass);
+ return this;
+ }
+
+ /**
+ * Shortcut for calling <code>getContextFactory().setClassLoader(<jf>classLoader</jf>)</code>.
+ *
+ * @param classLoader The new classloader.
+ * @throws LockedException If {@link ContextFactory#lock()} was called on this class or the bean context.
+ * @return This object (for method chaining).
+ * @see ContextFactory#setClassLoader(ClassLoader)
+ */
+ public CoreApi setClassLoader(ClassLoader classLoader) throws LockedException {
+ checkLock();
+ contextFactory.setClassLoader(classLoader);
+ return this;
+ }
+
+ /**
+ * Shortcut for calling {@link BeanContext#object()}.
+ *
+ * @return The reusable {@link ClassMeta} for representing the {@link Object} class.
+ */
+ public ClassMeta<Object> object() {
+ return getBeanContext().object();
+ }
+
+ /**
+ * Shortcut for calling {@link BeanContext#string()}.
+ *
+ * @return The reusable {@link ClassMeta} for representing the {@link String} class.
+ */
+ public ClassMeta<String> string() {
+ return getBeanContext().string();
+ }
+
+
+ //--------------------------------------------------------------------------------
+ // Overridden methods
+ //--------------------------------------------------------------------------------
+
+ @Override /* Lockable */
+ public void checkLock() {
+ super.checkLock();
+ beanContext = null;
+ }
+
+ @Override /* Lockable */
+ public CoreApi lock() {
+ try {
+ super.lock();
+ contextFactory = contextFactory.clone();
+ contextFactory.lock();
+ beanContext = contextFactory.getContext(BeanContext.class);
+ return this;
+ } catch (CloneNotSupportedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override /* Lockable */
+ public CoreApi clone() throws CloneNotSupportedException {
+ CoreApi c = (CoreApi)super.clone();
+ c.contextFactory = ContextFactory.create(contextFactory);
+ c.beanContext = null;
+ return c;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/Delegate.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/Delegate.java b/juneau-core/src/main/java/org/apache/juneau/Delegate.java
new file mode 100644
index 0000000..dff2d66
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/Delegate.java
@@ -0,0 +1,33 @@
+/***************************************************************************************************************************
+ * 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;
+
+/**
+ * An object that represents another object, often wrapping that object.
+ * <p>
+ * <b>*** Internal Interface - Not intended for external use ***</b>
+ * <p>
+ * For example, {@link BeanMap} is a map representation of a bean.
+ *
+ * @author James Bognar (james.bognar@salesforce.com)
+ * @param <T> The represented class type.
+ */
+public interface Delegate<T> {
+
+ /**
+ * The {@link ClassMeta} of the class of the represented object.
+ *
+ * @return The class type of the represented object.
+ */
+ public ClassMeta<T> getClassMeta();
+}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/FormattedException.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/FormattedException.java b/juneau-core/src/main/java/org/apache/juneau/FormattedException.java
new file mode 100644
index 0000000..add0a79
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/FormattedException.java
@@ -0,0 +1,57 @@
+/***************************************************************************************************************************
+ * 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 java.text.*;
+
+/**
+ * Subclass of non-runtime exceptions that take in a message and zero or more arguments.
+ * <p>
+ *
+ * @author James Bognar (james.bognar@salesforce.com)
+ */
+public class FormattedException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor.
+ *
+ * @param message The {@link MessageFormat}-style message.
+ * @param args The arguments in the message.
+ */
+ public FormattedException(String message, Object...args) {
+ super(args.length == 0 ? message : MessageFormat.format(message, args));
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param causedBy The cause of this exception.
+ * @param message The {@link MessageFormat}-style message.
+ * @param args The arguments in the message.
+ */
+ public FormattedException(Throwable causedBy, String message, Object...args) {
+ this(message, args);
+ initCause(causedBy);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param causedBy The cause of this exception.
+ */
+ public FormattedException(Throwable causedBy) {
+ this(causedBy, causedBy.getLocalizedMessage());
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/FormattedRuntimeException.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/FormattedRuntimeException.java b/juneau-core/src/main/java/org/apache/juneau/FormattedRuntimeException.java
new file mode 100644
index 0000000..f010d9f
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/FormattedRuntimeException.java
@@ -0,0 +1,47 @@
+/***************************************************************************************************************************
+ * 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 java.text.*;
+
+/**
+ * Subclass of runtime exceptions that take in a message and zero or more arguments.
+ *
+ * @author James Bognar (james.bognar@salesforce.com)
+ */
+public class FormattedRuntimeException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor.
+ *
+ * @param message The {@link MessageFormat}-style message.
+ * @param args The arguments in the message.
+ */
+ public FormattedRuntimeException(String message, Object...args) {
+ super(args.length == 0 ? message : MessageFormat.format(message, args));
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param causedBy The cause of this exception.
+ * @param message The {@link MessageFormat}-style message.
+ * @param args The arguments in the message.
+ */
+ public FormattedRuntimeException(Throwable causedBy, String message, Object...args) {
+ this(message, args);
+ initCause(causedBy);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/InvalidDataConversionException.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/InvalidDataConversionException.java b/juneau-core/src/main/java/org/apache/juneau/InvalidDataConversionException.java
new file mode 100644
index 0000000..21d0e57
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/InvalidDataConversionException.java
@@ -0,0 +1,54 @@
+/***************************************************************************************************************************
+ * 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 java.text.*;
+
+import org.apache.juneau.internal.*;
+import org.apache.juneau.json.*;
+
+/**
+ * General invalid conversion exception.
+ * <p>
+ * Exception that gets thrown if you try to perform an invalid conversion, such as when calling {@code ObjectMap.getInt(...)} on a non-numeric <code>String</code>.
+ *
+ * @author James Bognar (james.bognar@salesforce.com)
+ */
+public final class InvalidDataConversionException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * @param toType Attempting to convert to this class type.
+ * @param cause The cause.
+ * @param value The value being converted.
+ */
+ public InvalidDataConversionException(Object value, Class<?> toType, Exception cause) {
+ super(MessageFormat.format("Invalid data conversion from type ''{0}'' to type ''{1}''. Value={2}.", ClassUtils.getReadableClassNameForObject(value), ClassUtils.getReadableClassName(toType), getValue(value)), cause);
+ }
+
+ /**
+ * @param toType Attempting to convert to this class type.
+ * @param cause The cause.
+ * @param value The value being converted.
+ */
+ public InvalidDataConversionException(Object value, ClassMeta<?> toType, Exception cause) {
+ super(MessageFormat.format("Invalid data conversion from type ''{0}'' to type ''{1}''. Value={2}.", ClassUtils.getReadableClassNameForObject(value), toType.toString(), getValue(value)), cause);
+ }
+
+ private static String getValue(Object o) {
+ if (o instanceof Class)
+ return "'" + ClassUtils.getReadableClassName((Class<?>)o) + "'";
+ return JsonSerializer.DEFAULT_LAX == null ? "'" + o.toString() + "'" : JsonSerializer.DEFAULT_LAX.toString(o);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/Lockable.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/Lockable.java b/juneau-core/src/main/java/org/apache/juneau/Lockable.java
new file mode 100644
index 0000000..4d3d470
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/Lockable.java
@@ -0,0 +1,88 @@
+/***************************************************************************************************************************
+ * 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;
+
+/**
+ * Superclass of all classes that have a locked state.
+ * <p>
+ * Used to mark bean contexts, serializers, and parsers as read-only so that
+ * settings can no longer be modified.
+ * <p>
+ * Also keeps track of when the object has been cloned and allows for lazy cloning through
+ * the {@link #onUnclone()} method. The idea behind this is that certain expensive fields don't
+ * need to be cloned unless the object is actually being modified.
+ * <p>
+ * Calling {@link #lock()} on the object causes it to be put into a read-only state.
+ * Once called, subsequent calls to {@link #checkLock()} will cause {@link LockedException LockedExceptions}
+ * to be thrown.
+ * <p>
+ * As a rule, cloned objects are unlocked by default.
+ *
+ * @author James Bognar (james.bognar@salesforce.com)
+ */
+public abstract class Lockable implements Cloneable {
+
+ private boolean isLocked = false;
+ private boolean isCloned = false;
+
+ /**
+ * Locks this object so that settings on it cannot be modified.
+ *
+ * @return This object (for method chaining).
+ */
+ public Lockable lock() {
+ isLocked = true;
+ return this;
+ }
+
+ /**
+ * @return <code><jk>true</jk></code> if this object has been locked.
+ */
+ public boolean isLocked() {
+ return isLocked;
+ }
+
+ /**
+ * Causes a {@link LockedException} to be thrown if this object has been locked.
+ * <p>
+ * Also calls {@link #onUnclone()} if this is the first time this method has been called since cloning.
+ *
+ * @throws LockedException If {@link #lock()} has been called on this object.
+ */
+ public void checkLock() throws LockedException {
+ if (isLocked)
+ throw new LockedException();
+ if (isCloned)
+ onUnclone();
+ isCloned = false;
+ }
+
+ /**
+ * Subclass can override this method to handle lazy-cloning on the first time {@link #checkLock()} is called after
+ * the object has been cloned.
+ */
+ public void onUnclone() {}
+
+ /**
+ * Creates an unlocked clone of this object.
+ *
+ * @throws CloneNotSupportedException If class cannot be cloned.
+ */
+ @Override /* Object */
+ public Lockable clone() throws CloneNotSupportedException {
+ Lockable c = (Lockable)super.clone();
+ c.isLocked = false;
+ c.isCloned = true;
+ return c;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/LockedException.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/LockedException.java b/juneau-core/src/main/java/org/apache/juneau/LockedException.java
new file mode 100644
index 0000000..4d841f5
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/LockedException.java
@@ -0,0 +1,32 @@
+/***************************************************************************************************************************
+ * 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;
+
+/**
+ * Exception that gets thrown when trying to modify settings on a locked {@link Lockable} object.
+ * <p>
+ * A locked exception indicates a programming error.
+ * Certain objects that are meant for reuse, such as serializers and parsers, provide
+ * the ability to lock the current settings so that they cannot be later changed.
+ * This exception indicates that a setting change was attempted on a previously locked object.
+ *
+ * @author James Bognar (james.bognar@salesforce.com)
+ */
+public final class LockedException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ LockedException() {
+ super("Object is locked and object settings cannot be modified.");
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/MediaRange.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/MediaRange.java b/juneau-core/src/main/java/org/apache/juneau/MediaRange.java
new file mode 100644
index 0000000..788243d
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/MediaRange.java
@@ -0,0 +1,316 @@
+/***************************************************************************************************************************
+ * 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 java.util.*;
+import java.util.Map.*;
+
+/**
+ * Describes a single type used in content negotiation between an HTTP client and server, as described in
+ * Section 14.1 and 14.7 of RFC2616 (the HTTP/1.1 specification).
+ */
+public final class MediaRange implements Comparable<MediaRange> {
+
+ private final String type; // The media type (e.g. "text" for Accept, "utf-8" for Accept-Charset)
+ private final String subType; // The media sub-type (e.g. "json" for Accept, not used for Accept-Charset)
+ private final Float qValue;
+ private final Map<String,Set<String>> parameters, extensions;
+
+ /**
+ * Returns the media type enclosed by this media range.
+ * <p>
+ * Examples:
+ * <ul>
+ * <li><js>"text/html"</js>
+ * <li><js>"text/*"</js>
+ * <li><js>"*\/*"</js>
+ * </ul>
+ *
+ * @return The media type of this media range, lowercased, never <jk>null</jk>.
+ */
+ public String getMediaType() {
+ return type + "/" + subType;
+ }
+
+ /**
+ * Return just the type portion of this media range.
+ *
+ * @return The type portion of this media range.
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Returns the <js>'q'</js> (quality) value for this type, as described in Section 3.9 of RFC2616.
+ * <p>
+ * The quality value is a float between <code>0.0</code> (unacceptable) and <code>1.0</code> (most acceptable).
+ * <p>
+ * If 'q' value doesn't make sense for the context (e.g. this range was extracted from a <js>"content-*"</js> header, as opposed to <js>"accept-*"</js>
+ * header, its value will always be <js>"1"</js>.
+ *
+ * @return The 'q' value for this type, never <jk>null</jk>.
+ */
+ public Float getQValue() {
+ return qValue;
+ }
+
+ /**
+ * Returns the optional set of parameters associated to the type as returned by {@link #getMediaType()}.
+ * <p>
+ * The parameters are those values as described in standardized MIME syntax.
+ * An example of such a parameter in string form might be <js>"level=1"</js>.
+ * <p>
+ * Values are lowercase and never <jk>null</jk>.
+ *
+ * @return The optional list of parameters, never <jk>null</jk>.
+ */
+ public Map<String,Set<String>> getParameters() {
+ return parameters;
+ }
+
+ /**
+ * Returns the optional set of custom extensions defined for this type.
+ * <p>
+ * Values are lowercase and never <jk>null</jk>.
+ *
+ * @return The optional list of extensions, never <jk>null</jk>.
+ */
+ public Map<String,Set<String>> getExtensions() {
+ return extensions;
+ }
+
+ /**
+ * Provides a string representation of this media range, suitable for use as an <code>Accept</code> header value.
+ * <p>
+ * The literal text generated will be all lowercase.
+ *
+ * @return A media range suitable for use as an Accept header value, never <code>null</code>.
+ */
+ @Override /* Object */
+ public String toString() {
+ StringBuffer sb = new StringBuffer().append(type).append('/').append(subType);
+
+ if (! parameters.isEmpty())
+ for (Entry<String,Set<String>> e : parameters.entrySet()) {
+ String k = e.getKey();
+ for (String v : e.getValue())
+ sb.append(';').append(k).append('=').append(v);
+ }
+
+ // '1' is equivalent to specifying no qValue. If there's no extensions, then we won't include a qValue.
+ if (qValue.floatValue() == 1.0) {
+ if (! extensions.isEmpty()) {
+ sb.append(";q=").append(qValue);
+ for (Entry<String,Set<String>> e : extensions.entrySet()) {
+ String k = e.getKey();
+ for (String v : e.getValue())
+ sb.append(';').append(k).append('=').append(v);
+ }
+ }
+ } else {
+ sb.append(";q=").append(qValue);
+ for (Entry<String,Set<String>> e : extensions.entrySet()) {
+ String k = e.getKey();
+ for (String v : e.getValue())
+ sb.append(';').append(k).append('=').append(v);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns <jk>true</jk> if the specified object is also a <code>MediaType</code>, and has the same qValue, type, parameters, and extensions.
+ *
+ * @return <jk>true</jk> if object is equivalent.
+ */
+ @Override /* Object */
+ public boolean equals(Object o) {
+
+ if (o == null || !(o instanceof MediaRange))
+ return false;
+
+ if (this == o)
+ return true;
+
+ MediaRange o2 = (MediaRange) o;
+ return qValue.equals(o2.qValue)
+ && type.equals(o2.type)
+ && subType.equals(o2.subType)
+ && parameters.equals(o2.parameters)
+ && extensions.equals(o2.extensions);
+ }
+
+ /**
+ * Returns a hash based on this instance's <code>media-type</code>.
+ *
+ * @return A hash based on this instance's <code>media-type</code>.
+ */
+ @Override /* Object */
+ public int hashCode() {
+ return type.hashCode() + subType.hashCode();
+ }
+
+ /**
+ * Creates a <code>MediaRange</code> object with the referenced values.
+ *
+ * @param type The MIME type of this media range (e.g. <js>"application"</js> in <js>"application/json"</js>)
+ * @param subType The MIME subtype of this media range (e.g. <js>"json"</js> in <js>"application/json"</js>).
+ * @param parameters The optional parameters for this range.
+ * @param qValue The quality value of this range. Must be between <code>0</code> and <code>1.0</code>.
+ * @param extensions The optional extensions to this quality value.
+ */
+ private MediaRange(String type, String subType, Map<String,Set<String>> parameters, Float qValue, Map<String,Set<String>> extensions) {
+ this.type = type;
+ this.subType = subType;
+ this.parameters = (parameters == null ? new TreeMap<String,Set<String>>() : parameters);
+ this.extensions = (extensions == null ? new TreeMap<String,Set<String>>() : extensions);
+ this.qValue = qValue;
+ }
+
+ /**
+ * Parses an <code>Accept</code> header value into an array of media ranges.
+ * <p>
+ * The returned media ranges are sorted such that the most acceptable media is available at ordinal position <js>'0'</js>, and the least acceptable at position n-1.
+ * <p>
+ * The syntax expected to be found in the referenced <code>value</code> complies with the syntax described in RFC2616, Section 14.1, as described below:
+ * <p class='bcode'>
+ * Accept = "Accept" ":"
+ * #( media-range [ accept-params ] )
+ *
+ * media-range = ( "*\/*"
+ * | ( type "/" "*" )
+ * | ( type "/" subtype )
+ * ) *( ";" parameter )
+ * accept-params = ";" "q" "=" qvalue *( accept-extension )
+ * accept-extension = ";" token [ "=" ( token | quoted-string ) ]
+ * </p>
+ * This method can also be used on other headers such as <code>Accept-Charset</code> and <code>Accept-Encoding</code>...
+ * <p class='bcode'>
+ * Accept-Charset = "Accept-Charset" ":"
+ * 1#( ( charset | "*" )[ ";" "q" "=" qvalue ] )
+ * </p>
+ *
+ * @param value The value to parse. If <jk>null</jk> or empty, returns a single <code>MediaRange</code> is returned that represents all types.
+ * @return The media ranges described by the string.
+ * The ranges are sorted such that the most acceptable media is available at ordinal position <js>'0'</js>, and the least acceptable at position n-1.
+ */
+ public static MediaRange[] parse(String value) {
+
+ Set<MediaRange> ranges = new TreeSet<MediaRange>();
+
+ if (value == null || value.length() == 0)
+ return new MediaRange[]{new MediaRange("*", "*", null, 1f, null)};
+
+ value = value.toLowerCase(Locale.ENGLISH);
+
+ for (String r : value.trim().split("\\s*,\\s*")) {
+ r = r.trim();
+
+ if (r.isEmpty())
+ continue;
+
+ String[] tokens = r.split("\\s*;\\s*");
+
+ tokens[0] = tokens[0].replace(' ', '+');
+
+ // There is at least a type.
+ String[] t = tokens[0].split("/");
+ String type = t[0], subType = (t.length == 1 ? "*" : t[1]);
+
+ // Only the type of the range is specified
+ if (tokens.length == 1) {
+ ranges.add(new MediaRange(type, subType, null, 1f, null));
+ continue;
+ }
+
+ Float qValue = 1f;
+ Map<String,Set<String>> params = new TreeMap<String,Set<String>>();
+ Map<String,Set<String>> exts = new TreeMap<String,Set<String>>();
+
+ boolean isInExtensions = false;
+ for (int i = 1; i < tokens.length; i++) {
+ String[] parm = tokens[i].split("\\s*=\\s*");
+ if (parm.length == 2) {
+ String k = parm[0], v = parm[1];
+ if (isInExtensions) {
+ if (! exts.containsKey(parm[0]))
+ exts.put(parm[0], new TreeSet<String>());
+ exts.get(parm[0]).add(parm[1]);
+ } else if (k.equals("q")) {
+ qValue = new Float(v);
+ isInExtensions = true;
+ } else /*(! isInExtensions)*/ {
+ if (! params.containsKey(parm[0]))
+ params.put(parm[0], new TreeSet<String>());
+ params.get(parm[0]).add(parm[1]);
+ }
+ }
+ }
+
+ ranges.add(new MediaRange(type, subType, params, qValue, exts));
+ }
+
+ return ranges.toArray(new MediaRange[ranges.size()]);
+ }
+
+ /**
+ * Compares two MediaRanges for equality.
+ * <p>
+ * The values are first compared according to <code>qValue</code> values.
+ * Should those values be equal, the <code>type</code> is then lexicographically compared (case-insensitive) in ascending order,
+ * with the <js>"*"</js> type demoted last in that order.
+ * <code>MediaRanges</code> with the same type but different sub-types are compared - a more specific subtype is
+ * promoted over the 'wildcard' subtype.
+ * <code>MediaRanges</code> with the same types but with extensions are promoted over those same types with no extensions.
+ *
+ * @param o The range to compare to. Never <jk>null</jk>.
+ */
+ @Override /* Comparable */
+ public int compareTo(MediaRange o) {
+
+ // Compare q-values.
+ int qCompare = Float.compare(o.qValue, qValue);
+ if (qCompare != 0)
+ return qCompare;
+
+ // Compare media-types.
+ // Note that '*' comes alphabetically before letters, so just do a reverse-alphabetical comparison.
+ int i = o.type.compareTo(type);
+ if (i == 0)
+ i = o.subType.compareTo(subType);
+ return i;
+ }
+
+ /**
+ * Returns <jk>true</jk> if the specified <code>MediaRange</code> matches this range.
+ * <p>
+ * This implies the types and subtypes are the same as or encompasses the other (e.g. <js>'application/xml'</js> and <js>'application/*'</js>).
+ *
+ * @param o The other media rage.
+ * @return <jk>true</jk> if the media ranges are the same or one encompasses the other.
+ */
+ public boolean matches(MediaRange o) {
+ if (this == o)
+ return true;
+
+ if (qValue == 0 || o.qValue == 0)
+ return false;
+
+ if (type.equals(o.type) || (type.equals("*")) || (o.type.equals("*")))
+ if (subType.equals(o.subType) || subType.equals("*") || o.subType.equals("*"))
+ return true;
+
+ return false;
+ }
+}