http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/95e832e1/juneau-core/src/main/java/org/apache/juneau/PropertyStore.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/PropertyStore.java b/juneau-core/src/main/java/org/apache/juneau/PropertyStore.java new file mode 100644 index 0000000..2bfc27b --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/PropertyStore.java @@ -0,0 +1,1384 @@ +// *************************************************************************************************************************** +// * 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 store for instantiating {@link Context} objects. + * <p> + * The hierarchy of these objects are... + * <ul class='spaced-list'> + * <li>{@link PropertyStore} - 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 store.<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'>PropertyStore objects</h6> + * <p> + * Property stores 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> + * Property stores are used to create and cache {@link Context} objects using the {@link #getContext(Class)} method. + * <p> + * As a general rule, {@link PropertyStore} objects are 'slow'.<br> + * Setting and retrieving properties on a store 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> + * Property stores can be used to store context properties for a variety of contexts.<br> + * For example, a single store 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> + * + * <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'> + * PropertyStore ps = <jk>new</jk> PropertyStore().setProperty(<js>"BeanContext.notBeanClasses.set"</js>, Collections.<jsm>emptySet</jsm>()); + * + * <jc>// Append to set property using addTo().</jc> + * ps.addToProperty(<js>"BeanContext.notBeanClasses.set"</js>, MyNotBeanClass.<jk>class</jk>); + * + * <jc>// Append to set property using set().</jc> + * ps.setProperty(<js>"BeanContext.notBeanClasses.set.add"</js>, MyNotBeanClass.<jk>class</jk>); + * </p> + * <p> + * SET and LIST properties can also be set and manipulated using JSON strings. + * <p class='bcode'> + * PropertyStore ps = PropertyStore.<jsm>create</jsm>(); + * + * <jc>// Set SET value using JSON array. + * ps.setProperty(<js>"BeanContext.notBeanClasses.set"</js>, <js>"['com.my.MyNotBeanClass1']"</js>); + * + * <jc>// Add to SET using simple string. + * ps.addToProperty(<js>"BeanContext.notBeanClasses.set"</js>, <js>"com.my.MyNotBeanClass2"</js>); + * + * <jc>// Add an array of values as a JSON array.. + * ps.addToProperty(<js>"BeanContext.notBeanClasses.set"</js>, <js>"['com.my.MyNotBeanClass3']"</js>); + * + * <jc>// Remove an array of values as a JSON array.. + * ps.removeFromProperty(<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>putToProperty(<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'> + * PropertyStore ps = PropertyStore.<jsm>create</jsm>().setProperty(<js>"BeanContext.implClasses.map"</js>, Collections.<jsm>emptyMap</jsm>()); + * + * <jc>// Append to map property using putTo().</jc> + * ps.putToProperty(<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>)}}; + * ps.setProperty(<js>"BeanContext.implClasses.map.put"</js>, m); + * </p> + * <p> + * MAP properties can also be set and manipulated using JSON strings. + * <p class='bcode'> + * PropertyStore ps = PropertyStore.<jsm>create</jsm>(); + * + * <jc>// Set MAP value using JSON object.</jc> + * ps.setProperty(<js>"BeanContext.implClasses.map"</js>, <js>"{'com.my.MyInterface1':'com.my.MyInterfaceImpl1'}"</js>); + * + * <jc>// Add to MAP using JSON object.</jc> + * ps.putToProperty(<js>"BeanContext.implClasses.map"</js>, <js>"{'com.my.MyInterface2':'com.my.MyInterfaceImpl2'}"</js>); + * + * <jc>// Remove from MAP using JSON object.</jc> + * ps.putToProperty(<js>"BeanContext.implClasses.map"</js>, <js>"{'com.my.MyInterface2':null}"</js>); + * </p> + * <p> + * Context properties are retrieved from this store 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 store.<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 store have not been modified, the store will return a cached copy + * of a context. + * <p class='bcode'> + * PropertyStore ps = PropertyStore.<jsm>create</jsm>(); + * + * <jc>// Get BeanContext with default store settings.</jc> + * BeanContext bc = ps.getContext(BeanContext.<jk>class</jk>); + * + * <jc>// Get another one. This will be the same one.</jc> + * BeanContext bc2 = ps.getContext(BeanContext.<jk>class</jk>); + * <jsm>assertTrue</jsm>(bc1 == bc2); + * + * <jc>// Set a property.</jc> + * ps.setProperty(<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 PropertyStore { + + // 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. + // Property stores 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 property store with default settings. + * + * @return A new property store with default settings. + */ + public static PropertyStore create() { + PropertyStore f = new PropertyStore(); + BeanContext.loadDefaults(f); + return f; + } + + /** + * Create a new property store with settings copied from the specified store. + * + * @param copyFrom The existing store to copy properties from. + * @return A new property store with default settings. + */ + public static PropertyStore create(PropertyStore copyFrom) { + return new PropertyStore().copyFrom(copyFrom); + } + + + PropertyStore() {} + + /** + * Copy constructor. + * + * @param copyFrom The store to copy properties from. + */ + private PropertyStore(PropertyStore copyFrom) { + copyFrom(copyFrom); + } + + /** + * Copies the properties from the specified store into this store. + * <p> + * Properties of type set/list/collection will be appended to the existing + * properties if they already exist. + * + * @param ps The store to copy from. + * @return This object (for method chaining). + */ + public PropertyStore copyFrom(PropertyStore ps) { + // TODO - Needs more testing. + for (Map.Entry<String,PropertyMap> e : ps.properties.entrySet()) + this.properties.put(e.getKey(), new PropertyMap(this.properties.get(e.getKey()), e.getValue())); + this.classLoader = ps.classLoader; + this.defaultParser = ps.defaultParser; + return this; + } + + /** + * Creates a new store with the specified override properties applied to this store. + * + * @param overrideProperties The properties to apply to the copy of this store. + * @return Either this unmodified store, or a new store with override properties applied. + */ + public PropertyStore create(Map<String,Object> overrideProperties) { + return new PropertyStore(this).setProperties(overrideProperties); + } + + /** + * Sets a configuration property value on this object. + * <p> + * A typical usage is to set or overwrite configuration values like so... + * <p class='bcode'> + * PropertyStore ps = PropertyStore.<jsm>create</jsm>(); + * ps.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 PropertyStore 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 { + 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 store. + * + * @param newProperties The new properties to set. + * @return This object (for method chaining). + */ + public PropertyStore setProperties(Map<String,Object> newProperties) { + if (newProperties == null || newProperties.isEmpty()) + return this; + wl.lock(); + try { + contexts.clear(); + for (Map.Entry<String,Object> e : 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 several properties to this store. + * + * @param properties The properties to add to this store. + * @return This object (for method chaining). + */ + @SuppressWarnings("hiding") + public PropertyStore addProperties(Map<String,Object> properties) { + if (properties != null) + for (Map.Entry<String,Object> e : properties.entrySet()) + setProperty(e.getKey(), e.getValue()); + 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 PropertyStore addToProperty(String name, Object value) { + String prefix = prefix(name); + wl.lock(); + try { + 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 PropertyStore putToProperty(String name, Object key, Object value) { + String prefix = prefix(name); + wl.lock(); + try { + 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 PropertyStore putToProperty(String name, Object value) { + String prefix = prefix(name); + wl.lock(); + try { + 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 PropertyStore removeFromProperty(String name, Object value) { + String prefix = prefix(name); + wl.lock(); + try { + 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 store. + * <p> + * Multiple calls to this method for the same store class will return the same + * cached value as long as the properties on this store are not touched. + * <p> + * As soon as any properties are modified on this store, 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(PropertyStore.class).newInstance(this)); + + contexts.put(c, cacheForThisConfig.get(c)); + } + return (T)contexts.get(c); + } catch (Exception e) { + throw new ConfigException("Could not instantiate context 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. + * @return This object (for method chaining). + */ + public PropertyStore setClassLoader(ClassLoader classLoader) { + 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. + * @return This object (for method chaining). + */ + public PropertyStore setDefaultParser(ReaderParser defaultParser) { + 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>setProperty(<jsf>BEAN_notBeanClasses</jsf>, <jf>classes</jf>)</code>. + * + * @param classes The new setting value for the bean context. + * @return This object (for method chaining). + * @see PropertyStore#setProperty(String, Object) + * @see BeanContext#BEAN_notBeanClasses + */ + public PropertyStore setNotBeanClasses(Class<?>...classes) { + setProperty(BEAN_notBeanClasses, classes); + return this; + } + + /** + * Shortcut for calling <code>addToProperty(<jsf>BEAN_notBeanClasses</jsf>, <jf>classes</jf>)</code>. + * + * @see PropertyStore#addToProperty(String,Object) + * @param classes The new setting value for the bean context. + * @return This object (for method chaining). + * @see PropertyStore#addToProperty(String, Object) + * @see BeanContext#BEAN_notBeanClasses + */ + public PropertyStore addNotBeanClasses(Class<?>...classes) { + addToProperty(BEAN_notBeanClasses, classes); + return this; + } + + /** + * Shortcut for calling <code>setProperty(<jsf>BEAN_beanFilters</jsf>, <jf>classes</jf>)</code>. + * + * @param classes The new setting value for the bean context. + * @return This object (for method chaining). + * @see PropertyStore#setProperty(String, Object) + * @see BeanContext#BEAN_beanFilters + */ + public PropertyStore setBeanFilters(Class<?>...classes) { + setProperty(BEAN_beanFilters, classes); + return this; + } + + /** + * Shortcut for calling <code>addToProperty(<jsf>BEAN_beanFilters</jsf>, <jf>classes</jf>)</code>. + * + * @param classes The new setting value for the bean context. + * @return This object (for method chaining). + * @see PropertyStore#addToProperty(String, Object) + * @see BeanContext#BEAN_beanFilters + */ + public PropertyStore addBeanFilters(Class<?>...classes) { + addToProperty(BEAN_beanFilters, classes); + return this; + } + + /** + * Shortcut for calling <code>setProperty(<jsf>BEAN_pojoSwaps</jsf>, <jf>classes</jf>)</code>. + * + * @param classes The new setting value for the bean context. + * @return This object (for method chaining). + * @see PropertyStore#setProperty(String, Object) + * @see BeanContext#BEAN_pojoSwaps + */ + public PropertyStore setPojoSwaps(Class<?>...classes) { + setProperty(BEAN_pojoSwaps, classes); + return this; + } + + /** + * Shortcut for calling <code>addToProperty(<jsf>BEAN_pojoSwaps</jsf>, <jf>classes</jf>)</code>. + * + * @param classes The new setting value for the bean context. + * @return This object (for method chaining). + * @see PropertyStore#addToProperty(String, Object) + * @see BeanContext#BEAN_pojoSwaps + */ + public PropertyStore addPojoSwaps(Class<?>...classes) { + addToProperty(BEAN_pojoSwaps, classes); + return this; + } + + /** + * Shortcut for calling <code>setProperty(<jsf>BEAN_beanDictionary</jsf>, <jf>classes</jf>)</code>. + * + * @param classes The new setting value for the bean context. + * @return This object (for method chaining). + * @see PropertyStore#setProperty(String, Object) + * @see BeanContext#BEAN_beanDictionary + */ + public PropertyStore setBeanDictionary(Class<?>...classes) { + addToProperty(BEAN_beanDictionary, classes); + return this; + } + + /** + * Shortcut for calling <code>addToProperty(<jsf>BEAN_beanDictionary</jsf>, <jf>classes</jf>)</code>. + * + * @param classes The new setting value for the bean context. + * @return This object (for method chaining). + * @see PropertyStore#addToProperty(String, Object) + * @see BeanContext#BEAN_beanDictionary + */ + public PropertyStore addToBeanDictionary(Class<?>...classes) { + 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. + * @param <T> The class type of the interface. + * @return This object (for method chaining). + * @see PropertyStore#putToProperty(String, Object, Object) + * @see BeanContext#BEAN_implClasses + */ + public <T> PropertyStore addImplClass(Class<T> interfaceClass, Class<? extends T> implClass) { + 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 PropertyStore.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, PropertyMap apply) { + this.prefix = apply.prefix; + if (orig != null) + for (Map.Entry<String,Property> e : orig.map.entrySet()) + this.map.put(e.getKey(), Property.create(e.getValue().name, e.getValue().value())); + for (Map.Entry<String,Property> e : apply.map.entrySet()) { + Property prev = this.map.get(e.getKey()); + if (prev == null || "SIMPLE".equals(prev.type)) + this.map.put(e.getKey(), Property.create(e.getValue().name, e.getValue().value())); + else { + if ("SET".equals(prev.type) || "LIST".equals(prev.type)) + prev.add(e.getValue().value()); + else if ("MAP".equals(prev.type)) + prev.put(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 property store 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 property store 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(); + } + } +}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/95e832e1/juneau-core/src/main/java/org/apache/juneau/Session.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/Session.java b/juneau-core/src/main/java/org/apache/juneau/Session.java index b870421..e0864e1 100644 --- a/juneau-core/src/main/java/org/apache/juneau/Session.java +++ b/juneau-core/src/main/java/org/apache/juneau/Session.java @@ -26,7 +26,7 @@ import org.apache.juneau.serializer.*; * Serializers and parsers use session objects to retrieve config properties and to use it * as a scratchpad during serialize and parse actions. * - * @see ContextFactory + * @see PropertyStore */ public abstract class Session { http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/95e832e1/juneau-core/src/main/java/org/apache/juneau/annotation/Pojo.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/annotation/Pojo.java b/juneau-core/src/main/java/org/apache/juneau/annotation/Pojo.java index 18897a1..1a4a5d5 100644 --- a/juneau-core/src/main/java/org/apache/juneau/annotation/Pojo.java +++ b/juneau-core/src/main/java/org/apache/juneau/annotation/Pojo.java @@ -75,8 +75,8 @@ public @interface Pojo { * <p> * Note that using this annotation is functionally equivalent to adding swaps to the serializers and parsers: * <p class='bcode'> - * WriterSerializer s = <jk>new</jk> JsonSerializer.addPojoSwaps(BSwap.<jk>class</jk>); - * ReaderParser p = <jk>new</jk> JsonParser.addPojoSwaps(BSwap.<jk>class</jk>); + * WriterSerializer s = <jk>new</jk> JsonSerializerBuilder().pojoSwaps(BSwap.<jk>class</jk>).build(); + * ReaderParser p = <jk>new</jk> JsonParserBuilder().pojoSwaps(BSwap.<jk>class</jk>).build(); * </p> */ Class<?> swap() default Null.class; http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/95e832e1/juneau-core/src/main/java/org/apache/juneau/csv/CsvParser.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/csv/CsvParser.java b/juneau-core/src/main/java/org/apache/juneau/csv/CsvParser.java new file mode 100644 index 0000000..753e85c --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/csv/CsvParser.java @@ -0,0 +1,60 @@ +// *************************************************************************************************************************** +// * 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.csv; + +import org.apache.juneau.*; +import org.apache.juneau.annotation.*; +import org.apache.juneau.parser.*; + +/** + * TODO - Work in progress. CSV parser. + */ +@Consumes("text/csv") +public class CsvParser extends ReaderParser { + + /** Default parser, all default settings.*/ + public static final CsvParser DEFAULT = new CsvParser(PropertyStore.create()); + + + /** + * Constructor. + * @param propertyStore The property store containing all the settings for this object. + */ + public CsvParser(PropertyStore propertyStore) { + super(propertyStore); + } + + @Override /* CoreObject */ + public CsvParserBuilder builder() { + return new CsvParserBuilder(propertyStore); + } + + @SuppressWarnings("unused") + private <T> T parseAnything(ParserSession session, ClassMeta<T> eType, ParserReader r, Object outer, BeanPropertyMeta pMeta) throws Exception { + throw new NoSuchMethodException("Not implemented."); + } + + //-------------------------------------------------------------------------------- + // Entry point methods + //-------------------------------------------------------------------------------- + + @Override /* Parser */ + protected <T> T doParse(ParserSession session, ClassMeta<T> type) throws Exception { + CsvParserSession s = (CsvParserSession)session; + ParserReader r = s.getReader(); + if (r == null) + return null; + T o = parseAnything(s, type, r, s.getOuter(), null); + return o; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/95e832e1/juneau-core/src/main/java/org/apache/juneau/csv/CsvParserBuilder.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/csv/CsvParserBuilder.java b/juneau-core/src/main/java/org/apache/juneau/csv/CsvParserBuilder.java new file mode 100644 index 0000000..a8d1fb1 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/csv/CsvParserBuilder.java @@ -0,0 +1,456 @@ +// *************************************************************************************************************************** +// * 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.csv; + +import java.util.*; + +import org.apache.juneau.*; +import org.apache.juneau.parser.*; + +/** + * Builder class for building instances of CSV parsers. + */ +public class CsvParserBuilder extends ParserBuilder { + + /** + * Constructor, default settings. + */ + public CsvParserBuilder() { + super(); + } + + /** + * Constructor. + * @param propertyStore The initial configuration settings for this builder. + */ + public CsvParserBuilder(PropertyStore propertyStore) { + super(propertyStore); + } + + @Override /* CoreObjectBuilder */ + public CsvParser build() { + return new CsvParser(propertyStore); + } + + //-------------------------------------------------------------------------------- + // Properties + //-------------------------------------------------------------------------------- + + @Override /* ParserBuilder */ + public CsvParserBuilder trimStrings(boolean value) { + super.trimStrings(value); + return this; + } + + @Override /* ParserBuilder */ + public CsvParserBuilder strict(boolean value) { + super.strict(value); + return this; + } + + @Override /* ParserBuilder */ + public CsvParserBuilder strict() { + super.strict(); + return this; + } + + @Override /* ParserBuilder */ + public CsvParserBuilder inputStreamCharset(String value) { + super.inputStreamCharset(value); + return this; + } + + @Override /* ParserBuilder */ + public CsvParserBuilder fileCharset(String value) { + super.fileCharset(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder beansRequireDefaultConstructor(boolean value) { + super.beansRequireDefaultConstructor(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder beansRequireSerializable(boolean value) { + super.beansRequireSerializable(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder beansRequireSettersForGetters(boolean value) { + super.beansRequireSettersForGetters(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder beansRequireSomeProperties(boolean value) { + super.beansRequireSomeProperties(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder beanMapPutReturnsOldValue(boolean value) { + super.beanMapPutReturnsOldValue(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder beanConstructorVisibility(Visibility value) { + super.beanConstructorVisibility(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder beanClassVisibility(Visibility value) { + super.beanClassVisibility(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder beanFieldVisibility(Visibility value) { + super.beanFieldVisibility(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder methodVisibility(Visibility value) { + super.methodVisibility(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder useJavaBeanIntrospector(boolean value) { + super.useJavaBeanIntrospector(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder useInterfaceProxies(boolean value) { + super.useInterfaceProxies(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder ignoreUnknownBeanProperties(boolean value) { + super.ignoreUnknownBeanProperties(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder ignoreUnknownNullBeanProperties(boolean value) { + super.ignoreUnknownNullBeanProperties(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder ignorePropertiesWithoutSetters(boolean value) { + super.ignorePropertiesWithoutSetters(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder ignoreInvocationExceptionsOnGetters(boolean value) { + super.ignoreInvocationExceptionsOnGetters(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder ignoreInvocationExceptionsOnSetters(boolean value) { + super.ignoreInvocationExceptionsOnSetters(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder sortProperties(boolean value) { + super.sortProperties(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder notBeanPackages(String...values) { + super.notBeanPackages(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder notBeanPackages(Collection<String> values) { + super.notBeanPackages(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder setNotBeanPackages(String...values) { + super.setNotBeanPackages(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder setNotBeanPackages(Collection<String> values) { + super.setNotBeanPackages(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder removeNotBeanPackages(String...values) { + super.removeNotBeanPackages(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder removeNotBeanPackages(Collection<String> values) { + super.removeNotBeanPackages(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder notBeanClasses(Class<?>...values) { + super.notBeanClasses(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder notBeanClasses(Collection<Class<?>> values) { + super.notBeanClasses(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder setNotBeanClasses(Class<?>...values) { + super.setNotBeanClasses(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder setNotBeanClasses(Collection<Class<?>> values) { + super.setNotBeanClasses(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder removeNotBeanClasses(Class<?>...values) { + super.removeNotBeanClasses(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder removeNotBeanClasses(Collection<Class<?>> values) { + super.removeNotBeanClasses(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder beanFilters(Class<?>...values) { + super.beanFilters(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder beanFilters(Collection<Class<?>> values) { + super.beanFilters(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder setBeanFilters(Class<?>...values) { + super.setBeanFilters(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder setBeanFilters(Collection<Class<?>> values) { + super.setBeanFilters(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder removeBeanFilters(Class<?>...values) { + super.removeBeanFilters(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder removeBeanFilters(Collection<Class<?>> values) { + super.removeBeanFilters(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder pojoSwaps(Class<?>...values) { + super.pojoSwaps(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder pojoSwaps(Collection<Class<?>> values) { + super.pojoSwaps(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder setPojoSwaps(Class<?>...values) { + super.setPojoSwaps(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder setPojoSwaps(Collection<Class<?>> values) { + super.setPojoSwaps(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder removePojoSwaps(Class<?>...values) { + super.removePojoSwaps(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder removePojoSwaps(Collection<Class<?>> values) { + super.removePojoSwaps(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder implClasses(Map<Class<?>,Class<?>> values) { + super.implClasses(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public <T> CsvParserBuilder implClass(Class<T> interfaceClass, Class<? extends T> implClass) { + super.implClass(interfaceClass, implClass); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder beanDictionary(Class<?>...values) { + super.beanDictionary(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder beanDictionary(Collection<Class<?>> values) { + super.beanDictionary(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder setBeanDictionary(Class<?>...values) { + super.setBeanDictionary(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder setBeanDictionary(Collection<Class<?>> values) { + super.setBeanDictionary(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder removeFromBeanDictionary(Class<?>...values) { + super.removeFromBeanDictionary(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder removeFromBeanDictionary(Collection<Class<?>> values) { + super.removeFromBeanDictionary(values); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder beanTypePropertyName(String value) { + super.beanTypePropertyName(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder defaultParser(Class<?> value) { + super.defaultParser(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder locale(Locale value) { + super.locale(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder timeZone(TimeZone value) { + super.timeZone(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder mediaType(MediaType value) { + super.mediaType(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder debug(boolean value) { + super.debug(value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder property(String name, Object value) { + super.property(name, value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder properties(Map<String,Object> properties) { + super.properties(properties); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder addToProperty(String name, Object value) { + super.addToProperty(name, value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder putToProperty(String name, Object key, Object value) { + super.putToProperty(name, key, value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder putToProperty(String name, Object value) { + super.putToProperty(name, value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder removeFromProperty(String name, Object value) { + super.removeFromProperty(name, value); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder classLoader(ClassLoader classLoader) { + super.classLoader(classLoader); + return this; + } + + @Override /* CoreObjectBuilder */ + public CsvParserBuilder apply(PropertyStore copyFrom) { + super.apply(copyFrom); + return this; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/95e832e1/juneau-core/src/main/java/org/apache/juneau/csv/CsvParserContext.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/csv/CsvParserContext.java b/juneau-core/src/main/java/org/apache/juneau/csv/CsvParserContext.java new file mode 100644 index 0000000..17c1c34 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/csv/CsvParserContext.java @@ -0,0 +1,53 @@ +// *************************************************************************************************************************** +// * 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.csv; + +import org.apache.juneau.*; +import org.apache.juneau.parser.*; + +/** + * Configurable properties on the {@link CsvParser} class. + * <p> + * Context properties are set by calling {@link PropertyStore#setProperty(String, Object)} on the property store + * passed into the constructor. + * <p> + * See {@link PropertyStore} for more information about context properties. + * + * <h5 class='section'>Inherited configurable properties:</h5> + * <ul class='javahierarchy'> + * <li class='c'><a class="doclink" href="../BeanContext.html#ConfigProperties">BeanContext</a> - Properties associated with handling beans on serializers and parsers. + * <ul> + * <li class='c'><a class="doclink" href="../parser/ParserContext.html#ConfigProperties">ParserContext</a> - Configurable properties common to all parsers. + * </ul> + * </ul> + */ +public final class CsvParserContext extends ParserContext { + + /** + * Constructor. + * <p> + * Typically only called from {@link PropertyStore#getContext(Class)}. + * + * @param ps The property store that created this context. + */ + public CsvParserContext(PropertyStore ps) { + super(ps); + } + + @Override /* Context */ + public ObjectMap asMap() { + return super.asMap() + .append("CsvParserContext", new ObjectMap() + ); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/95e832e1/juneau-core/src/main/java/org/apache/juneau/csv/CsvParserSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/csv/CsvParserSession.java b/juneau-core/src/main/java/org/apache/juneau/csv/CsvParserSession.java new file mode 100644 index 0000000..151b8b6 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/csv/CsvParserSession.java @@ -0,0 +1,111 @@ +// *************************************************************************************************************************** +// * 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.csv; + +import java.io.*; +import java.lang.reflect.*; +import java.util.*; + +import org.apache.juneau.*; +import org.apache.juneau.parser.*; + +/** + * Session object that lives for the duration of a single use of {@link CsvParser}. + * <p> + * This class is NOT thread safe. It is meant to be discarded after one-time use. + */ +public final class CsvParserSession extends ParserSession { + + private ParserReader reader; + + /** + * Create a new session using properties specified in the context. + * + * @param ctx The context creating this session object. + * The context contains all the configuration settings for this object. + * @param input The input. Can be any of the following types: + * <ul> + * <li><jk>null</jk> + * <li>{@link Reader} + * <li>{@link CharSequence} + * <li>{@link InputStream} containing UTF-8 encoded text. + * <li>{@link File} containing system encoded text. + * </ul> + * @param op The override properties. + * These override any context properties defined in the context. + * @param javaMethod The java method that called this parser, usually the method in a REST servlet. + * @param outer The outer object for instantiating top-level non-static inner classes. + * @param locale The session locale. + * If <jk>null</jk>, then the locale defined on the context is used. + * @param timeZone The session timezone. + * If <jk>null</jk>, then the timezone defined on the context is used. + * @param mediaType The session media type (e.g. <js>"application/json"</js>). + */ + public CsvParserSession(CsvParserContext ctx, ObjectMap op, Object input, Method javaMethod, Object outer, Locale locale, TimeZone timeZone, MediaType mediaType) { + super(ctx, op, input, javaMethod, outer, locale, timeZone, mediaType); + } + + @Override /* ParserSession */ + public ParserReader getReader() throws Exception { + if (reader == null) { + Object input = getInput(); + if (input == null) + return null; + if (input instanceof CharSequence) + reader = new ParserReader((CharSequence)input); + else + reader = new ParserReader(super.getReader()); + } + return reader; + } + + /** + * Returns <jk>true</jk> if the specified character is whitespace. + * <p> + * The definition of whitespace is different for strict vs lax mode. + * Strict mode only interprets 0x20 (space), 0x09 (tab), 0x0A (line feed) and 0x0D (carriage return) as whitespace. + * Lax mode uses {@link Character#isWhitespace(int)} to make the determination. + * + * @param cp The codepoint. + * @return <jk>true</jk> if the specified character is whitespace. + */ + public final boolean isWhitespace(int cp) { + if (isStrict()) + return cp <= 0x20 && (cp == 0x09 || cp == 0x0A || cp == 0x0D || cp == 0x20); + return Character.isWhitespace(cp); + } + + /** + * Returns <jk>true</jk> if the specified character is whitespace or '/'. + * + * @param cp The codepoint. + * @return <jk>true</jk> if the specified character is whitespace or '/'. + */ + public final boolean isCommentOrWhitespace(int cp) { + if (cp == '/') + return true; + if (isStrict()) + return cp <= 0x20 && (cp == 0x09 || cp == 0x0A || cp == 0x0D || cp == 0x20); + return Character.isWhitespace(cp); + } + + @Override /* ParserSession */ + public Map<String,Object> getLastLocation() { + Map<String,Object> m = super.getLastLocation(); + if (reader != null) { + m.put("line", reader.getLine()); + m.put("column", reader.getColumn()); + } + return m; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/95e832e1/juneau-core/src/main/java/org/apache/juneau/csv/CsvSerializer.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/csv/CsvSerializer.java b/juneau-core/src/main/java/org/apache/juneau/csv/CsvSerializer.java index db04ea0..c2a21df 100644 --- a/juneau-core/src/main/java/org/apache/juneau/csv/CsvSerializer.java +++ b/juneau-core/src/main/java/org/apache/juneau/csv/CsvSerializer.java @@ -26,6 +26,23 @@ import org.apache.juneau.serializer.*; @SuppressWarnings({"rawtypes"}) public final class CsvSerializer extends WriterSerializer { + /** Default serializer, all default settings.*/ + public static final CsvSerializer DEFAULT = new CsvSerializer(PropertyStore.create()); + + + /** + * Constructor. + * @param propertyStore The property store containing all the settings for this object. + */ + public CsvSerializer(PropertyStore propertyStore) { + super(propertyStore); + } + + @Override /* CoreObject */ + public CsvSerializerBuilder builder() { + return new CsvSerializerBuilder(propertyStore); + } + //-------------------------------------------------------------------------------- // Entry point methods //-------------------------------------------------------------------------------- @@ -65,7 +82,7 @@ public final class CsvSerializer extends WriterSerializer { } } - private void append(Writer w, Object o) throws IOException { + private static void append(Writer w, Object o) throws IOException { if (o == null) w.append("null"); else { @@ -82,18 +99,4 @@ public final class CsvSerializer extends WriterSerializer { w.append(s); } } - - - //-------------------------------------------------------------------------------- - // Overridden methods - //-------------------------------------------------------------------------------- - - @Override /* Serializer */ - public CsvSerializer clone() { - try { - return (CsvSerializer)super.clone(); - } catch (CloneNotSupportedException e) { - throw new RuntimeException(e); // Shouldn't happen. - } - } }
