diff options
author | Draknyte1 <Draknyte1@hotmail.com> | 2016-07-25 17:55:49 +1000 |
---|---|---|
committer | Draknyte1 <Draknyte1@hotmail.com> | 2016-07-25 17:55:49 +1000 |
commit | f7246428becd594d59e459bc4e08a601410d8210 (patch) | |
tree | 05160f1375829d6ecc2c8679b816a444eb4ff68c /src | |
parent | 15c7bda5eaa25b451b44b7cb8cf2e0f602ad3e6d (diff) | |
download | GT5-Unofficial-f7246428becd594d59e459bc4e08a601410d8210.tar.gz GT5-Unofficial-f7246428becd594d59e459bc4e08a601410d8210.tar.bz2 GT5-Unofficial-f7246428becd594d59e459bc4e08a601410d8210.zip |
+ Added A Config File and handler for it.
% Trying a new recipe handler for the Industrial Centrifuge.
Diffstat (limited to 'src')
51 files changed, 4387 insertions, 137 deletions
diff --git a/src/Java/miscutil/core/lib/CORE.java b/src/Java/miscutil/core/lib/CORE.java index 19ace0bf47..270ebee91c 100644 --- a/src/Java/miscutil/core/lib/CORE.java +++ b/src/Java/miscutil/core/lib/CORE.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; import miscutil.core.creative.AddToCreativeTab; +import miscutil.core.util.aeonbits.owner.ConfigFactory; import miscutil.core.xmod.gregtech.api.enums.GregtechOrePrefixes.GT_Materials; import miscutil.core.xmod.gregtech.api.interfaces.internal.IGregtech_RecipeAdder; import miscutil.core.xmod.gregtech.common.Meta_GT_Proxy; @@ -31,6 +32,7 @@ public class CORE { public static IIconRegister GT_BlockIcons, GT_ItemIcons; public static List<Runnable> GT_BlockIconload = new ArrayList<Runnable>(); public static final Class<AddToCreativeTab> TAB = AddToCreativeTab.class; + public static ConfigHandler cfg = ConfigFactory.create(ConfigHandler.class); /** * A List containing all the Materials, which are somehow in use by GT and therefor receive a specific Set of Items. */ diff --git a/src/Java/miscutil/core/lib/ConfigHandler.java b/src/Java/miscutil/core/lib/ConfigHandler.java new file mode 100644 index 0000000000..d8ec0d6ff9 --- /dev/null +++ b/src/Java/miscutil/core/lib/ConfigHandler.java @@ -0,0 +1,14 @@ +package miscutil.core.lib; + +import miscutil.core.util.aeonbits.owner.Config; + +public interface ConfigHandler extends Config{ + + @DefaultValue("false") + boolean debugMode(); + + @DefaultValue("true") + boolean disableEnderIOIntegration(); + + +} diff --git a/src/Java/miscutil/core/lib/LoadedMods.java b/src/Java/miscutil/core/lib/LoadedMods.java index 9ea04cfa1a..2412482f6b 100644 --- a/src/Java/miscutil/core/lib/LoadedMods.java +++ b/src/Java/miscutil/core/lib/LoadedMods.java @@ -53,7 +53,7 @@ public class LoadedMods { totalMods++; } - if (Loader.isModLoaded("EnderIO") == true){ + if (Loader.isModLoaded("EnderIO") == true && !CORE.cfg.disableEnderIOIntegration()){ EnderIO = true; Utils.LOG_INFO("Components enabled for: EnderIO"); totalMods++; diff --git a/src/Java/miscutil/core/recipe/Gregtech_Recipe_Adder.java b/src/Java/miscutil/core/recipe/Gregtech_Recipe_Adder.java new file mode 100644 index 0000000000..631d4d4bea --- /dev/null +++ b/src/Java/miscutil/core/recipe/Gregtech_Recipe_Adder.java @@ -0,0 +1,98 @@ +package miscutil.core.recipe; + +import gregtech.api.enums.GT_Values; +import gregtech.api.util.GT_ModHandler; +import miscutil.core.util.Utils; +import miscutil.core.util.item.UtilsItems; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; + +public class Gregtech_Recipe_Adder { + + private static int euT; + private static int ticks; + private static ItemStack inputStack1; + private static ItemStack inputStack2; + private static ItemStack outputStack1; + private static ItemStack outputStack2; + + public static void addRecipe( + Item maceratorInput, int maceratorInputAmount1, + Item maceratorOutput, int maceratorOutputAmount1, + Item compressorInput, int compressorInputAmount1, + Item compressorOutput, int compressorOutputAmount1, + Item blastFurnaceInput, int blastFurnaceInputAmount1, + Item blastFurnaceOutput, int blastFurnaceOutputAmount1, + Item blastFurnaceInput2, int blastFurnaceInputAmount2, + Item blastFurnaceOutput2, int blastFurnaceOutputAmount2, + Item smeltingInput, int smeltingInputAmount1, + Item smeltingOutput, int smeltingOutputAmount1, + + int euPerTick, int timeInTicks, + boolean addMaceratorRecipe, boolean addCompressorRecipe, boolean addBlastFurnaceRecipe, int blastFurnaceTemp, boolean addSmeltingRecipe, boolean addMixerRecipe){ + euT = euPerTick; + ticks = timeInTicks; + + resetVars(); + if (addMaceratorRecipe){ + inputStack1 = UtilsItems.getSimpleStack(maceratorInput, maceratorInputAmount1); + outputStack1 = UtilsItems.getSimpleStack(maceratorOutput, maceratorOutputAmount1); + addMaceratorRecipe(inputStack1, outputStack1); + } + resetVars(); + if (addCompressorRecipe){ + inputStack1 = UtilsItems.getSimpleStack(compressorInput, compressorInputAmount1); + outputStack1 = UtilsItems.getSimpleStack(compressorOutput, compressorOutputAmount1); + addCompressorRecipe(inputStack1, outputStack1); + } + resetVars(); + if (addBlastFurnaceRecipe){ + inputStack1 = UtilsItems.getSimpleStack(blastFurnaceInput, blastFurnaceInputAmount1); + inputStack2 = UtilsItems.getSimpleStack(blastFurnaceInput2, blastFurnaceInputAmount2); + outputStack1 = UtilsItems.getSimpleStack(blastFurnaceOutput, blastFurnaceOutputAmount1); + outputStack2 = UtilsItems.getSimpleStack(blastFurnaceOutput2, blastFurnaceOutputAmount2); + addBlastFurnaceRecipe(inputStack1, inputStack2, outputStack1, outputStack2, blastFurnaceTemp); + } + resetVars(); + if (addSmeltingRecipe){ + inputStack1 = UtilsItems.getSimpleStack(smeltingInput, smeltingInputAmount1); + outputStack1 = UtilsItems.getSimpleStack(smeltingOutput, smeltingOutputAmount1); + addSmeltingRecipe(inputStack1, outputStack1); + } + resetVars(); + + } + + private static void resetVars(){ + inputStack1 = null; + inputStack2 = null; + outputStack1 = null; + outputStack2 = null; + } + + private static void addMaceratorRecipe(ItemStack input1, ItemStack output1){ + GT_ModHandler.addPulverisationRecipe(input1, output1); + } + + private static void addCompressorRecipe(ItemStack input1, ItemStack output1){ + GT_ModHandler.addCompressionRecipe(input1, output1); + } + + private static void addBlastFurnaceRecipe(ItemStack input1, ItemStack input2, ItemStack output1, ItemStack output2, int tempRequired){ + Utils.LOG_INFO("Registering Blast Furnace Recipes."); + GT_Values.RA.addBlastRecipe( + input1, + input2, + GT_Values.NF, GT_Values.NF, + output1, + output2, + ticks, + euT, + tempRequired); + } + + private static void addSmeltingRecipe(ItemStack input1, ItemStack output1){ + GT_ModHandler.addSmeltingRecipe(input1, output1); + } + +} diff --git a/src/Java/miscutil/core/recipe/RECIPES_GREGTECH.java b/src/Java/miscutil/core/recipe/RECIPES_GREGTECH.java index 4b5c2613b2..89e6f9a86b 100644 --- a/src/Java/miscutil/core/recipe/RECIPES_GREGTECH.java +++ b/src/Java/miscutil/core/recipe/RECIPES_GREGTECH.java @@ -29,6 +29,7 @@ public class RECIPES_GREGTECH { mixerRecipes(); extractorRecipes(); addFuels(); + blastFurnaceRecipes(); } private static void cokeOvenRecipes(){ @@ -118,17 +119,12 @@ public class RECIPES_GREGTECH { private static void distilleryRecipes(){ Utils.LOG_INFO("Registering Distillery/Distillation Tower Recipes."); - //Distillery GT_Values.RA.addDistilleryRecipe(ItemList.Circuit_Integrated.getWithDamage(0L, 4L, new Object[0]), FluidUtils.getFluidStack("air", 20000), FluidUtils.getFluidStack("helium", 1), 400, 30, false); GT_Values.RA.addDistillationTowerRecipe(FluidUtils.getFluidStack("air", 20000), FluidUtils.getFluidStackArray("helium", 1), null, 160, 60); } private static void addFuels(){ Utils.LOG_INFO("Registering New Fuels."); - //CORE.RA.addFuel(GT_ModHandler.getModItem("Minecraft", "bucket_lava", 1L, 1), null, 2800, 1); - //CORE.RA.addFuel(GT_ModHandler.getModItem("EnderIO", "bucketRocket_fuel", 1L, 1), null, 2800, 0); - //CORE.RA.addFuel(GT_ModHandler.getModItem("EnderIO", "bucketHootch", 1L, 1), null, 2800, 0); - //CORE.RA.addFuel(GT_ModHandler.getModItem("EnderIO", "bucketFire_water", 1L, 1), null, 2800, 0); GT_Values.RA.addFuel(UtilsItems.simpleMetaStack("EnderIO:bucketFire_water", 0, 1), null, 120, 0); GT_Values.RA.addFuel(UtilsItems.simpleMetaStack("EnderIO:bucketRocket_fuel", 0, 1), null, 112, 0); GT_Values.RA.addFuel(UtilsItems.simpleMetaStack("EnderIO:bucketHootch", 0, 1), null, 36, 0); @@ -143,6 +139,7 @@ public class RECIPES_GREGTECH { } private static void extractorRecipes(){ + Utils.LOG_INFO("Registering Extractor Recipes."); GT_ModHandler.addExtractionRecipe(GregtechItemList.Battery_RE_EV_Sodium.get(1L, new Object[0]), ItemList.Battery_Hull_HV.get(4L, new Object[0])); GT_ModHandler.addExtractionRecipe(GregtechItemList.Battery_RE_EV_Cadmium.get(1L, new Object[0]), ItemList.Battery_Hull_HV.get(4L, new Object[0])); GT_ModHandler.addExtractionRecipe(GregtechItemList.Battery_RE_EV_Lithium.get(1L, new Object[0]), ItemList.Battery_Hull_HV.get(4L, new Object[0])); @@ -153,20 +150,37 @@ public class RECIPES_GREGTECH { } private static void blastFurnaceRecipes(){ + Utils.LOG_INFO("Registering Blast Furnace Recipes."); + GT_Values.RA.addBlastRecipe( - UtilsItems.getItemStackOfAmountFromOreDict("ingotTitanium", 1), - UtilsItems.getItemStackOfAmountFromOreDict("ingotUranium", 8), + UtilsItems.simpleMetaStack("gregtech:gt.metaitem.01", 11028, 1), + UtilsItems.simpleMetaStack("gregtech:gt.metaitem.01", 11098, 1), GT_Values.NF, GT_Values.NF, - GT_OreDictUnificator.get(OrePrefixes.ingotHot, GT_Materials.Staballoy, 1L), + UtilsItems.getSimpleStack(ModItems.itemIngotStaballoy, 1), GT_OreDictUnificator.get(OrePrefixes.dustSmall, Materials.Titanium, 1L), (int) Math.max(GT_Materials.Staballoy.getMass() / 80L, 1L) * GT_Materials.Staballoy.mBlastFurnaceTemp, 1000, GT_Materials.Staballoy.mBlastFurnaceTemp); - GT_Values.RA.addBlastRecipe( - UtilsItems.getItemStackOfAmountFromOreDict("dustStaballoy", 1), + UtilsItems.getSimpleStack(ModItems.itemDustStaballoy, 1), + null, + GT_Values.NF, GT_Values.NF, + UtilsItems.getSimpleStack(ModItems.itemIngotStaballoy, 1), + GT_OreDictUnificator.get(OrePrefixes.dustSmall, Materials.Titanium, 1L), + (int) Math.max(GT_Materials.Staballoy.getMass() / 80L, 1L) * GT_Materials.Staballoy.mBlastFurnaceTemp, + 2000, GT_Materials.Staballoy.mBlastFurnaceTemp); + GT_Values.RA.addBlastRecipe( + UtilsItems.getSimpleStack(ModItems.itemDustSmallStaballoy, 4), + null, + GT_Values.NF, GT_Values.NF, + UtilsItems.getSimpleStack(ModItems.itemIngotStaballoy, 1), + GT_OreDictUnificator.get(OrePrefixes.dustSmall, Materials.Titanium, 1L), + (int) Math.max(GT_Materials.Staballoy.getMass() / 80L, 1L) * GT_Materials.Staballoy.mBlastFurnaceTemp, + 2000, GT_Materials.Staballoy.mBlastFurnaceTemp); + GT_Values.RA.addBlastRecipe( + UtilsItems.getSimpleStack(ModItems.itemDustTinyStaballoy, 9), null, GT_Values.NF, GT_Values.NF, - GT_OreDictUnificator.get(OrePrefixes.ingotHot, GT_Materials.Staballoy, 1L), + UtilsItems.getSimpleStack(ModItems.itemIngotStaballoy, 1), GT_OreDictUnificator.get(OrePrefixes.dustSmall, Materials.Titanium, 1L), (int) Math.max(GT_Materials.Staballoy.getMass() / 80L, 1L) * GT_Materials.Staballoy.mBlastFurnaceTemp, 2000, GT_Materials.Staballoy.mBlastFurnaceTemp); 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. + * + * @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 <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, Object key, + Class<? extends T> 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 <T> type of the interface. + * @return the {@link Config} object from the cache if exists, or <tt>null</tt> if it doesn't. + */ + @SuppressWarnings("unchecked") + public static <T extends Config> 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 <T> type of the interface. + * @return the previous value associated with the specified key, or + * <tt>null</tt> if there was no mapping for the key. + */ + @SuppressWarnings("unchecked") + public static <T extends Config> 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. + * + * <p>Returns previous instance associated to the given key in the cache, + * or <tt>null</tt> if the cache contained no instance for the given key. + * + * <p>The cache will not contain the instance for the specified key once the + * call returns. + * + * @param <T> type of the interface. + * @param key key whose instance is to be removed from the cache. + * @return the previous instance associated with <tt>key</tt>, or + * <tt>null</tt> if there was no instance for <tt>key</tt>. + */ + @SuppressWarnings("unchecked") + public static <T extends Config> 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. + * <p> + * By default a {@link Config} sub-interface is associated to a property having the same package name and class name as + * the interface itself.</p> + * <p> + * Method names are mapped to property names contained in the property files.</p> + * <p> + * 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. + * </p> + * @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 <T> type of the interface. + * @return an object implementing the given interface, which maps methods to property values. + */ + public static <T extends Config> T create(Class<? extends T> 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 <tt>null</tt> 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 <tt>null</tt> 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 <tt>null</tt>. + * @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 <T> the type of the class that should be returned from the conversion. + * @author Luigi R. Viggiano + * @since 1.0.4 + */ +public interface Converter<T> { + + /** + * 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 <tt>{@link Config} object</tt> + * @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<Object> collection = Arrays.asList(array); + Collection<Object> 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 <T> Collection<T> instantiateCollection(Class<? extends T> targetType) { + if (targetType.isInterface()) + return instantiateCollectionFromInterface(targetType); + return instantiateCollectionFromClass(targetType); + } + + @SuppressWarnings("unchecked") + private <T> Collection<T> instantiateCollectionFromClass(Class<? extends T> targetType) { + try { + return (Collection<T>) targetType.newInstance(); + } catch (Exception e) { + throw unsupported(e, "Cannot instantiate collection of type '%s'", targetType.getCanonicalName()); + } + } + + private <T> Collection<T> instantiateCollectionFromInterface(Class<? extends T> targetType) { + if (List.class.isAssignableFrom(targetType)) + return new ArrayList<T>(); + else if (SortedSet.class.isAssignableFrom(targetType)) + return new TreeSet<T>(); + else if (Set.class.isAssignableFrom(targetType)) + return new LinkedHashSet<T>(); + return new ArrayList<T>(); + } + + }, + + 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<? extends Converter> 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 extends Config> T create(Class<? extends T> 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 <T extends Config> Class<?>[] interfaces(Class<? extends T> 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 <T> type of the interface. + * @return an object implementing the given interface, which maps methods to property values. + * @since 1.0.5 + */ + <T extends Config> T create(Class<? extends T> clazz, Map<?, ?>... imports); + + /** + * Returns the value for a given property. + * + * @param key the key for the property + * @return the value for the property, or <tt>null</tt> 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 <tt>null</tt> 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 <tt>null</tt>. + * @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<WatchableFile> watchableFiles = new ArrayList<WatchableFile>(); + + private static class WatchableFile implements Serializable { + private final File file; + private long lastModifiedTime; + + WatchableFile(File file) { + this.file = file; + this.lastModifiedTime = file.lastModified(); + } + + public boolean isChanged() { + long lastModifiedTimeNow = file.lastModified(); + boolean changed = lastModifiedTime != lastModifiedTimeNow; + if (changed) + lastModifiedTime = lastModifiedTimeNow; + return changed; + } + } + + public HotReloadLogic(HotReload hotReload, List<URI> uris, PropertiesManager manager) { + this.manager = manager; + type = hotReload.type(); + interval = hotReload.unit().toMillis(hotReload.value()); + setupWatchableResources(uris); + } + + private void setupWatchableResources(List<URI> uris) { + Set<File> files = new LinkedHashSet<File>(); + for (URI uri : uris) { + File file = fileFromURI(uri); + if (file != null) + files.add(file); + } + for (File file : files) + watchableFiles.add(new WatchableFile(file)); + } + + synchronized void checkAndReload() { + if (needsReload()) + manager.reload(); + } + + private boolean needsReload() { + if (manager.isLoading()) return false; + + long now = now(); + if (now < lastCheckTime + interval) + return false; + + try { + for (WatchableFile resource : watchableFiles) + if (resource.isChanged()) + return true; + return false; + } finally { + lastCheckTime = now; + } + } + + boolean isAsync() { + return type == ASYNC; + } + + boolean isSync() { + return type == SYNC; + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/JMXSupport.java b/src/Java/miscutil/core/util/aeonbits/owner/JMXSupport.java new file mode 100644 index 0000000000..1e3a5ccbe8 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/JMXSupport.java @@ -0,0 +1,109 @@ +/* + * 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.Attribute; +import javax.management.AttributeList; +import javax.management.AttributeNotFoundException; +import javax.management.InvalidAttributeValueException; +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanException; +import javax.management.MBeanInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanParameterInfo; +import javax.management.ReflectionException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * @author Robin Meißner + * @author Luigi R. Viggiano + */ +class JMXSupport implements Serializable { + + private final Class<?> clazz; + private final PropertiesManager manager; + + public JMXSupport(Class<?> clazz, PropertiesManager manager) { + this.clazz = clazz; + this.manager = manager; + } + + @Delegate + public Object getAttribute(String attribute) + throws AttributeNotFoundException, MBeanException, + ReflectionException { + return manager.getProperty(attribute); + } + + @Delegate + public void setAttribute(Attribute attribute) + throws AttributeNotFoundException, InvalidAttributeValueException, + MBeanException, ReflectionException { + manager.setProperty(attribute.getName(), (String) attribute.getValue()); + } + + @Delegate + public AttributeList getAttributes(String[] attributes) { + List<Attribute> attrList = new LinkedList<Attribute>(); + for (String propertyName : attributes) + attrList.add(new Attribute(propertyName, manager.getProperty(propertyName))); + return new AttributeList(attrList); + } + + @Delegate + public AttributeList setAttributes(AttributeList attributes) { + for (Attribute attr : attributes.asList()) + manager.setProperty(attr.getName(), (String) attr.getValue()); + return attributes; + } + + @Delegate + public Object invoke(String actionName, Object[] params, String[] signature) + throws MBeanException, ReflectionException { + if (actionName.equals("getProperty") && params != null && params.length == 1) { + return manager.getProperty((String) params[0]); + } else if (actionName.equals("setProperty") && params != null && params.length == 2) { + manager.setProperty((String) params[0], (String) params[1]); + return null; + } else if (actionName.equals("reload") && (params == null || params.length == 0)) { + manager.reload(); + return null; + } + throw new ReflectionException(new NoSuchMethodException(actionName)); + } + + @Delegate + public MBeanInfo getMBeanInfo() { + List<MBeanAttributeInfo> attributesInfo = new ArrayList<MBeanAttributeInfo>(); + Set<String> propertyNames = manager.propertyNames(); + for (String name : propertyNames) + attributesInfo.add(new MBeanAttributeInfo(name, "java.lang.String", name, true, true, false)); + + MBeanAttributeInfo[] attributes = attributesInfo.toArray(new MBeanAttributeInfo[propertyNames.size()]); + + MBeanParameterInfo key = new MBeanParameterInfo("key", "java.lang.String", "Key of the property"); + MBeanParameterInfo value = new MBeanParameterInfo("value", "java.lang.String", "Value of the property"); + + MBeanOperationInfo[] operations = new MBeanOperationInfo[] { + new MBeanOperationInfo("getProperty", "Gets value for a property", + new MBeanParameterInfo[] { key }, "java.lang.String", MBeanOperationInfo.INFO), + new MBeanOperationInfo("setProperty", "Sets the value for a property", + new MBeanParameterInfo[] { key, value }, "void", MBeanOperationInfo.ACTION), + new MBeanOperationInfo("reload", "Reload properties", null, "void", MBeanOperationInfo.ACTION) + }; + + return new MBeanInfo(clazz.getName(), clazz.getSimpleName() + " OWNER MBean", + attributes, null, operations, null); + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/LoadersManager.java b/src/Java/miscutil/core/util/aeonbits/owner/LoadersManager.java new file mode 100644 index 0000000000..d3f05d0c45 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/LoadersManager.java @@ -0,0 +1,95 @@ +/* + * 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.net.URI; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import miscutil.core.util.aeonbits.owner.loaders.Loader; +import miscutil.core.util.aeonbits.owner.loaders.PropertiesLoader; +import miscutil.core.util.aeonbits.owner.loaders.XMLLoader; +import static miscutil.core.util.aeonbits.owner.Util.unsupported; + + +/** + * This class is responsible of locating an appropriate Loader for a given URL (based the extension in the resource + * name)and load the properties from it. + * + * @author Luigi R. Viggiano + * @since 1.0.5 + */ +class LoadersManager implements Serializable { + + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private final List<Loader> loaders = new LinkedList<Loader>(); + + LoadersManager() { + registerLoader(new PropertiesLoader()); + registerLoader(new XMLLoader()); + } + + void load(Properties result, URI uri) throws IOException { + Loader loader = findLoader(uri); + loader.load(result, uri); + } + + Loader findLoader(URI uri) { + lock.readLock().lock(); + try { + for (Loader loader : loaders) + if (loader.accept(uri)) + return loader; + throw unsupported("Can't resolve a Loader for the URL %s.", uri.toString()); + } finally { + lock.readLock().unlock(); + } + } + + final void registerLoader(Loader loader) { + if (loader == null) + throw new IllegalArgumentException("loader can't be null"); + lock.writeLock().lock(); + try { + loaders.add(0, loader); + } finally { + lock.writeLock().unlock(); + } + } + + void clear() { + lock.writeLock().lock(); + try{ + loaders.clear(); + } finally { + lock.writeLock().unlock(); + } + } + + String[] defaultSpecs(String prefix) { + lock.readLock().lock(); + try { + List<String> defaultSpecs = new ArrayList<String>(loaders.size()); + for (Loader loader : loaders) { + String spec = loader.defaultSpecFor(prefix); + if (spec != null) + defaultSpecs.add(spec); + } + return defaultSpecs.toArray(new String[0]); + } finally { + lock.readLock().unlock(); + } + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/Mutable.java b/src/Java/miscutil/core/util/aeonbits/owner/Mutable.java new file mode 100644 index 0000000000..72a9c582e3 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/Mutable.java @@ -0,0 +1,118 @@ +/* + * 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.PropertyChangeListener; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + +/** + * <p>Allows a <tt>Config</tt> object to change its property values at runtime.</p> + * <p>Example:</p> + * <pre> + * public interface MyConfig extends Config, Mutable { + * @DefaultValue("18") + * int minAge(); + * } + * + * public void example() { + * MyConfig cfg = ConfigFactory.create(MyConfig.class); + * int before = cfg.minAge(); // before = 18 + * int old = cfg.setProperty("minAge", "21"); // old = 18 + * int after = cfg.minAge(); // after = 21 + * int old2 = cfg.removeProperty("minAge"); // old2 = 21 + * int end = cfg.minAge(); // end = null + * } + * </pre> + * + * @author Luigi R. Viggiano + * @since 1.0.4 + */ +public interface Mutable extends Config { + + /** + * <p>Sets a given property to the specified value.</p> + * <p>Differently than {@link + * java.util.Properties#setProperty(String, String)}, if <tt>key</tt> is set to <tt>null</tt> then this call is + * equivalent to {@link #removeProperty(String)}.</p> + * + * @param key the key to be placed into the property list. + * @param value the value corresponding to <tt>key</tt>, or <tt>null</tt> if the property must be removed. + * @return the previous value of the specified key, or <code>null</code> if it did not have one. + * @since 1.0.4 + */ + String setProperty(String key, String value); + + /** + * Removes a given property. + * + * @param key the key of the property to remove. + * @return the previous value of the specified key, or <code>null</code> if it did not have one. + * @see java.util.Hashtable#remove(Object) + * @since 1.0.4 + */ + String removeProperty(String key); + + /** + * Clears all properties. + * + * @since 1.0.4 + */ + void clear(); + + /** + * Reads a property list (key and element pairs) from the input byte stream. + * + * @param inStream the input stream. + * @throws java.io.IOException if an error occurred when reading from the input stream. + * @throws IllegalArgumentException if the input stream contains a malformed Unicode escape sequence. + * @see java.util.Properties#load(java.io.InputStream) + * @since 1.0.4 + */ + void load(InputStream inStream) throws IOException; + + /** + * Reads a property list (key and element pairs) from the input character stream in a simple line-oriented format. + * + * @param reader the input character stream. + * @throws IOException if an error occurred when reading from the input stream. + * @throws IllegalArgumentException if a malformed Unicode escape appears in the input. + * @see java.util.Properties#load(java.io.Reader) + * @since 1.0.4 + */ + void load(Reader reader) throws IOException; + + /** + * Adds a {@link PropertyChangeListener} to the Mutable interface. + * + * @param listener the listener to be added. + * @since 1.0.5 + */ + void addPropertyChangeListener(PropertyChangeListener listener); + + /** + * Removes a {@link PropertyChangeListener} from the Mutable interface. + * + * @param listener the property change listener to be removed + */ + void removePropertyChangeListener(PropertyChangeListener listener); + + /** + * Adds a PropertyChangeListener to the listener list for a specific + * property. + * If <code>propertyName</code> or <code>listener</code> is <code>null</code>, + * no exception is thrown and no action is taken. + * + * @param propertyName one of the property names listed above + * @param listener the property change listener to be added + */ + void addPropertyChangeListener(String propertyName, PropertyChangeListener listener); + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/Preprocessor.java b/src/Java/miscutil/core/util/aeonbits/owner/Preprocessor.java new file mode 100644 index 0000000000..ad09741d07 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/Preprocessor.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; + +/** + * Preprocessor interface specifies how to pre-process an input string coming from a property value before being used by + * OWNER. + * + * @author Luigi R. Viggiano + * @since 1.0.9 + */ +public interface Preprocessor { + String process(String input); +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/PreprocessorResolver.java b/src/Java/miscutil/core/util/aeonbits/owner/PreprocessorResolver.java new file mode 100644 index 0000000000..20be663c71 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/PreprocessorResolver.java @@ -0,0 +1,47 @@ +/* + * 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; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import miscutil.core.util.aeonbits.owner.Config.PreprocessorClasses; +import static java.util.Collections.emptyList; +import static miscutil.core.util.aeonbits.owner.Util.newInstance; + +/** + * @author Luigi R. Viggiano + */ +final class PreprocessorResolver { + + /** Don't let anyone instantiate this class */ + private PreprocessorResolver() {} + + public static List<Preprocessor> resolvePreprocessors(Method method) { + List<Preprocessor> result = new ArrayList<Preprocessor>(); + List<Preprocessor> preprocessorsOnMethod = getPreprocessor(method.getAnnotation(PreprocessorClasses.class)); + result.addAll(preprocessorsOnMethod); + + List<Preprocessor> preprocessorsOnClass = getPreprocessor(method.getDeclaringClass().getAnnotation(PreprocessorClasses.class)); + result.addAll(preprocessorsOnClass); + + return result; + } + + private static List<Preprocessor> getPreprocessor(PreprocessorClasses preprocessorClassesAnnotation) { + if (preprocessorClassesAnnotation == null) return emptyList(); + Class<? extends Preprocessor>[] preprocessorClasses = preprocessorClassesAnnotation.value(); + if (preprocessorClasses == null) return emptyList(); + List<Preprocessor> result = new LinkedList<Preprocessor>(); + return newInstance(preprocessorClassesAnnotation.value(), result); + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/PropertiesInvocationHandler.java b/src/Java/miscutil/core/util/aeonbits/owner/PropertiesInvocationHandler.java new file mode 100644 index 0000000000..4a51f5e86e --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/PropertiesInvocationHandler.java @@ -0,0 +1,141 @@ +/* + * 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.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.LinkedList; +import java.util.List; + +import static miscutil.core.util.aeonbits.owner.Config.DisableableFeature.PARAMETER_FORMATTING; +import static miscutil.core.util.aeonbits.owner.Config.DisableableFeature.VARIABLE_EXPANSION; +import static miscutil.core.util.aeonbits.owner.Converters.convert; +import static miscutil.core.util.aeonbits.owner.Converters.SpecialValue.NULL; +import static miscutil.core.util.aeonbits.owner.PreprocessorResolver.resolvePreprocessors; +import static miscutil.core.util.aeonbits.owner.PropertiesMapper.key; +import static miscutil.core.util.aeonbits.owner.Util.isFeatureDisabled; +import static miscutil.core.util.aeonbits.owner.util.Reflection.invokeDefaultMethod; +import static miscutil.core.util.aeonbits.owner.util.Reflection.isDefault; + +/** + * This {@link InvocationHandler} receives method calls from the delegate instantiated by {@link ConfigFactory} and maps + * it to a property value from a property file, or a {@link Config.DefaultValue} specified in method annotation. + * <p> + * The {@link Config.Key} annotation can be used to override default mapping between method names and property names. + * </p> + * <p> + * Automatic conversion is handled between the property value and the return type expected by the method of the + * delegate. + * </p> + * + * @author Luigi R. Viggiano + */ +class PropertiesInvocationHandler implements InvocationHandler, Serializable { + + private static final long serialVersionUID = 5432212884255718342L; + private transient List<DelegateMethodHandle> delegates; + private final Object jmxSupport; + private final StrSubstitutor substitutor; + final PropertiesManager propertiesManager; + + + PropertiesInvocationHandler(PropertiesManager manager, Object jmxSupport) { + this.propertiesManager = manager; + this.jmxSupport = jmxSupport; + delegates = findDelegates(manager, jmxSupport); + this.substitutor = new StrSubstitutor(manager.load()); + } + + public Object invoke(Object proxy, Method invokedMethod, Object... args) throws Throwable { + propertiesManager.syncReloadCheck(); + + if (isDefault(invokedMethod)) + return invokeDefaultMethod(proxy, invokedMethod, args); + + DelegateMethodHandle delegate = getDelegateMethod(invokedMethod); + if (delegate != null) + return delegate.invoke(args); + + return resolveProperty(invokedMethod, args); + } + + private DelegateMethodHandle getDelegateMethod(Method invokedMethod) { + for (DelegateMethodHandle delegate : delegates) + if (delegate.matches(invokedMethod)) + return delegate; + return null; + } + + private Object resolveProperty(Method method, Object... args) { + String key = expandKey(method); + String value = propertiesManager.getProperty(key); + if (value == null && !isFeatureDisabled(method, VARIABLE_EXPANSION)) { // TODO: this if should go away! See #84 and #86 + String unexpandedKey = key(method); + value = propertiesManager.getProperty(unexpandedKey); + } + if (value == null) + return null; + value = preProcess(method, value); + Object result = convert(method, method.getReturnType(), format(method, expandVariables(method, value), args)); + if (result == NULL) return null; + return result; + } + + private String preProcess(Method method, String value) { + List<Preprocessor> preprocessors = resolvePreprocessors(method); + String result = value; + for (Preprocessor preprocessor : preprocessors) + result = preprocessor.process(result); + return result; + } + + private String expandKey(Method method) { + String key = key(method); + if (isFeatureDisabled(method, VARIABLE_EXPANSION)) + return key; + return substitutor.replace(key); + } + + private String format(Method method, String format, Object... args) { + if (isFeatureDisabled(method, PARAMETER_FORMATTING)) + return format; + return String.format(format, args); + } + + private String expandVariables(Method method, String value) { + if (isFeatureDisabled(method, VARIABLE_EXPANSION)) + return value; + return substitutor.replace(value); + } + + private List<DelegateMethodHandle> findDelegates(Object... targets) { + List<DelegateMethodHandle> result = new LinkedList<DelegateMethodHandle>(); + for (Object target : targets) { + if (target == null) + continue; + Method[] methods = target.getClass().getMethods(); + for (Method m : methods) + if (m.getAnnotation(Delegate.class) != null) + result.add(new DelegateMethodHandle(target, m)); + } + return result; + } + + public <T extends Config> void setProxy(T proxy) { + propertiesManager.setProxy(proxy); + } + + private void readObject(java.io.ObjectInputStream in) + throws IOException, ClassNotFoundException { + in.defaultReadObject(); + delegates = findDelegates(propertiesManager, jmxSupport); + } +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/PropertiesManager.java b/src/Java/miscutil/core/util/aeonbits/owner/PropertiesManager.java new file mode 100644 index 0000000000..075ae2c35f --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/PropertiesManager.java @@ -0,0 +1,584 @@ +/* + * 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.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.Serializable; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; + +import miscutil.core.util.aeonbits.owner.event.ReloadEvent; +import miscutil.core.util.aeonbits.owner.event.ReloadListener; +import miscutil.core.util.aeonbits.owner.event.RollbackBatchException; +import miscutil.core.util.aeonbits.owner.event.RollbackException; +import miscutil.core.util.aeonbits.owner.event.RollbackOperationException; +import miscutil.core.util.aeonbits.owner.event.TransactionalPropertyChangeListener; +import miscutil.core.util.aeonbits.owner.event.TransactionalReloadListener; +import static java.util.Collections.synchronizedList; +import static miscutil.core.util.aeonbits.owner.Config.LoadType.FIRST; +import static miscutil.core.util.aeonbits.owner.PropertiesMapper.defaults; +import static miscutil.core.util.aeonbits.owner.Util.asString; +import static miscutil.core.util.aeonbits.owner.Util.eq; +import static miscutil.core.util.aeonbits.owner.Util.ignore; +import static miscutil.core.util.aeonbits.owner.Util.reverse; +import static miscutil.core.util.aeonbits.owner.Util.unsupported; + +/** + * Loads properties and manages access to properties handling concurrency. + * + * @author Luigi R. Viggiano + */ +class PropertiesManager implements Reloadable, Accessible, Mutable { + + private final Class<? extends Config> clazz; + private final Map<?, ?>[] imports; + private final Properties properties; + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private final ReadLock readLock = lock.readLock(); + private final WriteLock writeLock = lock.writeLock(); + + private final LoadType loadType; + private final List<URI> uris; + private final HotReloadLogic hotReloadLogic; + + private volatile boolean loading = false; + + final List<ReloadListener> reloadListeners = synchronizedList(new LinkedList<ReloadListener>()); + + private Object proxy; + private final LoadersManager loaders; + + final List<PropertyChangeListener> propertyChangeListeners = synchronizedList( + new LinkedList<PropertyChangeListener>() { + @Override + public boolean remove(Object o) { + Iterator iterator = iterator(); + while (iterator.hasNext()) { + Object item = iterator.next(); + if (item.equals(o)) { + iterator.remove(); + return true; + } + } + return false; + } + }); + + PropertiesManager(Class<? extends Config> clazz, Properties properties, ScheduledExecutorService scheduler, + VariablesExpander expander, LoadersManager loaders, Map<?, ?>... imports) { + this.clazz = clazz; + this.properties = properties; + this.loaders = loaders; + this.imports = imports; + + ConfigURIFactory urlFactory = new ConfigURIFactory(clazz.getClassLoader(), expander); + uris = toURIs(clazz.getAnnotation(Sources.class), urlFactory); + + LoadPolicy loadPolicy = clazz.getAnnotation(LoadPolicy.class); + loadType = (loadPolicy != null) ? loadPolicy.value() : FIRST; + + HotReload hotReload = clazz.getAnnotation(HotReload.class); + if (hotReload != null) { + hotReloadLogic = new HotReloadLogic(hotReload, uris, this); + + if (hotReloadLogic.isAsync()) + scheduler.scheduleAtFixedRate(new Runnable() { + public void run() { + hotReloadLogic.checkAndReload(); + } + }, hotReload.value(), hotReload.value(), hotReload.unit()); + } else { + hotReloadLogic = null; + } + } + + private List<URI> toURIs(Sources sources, ConfigURIFactory uriFactory) { + String[] specs = specs(sources, uriFactory); + List<URI> result = new ArrayList<URI>(); + for (String spec : specs) { + try { + URI uri = uriFactory.newURI(spec); + if (uri != null) + result.add(uri); + } catch (URISyntaxException e) { + throw unsupported(e, "Can't convert '%s' to a valid URI", spec); + } + } + return result; + } + + private String[] specs(Sources sources, ConfigURIFactory uriFactory) { + if (sources != null) return sources.value(); + return defaultSpecs(uriFactory); + } + + private String[] defaultSpecs(ConfigURIFactory uriFactory) { + String prefix = uriFactory.toClasspathURLSpec(clazz.getName()); + return loaders.defaultSpecs(prefix); + } + + Properties load() { + writeLock.lock(); + try { + return load(properties); + } finally { + writeLock.unlock(); + } + } + + private Properties load(Properties props) { + try { + loading = true; + defaults(props, clazz); + Properties loadedFromFile = doLoad(); + merge(props, loadedFromFile); + merge(props, reverse(imports)); + return props; + } finally { + loading = false; + } + } + + @Delegate + public void reload() { + writeLock.lock(); + try { + Properties loaded = load(new Properties()); + List<PropertyChangeEvent> events = + fireBeforePropertyChangeEvents(keys(properties, loaded), properties, loaded); + ReloadEvent reloadEvent = fireBeforeReloadEvent(events, properties, loaded); + applyPropertyChangeEvents(events); + firePropertyChangeEvents(events); + fireReloadEvent(reloadEvent); + } catch (RollbackBatchException e) { + ignore(); + } finally { + writeLock.unlock(); + } + } + + private Set<?> keys(Map<?, ?>... maps) { + Set<Object> keys = new HashSet<Object>(); + for (Map<?, ?> map : maps) + keys.addAll(map.keySet()); + return keys; + } + + private void applyPropertyChangeEvents(List<PropertyChangeEvent> events) { + for (PropertyChangeEvent event : events) + performSetProperty(event.getPropertyName(), event.getNewValue()); + } + + private void fireReloadEvent(ReloadEvent reloadEvent) { + for (ReloadListener listener : reloadListeners) + listener.reloadPerformed(reloadEvent); + } + + private ReloadEvent fireBeforeReloadEvent(List<PropertyChangeEvent> events, Properties oldProperties, + Properties newProperties) throws RollbackBatchException { + ReloadEvent reloadEvent = new ReloadEvent(proxy, events, oldProperties, newProperties); + for (ReloadListener listener : reloadListeners) + if (listener instanceof TransactionalReloadListener) + ((TransactionalReloadListener) listener).beforeReload(reloadEvent); + return reloadEvent; + } + + + @Delegate + public void addReloadListener(ReloadListener listener) { + if (listener != null) + reloadListeners.add(listener); + } + + @Delegate + public void removeReloadListener(ReloadListener listener) { + if (listener != null) + reloadListeners.remove(listener); + } + + @Delegate + public void addPropertyChangeListener(PropertyChangeListener listener) { + if (listener != null) + propertyChangeListeners.add(listener); + } + + @Delegate + public void removePropertyChangeListener(PropertyChangeListener listener) { + if (listener != null) + propertyChangeListeners.remove(listener); + } + + @Delegate + public void addPropertyChangeListener(final String propertyName, final PropertyChangeListener listener) { + if (propertyName == null || listener == null) return; + + final boolean transactional = listener instanceof TransactionalPropertyChangeListener; + propertyChangeListeners.add(new PropertyChangeListenerWrapper(propertyName, listener, transactional)); + } + + private static class PropertyChangeListenerWrapper implements TransactionalPropertyChangeListener, Serializable { + + private final String propertyName; + private final PropertyChangeListener listener; + private final boolean transactional; + + public PropertyChangeListenerWrapper(String propertyName, PropertyChangeListener listener, + boolean transactional) { + this.propertyName = propertyName; + this.listener = listener; + this.transactional = transactional; + + } + + public void beforePropertyChange(PropertyChangeEvent event) throws RollbackOperationException, + RollbackBatchException { + if (transactional && propertyNameMatches(event)) + ((TransactionalPropertyChangeListener) listener).beforePropertyChange(event); + } + + private boolean propertyNameMatches(PropertyChangeEvent event) { + return propertyName.equals(event.getPropertyName()); + } + + public void propertyChange(PropertyChangeEvent event) { + if (propertyNameMatches(event)) + listener.propertyChange(event); + } + + @Override + public boolean equals(Object obj) { + return listener.equals(obj); + } + + @Override + public int hashCode() { + return listener.hashCode(); + } + } + + Properties doLoad() { + return loadType.load(uris, loaders); + } + + private static void merge(Properties results, Map<?, ?>... inputs) { + for (Map<?, ?> input : inputs) + results.putAll(input); + } + + @Delegate + public String getProperty(String key) { + readLock.lock(); + try { + return properties.getProperty(key); + } finally { + readLock.unlock(); + } + } + + void syncReloadCheck() { + if (hotReloadLogic != null && hotReloadLogic.isSync()) + hotReloadLogic.checkAndReload(); + } + + @Delegate + public String getProperty(String key, String defaultValue) { + readLock.lock(); + try { + return properties.getProperty(key, defaultValue); + } finally { + readLock.unlock(); + } + } + + @Delegate + public void storeToXML(OutputStream os, String comment) throws IOException { + readLock.lock(); + try { + properties.storeToXML(os, comment); + } finally { + readLock.unlock(); + } + } + + @Delegate + public Set<String> propertyNames() { + readLock.lock(); + try { + LinkedHashSet<String> result = new LinkedHashSet<String>(); + for (Enumeration<?> propertyNames = properties.propertyNames(); propertyNames.hasMoreElements(); ) + result.add((String) propertyNames.nextElement()); + return result; + } finally { + readLock.unlock(); + } + } + + @Delegate + public void list(PrintStream out) { + readLock.lock(); + try { + properties.list(out); + } finally { + readLock.unlock(); + } + } + + @Delegate + public void list(PrintWriter out) { + readLock.lock(); + try { + properties.list(out); + } finally { + readLock.unlock(); + } + } + + @Delegate + public void store(OutputStream out, String comments) throws IOException { + readLock.lock(); + try { + properties.store(out, comments); + } finally { + readLock.unlock(); + } + } + + @Delegate + public void fill(Map map) { + readLock.lock(); + try { + for (String propertyName : propertyNames()) + map.put(propertyName, getProperty(propertyName)); + } finally { + readLock.unlock(); + } + } + + @Delegate + public String setProperty(String key, String newValue) { + writeLock.lock(); + try { + String oldValue = properties.getProperty(key); + try { + if (eq(oldValue, newValue)) return oldValue; + + PropertyChangeEvent event = new PropertyChangeEvent(proxy, key, oldValue, newValue); + fireBeforePropertyChange(event); + String result = performSetProperty(key, newValue); + firePropertyChange(event); + return result; + } catch (RollbackException e) { + return oldValue; + } + } finally { + writeLock.unlock(); + } + } + + private String performSetProperty(String key, Object value) { + return (value == null) ? + performRemoveProperty(key) : + asString(properties.setProperty(key, asString(value))); + } + + @Delegate + public String removeProperty(String key) { + writeLock.lock(); + try { + String oldValue = properties.getProperty(key); + String newValue = null; + PropertyChangeEvent event = new PropertyChangeEvent(proxy, key, oldValue, newValue); + fireBeforePropertyChange(event); + String result = performRemoveProperty(key); + firePropertyChange(event); + return result; + } catch (RollbackException e) { + return properties.getProperty(key); + } finally { + writeLock.unlock(); + } + } + + private String performRemoveProperty(String key) { + return asString(properties.remove(key)); + } + + @Delegate + public void clear() { + writeLock.lock(); + try { + List<PropertyChangeEvent> events = + fireBeforePropertyChangeEvents(keys(properties), properties, new Properties()); + applyPropertyChangeEvents(events); + firePropertyChangeEvents(events); + } catch (RollbackBatchException e) { + ignore(); + } finally { + writeLock.unlock(); + } + } + + @Delegate + public void load(InputStream inStream) throws IOException { + writeLock.lock(); + try { + Properties loaded = new Properties(); + loaded.load(inStream); + performLoad(keys(loaded), loaded); + } catch (RollbackBatchException ex) { + ignore(); + } finally { + writeLock.unlock(); + } + } + + private void performLoad(Set keys, Properties props) throws RollbackBatchException { + List<PropertyChangeEvent> events = fireBeforePropertyChangeEvents(keys, properties, props); + applyPropertyChangeEvents(events); + firePropertyChangeEvents(events); + } + + @Delegate + public void load(Reader reader) throws IOException { + writeLock.lock(); + try { + Properties loaded = new Properties(); + loaded.load(reader); + performLoad(keys(loaded), loaded); + } catch (RollbackBatchException ex) { + ignore(); + } finally { + writeLock.unlock(); + } + } + + void setProxy(Object proxy) { + this.proxy = proxy; + } + + @Delegate + @Override + public String toString() { + readLock.lock(); + try { + return properties.toString(); + } finally { + readLock.unlock(); + } + } + + boolean isLoading() { + return loading; + } + + private List<PropertyChangeEvent> fireBeforePropertyChangeEvents( + Set keys, Properties oldValues, Properties newValues) throws RollbackBatchException { + List<PropertyChangeEvent> events = new ArrayList<PropertyChangeEvent>(); + for (Object keyObject : keys) { + String key = (String) keyObject; + String oldValue = oldValues.getProperty(key); + String newValue = newValues.getProperty(key); + if (!eq(oldValue, newValue)) { + PropertyChangeEvent event = + new PropertyChangeEvent(proxy, key, oldValue, newValue); + try { + fireBeforePropertyChange(event); + events.add(event); + } catch (RollbackOperationException e) { + ignore(); + } + } + } + return events; + } + + private void firePropertyChangeEvents(List<PropertyChangeEvent> events) { + for (PropertyChangeEvent event : events) + firePropertyChange(event); + } + + private void fireBeforePropertyChange(PropertyChangeEvent event) throws RollbackBatchException, + RollbackOperationException { + for (PropertyChangeListener listener : propertyChangeListeners) + if (listener instanceof TransactionalPropertyChangeListener) + ((TransactionalPropertyChangeListener) listener).beforePropertyChange(event); + } + + private void firePropertyChange(PropertyChangeEvent event) { + for (PropertyChangeListener listener : propertyChangeListeners) + listener.propertyChange(event); + } + + @Delegate + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Proxy)) return false; + InvocationHandler handler = Proxy.getInvocationHandler(obj); + if (!(handler instanceof PropertiesInvocationHandler)) + return false; + PropertiesInvocationHandler propsInvocationHandler = (PropertiesInvocationHandler) handler; + PropertiesManager that = propsInvocationHandler.propertiesManager; + return this.equals(that); + } + + private boolean equals(PropertiesManager that) { + if (!this.isAssignationCompatibleWith(that)) + return false; + this.readLock.lock(); + try { + that.readLock.lock(); + try { + return this.properties.equals(that.properties); + } finally { + that.readLock.unlock(); + } + } finally { + this.readLock.unlock(); + } + } + + private boolean isAssignationCompatibleWith(PropertiesManager that) { + return this.clazz.isAssignableFrom(that.clazz) || that.clazz.isAssignableFrom(this.clazz); + } + + @Delegate + @Override + public int hashCode() { + readLock.lock(); + try { + return properties.hashCode(); + } finally { + readLock.unlock(); + } + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/PropertiesMapper.java b/src/Java/miscutil/core/util/aeonbits/owner/PropertiesMapper.java new file mode 100644 index 0000000000..9e4ddf2ce2 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/PropertiesMapper.java @@ -0,0 +1,47 @@ +/* + * 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; +import java.util.Properties; + +import miscutil.core.util.aeonbits.owner.Config.DefaultValue; +import miscutil.core.util.aeonbits.owner.Config.Key; + +/** + * Maps methods to properties keys and defaultValues. Maps a class to default property values. + * + * @author Luigi R. Viggiano + */ +final class PropertiesMapper { + + /** Don't let anyone instantiate this class */ + private PropertiesMapper() {} + + static String key(Method method) { + Key key = method.getAnnotation(Key.class); + return (key == null) ? method.getName() : key.value(); + } + + static String defaultValue(Method method) { + DefaultValue defaultValue = method.getAnnotation(DefaultValue.class); + return defaultValue != null ? defaultValue.value() : null; + } + + static void defaults(Properties properties, Class<? extends Config> clazz) { + Method[] methods = clazz.getMethods(); + for (Method method : methods) { + String key = key(method); + String value = defaultValue(method); + if (value != null) + properties.put(key, value); + } + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/Reloadable.java b/src/Java/miscutil/core/util/aeonbits/owner/Reloadable.java new file mode 100644 index 0000000000..802405592c --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/Reloadable.java @@ -0,0 +1,70 @@ +/* + * 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 miscutil.core.util.aeonbits.owner.event.ReloadListener; + +/** + * <p>Allows a <tt>Config</tt> object to implement the reloading of the properties at runtime.</p> + * + * <p>Example:</p> + * + * <pre> + * public interface MyConfig extends Config, Reloadable { + * int someProperty(); + * } + * + * public void doSomething() { + * + * // loads the properties from the files for the first time. + * MyConfig cfg = ConfigFactory.create(MyConfig.class); + * int before = cfg.someProperty(); + * + * // after changing the local files... + * cfg.reload(); + * int after = cfg.someProperty(); + * + * // before and after may differ now. + * if (before != after) { ... } + * } + * </pre> + * + * <p>The reload method will reload the properties using the same sources used when it was instantiated the first time. + * This can be useful to programmatically reload the configuration after the configuration files were changed.</p> + * + * @author Luigi R. Viggiano + * @since 1.0.4 + */ +public interface Reloadable extends Config { + + /** + * Reloads the properties using the same logic as when the object was instantiated by {@link + * ConfigFactory#create(Class, java.util.Map[])}. + * + * @since 1.0.4 + */ + void reload(); + + /** + * Add a ReloadListener. + * @param listener the listener to be added + * + * @since 1.0.4 + */ + void addReloadListener(ReloadListener listener); + + /** + * Remove a ReloadListener. + * @param listener the listener to be removed + * + * @since 1.0.4 + */ + void removeReloadListener(ReloadListener listener); + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/SplitAndTrimTokenizer.java b/src/Java/miscutil/core/util/aeonbits/owner/SplitAndTrimTokenizer.java new file mode 100644 index 0000000000..2dde1791e4 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/SplitAndTrimTokenizer.java @@ -0,0 +1,33 @@ +/* + * 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; + +/** + * Tokenizer implementation based on {@link String#split(String, int)} and {@link String#trim()}. + * This class is used to implement <tt>tokenizer</tt>s for the {@link Config.Separator} annotation. + * + * @since 1.0.4 + * @author Luigi R. Viggiano + */ +class SplitAndTrimTokenizer implements Tokenizer { + + private final String regex; + + public SplitAndTrimTokenizer(String regex) { + this.regex = regex; + } + + public String[] tokens(String values) { + String[] chunks = values.split(regex, -1); + for(int i = 0; i < chunks.length; i++) + chunks[i] = chunks[i].trim(); + return chunks; + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/StrSubstitutor.java b/src/Java/miscutil/core/util/aeonbits/owner/StrSubstitutor.java new file mode 100644 index 0000000000..017e465787 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/StrSubstitutor.java @@ -0,0 +1,86 @@ +/* + * 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.Serializable; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.util.regex.Pattern.compile; +import static miscutil.core.util.aeonbits.owner.Util.fixBackslashForRegex; + +/** + * <p> + * Substitutes variables within a string by values. + * </p> + * <p> + * This class takes a piece of text and substitutes all the variables within it. The definition of a variable is + * <code>${variableName}</code>. + * </p> + * <p> + * Typical usage of this class follows the following pattern: First an instance is created and initialized with the + * values that contains the values for the available variables. If a prefix and/or suffix for variables should be used + * other than the default ones, the appropriate settings can be performed. After that the <code>replace()</code> method + * can be called passing in the source text for interpolation. In the returned text all variable references (as long as + * their values are known) will be resolved. The following example demonstrates this: + * </p> + * <pre> + * Map valuesMap = new HashMap(); + * valuesMap.put("animal", "quick brown fox"); + * valuesMap.put("target", "lazy dog"); + * String templateString = "The ${animal} jumped over the ${target}."; + * StrSubstitutor sub = new StrSubstitutor(valuesMap); + * String resolvedString = sub.replace(templateString); + * </pre> + * yielding: + * <pre> + * The quick brown fox jumped over the lazy dog. + * </pre> + * + * @author Luigi R. Viggiano + */ +class StrSubstitutor implements Serializable { + + private final Properties values; + private static final Pattern PATTERN = compile("\\$\\{(.+?)\\}"); + + /** + * Creates a new instance and initializes it. Uses defaults for variable prefix and suffix and the escaping + * character. + * + * @param values the variables' values, may be null + */ + StrSubstitutor(Properties values) { + this.values = values; + } + + /** + * Replaces all the occurrences of variables with their matching values from the resolver using the given source + * string as a template. + * + * @param source the string to replace in, null returns null + * @return the result of the replace operation + */ + String replace(String source) { + if (source == null) + return null; + Matcher m = PATTERN.matcher(source); + StringBuffer sb = new StringBuffer(); + while (m.find()) { + String var = m.group(1); + String value = values.getProperty(var); + String replacement = (value != null) ? replace(value) : ""; + m.appendReplacement(sb, fixBackslashForRegex(replacement)); + } + m.appendTail(sb); + return sb.toString(); + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/Tokenizer.java b/src/Java/miscutil/core/util/aeonbits/owner/Tokenizer.java new file mode 100644 index 0000000000..700b453904 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/Tokenizer.java @@ -0,0 +1,29 @@ +/* + * 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; + +/** + * Tokenizer interface that specifies how to split a single value into tokens to be used as elements for arrays and + * collections. + * + * @author Luigi R. Viggiano + * @since 1.0.4 + */ +public interface Tokenizer { + + /** + * Splits the given string, into tokens that identify single elements. + * + * @since 1.0.4 + * @param values the string representation for the properties values + * @return the items identifying single elements to convert. + */ + String[] tokens(String values); + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/TokenizerResolver.java b/src/Java/miscutil/core/util/aeonbits/owner/TokenizerResolver.java new file mode 100644 index 0000000000..78d25d7926 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/TokenizerResolver.java @@ -0,0 +1,76 @@ +/* + * 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; + +import miscutil.core.util.aeonbits.owner.Config.Separator; +import miscutil.core.util.aeonbits.owner.Config.TokenizerClass; +import static miscutil.core.util.aeonbits.owner.Util.newInstance; +import static miscutil.core.util.aeonbits.owner.Util.unsupported; + +/** + * @author Luigi R. Viggiano + */ +final class TokenizerResolver { + + /** Don't let anyone instantiate this class */ + private TokenizerResolver() {} + + private static final Tokenizer DEFAULT_TOKENIZER = new SplitAndTrimTokenizer(","); + + static Tokenizer resolveTokenizer(Method targetMethod) { + Tokenizer methodLevelTokenizer = resolveTokenizerOnMethodLevel(targetMethod); + if (methodLevelTokenizer != null) + return methodLevelTokenizer; + + Tokenizer classLevelTokenizer = resolveTokenizerOnClassLevel(targetMethod.getDeclaringClass()); + if (classLevelTokenizer != null) + return classLevelTokenizer; + + return DEFAULT_TOKENIZER; + } + + private static Tokenizer resolveTokenizerOnClassLevel(Class<?> declaringClass) { + Separator separatorAnnotationOnClassLevel = declaringClass.getAnnotation(Separator.class); + TokenizerClass tokenizerClassAnnotationOnClassLevel = declaringClass.getAnnotation(TokenizerClass.class); + + if (separatorAnnotationOnClassLevel != null && tokenizerClassAnnotationOnClassLevel != null) + throw unsupported( + "You cannot specify @Separator and @TokenizerClass both together on class level for '%s'", + declaringClass.getCanonicalName()); + + if (separatorAnnotationOnClassLevel != null) + return new SplitAndTrimTokenizer(separatorAnnotationOnClassLevel.value()); + + if (tokenizerClassAnnotationOnClassLevel != null) + return newInstance(tokenizerClassAnnotationOnClassLevel.value()); + + return null; + } + + private static Tokenizer resolveTokenizerOnMethodLevel(Method targetMethod) { + Separator separatorAnnotationOnMethodLevel = targetMethod.getAnnotation(Separator.class); + TokenizerClass tokenizerClassAnnotationOnMethodLevel = targetMethod.getAnnotation(TokenizerClass.class); + + if (separatorAnnotationOnMethodLevel != null && tokenizerClassAnnotationOnMethodLevel != null) + throw unsupported( + "You cannot specify @Separator and @TokenizerClass both together on method level for '%s'", + targetMethod); + + if (separatorAnnotationOnMethodLevel != null) + return new SplitAndTrimTokenizer(separatorAnnotationOnMethodLevel.value()); + + if (tokenizerClassAnnotationOnMethodLevel != null) + return newInstance(tokenizerClassAnnotationOnMethodLevel.value()); + + return null; + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/Util.java b/src/Java/miscutil/core/util/aeonbits/owner/Util.java new file mode 100644 index 0000000000..79dd3faa60 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/Util.java @@ -0,0 +1,266 @@ +/* + * 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.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Method; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.jar.JarOutputStream; +import java.util.zip.ZipEntry; + +import miscutil.core.util.aeonbits.owner.Config.DisableFeature; +import miscutil.core.util.aeonbits.owner.Config.DisableableFeature; +import static java.io.File.createTempFile; +import static java.lang.String.format; +import static java.net.URLDecoder.decode; +import static java.util.Arrays.asList; + +/** + * This class contains utility methods used all over the library. + * + * @author Luigi R. Viggiano + */ +abstract class Util { + + interface TimeProvider { + long getTime(); + } + + interface SystemProvider { + String getProperty(String key); + + Map<String, String> getenv(); + + Properties getProperties(); + } + + static TimeProvider timeProvider = new TimeProvider() { + public long getTime() { + return System.currentTimeMillis(); + } + }; + + static SystemProvider system = new SystemProvider() { + public String getProperty(String key) { + return System.getProperty(key); + } + + public Map<String, String> getenv() { + return System.getenv(); + } + + public Properties getProperties() { + return System.getProperties(); + } + }; + + /** Don't let anyone instantiate this class */ + private Util() {} + + static <T> List<T> reverse(List<T> src) { + List<T> copy = new ArrayList<T>(src); + Collections.reverse(copy); + return copy; + } + + @SuppressWarnings("unchecked") + static <T> T[] reverse(T[] array) { + T[] copy = array.clone(); + Collections.reverse(asList(copy)); + return copy; + } + + static String expandUserHome(String text) { + if (text.equals("~")) + return system.getProperty("user.home"); + if (text.indexOf("~/") == 0 || text.indexOf("file:~/") == 0 || text.indexOf("jar:file:~/") == 0) + return text.replaceFirst("~/", fixBackslashForRegex(system.getProperty("user.home")) + "/"); + if (text.indexOf("~\\") == 0 || text.indexOf("file:~\\") == 0 || text.indexOf("jar:file:~\\") == 0) + return text.replaceFirst("~\\\\", fixBackslashForRegex(system.getProperty("user.home")) + "\\\\"); + return text; + } + + static String fixBackslashForRegex(String text) { + return text.replace("\\", "\\\\"); + } + + public static String fixBackslashesToSlashes(String path) { + return path.replace('\\', '/'); + } + + static <T> T ignore() { + // the ignore method does absolutely nothing, but it helps to shut up warnings by pmd and other reporting tools + // complaining about empty catch methods. + return null; + } + + static boolean isFeatureDisabled(Method method, DisableableFeature feature) { + Class<DisableFeature> annotation = DisableFeature.class; + return isFeatureDisabled(feature, method.getDeclaringClass().getAnnotation(annotation)) || + isFeatureDisabled(feature, method.getAnnotation(annotation)); + } + + private static boolean isFeatureDisabled(DisableableFeature feature, DisableFeature annotation) { + return annotation != null && asList(annotation.value()).contains(feature); + } + + static UnsupportedOperationException unsupported(Throwable cause, String msg, Object... args) { + return new UnsupportedOperationException(format(msg, args), cause); + } + + static UnsupportedOperationException unsupported(String msg, Object... args) { + return new UnsupportedOperationException(format(msg, args)); + } + + static <T> T unreachableButCompilerNeedsThis() { + throw new AssertionError("this code should never be reached"); + } + + static String asString(Object result) { + if (result == null) return null; + return String.valueOf(result); + } + + static long now() { + return timeProvider.getTime(); + } + + static File fileFromURI(URI uri) { + if ("file".equalsIgnoreCase(uri.getScheme())) { + String path = uri.getSchemeSpecificPart(); + try { + path = decode(path, "utf-8"); + return new File(path); + } catch (UnsupportedEncodingException e) { + return unreachableButCompilerNeedsThis(/* utf-8 is supported in jre libraries */); + } + } else if ("jar".equalsIgnoreCase(uri.getScheme())) { + String path = uri.getSchemeSpecificPart(); + try { + return fileFromURI(path.substring(0, path.indexOf('!'))); + } catch (URISyntaxException e) { + return ignore(/* non critical */); + } + } + return null; + } + + static File fileFromURI(String uriSpec) throws URISyntaxException { + return fileFromURI(new URI(uriSpec)); + } + + static boolean eq(Object o1, Object o2) { + return o1 == o2 || o1 != null && o1.equals(o2); + } + + static SystemProvider system() { + return system; + } + + static void save(File target, Properties p) throws IOException { + File parent = target.getParentFile(); + parent.mkdirs(); + if (isWindows()) { + store(target, p); + } else { + File tempFile = createTempFile(target.getName(), ".temp", parent); + store(tempFile, p); + rename(tempFile, target); + } + + } + + private static boolean isWindows() { + return System.getProperty("os.name").toLowerCase().indexOf("win") >= 0; + } + + static void delete(File target) { + target.delete(); + } + + private static void store(File target, Properties p) throws IOException { + OutputStream out = new FileOutputStream(target); + try { + store(out, p); + } finally { + out.close(); + } + } + + private static void store(OutputStream out, Properties p) throws IOException { + p.store(out, "saved for test"); + } + + static void saveJar(File target, String entryName, Properties props) throws IOException { + File parent = target.getParentFile(); + parent.mkdirs(); + storeJar(target, entryName, props); + } + + private static void rename(File source, File target) throws IOException { + if (!source.renameTo(target)) + throw new IOException(String.format("Failed to overwrite %s to %s", source.toString(), target.toString())); + } + + private static void storeJar(File target, String entryName, Properties props) throws IOException { + byte[] bytes = toBytes(props); + InputStream input = new ByteArrayInputStream(bytes); + JarOutputStream output = new JarOutputStream(new FileOutputStream(target)); + try { + ZipEntry entry = new ZipEntry(entryName); + output.putNextEntry(entry); + byte[] buffer = new byte[4096]; + int size; + while ((size = input.read(buffer)) != -1) + output.write(buffer, 0, size); + } finally { + input.close(); + output.close(); + } + } + + private static byte[] toBytes(Properties props) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + store(out, props); + return out.toByteArray(); + } finally { + out.close(); + } + } + + public static <T> T newInstance(Class<T> clazz) { + try { + return clazz.newInstance(); + } catch (Exception e) { + throw unsupported(e, + "Class '%s' cannot be instantiated; see the cause below in the stack trace", + clazz.getCanonicalName()); + } + } + + public static <T> List<T> newInstance(Class<? extends T>[] classes, List<T> result) { + for (Class<? extends T> clazz : classes) + result.add(newInstance(clazz)); + return result; + } +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/VariablesExpander.java b/src/Java/miscutil/core/util/aeonbits/owner/VariablesExpander.java new file mode 100644 index 0000000000..63bcdf07a8 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/VariablesExpander.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.io.Serializable; +import java.util.Properties; + +import static miscutil.core.util.aeonbits.owner.Util.expandUserHome; + +/** + * This class is used to expand variables in the format <tt>${variable}</tt>$, using values from + * {@link System#getenv()}, {@link System#getProperties()} and the <tt>Properties</tt> object specified in the + * constructor (in inverse order; first match is accepted). + * + * @author Luigi R. Viggiano + */ +class VariablesExpander implements Serializable { + + private final StrSubstitutor substitutor; + + VariablesExpander(Properties props) { + Properties variables = new Properties(); + variables.putAll(Util.system().getenv()); + variables.putAll(Util.system().getProperties()); + variables.putAll(props); + substitutor = new StrSubstitutor(variables); + } + + String expand(String path) { + String expanded = expandUserHome(path); + return substitutor.replace(expanded); + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/event/Event.java b/src/Java/miscutil/core/util/aeonbits/owner/event/Event.java new file mode 100644 index 0000000000..3459cd3158 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/event/Event.java @@ -0,0 +1,31 @@ +/* + * 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.event; + +import java.util.EventObject; + +/** + * The root event class for all OWNER events. + * + * @author Luigi R. Viggiano + * @since 1.0.4 + */ +public class Event extends EventObject { + + /** + * Constructs a prototypical Event. + * + * @param source The object on which the Event initially occurred. + * @throws IllegalArgumentException if source is null. + */ + public Event(Object source) { + super(source); + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/event/ReloadEvent.java b/src/Java/miscutil/core/util/aeonbits/owner/event/ReloadEvent.java new file mode 100644 index 0000000000..fcb37434cd --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/event/ReloadEvent.java @@ -0,0 +1,77 @@ +/* + * 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.event; + +import java.beans.PropertyChangeEvent; +import java.util.List; +import java.util.Properties; + +import static java.util.Collections.unmodifiableList; + +/** + * A semantic event which indicates that a reload occurred. + * + * @author Luigi R. Viggiano + * @see ReloadListener + * @since 1.0.4 + */ +public class ReloadEvent extends Event { + + private final List<PropertyChangeEvent> events; + private final Properties oldProperties; + private final Properties newProperties; + + /** + * Constructs a prototypical Event. + * + * @param source The object on which the Event initially occurred. + * @param events The {@link PropertyChangeEvent change events} regarding which properties have been modified + * during the reload. + * @param oldProperties the properties before the reload. + * @param newProperties the properties after the reload. + * @throws IllegalArgumentException if source is null. + */ + public ReloadEvent(Object source, List<PropertyChangeEvent> events, Properties oldProperties, + Properties newProperties) { + super(source); + this.events = unmodifiableList(events); + this.oldProperties = new UnmodifiableProperties(oldProperties); + this.newProperties = new UnmodifiableProperties(newProperties); + } + + /** + * Returns The {@link PropertyChangeEvent change events} regarding which properties have been modified during the + * reload. + * + * @return The {@link PropertyChangeEvent change events} regarding which properties have been modified during the + * reload. + */ + public List<PropertyChangeEvent> getEvents() { + return events; + } + + /** + * Returns the properties before the reload. + * + * @return the properties before the reload. + */ + public Properties getOldProperties() { + return oldProperties; + } + + /** + * Returns the properties after the reload. + * + * @return the properties after the reload. + */ + public Properties getNewProperties() { + return newProperties; + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/event/ReloadListener.java b/src/Java/miscutil/core/util/aeonbits/owner/event/ReloadListener.java new file mode 100644 index 0000000000..4342717927 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/event/ReloadListener.java @@ -0,0 +1,33 @@ +/* + * 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.event; + +import java.util.EventListener; + +/** + * The listener interface for receiving reload events. The class that is interested in processing a reload event + * implements this interface, and the object created with that class is registered with a component, using the + * component's <code>addReloadListener</code> method. When the reload event occurs, that object's + * <code>reloadPerformed</code> method is invoked. + * + * @author Luigi R. Viggiano + * @see ReloadEvent + * @since 1.0.4 + */ +public interface ReloadListener extends EventListener { + + /** + * This method is invoked after the property are reloaded. + * When this method is invoked we can assume that the changes are effective. + * + * @param event the {@link ReloadEvent event} of property reload. + */ + void reloadPerformed(ReloadEvent event); + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/event/RollbackBatchException.java b/src/Java/miscutil/core/util/aeonbits/owner/event/RollbackBatchException.java new file mode 100644 index 0000000000..90983190a2 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/event/RollbackBatchException.java @@ -0,0 +1,37 @@ +/* + * 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.event; + +/** + * Indicates that whole batch of event must be rolled back. + * A batch is intended as a group of operations are executed in a row. + * For instance this happens when the whole list of properties is reloaded or cleared. + * + * @author Luigi R. Viggiano + * @since 1.0.5 + */ +public class RollbackBatchException extends RollbackException { + + public RollbackBatchException() { + super(); + } + + public RollbackBatchException(String message) { + super(message); + } + + public RollbackBatchException(Throwable cause) { + super(cause); + } + + public RollbackBatchException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/event/RollbackException.java b/src/Java/miscutil/core/util/aeonbits/owner/event/RollbackException.java new file mode 100644 index 0000000000..48989a009e --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/event/RollbackException.java @@ -0,0 +1,34 @@ +/* + * 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.event; + +/** + * Superclass for event rollback. + * + * @author Luigi R. Viggiano + * @since 1.0.5 + */ +public abstract class RollbackException extends Exception { + + public RollbackException() { + super(); + } + + public RollbackException(String msg) { + super(msg); + } + + public RollbackException(Throwable cause) { + super(cause); + } + + public RollbackException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/event/RollbackOperationException.java b/src/Java/miscutil/core/util/aeonbits/owner/event/RollbackOperationException.java new file mode 100644 index 0000000000..5651f2410b --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/event/RollbackOperationException.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.event; + +/** + * Indicates that operation must be rolled back. + * + * @author Luigi R. Viggiano + * @since 1.0.5 + */ +public class RollbackOperationException extends RollbackException { + + public RollbackOperationException() { + super(); + } + + public RollbackOperationException(String message) { + super(message); + } + + public RollbackOperationException(Throwable cause) { + super(cause); + } + + public RollbackOperationException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/event/TransactionalPropertyChangeListener.java b/src/Java/miscutil/core/util/aeonbits/owner/event/TransactionalPropertyChangeListener.java new file mode 100644 index 0000000000..63b8787d21 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/event/TransactionalPropertyChangeListener.java @@ -0,0 +1,33 @@ +/* + * 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.event; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +/** + * A Listener that is aware of properties changes, with transactional capability. + * + * @since 1.0.5 + * @author Luigi R. Viggiano + */ +public interface TransactionalPropertyChangeListener extends PropertyChangeListener { + + /** + * This method is invoked before the property is changed. When this method is invoked we cannot assume that the + * change is effective, since some listener can ask to roll back the change operation. + * + * @param event the {@link PropertyChangeEvent event} of property change. + * @throws RollbackOperationException when the listener wants to rollback the change on the property intercepted + * @throws RollbackBatchException when the listener wants to rollback the entire set of changes if executed in + * the batch. + */ + void beforePropertyChange(PropertyChangeEvent event) throws RollbackOperationException, RollbackBatchException; + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/event/TransactionalReloadListener.java b/src/Java/miscutil/core/util/aeonbits/owner/event/TransactionalReloadListener.java new file mode 100644 index 0000000000..ac376122d3 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/event/TransactionalReloadListener.java @@ -0,0 +1,28 @@ +/* + * 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.event; + +/** + * A Listener that is aware of properties reloads, with transactional capability. + * + * @since 1.0.5 + * @author Luigi R. Viggiano + */ +public interface TransactionalReloadListener extends ReloadListener { + + /** + * This method is invoked before the property are reloaded. When this method is invoked we cannot assume that the + * changes are effective, since some listener can ask to roll back the change. + * + * @param event the {@link ReloadEvent event} of property reload. + * @throws RollbackBatchException when the listener wants to rollback the entire reload. + */ + void beforeReload(ReloadEvent event) throws RollbackBatchException; + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/event/UnmodifiableProperties.java b/src/Java/miscutil/core/util/aeonbits/owner/event/UnmodifiableProperties.java new file mode 100644 index 0000000000..7fb08bb26b --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/event/UnmodifiableProperties.java @@ -0,0 +1,64 @@ +/* + * 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.event; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; + +import static java.util.Collections.unmodifiableCollection; +import static java.util.Collections.unmodifiableSet; + +/** + * @author Luigi R. Viggiano + */ +class UnmodifiableProperties extends Properties { + + public UnmodifiableProperties(Properties properties) { + fill(properties); + } + + private void fill(Map<?, ?> t) { + for (Map.Entry<?, ?> e : t.entrySet()) + super.put(e.getKey(), e.getValue()); + } + + @Override + public synchronized Object put(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public synchronized Object remove(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public synchronized void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public Set<Object> keySet() { + return unmodifiableSet(super.keySet()); + } + + @Override + public Set<Entry<Object, Object>> entrySet() { + return unmodifiableSet(super.entrySet()); + } + + @Override + public Collection<Object> values() { + return unmodifiableCollection(super.values()); + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/event/package-info.java b/src/Java/miscutil/core/util/aeonbits/owner/event/package-info.java new file mode 100644 index 0000000000..2763d038a9 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/event/package-info.java @@ -0,0 +1,17 @@ +/* + * 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. + */ + +/** + * Provides interfaces and classes for dealing with different types of events fired by OWNER components. + * + * See the {@link miscutil.core.util.aeonbits.owner.event.Event} class. Events are fired by event sources. + * An event listener registers with an event source to receive notifications about the events of a + * particular type. This package defines events and event listeners, as well as event listener adapters, + * which are convenience classes to make easier the process of writing event listeners. + */ +package miscutil.core.util.aeonbits.owner.event; diff --git a/src/Java/miscutil/core/util/aeonbits/owner/loaders/Loader.java b/src/Java/miscutil/core/util/aeonbits/owner/loaders/Loader.java new file mode 100644 index 0000000000..7a42d9b5a3 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/loaders/Loader.java @@ -0,0 +1,50 @@ +/* + * 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.loaders; + +import java.io.IOException; +import java.io.Serializable; +import java.net.URI; +import java.util.Properties; + +/** + * Defines the interface of a generic Properties loader. + * + * @author Luigi R. Viggiano + * @since 1.0.5 + */ +public interface Loader extends Serializable { + + /** + * Indicates wether this Loader does accept the URL, guessing the content type from it. + * + * @since 1.1.0 + * @param uri the URI + * @return true, if the loader is able to handle the content of the URI. + */ + boolean accept(URI uri); + + /** + * Loads the given {@link URI uri} into the given {@link Properties result} + * + * @since 1.1.0 + * @param result the resulting properties where to load the {@link URI uri} + * @param uri the {@link URI} from where to load the properties. + * @throws java.io.IOException if there is some I/O error during the load. + */ + void load(Properties result, URI uri) throws IOException; + + /** + * Returns the default URI specification for a given url resource, that can be handled by this loader. + * + * @param uriPrefix the prefix identifying the url resource. + * @return the default URI specification for a given uri resource, that can be handled by this loader. + */ + String defaultSpecFor(String uriPrefix); +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/loaders/PropertiesLoader.java b/src/Java/miscutil/core/util/aeonbits/owner/loaders/PropertiesLoader.java new file mode 100644 index 0000000000..6eeea0439a --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/loaders/PropertiesLoader.java @@ -0,0 +1,57 @@ +/* + * 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.loaders; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.Properties; + +/** + * A {@link Loader loader} able to read properties from standard Java properties files. + * + * @since 1.0.5 + * @author Luigi R. Viggiano + */ +public class PropertiesLoader implements Loader { + + private static final long serialVersionUID = -1781643040589572341L; + private static final String DEFAULT_ENCODING = "UTF-8"; + + public boolean accept(URI uri) { + try { + uri.toURL(); + return true; + } catch (MalformedURLException ex) { + return false; + } + } + + public void load(Properties result, URI uri) throws IOException { + URL url = uri.toURL(); + InputStream input = url.openStream(); + try { + load(result, input); + } finally { + input.close(); + } + } + + void load(Properties result, InputStream input) throws IOException { + result.load(new InputStreamReader(input, DEFAULT_ENCODING)); + } + + public String defaultSpecFor(String uriPrefix) { + return uriPrefix + ".properties"; + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/loaders/XMLLoader.java b/src/Java/miscutil/core/util/aeonbits/owner/loaders/XMLLoader.java new file mode 100644 index 0000000000..ac8b8bedcd --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/loaders/XMLLoader.java @@ -0,0 +1,162 @@ +/* + * 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.loaders; + +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.ext.DefaultHandler2; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.Properties; +import java.util.Stack; + +/** + * A {@link Loader loader} able to read properties from standard XML Java properties files, as well as user defined + * XML properties files. + * + * @since 1.0.5 + * @author Luigi R. Viggiano + */ +public class XMLLoader implements Loader { + + private static final long serialVersionUID = -894351666332018767L; + private transient volatile SAXParserFactory factory = null; + + private SAXParserFactory factory() { + if (factory == null) { + synchronized (this) { + if (factory == null) { + factory = SAXParserFactory.newInstance(); + factory.setValidating(true); + factory.setNamespaceAware(true); + } + } + } + return factory; + } + + static class XmlToPropsHandler extends DefaultHandler2 { + + private static final String PROPS_DTD_URI = + "http://java.sun.com/dtd/properties.dtd"; + + private static final String PROPS_DTD = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + + "<!-- DTD for properties -->" + + "<!ELEMENT properties ( comment?, entry* ) >" + + "<!ATTLIST properties version CDATA #FIXED \"1.0\">" + + "<!ELEMENT comment (#PCDATA) >" + + "<!ELEMENT entry (#PCDATA) >" + + "<!ATTLIST entry key CDATA #REQUIRED>"; + + private boolean isJavaPropertiesFormat = false; + private final Properties props; + private final Stack<String> paths = new Stack<String>(); + private final Stack<StringBuilder> value = new Stack<StringBuilder>(); + + @Override + public InputSource resolveEntity(String name, String publicId, String baseURI, + String systemId) throws SAXException, IOException { + InputSource inputSource = null; + if (systemId.equals(PROPS_DTD_URI)) { + isJavaPropertiesFormat = true; + inputSource = new InputSource(new StringReader(PROPS_DTD)); + inputSource.setSystemId(PROPS_DTD_URI); + } + return inputSource; + } + + public XmlToPropsHandler(Properties props) { + this.props = props; + } + + @Override + public void startElement(String uri, String localName, String qName, + Attributes attributes) throws SAXException { + value.push(new StringBuilder()); + + if (isJavaPropertiesFormat) { + if ("entry".equals(qName)) + paths.push(attributes.getValue("key")); + else + paths.push(qName); + } else { + String path = (paths.size() == 0) ? qName : paths.peek() + "." + qName; + paths.push(path); + for (int i = 0; i < attributes.getLength(); i++) { + String attrName = attributes.getQName(i); + String attrValue = attributes.getValue(i); + props.setProperty(path + "." + attrName, attrValue); + } + } + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + value.peek().append(new String(ch, start, length)); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + String key = paths.peek(); + String propertyValue = this.value.peek().toString().trim(); + if (!propertyValue.isEmpty() && + !(isJavaPropertiesFormat && "comment".equals(key))) + props.setProperty(key, propertyValue); + value.pop(); + paths.pop(); + } + + @Override + public void error(SAXParseException e) throws SAXException { + if (isJavaPropertiesFormat) + throw e; + } + } + + public boolean accept(URI uri) { + try { + URL url = uri.toURL(); + return url.getFile().toLowerCase().endsWith(".xml"); + } catch (MalformedURLException ex) { + return false; + } + } + + public void load(Properties result, URI uri) throws IOException { + InputStream input = uri.toURL().openStream(); + try { + SAXParser parser = factory().newSAXParser(); + XmlToPropsHandler h = new XmlToPropsHandler(result); + parser.setProperty("http://xml.org/sax/properties/lexical-handler", h); + parser.parse(input, h); + } catch (ParserConfigurationException e) { + throw new IllegalArgumentException(e); + } catch (SAXException e) { + throw new IOException(e); + } finally { + input.close(); + } + } + + public String defaultSpecFor(String urlPrefix) { + return urlPrefix + ".xml"; + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/loaders/package-info.java b/src/Java/miscutil/core/util/aeonbits/owner/loaders/package-info.java new file mode 100644 index 0000000000..eefaa5b7cc --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/loaders/package-info.java @@ -0,0 +1,12 @@ +/* + * 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. + */ + +/** + * Provides interfaces and classes to allow OWNER to load properties from several file formats. + */ +package miscutil.core.util.aeonbits.owner.loaders; diff --git a/src/Java/miscutil/core/util/aeonbits/owner/package-info.java b/src/Java/miscutil/core/util/aeonbits/owner/package-info.java new file mode 100644 index 0000000000..199b3dd4bb --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/package-info.java @@ -0,0 +1,51 @@ +/* + * 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. + */ + +/** + * <p> + * The goal of OWNER API is to minimize the code required to handle application configuration through Java properties + * files. + * </p> + * + * <p> + * The approach used by OWNER APIs, is to define a Java interface associated to a properties file. + * </p> + * + * Suppose your properties file is defined as ServerConfig.properties: + * + * <pre> + * port=80 + * hostname=foobar.com + * maxThreads=100 + * </pre> + * + * To access this property you need to define a convenient Java interface in ServerConfig.java: + * + * <pre> + * public interface ServerConfig extends Config { + * int port(); + * String hostname(); + * + * @DefaultValue("42"); + * int maxThreads(); + * } + * </pre> + * + * <p> + * We'll call this interface the Properties Mapping Interface or just Mapping Interface since its goal is to map + * Properties into a an easy to use piece of code. + * </p> + * + * <p> + * Owner has a lot of features and its behavior is fully customizable to your needs. + * </p> + * <p> + * Have a look at the full documentation from the <a href="http://owner.aeonbits.org/" target="_top">OWNER website</a>. + * </p> + */ +package miscutil.core.util.aeonbits.owner; diff --git a/src/Java/miscutil/core/util/aeonbits/owner/util/Collections.java b/src/Java/miscutil/core/util/aeonbits/owner/util/Collections.java new file mode 100644 index 0000000000..b31611bb43 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/util/Collections.java @@ -0,0 +1,81 @@ +/* + * 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.util; + +import java.io.Serializable; +import java.util.AbstractMap; +import java.util.AbstractMap.SimpleEntry; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import static java.util.Arrays.asList; + +/** + * Utility class to create a maps, lists and sets + * <p> Examples of usage: </p> + * <pre> + * import static org.aeonbits.owner.util.Collections.map; + * import static org.aeonbits.owner.util.Collections.entry; + * import static org.aeonbits.owner.util.Collections.set; + * import static org.aeonbits.owner.util.Collections.list; + * + * Map<String, String> myMap = map("foo", "bar"); + * Map<String, String> myMap2 = map(entry("foo", "bar"), entry("baz", "qux"); + * Set<String> mySet = set("foo", "bar", "baz", "qux"); + * List<String> myList = list("foo", "bar", "baz", "qux"); + * </pre> + * + * @author Luigi R. Viggiano + * @since 1.0.6 + */ +public abstract class Collections { + + // Suppresses default constructor, ensuring non-instantiability. + private Collections() {} + + private static final class EntryMap<K, V> extends AbstractMap<K, V> implements Serializable { + private static final long serialVersionUID = -789853606407653214L; + private final Set<Entry<? extends K, ? extends V>> entries; + + private EntryMap(Entry<? extends K, ? extends V>... entries) { + this.entries = set(entries); + } + + @SuppressWarnings("unchecked") + @Override + public Set<Entry<K, V>> entrySet() { + return (Set) entries; + } + } + + public static <K, V> Entry<K, V> entry(K key, V value) { + return new SimpleEntry<K, V>(key, value); + } + + @SuppressWarnings("unchecked") + public static <K, V> Map<K, V> map(K key, V value) { + return map(entry(key, value)); + } + + public static <K, V> Map<K, V> map(Map.Entry<? extends K, ? extends V>... entries) { + return new EntryMap<K, V>(entries); + } + + public static <E> Set<E> set(E... elements) { + return new LinkedHashSet<E>(list(elements)); + } + + public static <E> List<E> list(E... elements) { + return asList(elements); + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/util/Reflection.java b/src/Java/miscutil/core/util/aeonbits/owner/util/Reflection.java new file mode 100644 index 0000000000..1ba3902b45 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/util/Reflection.java @@ -0,0 +1,68 @@ +/* + * 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.util; + +import java.lang.reflect.Method; + +/** + * @author Luigi R. Viggiano + */ +public final class Reflection { + + // Suppresses default LOOKUP_CONSTRUCTOR, ensuring non-instantiability. + private Reflection() { + } + + public static boolean isClassAvailable(String className) { + try { + Class.forName(className); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + + interface Java8Support { + boolean isDefault(Method method); + + Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable; + } + + private static final Java8Support JAVA_8_SUPPORT = getJava8Support(); + + private static Java8Support getJava8Support() { + try { + return (Java8Support) Class.forName("org.aeonbits.owner.util.Java8SupportImpl").newInstance(); + } catch (Exception e) { + return java8NotSupported(); + } + } + + private static Java8Support java8NotSupported() { + return new Java8Support() { + public boolean isDefault(Method method) { + return false; + } + + public Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable { + return null; + } + }; + } + + + public static boolean isDefault(Method method) { + return JAVA_8_SUPPORT.isDefault(method); + } + + public static Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable { + return JAVA_8_SUPPORT.invokeDefaultMethod(proxy, method, args); + } + +} diff --git a/src/Java/miscutil/core/util/aeonbits/owner/util/package-info.java b/src/Java/miscutil/core/util/aeonbits/owner/util/package-info.java new file mode 100644 index 0000000000..3707866c16 --- /dev/null +++ b/src/Java/miscutil/core/util/aeonbits/owner/util/package-info.java @@ -0,0 +1,12 @@ +/* + * 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. + */ + +/** + * Provides utility interfaces and classes. + */ +package miscutil.core.util.aeonbits.owner.util; diff --git a/src/Java/miscutil/core/xmod/gregtech/common/tileentities/machines/multi/GregtechMetaTileEntityIndustrialCentrifuge.java b/src/Java/miscutil/core/xmod/gregtech/common/tileentities/machines/multi/GregtechMetaTileEntityIndustrialCentrifuge.java index e0c1376d45..edc2981a53 100644 --- a/src/Java/miscutil/core/xmod/gregtech/common/tileentities/machines/multi/GregtechMetaTileEntityIndustrialCentrifuge.java +++ b/src/Java/miscutil/core/xmod/gregtech/common/tileentities/machines/multi/GregtechMetaTileEntityIndustrialCentrifuge.java @@ -13,21 +13,19 @@ import gregtech.api.util.GT_Utility; import java.util.ArrayList; import java.util.Arrays; -import java.util.List; import miscutil.core.block.ModBlocks; import miscutil.core.lib.CORE; import miscutil.core.util.Utils; import miscutil.core.xmod.gregtech.api.gui.GUI_MultiMachine; import miscutil.core.xmod.gregtech.api.metatileentity.implementations.base.GregtechMeta_MultiBlockBase; +import miscutil.core.xmod.gregtech.api.util.GregtechRecipe; import net.minecraft.block.Block; import net.minecraft.entity.player.InventoryPlayer; import net.minecraft.item.ItemStack; import net.minecraftforge.common.util.ForgeDirection; import net.minecraftforge.fluids.FluidStack; -import org.apache.commons.lang3.ArrayUtils; - public class GregtechMetaTileEntityIndustrialCentrifuge extends GregtechMeta_MultiBlockBase { private static boolean controller; @@ -104,129 +102,67 @@ extends GregtechMeta_MultiBlockBase { @Override public boolean checkRecipe(ItemStack aStack) { - long tVoltage = getMaxInputVoltage(); - byte tTier = (byte) Math.max(1, GT_Utility.getTier(tVoltage)); - - GT_Recipe.GT_Recipe_Map map = getRecipeMap(); - for (int i = 0; i < tInputList.size() - 1; i++) { - for (int j = i + 1; j < tInputList.size(); j++) { - if (GT_Utility.areStacksEqual((ItemStack) tInputList.get(i), (ItemStack) tInputList.get(j))) { - if (((ItemStack) tInputList.get(i)).stackSize >= ((ItemStack) tInputList.get(j)).stackSize) { - tInputList.remove(j--); - } else { - tInputList.remove(i--); - break; - } - } - } - } - ItemStack[] tInputs = (ItemStack[]) Arrays.copyOfRange(tInputList.toArray(new ItemStack[tInputList.size()]), 0, 2); - - ArrayList<FluidStack> tFluidList = getStoredFluids(); - for (int i = 0; i < tFluidList.size() - 1; i++) { - for (int j = i + 1; j < tFluidList.size(); j++) { - if (GT_Utility.areFluidsEqual((FluidStack) tFluidList.get(i), (FluidStack) tFluidList.get(j))) { - if (((FluidStack) tFluidList.get(i)).amount >= ((FluidStack) tFluidList.get(j)).amount) { - tFluidList.remove(j--); - } else { - tFluidList.remove(i--); - break; - } - } - } - } - FluidStack[] tFluids = (FluidStack[]) Arrays.copyOfRange(tFluidList.toArray(new FluidStack[tInputList.size()]), 0, 1); - if (tInputList.size() > 0 || tFluids.length > 0) { - GT_Recipe tRecipe = map.findRecipe(getBaseMetaTileEntity(), mLastRecipe, false, gregtech.api.enums.GT_Values.V[tTier], tFluids, tInputs); - if (tRecipe != null) { - if (tRecipe.mFluidInputs != null) { + ArrayList<ItemStack> tInputList = getStoredInputs(); + for (int i = 0; i < tInputList.size() - 1; i++) { + for (int j = i + 1; j < tInputList.size(); j++) { + if (GT_Utility.areStacksEqual((ItemStack) tInputList.get(i), (ItemStack) tInputList.get(j))) { + if (((ItemStack) tInputList.get(i)).stackSize >= ((ItemStack) tInputList.get(j)).stackSize) { + tInputList.remove(j--); + } else { + tInputList.remove(i--); + break; + } + } + } + } + ItemStack[] tInputs = (ItemStack[]) Arrays.copyOfRange(tInputList.toArray(new ItemStack[tInputList.size()]), 0, 2); - } - mLastRecipe = tRecipe; - this.mEUt = 0; - this.mOutputItems = null; - this.mOutputFluids = null; - int machines = Math.min(16, mInventory[1].stackSize); - int i = 0; - for (; i < machines; i++) { - if (!tRecipe.isRecipeInputEqual(true, tFluids, tInputs)) { - if (i == 0) { - Utils.LOG_INFO("false 1"); - return false; - } - break; - } - } - this.mMaxProgresstime = tRecipe.mDuration; - this.mEfficiency = (10000 - (getIdealStatus() - getRepairStatus()) * 1000); - this.mEfficiencyIncrease = 10000; - if (tRecipe.mEUt <= 16) { - this.mEUt = (tRecipe.mEUt * (1 << tTier - 1) * (1 << tTier - 1)); - this.mMaxProgresstime = (tRecipe.mDuration / (1 << tTier - 1)); - } else { - this.mEUt = tRecipe.mEUt; - this.mMaxProgresstime = tRecipe.mDuration; - while (this.mEUt <= gregtech.api.enums.GT_Values.V[(tTier - 1)]) { - this.mEUt *= 4; - this.mMaxProgresstime /= 4; - } - } - this.mEUt *= i; - if (this.mEUt > 0) { - this.mEUt = (-this.mEUt); - } - ItemStack[] tOut = new ItemStack[tRecipe.mOutputs.length]; - for (int h = 0; h < tRecipe.mOutputs.length; h++) { - tOut[h] = tRecipe.getOutput(h).copy(); - tOut[h].stackSize = 0; - } - FluidStack tFOut = null; - if (tRecipe.getFluidOutput(0) != null) tFOut = tRecipe.getFluidOutput(0).copy(); - for (int f = 0; f < tOut.length; f++) { - if (tRecipe.mOutputs[f] != null && tOut[f] != null) { - for (int g = 0; g < i; g++) { - if (getBaseMetaTileEntity().getRandomNumber(10000) < tRecipe.getOutputChance(f)) - tOut[f].stackSize += tRecipe.mOutputs[f].stackSize; - } - } - } - if (tFOut != null) { - int tSize = tFOut.amount; - tFOut.amount = tSize * i; - } - this.mMaxProgresstime = Math.max(1, this.mMaxProgresstime); - List<ItemStack> overStacks = new ArrayList<ItemStack>(); - for (int f = 0; f < tOut.length; f++) { - if (tOut[f].getMaxStackSize() < tOut[f].stackSize) { - while (tOut[f].getMaxStackSize() < tOut[f].stackSize) { - ItemStack tmp = tOut[f].copy(); - tmp.stackSize = tmp.getMaxStackSize(); - tOut[f].stackSize = tOut[f].stackSize - tOut[f].getMaxStackSize(); - overStacks.add(tmp); - } - } - } - if (overStacks.size() > 0) { - ItemStack[] tmp = new ItemStack[overStacks.size()]; - tmp = overStacks.toArray(tmp); - tOut = ArrayUtils.addAll(tOut, tmp); - } - List<ItemStack> tSList = new ArrayList<ItemStack>(); - for (ItemStack tS : tOut) { - if (tS.stackSize > 0) tSList.add(tS); - } - tOut = tSList.toArray(new ItemStack[tSList.size()]); - this.mOutputItems = tOut; - this.mOutputFluids = new FluidStack[]{tFOut}; - updateSlots(); - //recipesComplete++; - Utils.LOG_INFO("true 1"); - return true; - } - } - Utils.LOG_INFO("false 2"); - return false; - } + ArrayList<FluidStack> tFluidList = getStoredFluids(); + for (int i = 0; i < tFluidList.size() - 1; i++) { + for (int j = i + 1; j < tFluidList.size(); j++) { + if (GT_Utility.areFluidsEqual((FluidStack) tFluidList.get(i), (FluidStack) tFluidList.get(j))) { + if (((FluidStack) tFluidList.get(i)).amount >= ((FluidStack) tFluidList.get(j)).amount) { + tFluidList.remove(j--); + } else { + tFluidList.remove(i--); + break; + } + } + } + } + FluidStack[] tFluids = (FluidStack[]) Arrays.copyOfRange(tFluidList.toArray(new FluidStack[tInputList.size()]), 0, 1); + if (tInputList.size() > 0) { + long tVoltage = getMaxInputVoltage(); + byte tTier = (byte) Math.max(1, GT_Utility.getTier(tVoltage)); + GT_Recipe tRecipe = GregtechRecipe.Gregtech_Recipe_Map.sCokeOvenRecipes.findRecipe(getBaseMetaTileEntity(), false, gregtech.api.enums.GT_Values.V[tTier], tFluids, tInputs); + if ((tRecipe != null) && (3 >= tRecipe.mSpecialValue) && (tRecipe.isRecipeInputEqual(true, tFluids, tInputs))) { + this.mEfficiency = (10000 - (getIdealStatus() - getRepairStatus()) * 1000); + this.mEfficiencyIncrease = 10000; + if (tRecipe.mEUt <= 16) { + this.mEUt = (tRecipe.mEUt * (1 << tTier - 1) * (1 << tTier - 1)); + this.mMaxProgresstime = (tRecipe.mDuration / (1 << tTier - 1)); + } else { + this.mEUt = tRecipe.mEUt; + this.mMaxProgresstime = tRecipe.mDuration; + while (this.mEUt <= gregtech.api.enums.GT_Values.V[(tTier - 1)]) { + this.mEUt *= 4; + this.mMaxProgresstime /= 2; + } + } + if (this.mEUt > 0) { + this.mEUt = (-this.mEUt); + } + this.mMaxProgresstime = Math.max(1, this.mMaxProgresstime); + this.mOutputItems = new ItemStack[]{tRecipe.getOutput(0)}; + this.mOutputFluids = new FluidStack[]{tRecipe.getFluidOutput(0)}; + updateSlots(); + Utils.LOG_INFO("Centrifuge: True"); + return true; + } + } + Utils.LOG_INFO("Centrifuge: False"); + return false; + } @SuppressWarnings("static-method") public Block getCasingBlock() { |