aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Java/miscutil/core/lib/CORE.java2
-rw-r--r--src/Java/miscutil/core/lib/ConfigHandler.java14
-rw-r--r--src/Java/miscutil/core/lib/LoadedMods.java2
-rw-r--r--src/Java/miscutil/core/recipe/Gregtech_Recipe_Adder.java98
-rw-r--r--src/Java/miscutil/core/recipe/RECIPES_GREGTECH.java36
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/Accessible.java156
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/Config.java345
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/ConfigCache.java145
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/ConfigFactory.java136
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/ConfigURIFactory.java53
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/Converter.java35
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/Converters.java314
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/DefaultFactory.java103
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/Delegate.java20
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/DelegateMethodHandle.java41
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/Factory.java91
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/HotReloadLogic.java103
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/JMXSupport.java109
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/LoadersManager.java95
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/Mutable.java118
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/Preprocessor.java20
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/PreprocessorResolver.java47
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/PropertiesInvocationHandler.java141
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/PropertiesManager.java584
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/PropertiesMapper.java47
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/Reloadable.java70
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/SplitAndTrimTokenizer.java33
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/StrSubstitutor.java86
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/Tokenizer.java29
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/TokenizerResolver.java76
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/Util.java266
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/VariablesExpander.java41
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/event/Event.java31
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/event/ReloadEvent.java77
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/event/ReloadListener.java33
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/event/RollbackBatchException.java37
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/event/RollbackException.java34
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/event/RollbackOperationException.java35
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/event/TransactionalPropertyChangeListener.java33
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/event/TransactionalReloadListener.java28
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/event/UnmodifiableProperties.java64
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/event/package-info.java17
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/loaders/Loader.java50
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/loaders/PropertiesLoader.java57
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/loaders/XMLLoader.java162
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/loaders/package-info.java12
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/package-info.java51
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/util/Collections.java81
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/util/Reflection.java68
-rw-r--r--src/Java/miscutil/core/util/aeonbits/owner/util/package-info.java12
-rw-r--r--src/Java/miscutil/core/xmod/gregtech/common/tileentities/machines/multi/GregtechMetaTileEntityIndustrialCentrifuge.java186
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>
+ * &#64;HotReload // will check for file changes every 5 seconds.
+ * &#64;Sources("file:foo/bar/baz.properties")
+ * interface MyConfig extends Config { ... }
+ *
+ * &#64;HotReload(2) // will check for file changes every 2 seconds.
+ * &#64;Sources("file:foo/bar/baz.properties")
+ * interface MyConfig extends Config { ... }
+ *
+ * &#64;HotReload(500, unit = TimeUnit.MILLISECONDS); // will check for file changes every 500 milliseconds.
+ * &#64;Sources("file:foo/bar/baz.properties")
+ * interface MyConfig extends Config { ... }
+ *
+ * &#64;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).
+ * &#64;Sources("file:foo/bar/baz.properties")
+ * interface MyConfig extends Config { ... }
+ *
+ * &#64;HotReload(2, type=HotReloadType.ASYNC); // will use ASYNC reload type and will check every 2 seconds.
+ * &#64;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 {
+ * &#64;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(&quot;animal&quot;, &quot;quick brown fox&quot;);
+ * valuesMap.put(&quot;target&quot;, &quot;lazy dog&quot;);
+ * String templateString = &quot;The ${animal} jumped over the ${target}.&quot;;
+ * 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();
+ *
+ * &#64;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&lt;String, String&gt; myMap = map("foo", "bar");
+ * Map&lt;String, String&gt; myMap2 = map(entry("foo", "bar"), entry("baz", "qux");
+ * Set&lt;String&gt; mySet = set("foo", "bar", "baz", "qux");
+ * List&lt;String&gt; 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() {