diff options
Diffstat (limited to 'src/Java/miscutil/core/util/aeonbits')
45 files changed, 4186 insertions, 0 deletions
diff --git a/src/Java/miscutil/core/util/aeonbits/owner/Accessible.java b/src/Java/miscutil/core/util/aeonbits/owner/Accessible.java new file mode 100644 index 0000000000..cbd770f4a8 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/Accessible.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2012-2015, Luigi R. Viggiano + * All rights reserved. + * + * This software is distributable under the BSD license. + * See the terms of the BSD license in the documentation provided with this software. + */ + +package miscutil.core.util.aeonbits.owner; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.Map; +import java.util.Set; + +/** + * <p>Allows a <tt>Config</tt> object to access the contents of the properties, providing utility methods to perform + * consequent operations.</p> + * <p>Example:</p> + * <pre> + * public interface MyConfig extends Config, Accessible { + * int someProperty(); + * } + * + * public void doSomething() { + * MyConfig cfg = ConfigFactory.create(MyConfig.class); + * cfg.list(System.out); + * } + * </pre> + * <p>These methods will print the list of properties, see {@link java.util.Properties#list(java.io.PrintStream)} and + * {@link java.util.Properties#list(java.io.PrintWriter)}.</p> + * + * @author Luigi R. Viggiano + * @since 1.0.4 + */ +public interface Accessible extends Config { + + /** + * Prints this property list out to the specified output stream. This method is useful for debugging. + * + * @param out an output stream. + * @throws ClassCastException if any key in this property list is not a string. + * @see java.util.Properties#list(java.io.PrintStream) + * @since 1.0.4 + */ + void list(PrintStream out); + + /** + * Prints this property list out to the specified output stream. This method is useful for debugging. + * + * @param out an output stream. + * @throws ClassCastException if any key in this property list is not a string. + * @see java.util.Properties#list(java.io.PrintWriter) + * @since 1.0.4 + */ + void list(PrintWriter out); + + /** + * Stores the underlying properties into an {@link java.io.OutputStream}. + * <p> + * Notice that method {@link java.util.Properties#store(java.io.Writer, String)} is not implemented since it's not + * available in JDK 1.5 (while the target of this library is Java 1.5+). + * + * @param out an output stream. + * @param comments a description of the property list. + * @throws IOException if writing this property list to the specified output stream throws an <tt>IOException</tt>. + * @see java.util.Properties#store(java.io.OutputStream, String) + * @since 1.0.4 + */ + void store(OutputStream out, String comments) throws IOException; + + /** + * Fills the given {@link java.util.Map} with the properties contained by this object. <br> + * This is useful to extract the content of the config object into a {@link java.util.Map}. + * <p> + * Notice that you can specify a properties object as parameter instead of a map, + * since {@link java.util.Properties} implements the {@link java.util.Map} interface. + * + * @param map the {@link java.util.Map} to fill. + * @since 1.0.9 + */ + void fill(Map map); + + /** + * Searches for the property with the specified key in this property list. + * If the key is not found in this property list, the default property list, + * and its defaults, recursively, are then checked. The method returns + * <code>null</code> if the property is not found. + * + * @param key the property key. + * @return the value in this property list with the specified key value. + * @see java.util.Properties#getProperty(String) + * @since 1.0.4 + */ + String getProperty(String key); + + /** + * Searches for the property with the specified key in this property list. + * If the key is not found in this property list, the default property list, + * and its defaults, recursively, are then checked. The method returns the + * default value argument if the property is not found. + * + * @param key the property key. + * @param defaultValue a default value. + * @return the value in this property list with the specified key value. + * @see java.util.Properties#getProperty(String, String) + * + * @since 1.0.4 + */ + String getProperty(String key, String defaultValue); + + /** + * Emits an XML document representing all of the properties contained + * in this table. + * + * <p> An invocation of this method of the form <tt>props.storeToXML(os, + * comment)</tt> behaves in exactly the same way as the invocation + * <tt>props.storeToXML(os, comment, "UTF-8");</tt>. + * + * @param os the output stream on which to emit the XML document. + * @param comment a description of the property list, or <code>null</code> + * if no comment is desired. + * @throws IOException if writing to the specified output stream + * results in an <tt>IOException</tt>. + * @throws NullPointerException if <code>os</code> is null. + * @throws ClassCastException if this <code>Properties</code> object + * contains any keys or values that are not + * <code>Strings</code>. + * @since 1.0.5 + */ + void storeToXML(OutputStream os, String comment) throws IOException; + + /** + * Returns a set of keys in this property list + * including distinct keys in the default property list if a key + * of the same name has not already been found from the main + * properties list. + * <p> + * The returned set is not backed by the <tt>Properties</tt> object. + * Changes to this <tt>Properties</tt> are not reflected in the set, + * or vice versa. + * + * @return a set of keys in this property list, including the keys in the + * default property list. + * @throws ClassCastException if any key in this property list + * is not a string. + * @see java.util.Properties#defaults + * @see java.util.Properties#stringPropertyNames() + * @see java.util.Properties#propertyNames() + * @since 1.0.5 + */ + Set<String> propertyNames(); + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/Config.java b/src/Java/miscutil/core/util/aeonbits/owner/Config.java new file mode 100644 index 0000000000..ab7fa23cc1 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/Config.java @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2012-2015, Luigi R. Viggiano + * All rights reserved. + * + * This software is distributable under the BSD license. + * See the terms of the BSD license in the documentation provided with this software. + */ + +package miscutil.core.util.aeonbits.owner; + + +import java.io.IOException; +import java.io.Serializable; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.net.URI; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static java.util.concurrent.TimeUnit.SECONDS; +import static miscutil.core.util.aeonbits.owner.Config.HotReloadType.SYNC; +import static miscutil.core.util.aeonbits.owner.Config.LoadType.FIRST; +import static miscutil.core.util.aeonbits.owner.Util.ignore; +import static miscutil.core.util.aeonbits.owner.Util.reverse; + +/** + * Marker interface that must be implemented by all Config sub-interfaces. + * <p> + * Sub-interfaces may also extend {@link Accessible} to allow some debugging facility, or {@link Reloadable} to allow the + * user to programmatically reload properties. + * </p> + * + * @author Luigi R. Viggiano + * @see java.util.Properties + */ +public interface Config extends Serializable { + + /** + * Specifies the policy for loading the properties files. By default the first available properties file specified + * by {@link Sources} will be loaded, see {@link LoadType#FIRST}. User can also specify that the load policy is + * {@link LoadType#MERGE} to have the properties files merged: properties are loaded in order from the first file to + * the last, if there are conflicts in properties names the earlier files loaded prevail. + * + * @since 1.0.2 + */ + @Retention(RUNTIME) + @Target(TYPE) + @Documented + @interface LoadPolicy { + LoadType value() default FIRST; + } + + /** + * Specifies the source from which to load the properties file. It has to be specified in a URI string format. + * By default, allowed protocols are the ones allowed by {@link java.net.URL} plus + * <tt>classpath:path/to/resource.properties</tt>, but user can specify his own additional protocols. + * + * @since 1.0.2 + */ + @Retention(RUNTIME) + @Target(TYPE) + @Documented + @interface Sources { + String[] value(); + } + + /** + * Default value to be used if no property is found. No quoting (other than normal Java string quoting) is done. + */ + @Retention(RUNTIME) + @Target(METHOD) + @Documented + @interface DefaultValue { + String value(); + } + + /** + * The key used for lookup for the property. If not present, the key will be generated based on the unqualified + * method name. + */ + @Retention(RUNTIME) + @Target(METHOD) + @Documented + @interface Key { + String value(); + } + + /** + * Specifies the policy type to use to load the {@link miscutil.core.util.aeonbits.owner.Config.Sources} files for properties. + * + * @since 1.0.2 + */ + enum LoadType { + + /** + * The first available of the specified sources will be loaded. + */ + FIRST { + @Override + Properties load(List<URI> uris, LoadersManager loaders) { + Properties result = new Properties(); + for (URI uri : uris) + try { + loaders.load(result, uri); + break; + } catch (IOException ex) { + // happens when a file specified in the sources is not found or cannot be read. + ignore(); + } + return result; + } + }, + + /** + * All the specified sources will be loaded and merged. If the same property key is + * specified from more than one source, the one specified first will prevail. + */ + MERGE { + @Override + Properties load(List<URI> uris, LoadersManager loaders) { + Properties result = new Properties(); + for (URI uri : reverse(uris)) + try { + loaders.load(result, uri); + } catch (IOException ex) { + // happens when a file specified in the sources is not found or cannot be read. + ignore(); + } + return result; + } + }; + + abstract Properties load(List<URI> uris, LoadersManager loaders); + } + + /** + * Specify that the class implements hot reloading of properties from filesystem baked {@link Sources} (hot + * reloading can't be applied to all types of URIs). + * <p> + * It is possible to specify an interval to indicate how frequently the library shall check the files for + * modifications and perform the reload. + * </p> + * Examples: + * <pre> + * @HotReload // will check for file changes every 5 seconds. + * @Sources("file:foo/bar/baz.properties") + * interface MyConfig extends Config { ... } + * + * @HotReload(2) // will check for file changes every 2 seconds. + * @Sources("file:foo/bar/baz.properties") + * interface MyConfig extends Config { ... } + * + * @HotReload(500, unit = TimeUnit.MILLISECONDS); // will check for file changes every 500 milliseconds. + * @Sources("file:foo/bar/baz.properties") + * interface MyConfig extends Config { ... } + * + * @HotReload(type=HotReloadType.ASYNC); // will use ASYNC reload type: will span a separate thread + * // that will check for the file change every 5 seconds (default). + * @Sources("file:foo/bar/baz.properties") + * interface MyConfig extends Config { ... } + * + * @HotReload(2, type=HotReloadType.ASYNC); // will use ASYNC reload type and will check every 2 seconds. + * @Sources("file:foo/bar/baz.properties") + * interface MyConfig extends Config { ... } + * </pre> + * + * <p> + * To intercept the {@link miscutil.core.util.aeonbits.owner.event.ReloadEvent} see {@link Reloadable#addReloadListener(miscutil.core.util.aeonbits.owner.event.ReloadListener)}. + * + * @since 1.0.4 + */ + @Retention(RUNTIME) + @Target(TYPE) + @Documented + @interface HotReload { + /** + * The interval, expressed in seconds (by default), to perform checks on the filesystem to identify modified + * files and eventually perform the reloading of the properties. By default is 5 seconds. + * + * @return the hot reload value; default is 5. + */ + long value() default 5; + + /** + * <p> + * The time unit for the interval. By default it is {@link TimeUnit#SECONDS}. + * </p> + * <p> + * Date resolution vary from filesystem to filesystem.<br> + * For instance, for Ext3, ReiserFS and HSF+ the date resolution is of 1 second.<br> + * For FAT32 the date resolution for the last modified time is 2 seconds. <br> + * For Ext4 the date resolution is in nanoseconds. + * </p> + * <p> + * So, it is a good idea to express the time unit in seconds or more, since higher time resolution + * will probably not be supported by the underlying filesystem. + * </p> + * @return the time unit; default is SECONDS. + */ + TimeUnit unit() default SECONDS; + + /** + * The type of HotReload to use. It can be: + * + * <p> + * {@link HotReloadType#SYNC Synchronous}: the configuration file is checked when a method is invoked on the + * config object. So if the config object is not used for some time, the configuration doesn't get reloaded, + * until its next usage. i.e. until next method invocation. + * <p> + * {@link HotReloadType#ASYNC}: the configuration file is checked by a background thread despite the fact that + * the config object is used or not. + * + * @return the hot reload type; default is SYNC. + */ + HotReloadType type() default SYNC; + } + + /** + * Allows to specify which type of HotReload should be applied. + */ + enum HotReloadType { + /** + * The hot reload will happen when one of the methods is invoked on the <tt>Config</tt> class. + */ + SYNC, + + /** + * The hot reload will happen in background at the specified interval. + */ + ASYNC + } + + /** + * Specifies to disable some of the features supported by the API. + * This may be useful in case the user prefers to implement by his own, or just has troubles with something that + * is unwanted. + * Features that can be disabled are specified in the enum {@link DisableableFeature}. + * + * @since 1.0.4 + */ + @Retention(RUNTIME) + @Target({METHOD, TYPE}) + @Documented + @interface DisableFeature { + DisableableFeature[] value(); + } + + /** + * This enum contains the features that can be disabled using the annotation {@link DisableFeature}. + * + * @since 1.0.4 + */ + enum DisableableFeature { + VARIABLE_EXPANSION, + PARAMETER_FORMATTING + } + + /** + * Specifies simple <tt>{@link String}</tt> as separator to tokenize properties values specified as a + * single string value, into elements for vectors and collections. + * The value specified is used as per {@link String#split(String, int)} with int=-1, every element is also + * trimmed out from spaces using {@link String#trim()}. + * + * Notice that {@link TokenizerClass} and {@link Separator} do conflict with each-other when they are both specified + * together on the same level: + * <ul> + * <li> + * You cannot specify {@link TokenizerClass} and {@link Separator} both together on the same method + * </li> + * <li> + * You cannot specify {@link TokenizerClass} and {@link Separator} both together on the same class + * </li> + * </ul> + * in the two above cases an {@link UnsupportedOperationException} will be thrown when the corresponding conversion + * is executed. + * + * @since 1.0.4 + */ + @Retention(RUNTIME) + @Target({METHOD, TYPE}) + @Documented + @interface Separator { + /** + * @return the value specified is used as per {@link java.lang.String#split(String, int)} with int=-1 + */ + String value(); + } + + /** + * Specifies a <tt>{@link Tokenizer}</tt> class to allow the user to define a custom logic to split + * the property value into tokens to be used as single elements for vectors and collections. + * + * Notice that {@link TokenizerClass} and {@link Separator} do conflict with each-other when they are both specified + * together on the same level: + * <ul> + * <li> + * You cannot specify {@link TokenizerClass} and {@link Separator} both together on the same method + * </li> + * <li> + * You cannot specify {@link TokenizerClass} and {@link Separator} both together on the same class + * </li> + * </ul> + * in the two above cases an {@link UnsupportedOperationException} will be thrown when the corresponding conversion + * is executed. + * + * @since 1.0.4 + */ + @Retention(RUNTIME) + @Target({METHOD, TYPE}) + @Documented + @interface TokenizerClass { + Class<? extends Tokenizer> value(); + } + + /** + * Specifies a <tt>{@link Converter}</tt> class to allow the user to define a custom conversion logic for the + * type returned by the method. If the method returns a collection, the Converter is used to convert a single + * element. + */ + @Retention(RUNTIME) + @Target(METHOD) + @Documented + @interface ConverterClass { + Class<? extends Converter> value(); + } + + /** + * Specifies a <tt>{@link Preprocessor}</tt> class to allow the user to define a custom logic to pre-process + * the property value before being used by the library. + * + * @since 1.0.9 + */ + @Retention(RUNTIME) + @Target({METHOD, TYPE}) + @Documented + @interface PreprocessorClasses { + Class<? extends Preprocessor>[] value(); + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/ConfigCache.java b/src/Java/miscutil/core/util/aeonbits/owner/ConfigCache.java new file mode 100644 index 0000000000..5a061e56f8 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/ConfigCache.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2012-2015, Luigi R. Viggiano + * All rights reserved. + * + * This software is distributable under the BSD license. + * See the terms of the BSD license in the documentation provided with this software. + */ + +package miscutil.core.util.aeonbits.owner; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Utility class caching Config instances that can be used as Singletons. + * + * This class is designed to be thread safe. + * + * @author Luigi R. Viggiano + * @since 1.0.6 + */ +public final class ConfigCache { + private static final ConcurrentMap<Object, Config> CACHE = new ConcurrentHashMap<Object, Config>(); + + /** Don't let anyone instantiate this class */ + private ConfigCache() {} + + /** + * Gets from the cache or create, an instance of the given class using the given imports. + * The factory used to create new instances is the static {@link ConfigFactory#INSTANCE}. + * + * @param clazz the interface extending from {@link Config} that you want to instantiate. + * @param imports additional variables to be used to resolve the properties. + * @param <T> type of the interface. + * @return an object implementing the given interface, that can be taken from the cache, + * which maps methods to property values. + */ + public static <T extends Config> T getOrCreate(Class<? extends T> clazz, Map<?, ?>... imports) { + return getOrCreate(ConfigFactory.INSTANCE, clazz, clazz, imports); + } + + /** + * Gets from the cache or create, an instance of the given class using the given imports. + * + * @param factory the factory to use to eventually create the instance. + * @param clazz the interface extending from {@link Config} that you want to instantiate. + * @param imports additional variables to be used to resolve the properties. + * @param <T> type of the interface. + * @return an object implementing the given interface, that can be taken from the cache, + * which maps methods to property values. + */ + public static <T extends Config> T getOrCreate(Factory factory, Class<? extends T> clazz, Map<?, ?>... imports) { + return getOrCreate(factory, clazz, clazz, imports); + } + + /** + * Gets from the cache or create, an instance of the given class using the given imports. + * The factory used to create new instances is the static {@link ConfigFactory#INSTANCE}. + * + * @param key the key object to be used to identify the instance in the cache. + * @param clazz the interface extending from {@link Config} that you want to instantiate. + * @param imports additional variables to be used to resolve the properties. + * @param <T> type of the interface. + * @return an object implementing the given interface, that can be taken from the cache, + * which maps methods to property values. + */ + public static <T extends Config> T getOrCreate(Object key, Class<? extends T> clazz, Map<?, ?>... imports) { + return getOrCreate(ConfigFactory.INSTANCE, key, clazz, imports); + } + + /** + * Gets from the cache or create, an instance of the given class using the given imports. + * |
