From f7246428becd594d59e459bc4e08a601410d8210 Mon Sep 17 00:00:00 2001 From: Draknyte1 Date: Mon, 25 Jul 2016 17:55:49 +1000 Subject: + Added A Config File and handler for it. % Trying a new recipe handler for the Industrial Centrifuge. --- .../core/util/aeonbits/owner/Accessible.java | 156 ++++++ .../miscutil/core/util/aeonbits/owner/Config.java | 345 ++++++++++++ .../core/util/aeonbits/owner/ConfigCache.java | 145 +++++ .../core/util/aeonbits/owner/ConfigFactory.java | 136 +++++ .../core/util/aeonbits/owner/ConfigURIFactory.java | 53 ++ .../core/util/aeonbits/owner/Converter.java | 35 ++ .../core/util/aeonbits/owner/Converters.java | 314 +++++++++++ .../core/util/aeonbits/owner/DefaultFactory.java | 103 ++++ .../core/util/aeonbits/owner/Delegate.java | 20 + .../util/aeonbits/owner/DelegateMethodHandle.java | 41 ++ .../miscutil/core/util/aeonbits/owner/Factory.java | 91 ++++ .../core/util/aeonbits/owner/HotReloadLogic.java | 103 ++++ .../core/util/aeonbits/owner/JMXSupport.java | 109 ++++ .../core/util/aeonbits/owner/LoadersManager.java | 95 ++++ .../miscutil/core/util/aeonbits/owner/Mutable.java | 118 +++++ .../core/util/aeonbits/owner/Preprocessor.java | 20 + .../util/aeonbits/owner/PreprocessorResolver.java | 47 ++ .../owner/PropertiesInvocationHandler.java | 141 +++++ .../util/aeonbits/owner/PropertiesManager.java | 584 +++++++++++++++++++++ .../core/util/aeonbits/owner/PropertiesMapper.java | 47 ++ .../core/util/aeonbits/owner/Reloadable.java | 70 +++ .../util/aeonbits/owner/SplitAndTrimTokenizer.java | 33 ++ .../core/util/aeonbits/owner/StrSubstitutor.java | 86 +++ .../core/util/aeonbits/owner/Tokenizer.java | 29 + .../util/aeonbits/owner/TokenizerResolver.java | 76 +++ .../miscutil/core/util/aeonbits/owner/Util.java | 266 ++++++++++ .../util/aeonbits/owner/VariablesExpander.java | 41 ++ .../core/util/aeonbits/owner/event/Event.java | 31 ++ .../util/aeonbits/owner/event/ReloadEvent.java | 77 +++ .../util/aeonbits/owner/event/ReloadListener.java | 33 ++ .../owner/event/RollbackBatchException.java | 37 ++ .../aeonbits/owner/event/RollbackException.java | 34 ++ .../owner/event/RollbackOperationException.java | 35 ++ .../event/TransactionalPropertyChangeListener.java | 33 ++ .../owner/event/TransactionalReloadListener.java | 28 + .../owner/event/UnmodifiableProperties.java | 64 +++ .../util/aeonbits/owner/event/package-info.java | 17 + .../core/util/aeonbits/owner/loaders/Loader.java | 50 ++ .../aeonbits/owner/loaders/PropertiesLoader.java | 57 ++ .../util/aeonbits/owner/loaders/XMLLoader.java | 162 ++++++ .../util/aeonbits/owner/loaders/package-info.java | 12 + .../core/util/aeonbits/owner/package-info.java | 51 ++ .../core/util/aeonbits/owner/util/Collections.java | 81 +++ .../core/util/aeonbits/owner/util/Reflection.java | 68 +++ .../util/aeonbits/owner/util/package-info.java | 12 + 45 files changed, 4186 insertions(+) create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/Accessible.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/Config.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/ConfigCache.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/ConfigFactory.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/ConfigURIFactory.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/Converter.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/Converters.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/DefaultFactory.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/Delegate.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/DelegateMethodHandle.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/Factory.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/HotReloadLogic.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/JMXSupport.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/LoadersManager.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/Mutable.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/Preprocessor.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/PreprocessorResolver.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/PropertiesInvocationHandler.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/PropertiesManager.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/PropertiesMapper.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/Reloadable.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/SplitAndTrimTokenizer.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/StrSubstitutor.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/Tokenizer.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/TokenizerResolver.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/Util.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/VariablesExpander.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/event/Event.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/event/ReloadEvent.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/event/ReloadListener.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/event/RollbackBatchException.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/event/RollbackException.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/event/RollbackOperationException.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/event/TransactionalPropertyChangeListener.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/event/TransactionalReloadListener.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/event/UnmodifiableProperties.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/event/package-info.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/loaders/Loader.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/loaders/PropertiesLoader.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/loaders/XMLLoader.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/loaders/package-info.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/package-info.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/util/Collections.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/util/Reflection.java create mode 100644 src/Java/miscutil/core/util/aeonbits/owner/util/package-info.java (limited to 'src/Java/miscutil/core/util') 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; + +/** + *

Allows a Config object to access the contents of the properties, providing utility methods to perform + * consequent operations.

+ *

Example:

+ *
+ *     public interface MyConfig extends Config, Accessible {
+ *         int someProperty();
+ *     }
+ *
+ *     public void doSomething() {
+ *         MyConfig cfg = ConfigFactory.create(MyConfig.class);
+ *         cfg.list(System.out);
+ *     }
+ * 
+ *

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)}.

+ * + * @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}. + *

+ * 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 IOException. + * @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.
+ * This is useful to extract the content of the config object into a {@link java.util.Map}. + *

+ * 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 + * null 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. + * + *

An invocation of this method of the form props.storeToXML(os, + * comment) behaves in exactly the same way as the invocation + * props.storeToXML(os, comment, "UTF-8");. + * + * @param os the output stream on which to emit the XML document. + * @param comment a description of the property list, or null + * if no comment is desired. + * @throws IOException if writing to the specified output stream + * results in an IOException. + * @throws NullPointerException if os is null. + * @throws ClassCastException if this Properties object + * contains any keys or values that are not + * Strings. + * @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. + *

+ * The returned set is not backed by the Properties object. + * Changes to this Properties 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 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. + *

+ * Sub-interfaces may also extend {@link Accessible} to allow some debugging facility, or {@link Reloadable} to allow the + * user to programmatically reload properties. + *

+ * + * @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 + * classpath:path/to/resource.properties, 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 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 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 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). + *

+ * It is possible to specify an interval to indicate how frequently the library shall check the files for + * modifications and perform the reload. + *

+ * Examples: + *
+     *      @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 { ... }
+     * 
+ * + *

+ * 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; + + /** + *

+ * The time unit for the interval. By default it is {@link TimeUnit#SECONDS}. + *

+ *

+ * Date resolution vary from filesystem to filesystem.
+ * For instance, for Ext3, ReiserFS and HSF+ the date resolution is of 1 second.
+ * For FAT32 the date resolution for the last modified time is 2 seconds.
+ * For Ext4 the date resolution is in nanoseconds. + *

+ *

+ * 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. + *

+ * @return the time unit; default is SECONDS. + */ + TimeUnit unit() default SECONDS; + + /** + * The type of HotReload to use. It can be: + * + *

+ * {@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. + *

+ * {@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 Config 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 {@link String} 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: + *

    + *
  • + * You cannot specify {@link TokenizerClass} and {@link Separator} both together on the same method + *
  • + *
  • + * You cannot specify {@link TokenizerClass} and {@link Separator} both together on the same class + *
  • + *
+ * 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 {@link Tokenizer} 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: + *
    + *
  • + * You cannot specify {@link TokenizerClass} and {@link Separator} both together on the same method + *
  • + *
  • + * You cannot specify {@link TokenizerClass} and {@link Separator} both together on the same class + *
  • + *
+ * 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 value(); + } + + /** + * Specifies a {@link Converter} 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 value(); + } + + /** + * Specifies a {@link Preprocessor} 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[] 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 CACHE = new ConcurrentHashMap(); + + /** 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 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 getOrCreate(Class 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 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 getOrCreate(Factory factory, Class 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 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 getOrCreate(Object key, Class 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. + * + * @param factory the factory to use to eventually create the 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 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 getOrCreate(Factory factory, Object key, + Class clazz, Map... imports) { + T existing = get(key); + if (existing != null) return existing; + T created = factory.create(clazz, imports); + T raced = add(key, created); + return raced != null ? raced : created; + } + + /** + * Gets from the cache the {@link Config} instance identified by the given key. + * + * @param key the key object to be used to identify the instance in the cache. + * @param type of the interface. + * @return the {@link Config} object from the cache if exists, or null if it doesn't. + */ + @SuppressWarnings("unchecked") + public static T get(Object key) { + return (T) CACHE.get(key); + } + + /** + * Adds a {@link Config} object into the cache. + * + * @param key the key object to be used to identify the instance in the cache. + * @param instance the instance of the {@link Config} object to be stored into the cache. + * @param type of the interface. + * @return the previous value associated with the specified key, or + * null if there was no mapping for the key. + */ + @SuppressWarnings("unchecked") + public static T add(Object key, T instance) { + return (T) CACHE.putIfAbsent(key, instance); + } + + /** + * Removes all of the cached instances. + * The cache will be empty after this call returns. + */ + public static void clear() { + CACHE.clear(); + } + + /** + * Removes the cached instance for the given key if it is present. + * + *

Returns previous instance associated to the given key in the cache, + * or null if the cache contained no instance for the given key. + * + *

The cache will not contain the instance for the specified key once the + * call returns. + * + * @param type of the interface. + * @param key key whose instance is to be removed from the cache. + * @return the previous instance associated with key, or + * null if there was no instance for key. + */ + @SuppressWarnings("unchecked") + public static T remove(Object key) { + return (T) CACHE.remove(key); + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/ConfigFactory.java b/src/Java/miscutil/core/util/aeonbits/owner/ConfigFactory.java new file mode 100644 index 0000000000..f138c6665b --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/ConfigFactory.java @@ -0,0 +1,136 @@ +/* + * 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.Properties; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; + +import miscutil.core.util.aeonbits.owner.loaders.Loader; +import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; + +/** + * A static factory class to instantiate {@link Config} instances. + *

+ * By default a {@link Config} sub-interface is associated to a property having the same package name and class name as + * the interface itself.

+ *

+ * Method names are mapped to property names contained in the property files.

+ *

+ * This is a singleton static class, to be used as convenience when only a single factory is needed inside an + * application. It exposes the {@link #newInstance()} method to create new instances of {@link Factory} objects. + *

+ * @author Luigi R. Viggiano + */ +public final class ConfigFactory { + + static final Factory INSTANCE = newInstance(); + + /** Don't let anyone instantiate this class */ + private ConfigFactory() {} + + /** + * Returns a new instance of a config Factory object. + * + * @return a new instance of a config Factory object. + */ + public static Factory newInstance() { + ScheduledExecutorService scheduler = newSingleThreadScheduledExecutor(new ThreadFactory() { + public Thread newThread(Runnable r) { + Thread result = new Thread(r); + result.setDaemon(true); + return result; + } + }); + Properties props = new Properties(); + return new DefaultFactory(scheduler, props); + } + + /** + * Creates a {@link Config} instance from the specified interface + * + * @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 type of the interface. + * @return an object implementing the given interface, which maps methods to property values. + */ + public static T create(Class clazz, Map... imports) { + return INSTANCE.create(clazz, imports); + } + + /** + * Set a property in the ConfigFactory. Those properties will be used to expand variables specified in the `@Source` + * annotation, or by the ConfigFactory to configure its own behavior. + * + * @param key the key for the property. + * @param value the value for the property. + * @return the old value. + * @since 1.0.4 + */ + public static String setProperty(String key, String value) { + return INSTANCE.setProperty(key, value); + } + + /** + * Those properties will be used to expand variables specified in the `@Source` annotation, or by the ConfigFactory + * to configure its own behavior. + * + * @return the properties in the ConfigFactory + * @since 1.0.4 + */ + public static Properties getProperties() { + return INSTANCE.getProperties(); + } + + /** + * Those properties will be used to expand variables specified in the `@Source` annotation, or by the ConfigFactory + * to configure its own behavior. + * + * @param properties the properties to set in the config Factory. + * @since 1.0.4 + */ + public static void setProperties(Properties properties) { + INSTANCE.setProperties(properties); + } + + /** + * Returns the value for a given property. + * + * @param key the key for the property + * @return the value for the property, or null if the property is not set. + * @since 1.0.4 + */ + public static String getProperty(String key) { + return INSTANCE.getProperty(key); + } + + /** + * Clears the value for the property having the given key. This means, that the given property is removed. + * + * @param key the key for the property to remove. + * @return the old value for the given key, or null if the property was not set. + * @since 1.0.4 + */ + public static String clearProperty(String key) { + return INSTANCE.clearProperty(key); + } + + /** + * Registers a loader to enables additional file formats. + * + * @param loader the loader to register. + * @throws NullPointerException if specified loader is null. + * @since 1.0.5 + */ + public static void registerLoader(Loader loader) { + INSTANCE.registerLoader(loader); + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/ConfigURIFactory.java b/src/Java/miscutil/core/util/aeonbits/owner/ConfigURIFactory.java new file mode 100644 index 0000000000..40d54b656b --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/ConfigURIFactory.java @@ -0,0 +1,53 @@ +/* + * 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.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +import static miscutil.core.util.aeonbits.owner.Util.fixBackslashesToSlashes; + +/** + * @author Luigi R. Viggiano + */ +class ConfigURIFactory { + + private static final String CLASSPATH_PROTOCOL = "classpath:"; + private final transient ClassLoader classLoader; + private final VariablesExpander expander; + + ConfigURIFactory(ClassLoader classLoader, VariablesExpander expander) { + this.classLoader = classLoader; + this.expander = expander; + } + + URI newURI(String spec) throws URISyntaxException { + String expanded = expand(spec); + String fixed = fixBackslashesToSlashes(expanded); + if (fixed.startsWith(CLASSPATH_PROTOCOL)) { + String path = fixed.substring(CLASSPATH_PROTOCOL.length()); + URL url = classLoader.getResource(path); + if (url == null) + return null; + return url.toURI(); + } else { + return new URI(fixed); + } + } + + private String expand(String path) { + return expander.expand(path); + } + + String toClasspathURLSpec(String name) { + return CLASSPATH_PROTOCOL + name.replace('.', '/'); + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/Converter.java b/src/Java/miscutil/core/util/aeonbits/owner/Converter.java new file mode 100644 index 0000000000..d460d74565 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/Converter.java @@ -0,0 +1,35 @@ +/* + * 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.lang.reflect.Method; + +/** + * Converter interface specifies how to convert an input string coming from a property value to a target object returned + * by the Config method. + * + * @param the type of the class that should be returned from the conversion. + * @author Luigi R. Viggiano + * @since 1.0.4 + */ +public interface Converter { + + /** + * Converts the given input into an Object of type T. + * If the method returns null, null will be returned by the Config object. + * The converter is instantiated for every call, so it shouldn't have any internal state. + * + * @param method the method invoked on the {@link Config} object + * @param input the property value specified as input text to be converted to the T return type + * @return the object of type T converted from the input string. + * @since 1.0.4 + */ + T convert(Method method, String input); + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/Converters.java b/src/Java/miscutil/core/util/aeonbits/owner/Converters.java new file mode 100644 index 0000000000..fcc8723a39 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/Converters.java @@ -0,0 +1,314 @@ +/* + * 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.beans.PropertyEditor; +import java.beans.PropertyEditorManager; +import java.io.File; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import miscutil.core.util.aeonbits.owner.Config.ConverterClass; +import static java.lang.reflect.Modifier.isStatic; +import static miscutil.core.util.aeonbits.owner.Converters.SpecialValue.NULL; +import static miscutil.core.util.aeonbits.owner.Converters.SpecialValue.SKIP; +import static miscutil.core.util.aeonbits.owner.Util.expandUserHome; +import static miscutil.core.util.aeonbits.owner.Util.unreachableButCompilerNeedsThis; +import static miscutil.core.util.aeonbits.owner.Util.unsupported; +import static miscutil.core.util.aeonbits.owner.util.Reflection.isClassAvailable; + +/** + * Converter class from {@link java.lang.String} to property types. + * + * @author Luigi R. Viggiano + */ +enum Converters { + + ARRAY { + @Override + Object tryConvert(Method targetMethod, Class targetType, String text) { + if (!targetType.isArray()) return SKIP; + + Class type = targetType.getComponentType(); + + if (text.trim().isEmpty()) + return Array.newInstance(type, 0); + + Tokenizer tokenizer = TokenizerResolver.resolveTokenizer(targetMethod); + String[] chunks = tokenizer.tokens(text); + + Converters converter = doConvert(targetMethod, type, chunks[0]).getConverter(); + Object result = Array.newInstance(type, chunks.length); + + for (int i = 0; i < chunks.length; i++) { + String chunk = chunks[i]; + Object value = converter.tryConvert(targetMethod, type, chunk); + Array.set(result, i, value); + } + + return result; + } + }, + + COLLECTION { + @Override + Object tryConvert(Method targetMethod, Class targetType, String text) { + if (!Collection.class.isAssignableFrom(targetType)) return SKIP; + + Object[] array = convertToArray(targetMethod, text); + Collection collection = Arrays.asList(array); + Collection result = instantiateCollection(targetType); + result.addAll(collection); + return result; + } + + private Object[] convertToArray(Method targetMethod, String text) { + Class type = getGenericType(targetMethod); + Object stub = Array.newInstance(type, 0); + return (Object[]) ARRAY.tryConvert(targetMethod, stub.getClass(), text); + } + + private Class getGenericType(Method targetMethod) { + if (targetMethod.getGenericReturnType() instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) targetMethod.getGenericReturnType(); + return (Class) parameterizedType.getActualTypeArguments()[0]; + } + // Default generic type for raw collections. + return String.class; + } + + private Collection instantiateCollection(Class targetType) { + if (targetType.isInterface()) + return instantiateCollectionFromInterface(targetType); + return instantiateCollectionFromClass(targetType); + } + + @SuppressWarnings("unchecked") + private Collection instantiateCollectionFromClass(Class targetType) { + try { + return (Collection) targetType.newInstance(); + } catch (Exception e) { + throw unsupported(e, "Cannot instantiate collection of type '%s'", targetType.getCanonicalName()); + } + } + + private Collection instantiateCollectionFromInterface(Class targetType) { + if (List.class.isAssignableFrom(targetType)) + return new ArrayList(); + else if (SortedSet.class.isAssignableFrom(targetType)) + return new TreeSet(); + else if (Set.class.isAssignableFrom(targetType)) + return new LinkedHashSet(); + return new ArrayList(); + } + + }, + + METHOD_WITH_CONVERTER_CLASS_ANNOTATION { + @Override + Object tryConvert(Method targetMethod, Class targetType, String text) { + ConverterClass annotation = targetMethod.getAnnotation(ConverterClass.class); + if (annotation == null) return SKIP; + + Class converterClass = annotation.value(); + Converter converter; + try { + converter = converterClass.newInstance(); + } catch (InstantiationException e) { + throw unsupported(e, "Converter class %s can't be instantiated: %s", + converterClass.getCanonicalName(), e.getMessage()); + } catch (IllegalAccessException e) { + throw unsupported(e, "Converter class %s can't be accessed: %s", + converterClass.getCanonicalName(), e.getMessage()); + } + Object result = converter.convert(targetMethod, text); + if (result == null) return NULL; + return result; + } + }, + + PROPERTY_EDITOR { + + @Override + Object tryConvert(Method targetMethod, Class targetType, String text) { + if (!canUsePropertyEditors()) + return SKIP; + + PropertyEditor editor = PropertyEditorManager.findEditor(targetType); + if (editor == null) return SKIP; + try { + editor.setAsText(text); + return editor.getValue(); + } catch (Exception e) { + throw unsupportedConversion(e, targetType, text); + } + } + + private boolean canUsePropertyEditors() { + return isPropertyEditorAvailable() && !isPropertyEditorDisabled(); + } + + private boolean isPropertyEditorAvailable() { + return isClassAvailable("java.beans.PropertyEditorManager"); + } + + private boolean isPropertyEditorDisabled() { + return Boolean.getBoolean("org.aeonbits.owner.property.editor.disabled"); + } + }, + + /* + * This is needed for cases like when the PropertyEditor classes are not available + */ + PRIMITIVE { + @Override + Object tryConvert(Method targetMethod, Class targetType, String text) { + if (! targetType.isPrimitive()) return SKIP; + if (targetType == Byte.TYPE) return Byte.parseByte(text); + if (targetType == Short.TYPE) return Short.parseShort(text); + if (targetType == Integer.TYPE) return Integer.parseInt(text); + if (targetType == Long.TYPE) return Long.parseLong(text); + if (targetType == Boolean.TYPE) return Boolean.parseBoolean(text); + if (targetType == Float.TYPE) return Float.parseFloat(text); + if (targetType == Double.TYPE) return Double.parseDouble(text); + return SKIP; + } + }, + + FILE { + @Override + Object tryConvert(Method targetMethod, Class targetType, String text) { + if (targetType != File.class) return SKIP; + return new File(expandUserHome(text)); + } + }, + + CLASS { + @Override + Object tryConvert(Method targetMethod, Class targetType, String text) { + if (targetType != Class.class) return SKIP; + try { + return Class.forName(text); + } catch (ClassNotFoundException ex) { + throw unsupported(ex, CANNOT_CONVERT_MESSAGE, text, targetType.getCanonicalName()); + } + } + }, + + CLASS_WITH_STRING_CONSTRUCTOR { + @Override + Object tryConvert(Method targetMethod, Class targetType, String text) { + try { + Constructor constructor = targetType.getConstructor(String.class); + return constructor.newInstance(text); + } catch (Exception e) { + return SKIP; + } + } + }, + + CLASS_WITH_VALUE_OF_METHOD { + @Override + Object tryConvert(Method targetMethod, Class targetType, String text) { + try { + Method method = targetType.getMethod("valueOf", String.class); + if (isStatic(method.getModifiers())) + return method.invoke(null, text); + return SKIP; + } catch (Exception e) { + return SKIP; + } + } + }, + + CLASS_WITH_OBJECT_CONSTRUCTOR { + @Override + Object tryConvert(Method targetMethod, Class targetType, String text) { + try { + Constructor constructor = targetType.getConstructor(Object.class); + return constructor.newInstance(text); + } catch (Exception e) { + return SKIP; + } + } + }, + + UNSUPPORTED { + @Override + Object tryConvert(Method targetMethod, Class targetType, String text) { + throw unsupportedConversion(targetType, text); + } + }; + + abstract Object tryConvert(Method targetMethod, Class targetType, String text); + + static Object convert(Method targetMethod, Class targetType, String text) { + return doConvert(targetMethod, targetType, text).getConvertedValue(); + } + + private static ConversionResult doConvert(Method targetMethod, Class targetType, String text) { + for (Converters converter : values()) { + Object convertedValue = converter.tryConvert(targetMethod, targetType, text); + if (convertedValue != SKIP) + return new ConversionResult(converter, convertedValue); + } + return unreachableButCompilerNeedsThis(); + } + + private static UnsupportedOperationException unsupportedConversion(Exception cause, Class targetType, String text) { + return unsupported(cause, CANNOT_CONVERT_MESSAGE, text, targetType.getCanonicalName()); + } + + private static UnsupportedOperationException unsupportedConversion(Class targetType, String text) { + return unsupported(CANNOT_CONVERT_MESSAGE, text, targetType.getCanonicalName()); + } + + private static class ConversionResult { + private final Converters converter; + private final Object convertedValue; + + public ConversionResult(Converters converter, Object convertedValue) { + this.converter = converter; + this.convertedValue = convertedValue; + } + + public Converters getConverter() { + return converter; + } + + public Object getConvertedValue() { + return convertedValue; + } + } + + enum SpecialValue { + /** + * The NULL object: when tryConvert returns this object, the conversion result is null. + */ + NULL, + + /** + * The SKIP object: when tryConvert returns this object the conversion is skipped in favour of the next one. + */ + SKIP + } + + static final String CANNOT_CONVERT_MESSAGE = "Cannot convert '%s' to %s"; + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/DefaultFactory.java b/src/Java/miscutil/core/util/aeonbits/owner/DefaultFactory.java new file mode 100644 index 0000000000..694a62f022 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/DefaultFactory.java @@ -0,0 +1,103 @@ +/* + * 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 javax.management.DynamicMBean; + +import miscutil.core.util.aeonbits.owner.loaders.Loader; + +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ScheduledExecutorService; + +import static java.lang.reflect.Proxy.newProxyInstance; +import static miscutil.core.util.aeonbits.owner.util.Reflection.isClassAvailable; + +/** + * Default implementation for {@link Factory}. + * + * @author Luigi R. Viggiano + */ +class DefaultFactory implements Factory { + + private static final boolean isJMXAvailable = isClassAvailable("javax.management.DynamicMBean"); + private final ScheduledExecutorService scheduler; + private Properties props; + final LoadersManager loadersManager; + + DefaultFactory(ScheduledExecutorService scheduler, Properties props) { + this.scheduler = scheduler; + this.props = props; + this.loadersManager = new LoadersManager(); + } + + @SuppressWarnings("unchecked") + public T create(Class clazz, Map... imports) { + Class[] interfaces = interfaces(clazz); + VariablesExpander expander = new VariablesExpander(props); + PropertiesManager manager = new PropertiesManager(clazz, new Properties(), scheduler, expander, loadersManager, + imports); + Object jmxSupport = getJMXSupport(clazz, manager); + PropertiesInvocationHandler handler = new PropertiesInvocationHandler(manager, jmxSupport); + T proxy = (T) newProxyInstance(clazz.getClassLoader(), interfaces, handler); + handler.setProxy(proxy); + return proxy; + } + + public String setProperty(String key, String value) { + checkKey(key); + return (String) props.setProperty(key, value); + } + + private void checkKey(String key) { + if (key == null) + throw new IllegalArgumentException("key can't be null"); + if (key.isEmpty()) + throw new IllegalArgumentException("key can't be empty"); + } + + public Properties getProperties() { + return props; + } + + public void setProperties(Properties properties) { + if (properties == null) + props = new Properties(); + else + props = properties; + } + + public void registerLoader(Loader loader) { + loadersManager.registerLoader(loader); + } + + public String getProperty(String key) { + checkKey(key); + return props.getProperty(key); + } + + public String clearProperty(String key) { + checkKey(key); + return (String) props.remove(key); + } + + private Object getJMXSupport(Class clazz, PropertiesManager manager) { + if (isJMXAvailable) + return new JMXSupport(clazz, manager); + return null; + } + + private Class[] interfaces(Class clazz) { + if (isJMXAvailable) + return new Class[]{clazz, DynamicMBean.class}; + else + return new Class[]{clazz}; + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/Delegate.java b/src/Java/miscutil/core/util/aeonbits/owner/Delegate.java new file mode 100644 index 0000000000..6b5e378769 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/Delegate.java @@ -0,0 +1,20 @@ +/* + * 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.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention(RUNTIME) +@Target(METHOD) +@interface Delegate { +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/DelegateMethodHandle.java b/src/Java/miscutil/core/util/aeonbits/owner/DelegateMethodHandle.java new file mode 100644 index 0000000000..9c22958e8a --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/DelegateMethodHandle.java @@ -0,0 +1,41 @@ +/* + * 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.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; + +/** + * @author Luigi R. Viggiano + */ +class DelegateMethodHandle { + private final Object target; + private final Method method; + + public DelegateMethodHandle(Object target, Method method) { + this.target = target; + this.method = method; + } + + public Object invoke(Object[] args) throws Throwable { + try { + return method.invoke(target, args); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } + + public boolean matches(Method invokedMethod) { + return invokedMethod.getName().equals(method.getName()) + && invokedMethod.getReturnType().equals(method.getReturnType()) + && Arrays.equals(invokedMethod.getParameterTypes(), method.getParameterTypes()); + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/Factory.java b/src/Java/miscutil/core/util/aeonbits/owner/Factory.java new file mode 100644 index 0000000000..06a3e5d85a --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/Factory.java @@ -0,0 +1,91 @@ +/* + * 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.Properties; + +import miscutil.core.util.aeonbits.owner.loaders.Loader; + +/** + * Interface for factory implementation used to instantiate {@link Config} instances. + * + * @author Luigi R. Viggiano + * @since 1.0.5 + */ +public interface Factory { + + /** + * Creates a {@link Config} instance from the specified interface + * + * @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 type of the interface. + * @return an object implementing the given interface, which maps methods to property values. + * @since 1.0.5 + */ + T create(Class clazz, Map... imports); + + /** + * Returns the value for a given property. + * + * @param key the key for the property + * @return the value for the property, or null if the property is not set. + * @since 1.0.5 + */ + String getProperty(String key); + + /** + * Set a property in the ConfigFactory. Those properties will be used to expand variables specified in the `@Source` + * annotation, or by the ConfigFactory to configure its own behavior. + * + * @param key the key for the property. + * @param value the value for the property. + * @return the old value. + * @since 1.0.5 + */ + String setProperty(String key, String value); + + /** + * Clears the value for the property having the given key. This means, that the given property is removed. + * + * @param key the key for the property to remove. + * @return the old value for the given key, or null if the property was not set. + * @since 1.0.5 + */ + String clearProperty(String key); + + /** + * Those properties will be used to expand variables specified in the `@Source` annotation, or by the ConfigFactory + * to configure its own behavior. + * + * @return the properties in the ConfigFactory + * @since 1.0.5 + */ + Properties getProperties(); + + /** + * Those properties will be used to expand variables specified in the `@Source` annotation, or by the ConfigFactory + * to configure its own behavior. + * + * @param properties the properties to set in the config Factory. + * @since 1.0.5 + */ + void setProperties(Properties properties); + + /** + * Registers a loader to enables additional file formats. + * + * @param loader the loader to register. + * @throws NullPointerException if specified loader is null. + * @since 1.0.5 + */ + void registerLoader(Loader loader); + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/HotReloadLogic.java b/src/Java/miscutil/core/util/aeonbits/owner/HotReloadLogic.java new file mode 100644 index 0000000000..49870b17c6 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/HotReloadLogic.java @@ -0,0 +1,103 @@ +/* + * 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.File; +import java.io.Serializable; +import java.net.URI; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import miscutil.core.util.aeonbits.owner.Config.HotReload; +import miscutil.core.util.aeonbits.owner.Config.HotReloadType; +import static miscutil.core.util.aeonbits.owner.Config.HotReloadType.ASYNC; +import static miscutil.core.util.aeonbits.owner.Config.HotReloadType.SYNC; +import static miscutil.core.util.aeonbits.owner.Util.fileFromURI; +import static miscutil.core.util.aeonbits.owner.Util.now; + +/** + * @author Luigi R. Viggiano + */ +class HotReloadLogic implements Serializable { + + private final PropertiesManager manager; + private final long interval; + private final HotReloadType type; + private volatile long lastCheckTime = now(); + private final List watchableFiles = new ArrayList(); + + private static class WatchableFile implements Serializable { + private final Fi