diff --git a/config/src/main/java/com/typesafe/config/BeanFactory.java b/config/src/main/java/com/typesafe/config/BeanFactory.java deleted file mode 100644 index 5da0bacc..00000000 --- a/config/src/main/java/com/typesafe/config/BeanFactory.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.typesafe.config; - -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; - -import com.typesafe.config.Config; -import com.typesafe.config.ConfigException; - -/** - * Sent as pull request to config project. - * https://github.com/typesafehub/config/pull/107 - */ -public class BeanFactory { - - public static T create(Config config, Class clazz) { - return create(config.root().unwrapped(), clazz); - } - - private static T create(Map config, Class clazz) { - Map configProps = new HashMap(); - for (Map.Entry configProp : config.entrySet()) { - configProps.put(toCamelCase(configProp.getKey()), configProp.getValue()); - } - - try { - T bean = clazz.newInstance(); - for (PropertyDescriptor beanProp : Introspector.getBeanInfo(clazz).getPropertyDescriptors()) { - if (beanProp.getReadMethod() == null || beanProp.getWriteMethod() == null) { - continue; - } - Method setter = beanProp.getWriteMethod(); - Object configValue = configProps.get(beanProp.getName()); - if (configValue == null) { - throw new ConfigException.Generic( - "Could not find " + beanProp.getName() + " from " + clazz.getName() + " in config."); - } - if (configValue instanceof Map) { - @SuppressWarnings("unchecked") - Map child = ((Map) configValue); - configValue = create(child, beanProp.getPropertyType()); - } - - setter.invoke(bean, configValue); - } - - return bean; - } catch (IntrospectionException e) { - throw new ConfigException.Generic("Could not resolve a string method name.", e); - } catch (InstantiationException e) { - throw new ConfigException.Generic(clazz + " needs a public no args constructor", e); - } catch (IllegalAccessException e) { - throw new ConfigException.Generic(clazz + " getters and setters are not accessible", e); - } catch (InvocationTargetException e) { - throw new ConfigException.Generic("Calling bean method caused exception", e); - } - } - - /** - * Converts from hyphenated name to camel case. - */ - protected static String toCamelCase(String originalName) { - String[] words = originalName.split("-+"); - StringBuilder nameBuilder = new StringBuilder(originalName.length()); - for (int i = 0; i < words.length; i++) { - if (nameBuilder.length() == 0) { - nameBuilder.append(words[i]); - } else { - nameBuilder.append(words[i].substring(0, 1).toUpperCase()); - nameBuilder.append(words[i].substring(1)); - } - } - return nameBuilder.toString(); - } - -} diff --git a/config/src/main/java/com/typesafe/config/ConfigBeanFactory.java b/config/src/main/java/com/typesafe/config/ConfigBeanFactory.java new file mode 100644 index 00000000..d432a8f9 --- /dev/null +++ b/config/src/main/java/com/typesafe/config/ConfigBeanFactory.java @@ -0,0 +1,131 @@ +package com.typesafe.config; + +import com.typesafe.config.impl.DurationUnit; +import com.typesafe.config.impl.MemoryUnit; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * Factory for automatic creation of config classes populated with values from config. + * + * Example usage: + * + * Config configSource = ConfigFactory.parseReader(new InputStreamReader("converters.conf")); + * ConverterConfig config = ConfigBeanFactory.create(configSource,ConverterConfig.class); + * + * Supports nested configs. + * Supports automatic types conversion + * (https://github.com/typesafehub/config/blob/master/HOCON.md#automatic-type-conversions). + */ +public class ConfigBeanFactory { + + /** + * Creates instance of class containing configuration info from config source + * @param config - source of config information + * @param clazz - class to be created + * @param + * @return - instance of config class populated with data from config source + */ + public static T create(Config config, Class clazz) { + return createInternal(config, clazz); + } + + private static T createInternal(Config config, Class clazz) { + Map configAsMap = config.root().unwrapped(); + Map configProps = new HashMap(); + Map originalNames = new HashMap(); + for (Map.Entry configProp : configAsMap.entrySet()) { + configProps.put(toCamelCase(configProp.getKey()), configProp.getValue()); + originalNames.put(toCamelCase(configProp.getKey()),configProp.getKey()); + } + + BeanInfo beanInfo = null; + try { + beanInfo = Introspector.getBeanInfo(clazz); + } catch(IntrospectionException e) { + throw new ConfigException.Generic("Could not get bean information for class " + clazz.getName(), e); + } + + try { + T bean = clazz.newInstance(); + for (PropertyDescriptor beanProp : beanInfo.getPropertyDescriptors()) { + if (beanProp.getReadMethod() == null || beanProp.getWriteMethod() == null) { + continue; + } + Method setter = beanProp.getWriteMethod(); + Object configValue = configProps.get(beanProp.getName()); + if (configValue == null) { + throw new ConfigException.Generic( + "Could not find property '" + beanProp.getName() + "' from class '" + clazz.getName() + "' in config."); + } + if (configValue instanceof Map) { + configValue = createInternal(config.getConfig(originalNames.get(beanProp.getDisplayName())), beanProp.getPropertyType()); + } else { + Class parameterClass = setter.getParameterTypes()[0]; + configValue = getValueWithAutoConversion(parameterClass, config, originalNames.get(beanProp.getDisplayName())); + } + setter.invoke(bean, configValue); + + } + return bean; + } catch (InstantiationException e) { + throw new ConfigException.Generic(clazz + " needs a public no args constructor", e); + } catch (IllegalAccessException e) { + throw new ConfigException.Generic(clazz + " getters and setters are not accessible", e); + } catch (InvocationTargetException e) { + throw new ConfigException.Generic("Calling bean method caused exception", e); + } + } + + private static Object getValueWithAutoConversion(Class parameterClass, Config config, String configPropName) { + if (parameterClass == Boolean.class || parameterClass == boolean.class) { + return config.getBoolean(configPropName); + } else if (parameterClass == Byte.class || parameterClass == byte.class) { + return Integer.valueOf(config.getInt(configPropName)).byteValue(); + } else if (parameterClass == Short.class || parameterClass == short.class) { + return Integer.valueOf(config.getInt(configPropName)).shortValue(); + } else if (parameterClass == Integer.class || parameterClass == int.class) { + return config.getInt(configPropName); + } else if (parameterClass == Double.class || parameterClass == double.class) { + return config.getDouble(configPropName); + } else if (parameterClass == Long.class || parameterClass == long.class) { + String rawVal = config.getString(configPropName); + if (DurationUnit.containsDurationToken(rawVal)) { + return config.getDuration(configPropName, TimeUnit.NANOSECONDS); + } else if (MemoryUnit.containsMemoryToken(rawVal)) { + return config.getBytes(configPropName); + } + return config.getLong(configPropName); + } else if (parameterClass == String.class) { + return config.getString(configPropName); + } + + return config.getAnyRef(configPropName); + } + + /** + * Converts from hyphenated name to camel case. + */ + static String toCamelCase(String originalName) { + String[] words = originalName.split("-+"); + StringBuilder nameBuilder = new StringBuilder(originalName.length()); + for (int i = 0; i < words.length; i++) { + if (nameBuilder.length() == 0) { + nameBuilder.append(words[i]); + } else { + nameBuilder.append(words[i].substring(0, 1).toUpperCase()); + nameBuilder.append(words[i].substring(1)); + } + } + return nameBuilder.toString(); + } + +} diff --git a/config/src/main/java/com/typesafe/config/impl/DurationUnit.java b/config/src/main/java/com/typesafe/config/impl/DurationUnit.java new file mode 100644 index 00000000..d64e986c --- /dev/null +++ b/config/src/main/java/com/typesafe/config/impl/DurationUnit.java @@ -0,0 +1,58 @@ +package com.typesafe.config.impl; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * https://github.com/typesafehub/config/blob/master/HOCON.md#duration-format + */ +public enum DurationUnit { + + + NANOSECONDS(TimeUnit.NANOSECONDS, Arrays.asList("ns", "nano", "nanos", "nanosecond", "nanoseconds")), + MICROSECONDS(TimeUnit.MICROSECONDS, Arrays.asList("us", "micro", "micros", "microsecond", "microseconds")), + MILLISECONDS(TimeUnit.MILLISECONDS,Arrays.asList("ms", "milli", "millis", "millisecond", "milliseconds")), + SECONDS(TimeUnit.SECONDS,Arrays.asList("s", "second", "seconds")), + MINUTES(TimeUnit.MINUTES,Arrays.asList("m", "minute", "minutes")), + HOURS(TimeUnit.HOURS,Arrays.asList("h", "hour", "hours")), + DAYS(TimeUnit.DAYS,Arrays.asList("d", "day", "days")); + + private List aliases; + private TimeUnit timeUnit; + + DurationUnit(TimeUnit timeUnit,List aliases) { + this.timeUnit = timeUnit; + this.aliases = aliases; + } + + /** + * Finds corresponding duration unit by its string representation. + * @param rawVal - string duration value ('s','ns' etc.) + * @return one of enum values or null if none applicable + */ + public static final DurationUnit fromString(String rawVal) { + for (DurationUnit durationUnit : DurationUnit.values()) { + // note that this is deliberately case-sensitive + if(durationUnit.aliases.contains(rawVal)) { + return durationUnit; + } + } + return null; + } + + /** + * Checks whether given string contains duration token. + * @param rawVal - string like '10s','45nanos' etc. + * String is case sensitive, i.e. '10S' is not treated as valid duration string + * @return true - if string contains one of enum aliases, false otherwise + */ + public static boolean containsDurationToken(String rawVal) { + String unitStr = rawVal.replaceAll("[^A-Za-z]",""); + return fromString(unitStr) != null; + } + + public TimeUnit getTimeUnit() { + return timeUnit; + } +} diff --git a/config/src/main/java/com/typesafe/config/impl/MemoryUnit.java b/config/src/main/java/com/typesafe/config/impl/MemoryUnit.java new file mode 100644 index 00000000..4c7e1294 --- /dev/null +++ b/config/src/main/java/com/typesafe/config/impl/MemoryUnit.java @@ -0,0 +1,95 @@ +package com.typesafe.config.impl; + +import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; + +/** + * https://github.com/typesafehub/config/blob/master/HOCON.md#size-in-bytes-format + */ +public enum MemoryUnit { + BYTES("", 1024, 0), + + KILOBYTES("kilo", 1000, 1), + MEGABYTES("mega", 1000, 2), + GIGABYTES("giga", 1000, 3), + TERABYTES("tera", 1000, 4), + PETABYTES("peta", 1000, 5), + EXABYTES("exa", 1000, 6), + ZETTABYTES("zetta", 1000, 7), + YOTTABYTES("yotta", 1000, 8), + + KIBIBYTES("kibi", 1024, 1), + MEBIBYTES("mebi", 1024, 2), + GIBIBYTES("gibi", 1024, 3), + TEBIBYTES("tebi", 1024, 4), + PEBIBYTES("pebi", 1024, 5), + EXBIBYTES("exbi", 1024, 6), + ZEBIBYTES("zebi", 1024, 7), + YOBIBYTES("yobi", 1024, 8); + + final String prefix; + final int powerOf; + final int power; + final BigInteger bytes; + + MemoryUnit(String prefix, int powerOf, int power) { + this.prefix = prefix; + this.powerOf = powerOf; + this.power = power; + this.bytes = BigInteger.valueOf(powerOf).pow(power); + } + + private static Map makeUnitsMap() { + Map map = new HashMap(); + for (MemoryUnit unit : MemoryUnit.values()) { + map.put(unit.prefix + "byte", unit); + map.put(unit.prefix + "bytes", unit); + if (unit.prefix.length() == 0) { + map.put("b", unit); + map.put("B", unit); + map.put("", unit); // no unit specified means bytes + } else { + String first = unit.prefix.substring(0, 1); + String firstUpper = first.toUpperCase(); + if (unit.powerOf == 1024) { + map.put(first, unit); // 512m + map.put(firstUpper, unit); // 512M + map.put(firstUpper + "i", unit); // 512Mi + map.put(firstUpper + "iB", unit); // 512MiB + } else if (unit.powerOf == 1000) { + if (unit.power == 1) { + map.put(first + "B", unit); // 512kB + } else { + map.put(firstUpper + "B", unit); // 512MB + } + } else { + throw new RuntimeException("broken MemoryUnit enum"); + } + } + } + return map; + } + + private static Map unitsMap = makeUnitsMap(); + + static MemoryUnit parseUnit(String unit) { + return unitsMap.get(unit); + } + + /** + * Checks whether given string contains memory token. + * @param rawVal - string like '10B','45bytes' etc. + * String is case sensitive, i.e. '10KILOS' is not treated as valid memory size string + * @return true - if string contains memory data, false otherwise + */ + public static boolean containsMemoryToken(String rawVal) { + String unitStr = rawVal.replaceAll("[^A-Za-z]",""); + for (String unit : unitsMap.keySet()) { + if(unit.equals(unitStr)) { + return true; + } + } + return false; + } +} diff --git a/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java b/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java index 421f98b2..34d9b941 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java @@ -547,27 +547,17 @@ final class SimpleConfig implements Config, MergeableValue, Serializable { if (unitString.length() > 2 && !unitString.endsWith("s")) unitString = unitString + "s"; - // note that this is deliberately case-sensitive - if (unitString.equals("") || unitString.equals("ms") || unitString.equals("millis") - || unitString.equals("milliseconds")) { + if(unitString.equals("")) { units = TimeUnit.MILLISECONDS; - } else if (unitString.equals("us") || unitString.equals("micros") || unitString.equals("microseconds")) { - units = TimeUnit.MICROSECONDS; - } else if (unitString.equals("ns") || unitString.equals("nanos") || unitString.equals("nanoseconds")) { - units = TimeUnit.NANOSECONDS; - } else if (unitString.equals("d") || unitString.equals("days")) { - units = TimeUnit.DAYS; - } else if (unitString.equals("h") || unitString.equals("hours")) { - units = TimeUnit.HOURS; - } else if (unitString.equals("s") || unitString.equals("seconds")) { - units = TimeUnit.SECONDS; - } else if (unitString.equals("m") || unitString.equals("minutes")) { - units = TimeUnit.MINUTES; } else { - throw new ConfigException.BadValue(originForException, + DurationUnit durationUnit = DurationUnit.fromString(unitString); + units = durationUnit != null ? durationUnit.getTimeUnit() : null; + if(units == null) { + throw new ConfigException.BadValue(originForException, pathForException, "Could not parse time unit '" + originalUnitString + "' (try ns, us, ms, s, m, h, d)"); + } } try { @@ -587,77 +577,6 @@ final class SimpleConfig implements Config, MergeableValue, Serializable { } } - private static enum MemoryUnit { - BYTES("", 1024, 0), - - KILOBYTES("kilo", 1000, 1), - MEGABYTES("mega", 1000, 2), - GIGABYTES("giga", 1000, 3), - TERABYTES("tera", 1000, 4), - PETABYTES("peta", 1000, 5), - EXABYTES("exa", 1000, 6), - ZETTABYTES("zetta", 1000, 7), - YOTTABYTES("yotta", 1000, 8), - - KIBIBYTES("kibi", 1024, 1), - MEBIBYTES("mebi", 1024, 2), - GIBIBYTES("gibi", 1024, 3), - TEBIBYTES("tebi", 1024, 4), - PEBIBYTES("pebi", 1024, 5), - EXBIBYTES("exbi", 1024, 6), - ZEBIBYTES("zebi", 1024, 7), - YOBIBYTES("yobi", 1024, 8); - - final String prefix; - final int powerOf; - final int power; - final BigInteger bytes; - - MemoryUnit(String prefix, int powerOf, int power) { - this.prefix = prefix; - this.powerOf = powerOf; - this.power = power; - this.bytes = BigInteger.valueOf(powerOf).pow(power); - } - - private static Map makeUnitsMap() { - Map map = new HashMap(); - for (MemoryUnit unit : MemoryUnit.values()) { - map.put(unit.prefix + "byte", unit); - map.put(unit.prefix + "bytes", unit); - if (unit.prefix.length() == 0) { - map.put("b", unit); - map.put("B", unit); - map.put("", unit); // no unit specified means bytes - } else { - String first = unit.prefix.substring(0, 1); - String firstUpper = first.toUpperCase(); - if (unit.powerOf == 1024) { - map.put(first, unit); // 512m - map.put(firstUpper, unit); // 512M - map.put(firstUpper + "i", unit); // 512Mi - map.put(firstUpper + "iB", unit); // 512MiB - } else if (unit.powerOf == 1000) { - if (unit.power == 1) { - map.put(first + "B", unit); // 512kB - } else { - map.put(firstUpper + "B", unit); // 512MB - } - } else { - throw new RuntimeException("broken MemoryUnit enum"); - } - } - } - return map; - } - - private static Map unitsMap = makeUnitsMap(); - - static MemoryUnit parseUnit(String unit) { - return unitsMap.get(unit); - } - } - /** * Parses a size-in-bytes string. If no units are specified in the string, * it is assumed to be in bytes. The returned value is in bytes. The purpose diff --git a/config/src/test/java/beanconfig/ArraysConfig.java b/config/src/test/java/beanconfig/ArraysConfig.java new file mode 100644 index 00000000..81096f78 --- /dev/null +++ b/config/src/test/java/beanconfig/ArraysConfig.java @@ -0,0 +1,80 @@ +package beanconfig; + +import java.util.List; + +public class ArraysConfig { + + List empty; + List ofInt; + List ofString; + List ofDouble; + List ofNull; + List ofBoolean; + List ofArray; + List ofObject; + + + public List getEmpty() { + return empty; + } + + public void setEmpty(List empty) { + this.empty = empty; + } + + public List getOfInt() { + return ofInt; + } + + public void setOfInt(List ofInt) { + this.ofInt = ofInt; + } + + public List getOfString() { + return ofString; + } + + public void setOfString(List ofString) { + this.ofString = ofString; + } + + public List getOfDouble() { + return ofDouble; + } + + public void setOfDouble(List ofDouble) { + this.ofDouble = ofDouble; + } + + public List getOfNull() { + return ofNull; + } + + public void setOfNull(List ofNull) { + this.ofNull = ofNull; + } + + public List getOfBoolean() { + return ofBoolean; + } + + public void setOfBoolean(List ofBoolean) { + this.ofBoolean = ofBoolean; + } + + public List getOfArray() { + return ofArray; + } + + public void setOfArray(List ofArray) { + this.ofArray = ofArray; + } + + public List getOfObject() { + return ofObject; + } + + public void setOfObject(List ofObject) { + this.ofObject = ofObject; + } +} diff --git a/config/src/test/java/beanconfig/BooleansConfig.java b/config/src/test/java/beanconfig/BooleansConfig.java new file mode 100644 index 00000000..f2b0111e --- /dev/null +++ b/config/src/test/java/beanconfig/BooleansConfig.java @@ -0,0 +1,59 @@ +package beanconfig; + + +public class BooleansConfig { + Boolean trueVal; + Boolean trueValAgain; + Boolean falseVal; + Boolean falseValAgain; + Boolean on; + Boolean off; + + public Boolean getTrueVal() { + return trueVal; + } + + public void setTrueVal(Boolean trueVal) { + this.trueVal = trueVal; + } + + public Boolean getTrueValAgain() { + return trueValAgain; + } + + public void setTrueValAgain(Boolean trueValAgain) { + this.trueValAgain = trueValAgain; + } + + public Boolean getFalseVal() { + return falseVal; + } + + public void setFalseVal(Boolean falseVal) { + this.falseVal = falseVal; + } + + public Boolean getFalseValAgain() { + return falseValAgain; + } + + public void setFalseValAgain(Boolean falseValAgain) { + this.falseValAgain = falseValAgain; + } + + public Boolean getOn() { + return on; + } + + public void setOn(Boolean on) { + this.on = on; + } + + public Boolean getOff() { + return off; + } + + public void setOff(Boolean off) { + this.off = off; + } +} diff --git a/config/src/test/java/beanconfig/BytesConfig.java b/config/src/test/java/beanconfig/BytesConfig.java new file mode 100644 index 00000000..bb0c08a6 --- /dev/null +++ b/config/src/test/java/beanconfig/BytesConfig.java @@ -0,0 +1,32 @@ +package beanconfig; + +public class BytesConfig { + + private Long kilobyte; + private Long kibibyte; + private Long thousandBytes; + + public Long getKilobyte() { + return kilobyte; + } + + public void setKilobyte(Long kilobyte) { + this.kilobyte = kilobyte; + } + + public Long getKibibyte() { + return kibibyte; + } + + public void setKibibyte(Long kibibyte) { + this.kibibyte = kibibyte; + } + + public Long getThousandBytes() { + return thousandBytes; + } + + public void setThousandBytes(Long thousandBytes) { + this.thousandBytes = thousandBytes; + } +} diff --git a/config/src/test/java/beanconfig/DurationsConfig.java b/config/src/test/java/beanconfig/DurationsConfig.java new file mode 100644 index 00000000..0ce9ae6b --- /dev/null +++ b/config/src/test/java/beanconfig/DurationsConfig.java @@ -0,0 +1,33 @@ +package beanconfig; + + +public class DurationsConfig { + Long second; + Long secondAsNumber; + Long halfSecond; + + + public Long getSecond() { + return second; + } + + public void setSecond(Long second) { + this.second = second; + } + + public Long getSecondAsNumber() { + return secondAsNumber; + } + + public void setSecondAsNumber(Long secondAsNumber) { + this.secondAsNumber = secondAsNumber; + } + + public Long getHalfSecond() { + return halfSecond; + } + + public void setHalfSecond(Long halfSecond) { + this.halfSecond = halfSecond; + } +} diff --git a/config/src/test/java/beanconfig/NoFoundPropBeanConfig.java b/config/src/test/java/beanconfig/NoFoundPropBeanConfig.java new file mode 100644 index 00000000..989e0c28 --- /dev/null +++ b/config/src/test/java/beanconfig/NoFoundPropBeanConfig.java @@ -0,0 +1,14 @@ +package beanconfig; + +public class NoFoundPropBeanConfig extends TestBeanConfig{ + + private String propNotListedInConfig; + + public String getPropNotListedInConfig() { + return propNotListedInConfig; + } + + public void setPropNotListedInConfig(String propNotListedInConfig) { + this.propNotListedInConfig = propNotListedInConfig; + } +} diff --git a/config/src/test/java/beanconfig/NumbersConfig.java b/config/src/test/java/beanconfig/NumbersConfig.java new file mode 100644 index 00000000..f6492557 --- /dev/null +++ b/config/src/test/java/beanconfig/NumbersConfig.java @@ -0,0 +1,98 @@ +package beanconfig; + + +public class NumbersConfig { + + + private byte byteVal; + private Byte byteObj; + private short shortVal; + private Short shortObj; + private int intVal; + private Integer intObj; + private long longVal; + private Long longObj; + private double doubleVal; + private Double doubleObj; + + + public int getIntVal() { + return intVal; + } + + public void setIntVal(int intVal) { + this.intVal = intVal; + } + + public Integer getIntObj() { + return intObj; + } + + public void setIntObj(Integer intObj) { + this.intObj = intObj; + } + + public long getLongVal() { + return longVal; + } + + public void setLongVal(long longVal) { + this.longVal = longVal; + } + + public Long getLongObj() { + return longObj; + } + + public void setLongObj(Long longObj) { + this.longObj = longObj; + } + + public double getDoubleVal() { + return doubleVal; + } + + public void setDoubleVal(double doubleVal) { + this.doubleVal = doubleVal; + } + + public Double getDoubleObj() { + return doubleObj; + } + + public void setDoubleObj(Double doubleObj) { + this.doubleObj = doubleObj; + } + + public byte getByteVal() { + return byteVal; + } + + public void setByteVal(byte byteVal) { + this.byteVal = byteVal; + } + + public Byte getByteObj() { + return byteObj; + } + + public void setByteObj(Byte byteObj) { + this.byteObj = byteObj; + } + + public short getShortVal() { + return shortVal; + } + + public void setShortVal(short shortVal) { + this.shortVal = shortVal; + } + + public Short getShortObj() { + return shortObj; + } + + public void setShortObj(Short shortObj) { + this.shortObj = shortObj; + } +} diff --git a/config/src/test/java/beanconfig/TestBeanConfig.java b/config/src/test/java/beanconfig/TestBeanConfig.java new file mode 100644 index 00000000..2f946a9b --- /dev/null +++ b/config/src/test/java/beanconfig/TestBeanConfig.java @@ -0,0 +1,15 @@ +package beanconfig; + +public class TestBeanConfig { + + + private NumbersConfig numbers; + + public NumbersConfig getNumbers() { + return numbers; + } + + public void setNumbers(NumbersConfig numbers) { + this.numbers = numbers; + } +} diff --git a/config/src/test/resources/beanconfig/beanconfig01.conf b/config/src/test/resources/beanconfig/beanconfig01.conf new file mode 100644 index 00000000..1c29f31e --- /dev/null +++ b/config/src/test/resources/beanconfig/beanconfig01.conf @@ -0,0 +1,62 @@ +{ + "booleans" : { + "trueVal" : true, + "trueValAgain" : ${booleans.trueVal}, + "falseVal" : false, + "falseValAgain" : ${booleans.falseVal}, + "on" : "on", + "off" : "off" + } + + "numbers" : { + "byteVal" : "1", + "byteObj" : ${numbers.byteVal}, + "shortVal" : "1", + "shortObj" : ${numbers.shortVal}, + "intVal" : "1", + "intObj" : ${numbers.intVal}, + "longVal" : "1", + "longObj" : ${numbers.longVal}, + "doubleVal" : "1.0", + "doubleObj" : ${numbers.doubleVal} + } +// + "strings" : { + "abcd" : "abcd", + "abcdAgain" : ${strings.a}${strings.b}${strings.c}${strings.d}, + "a" : "a", + "b" : "b", + "c" : "c", + "d" : "d", + "concatenated" : null bar 42 baz true 3.14 hi, + "double" : "3.14", + "number" : "57", + "null" : "null", + "true" : "true", + "yes" : "yes", + "false" : "false", + "no" : "no" + }, + + "arrays" : { + "empty" : [], + "ofInt" : [1, 2, 3], + "ofString" : [ ${strings.a}, ${strings.b}, ${strings.c} ], + "ofDouble" : [3.14, 4.14, 5.14], + "ofNull" : [null, null, null], + "ofBoolean" : [true, false], + "ofArray" : [${arrays.ofString}, ${arrays.ofString}, ${arrays.ofString}], + "ofObject" : [${numbers}, ${booleans}, ${strings}] + }, + "bytes" : { + "kilobyte" : "1kB", + "kibibyte" : "1K", + "thousandBytes" : "1000B" + }, + "durations" : { + "second" : 1s, + "secondAsNumber" : 1000000000, + "halfSecond" : 0.5s + }, + +} diff --git a/config/src/test/scala/com/typesafe/config/BeanFactoryTest.scala b/config/src/test/scala/com/typesafe/config/BeanFactoryTest.scala deleted file mode 100644 index 1a2cce07..00000000 --- a/config/src/test/scala/com/typesafe/config/BeanFactoryTest.scala +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (C) 2013 Typesafe Inc. - */ -package com.typesafe.config - -import org.junit.Assert._ -import org.junit._ - -class BeanFactoryTest { - - @Test - def toCamelCase() { - assertEquals("configProp", BeanFactory.toCamelCase("config-prop")) - assertEquals("fooBar", BeanFactory.toCamelCase("foo-----bar")) - assertEquals("foo", BeanFactory.toCamelCase("-foo")) - assertEquals("bar", BeanFactory.toCamelCase("bar-")) - } - -} diff --git a/config/src/test/scala/com/typesafe/config/ConfigBeanFactoryTest.scala b/config/src/test/scala/com/typesafe/config/ConfigBeanFactoryTest.scala new file mode 100644 index 00000000..eb7707e1 --- /dev/null +++ b/config/src/test/scala/com/typesafe/config/ConfigBeanFactoryTest.scala @@ -0,0 +1,94 @@ +/** + * Copyright (C) 2013 Typesafe Inc. + */ +package com.typesafe.config + +import java.io.{ InputStream, InputStreamReader } +import java.util.concurrent.TimeUnit + +import beanconfig._ +import org.junit.Assert._ +import org.junit._ + +class ConfigBeanFactoryTest { + + @Test + def toCamelCase() { + assertEquals("configProp", ConfigBeanFactory.toCamelCase("config-prop")) + assertEquals("fooBar", ConfigBeanFactory.toCamelCase("foo-----bar")) + assertEquals("foo", ConfigBeanFactory.toCamelCase("-foo")) + assertEquals("bar", ConfigBeanFactory.toCamelCase("bar-")) + } + + @Test + def testCreate() { + val configIs: InputStream = this.getClass().getClassLoader().getResourceAsStream("beanconfig/beanconfig01.conf") + val config: Config = ConfigFactory.parseReader(new InputStreamReader(configIs), + ConfigParseOptions.defaults.setSyntax(ConfigSyntax.CONF)).resolve + val beanConfig: TestBeanConfig = ConfigBeanFactory.create(config, classOf[TestBeanConfig]) + assertNotNull(beanConfig) + } + + @Test + def testUnknownProp() { + val configIs: InputStream = this.getClass().getClassLoader().getResourceAsStream("beanconfig/beanconfig01.conf") + val config: Config = ConfigFactory.parseReader(new InputStreamReader(configIs), + ConfigParseOptions.defaults.setSyntax(ConfigSyntax.CONF)).resolve + var expected: ConfigException.Generic = null + var beanConfig: NoFoundPropBeanConfig = null + try { + beanConfig = ConfigBeanFactory.create(config, classOf[NoFoundPropBeanConfig]) + } catch { + case cge: ConfigException.Generic => expected = cge + case e: Exception => expected = null + } + assertNotNull(expected) + assertEquals("Could not find property 'propNotListedInConfig' from class 'beanconfig.NoFoundPropBeanConfig' in config.", + expected.getMessage) + assertNull(beanConfig) + } + + @Test + def testCreateBool() { + val beanConfig: BooleansConfig = ConfigBeanFactory.create(loadConfig().getConfig("booleans"), classOf[BooleansConfig]) + assertNotNull(beanConfig) + } + + @Test + def testCreateNumber() { + val beanConfig: NumbersConfig = ConfigBeanFactory.create(loadConfig().getConfig("numbers"), classOf[NumbersConfig]) + assertNotNull(beanConfig) + } + + @Test + def testCreateList() { + val beanConfig: ArraysConfig = ConfigBeanFactory.create(loadConfig().getConfig("arrays"), classOf[ArraysConfig]) + assertNotNull(beanConfig) + } + + @Test + def testCreateDuration() { + val beanConfig: DurationsConfig = ConfigBeanFactory.create(loadConfig().getConfig("durations"), classOf[DurationsConfig]) + assertNotNull(beanConfig) + assertEquals(beanConfig.getHalfSecond, TimeUnit.MILLISECONDS.toNanos(500)) + assertEquals(beanConfig.getSecond, TimeUnit.SECONDS.toNanos(1)) + assertEquals(beanConfig.getSecondAsNumber, TimeUnit.SECONDS.toNanos(1)) + } + + @Test + def testCreateBytes() { + val beanConfig: BytesConfig = ConfigBeanFactory.create(loadConfig().getConfig("bytes"), classOf[BytesConfig]) + assertNotNull(beanConfig) + assertEquals(beanConfig.getKibibyte, 1024L) + assertEquals(beanConfig.getKilobyte, 1000L) + assertEquals(beanConfig.getThousandBytes, 1000L) + } + + private def loadConfig(): Config = { + val configIs: InputStream = this.getClass().getClassLoader().getResourceAsStream("beanconfig/beanconfig01.conf") + val config: Config = ConfigFactory.parseReader(new InputStreamReader(configIs), + ConfigParseOptions.defaults.setSyntax(ConfigSyntax.CONF)).resolve + config + } + +}