mirror of
https://github.com/lightbend/config.git
synced 2025-01-15 23:01:05 +08:00
more work
This commit is contained in:
parent
9ca157d34a
commit
6b54720ddd
@ -1,13 +1,150 @@
|
||||
package com.typesafe.config;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.typesafe.config.impl.ConfigFactory;
|
||||
|
||||
public class Config {
|
||||
public final class Config {
|
||||
public static ConfigObject load(ConfigConfig configConfig) {
|
||||
return ConfigFactory.getConfig(configConfig);
|
||||
return ConfigFactory.loadConfig(configConfig);
|
||||
}
|
||||
|
||||
public static ConfigObject load(String rootPath) {
|
||||
return ConfigFactory.getConfig(new ConfigConfig(rootPath, null));
|
||||
return ConfigFactory.loadConfig(new ConfigConfig(rootPath, null));
|
||||
}
|
||||
|
||||
private static String getUnits(String s) {
|
||||
int i = s.length() - 1;
|
||||
while (i >= 0) {
|
||||
char c = s.charAt(i);
|
||||
if (!Character.isLetter(c))
|
||||
break;
|
||||
i -= 1;
|
||||
}
|
||||
return s.substring(i + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a duration string. If no units are specified in the string, it is
|
||||
* assumed to be in milliseconds. The returned duration is in nanoseconds.
|
||||
*
|
||||
* @param input
|
||||
* the string to parse
|
||||
* @param originForException
|
||||
* origin of the value being parsed
|
||||
* @param pathForException
|
||||
* path to include in exceptions
|
||||
* @return duration in nanoseconds
|
||||
* @throws ConfigException
|
||||
* if string is invalid
|
||||
*/
|
||||
public static long parseDuration(String input,
|
||||
ConfigOrigin originForException, String pathForException) {
|
||||
String s = input.trim();
|
||||
String unitString = getUnits(s);
|
||||
String numberString = s.substring(0, s.length() - unitString.length()).trim();
|
||||
TimeUnit units = null;
|
||||
|
||||
// note that this is deliberately case-sensitive
|
||||
if (unitString == "" || unitString == "ms" || unitString == "milliseconds") {
|
||||
units = TimeUnit.MILLISECONDS;
|
||||
} else if (unitString == "us" || unitString == "microseconds") {
|
||||
units = TimeUnit.MICROSECONDS;
|
||||
} else if (unitString == "ns" || unitString == "nanoseconds") {
|
||||
units = TimeUnit.NANOSECONDS;
|
||||
} else if (unitString == "d" || unitString == "days") {
|
||||
units = TimeUnit.DAYS;
|
||||
} else if (unitString == "s" || unitString == "seconds") {
|
||||
units = TimeUnit.SECONDS;
|
||||
} else if (unitString == "m" || unitString == "minutes") {
|
||||
units = TimeUnit.MINUTES;
|
||||
} else {
|
||||
throw new ConfigException.BadValue(originForException,
|
||||
pathForException, "Could not parse time unit '"
|
||||
+ unitString + "' (try ns, us, ms, s, m, d)");
|
||||
}
|
||||
|
||||
try {
|
||||
// if the string is purely digits, parse as an integer to avoid possible precision loss;
|
||||
// otherwise as a double.
|
||||
if (numberString.matches("[0-9]+")) {
|
||||
return units.toNanos(Long.parseLong(numberString));
|
||||
} else {
|
||||
long nanosInUnit = units.toNanos(1);
|
||||
return (new Double(Double.parseDouble(numberString) * nanosInUnit)).longValue();
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ConfigException.BadValue(originForException, pathForException,
|
||||
"Could not parse duration number '"
|
||||
+ numberString + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private static enum MemoryUnit {
|
||||
BYTES(1), KILOBYTES(1024), MEGABYTES(1024 * 1024), GIGABYTES(
|
||||
1024 * 1024 * 1024);
|
||||
|
||||
int bytes;
|
||||
MemoryUnit(int bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a memory-size string. If no units are specified in the string, it
|
||||
* is assumed to be in bytes. The returned value is in bytes.
|
||||
*
|
||||
* @param input
|
||||
* the string to parse
|
||||
* @param originForException
|
||||
* origin of the value being parsed
|
||||
* @param pathForException
|
||||
* path to include in exceptions
|
||||
* @return size in bytes
|
||||
* @throws ConfigException
|
||||
* if string is invalid
|
||||
*/
|
||||
public static long parseMemorySize(String input,
|
||||
ConfigOrigin originForException, String pathForException) {
|
||||
String s = input.trim();
|
||||
String unitString = getUnits(s);
|
||||
String unitStringLower = unitString.toLowerCase();
|
||||
String numberString = s.substring(0, s.length() - unitString.length())
|
||||
.trim();
|
||||
MemoryUnit units = null;
|
||||
|
||||
// the short abbreviations are case-insensitive but you can't write the
|
||||
// long form words in all caps.
|
||||
if (unitString == "" || unitStringLower == "b"
|
||||
|| unitString == "bytes") {
|
||||
units = MemoryUnit.BYTES;
|
||||
} else if (unitStringLower == "k" || unitString == "kilobytes") {
|
||||
units = MemoryUnit.KILOBYTES;
|
||||
} else if (unitStringLower == "m" || unitString == "megabytes") {
|
||||
units = MemoryUnit.MEGABYTES;
|
||||
} else if (unitStringLower == "g" || unitString == "gigabytes") {
|
||||
units = MemoryUnit.GIGABYTES;
|
||||
} else {
|
||||
throw new ConfigException.BadValue(originForException,
|
||||
pathForException, "Could not parse size unit '"
|
||||
+ unitString + "' (try b, k, m, g)");
|
||||
}
|
||||
|
||||
try {
|
||||
// if the string is purely digits, parse as an integer to avoid
|
||||
// possible precision loss;
|
||||
// otherwise as a double.
|
||||
if (numberString.matches("[0-9]+")) {
|
||||
return Long.parseLong(numberString) * units.bytes;
|
||||
} else {
|
||||
return (new Double(Double.parseDouble(numberString)
|
||||
* units.bytes)).longValue();
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ConfigException.BadValue(originForException,
|
||||
pathForException, "Could not parse memory size number '"
|
||||
+ numberString
|
||||
+ "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package com.typesafe.config;
|
||||
/**
|
||||
* Configuration for a configuration!
|
||||
*/
|
||||
public class ConfigConfig {
|
||||
public final class ConfigConfig {
|
||||
|
||||
private String rootPath;
|
||||
private ConfigTransformer extraTransformer;
|
||||
|
@ -81,6 +81,27 @@ public class ConfigException extends RuntimeException {
|
||||
}
|
||||
}
|
||||
|
||||
public static class BadValue extends ConfigException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public BadValue(ConfigOrigin origin, String path, String message,
|
||||
Throwable cause) {
|
||||
super(origin, "Invalid value at '" + path + "': " + message, cause);
|
||||
}
|
||||
|
||||
public BadValue(ConfigOrigin origin, String path, String message) {
|
||||
this(origin, path, message, null);
|
||||
}
|
||||
|
||||
public BadValue(String path, String message, Throwable cause) {
|
||||
super("Invalid value at '" + path + "': " + message, cause);
|
||||
}
|
||||
|
||||
public BadValue(String path, String message) {
|
||||
this(path, message, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static class BadPath extends ConfigException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
@ -30,6 +30,23 @@ public interface ConfigObject extends ConfigValue {
|
||||
|
||||
ConfigValue get(String path);
|
||||
|
||||
/** Get value as a size in bytes (parses special strings like "128M") */
|
||||
Long getMemorySize(String path);
|
||||
|
||||
/**
|
||||
* Get value as a duration in milliseconds. If the value is already a
|
||||
* number, then it's left alone; if it's a string, it's parsed understanding
|
||||
* units suffixes like "10m" or "5ns"
|
||||
*/
|
||||
Long getMilliseconds(String path);
|
||||
|
||||
/**
|
||||
* Get value as a duration in nanoseconds. If the value is already a number
|
||||
* it's taken as milliseconds. If it's a string, it's parsed understanding
|
||||
* unit suffixes.
|
||||
*/
|
||||
Long getNanoseconds(String path);
|
||||
|
||||
List<ConfigValue> getList(String path);
|
||||
|
||||
List<Boolean> getBooleanList(String path);
|
||||
@ -46,6 +63,12 @@ public interface ConfigObject extends ConfigValue {
|
||||
|
||||
List<Object> getAnyList(String path);
|
||||
|
||||
List<Long> getMemorySizeList(String path);
|
||||
|
||||
List<Long> getMillisecondsList(String path);
|
||||
|
||||
List<Long> getNanosecondsList(String path);
|
||||
|
||||
boolean containsKey(String key);
|
||||
|
||||
Set<String> keySet();
|
||||
|
@ -1,7 +1,13 @@
|
||||
package com.typesafe.config.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigException;
|
||||
import com.typesafe.config.ConfigObject;
|
||||
import com.typesafe.config.ConfigOrigin;
|
||||
@ -19,6 +25,13 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
|
||||
this.transformer = transformer;
|
||||
}
|
||||
|
||||
/**
|
||||
* This looks up the key with no transformation or type conversion of any
|
||||
* kind, and returns null if the key is not present.
|
||||
*
|
||||
* @param key
|
||||
* @return the unmodified raw value or null
|
||||
*/
|
||||
protected abstract ConfigValue peek(String key);
|
||||
|
||||
@Override
|
||||
@ -26,6 +39,18 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
|
||||
return ConfigValueType.OBJECT;
|
||||
}
|
||||
|
||||
static AbstractConfigObject transformed(AbstractConfigObject obj,
|
||||
ConfigTransformer transformer) {
|
||||
if (obj.transformer != transformer)
|
||||
return new TransformedConfigObject(transformer, obj);
|
||||
else
|
||||
return obj;
|
||||
}
|
||||
|
||||
private AbstractConfigObject transformed(AbstractConfigObject obj) {
|
||||
return transformed(obj, transformer);
|
||||
}
|
||||
|
||||
static private ConfigValue resolve(AbstractConfigObject self, String path,
|
||||
ConfigValueType expected, ConfigTransformer transformer,
|
||||
String originalPath) {
|
||||
@ -59,6 +84,58 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
|
||||
return resolve(this, path, expected, transformer, originalPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stack should be from overrides to fallbacks (earlier items win). Test
|
||||
* suite should check: merging of objects with a non-object in the middle.
|
||||
* Override of object with non-object, override of non-object with object.
|
||||
* Merging 0, 1, N objects.
|
||||
*/
|
||||
static AbstractConfigObject merge(ConfigOrigin origin,
|
||||
List<AbstractConfigObject> stack,
|
||||
ConfigTransformer transformer) {
|
||||
if (stack.isEmpty()) {
|
||||
return new SimpleConfigObject(origin, transformer,
|
||||
Collections.<String, ConfigValue> emptyMap());
|
||||
} else if (stack.size() == 1) {
|
||||
return transformed(stack.get(0), transformer);
|
||||
} else {
|
||||
// for non-objects, we just take the first value; but for objects we
|
||||
// have to do work to combine them.
|
||||
Map<String, ConfigValue> merged = new HashMap<String, ConfigValue>();
|
||||
Map<String, List<AbstractConfigObject>> objects = new HashMap<String, List<AbstractConfigObject>>();
|
||||
for (AbstractConfigObject obj : stack) {
|
||||
for (String key : obj.keySet()) {
|
||||
ConfigValue v = obj.peek(key);
|
||||
if (!merged.containsKey(key)) {
|
||||
if (v.valueType() == ConfigValueType.OBJECT) {
|
||||
// requires recursive merge and transformer fixup
|
||||
List<AbstractConfigObject> stackForKey = null;
|
||||
if (objects.containsKey(key)) {
|
||||
stackForKey = objects.get(key);
|
||||
} else {
|
||||
stackForKey = new ArrayList<AbstractConfigObject>();
|
||||
}
|
||||
stackForKey.add(transformed(
|
||||
(AbstractConfigObject) v,
|
||||
transformer));
|
||||
} else {
|
||||
if (!objects.containsKey(key)) {
|
||||
merged.put(key, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (String key : objects.keySet()) {
|
||||
List<AbstractConfigObject> stackForKey = objects.get(key);
|
||||
AbstractConfigObject obj = merge(origin, stackForKey, transformer);
|
||||
merged.put(key, obj);
|
||||
}
|
||||
|
||||
return new SimpleConfigObject(origin, transformer, merged);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigValue get(String path) {
|
||||
return resolve(path, null, path);
|
||||
@ -105,8 +182,9 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
|
||||
|
||||
@Override
|
||||
public AbstractConfigObject getObject(String path) {
|
||||
ConfigValue v = resolve(path, ConfigValueType.OBJECT, path);
|
||||
return (AbstractConfigObject) v;
|
||||
AbstractConfigObject obj = (AbstractConfigObject) resolve(path,
|
||||
ConfigValueType.OBJECT, path);
|
||||
return transformed(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -115,6 +193,36 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
|
||||
return v.unwrapped();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getMemorySize(String path) {
|
||||
Long size = null;
|
||||
try {
|
||||
size = getLong(path);
|
||||
} catch (ConfigException.WrongType e) {
|
||||
ConfigValue v = resolve(path, ConfigValueType.STRING, path);
|
||||
size = Config.parseMemorySize((String) v.unwrapped(), v.origin(),
|
||||
path);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getMilliseconds(String path) {
|
||||
return TimeUnit.NANOSECONDS.toMillis(getNanoseconds(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getNanoseconds(String path) {
|
||||
Long ns = null;
|
||||
try {
|
||||
ns = getLong(path);
|
||||
} catch (ConfigException.WrongType e) {
|
||||
ConfigValue v = resolve(path, ConfigValueType.STRING, path);
|
||||
ns = Config.parseDuration((String) v.unwrapped(), v.origin(), path);
|
||||
}
|
||||
return ns;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> List<T> getHomogeneousUnwrappedList(String path,
|
||||
ConfigValueType expected) {
|
||||
@ -177,7 +285,7 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
|
||||
if (v.valueType() != ConfigValueType.OBJECT)
|
||||
throw new ConfigException.WrongType(v.origin(), path,
|
||||
ConfigValueType.OBJECT.name(), v.valueType().name());
|
||||
l.add((ConfigObject) v);
|
||||
l.add(transformed((AbstractConfigObject) v));
|
||||
}
|
||||
return l;
|
||||
}
|
||||
@ -191,4 +299,55 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getMemorySizeList(String path) {
|
||||
List<Long> l = new ArrayList<Long>();
|
||||
List<ConfigValue> list = getList(path);
|
||||
for (ConfigValue v : list) {
|
||||
if (v.valueType() == ConfigValueType.NUMBER) {
|
||||
l.add(((Number) v.unwrapped()).longValue());
|
||||
} else if (v.valueType() == ConfigValueType.STRING) {
|
||||
String s = (String) v.unwrapped();
|
||||
Long n = Config.parseMemorySize(s, v.origin(), path);
|
||||
l.add(n);
|
||||
} else {
|
||||
throw new ConfigException.WrongType(v.origin(), path,
|
||||
"memory size string or number of bytes", v.valueType()
|
||||
.name());
|
||||
}
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getMillisecondsList(String path) {
|
||||
List<Long> nanos = getNanosecondsList(path);
|
||||
List<Long> l = new ArrayList<Long>();
|
||||
for (Long n : nanos) {
|
||||
l.add(TimeUnit.NANOSECONDS.toMillis(n));
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getNanosecondsList(String path) {
|
||||
List<Long> l = new ArrayList<Long>();
|
||||
List<ConfigValue> list = getList(path);
|
||||
for (ConfigValue v : list) {
|
||||
if (v.valueType() == ConfigValueType.NUMBER) {
|
||||
l.add(((Number) v.unwrapped()).longValue());
|
||||
} else if (v.valueType() == ConfigValueType.STRING) {
|
||||
String s = (String) v.unwrapped();
|
||||
Long n = Config.parseDuration(s, v.origin(), path);
|
||||
l.add(n);
|
||||
} else {
|
||||
throw new ConfigException.WrongType(v.origin(), path,
|
||||
"duration string or number of nanoseconds", v
|
||||
.valueType().name());
|
||||
}
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
}
|
@ -15,7 +15,7 @@ import com.typesafe.config.ConfigValue;
|
||||
|
||||
/** This is public but is only supposed to be used by the "config" package */
|
||||
public class ConfigFactory {
|
||||
public static ConfigObject getConfig(ConfigConfig configConfig) {
|
||||
public static ConfigObject loadConfig(ConfigConfig configConfig) {
|
||||
AbstractConfigObject system = null;
|
||||
try {
|
||||
system = systemPropertiesConfig()
|
||||
@ -29,23 +29,36 @@ public class ConfigFactory {
|
||||
if (system != null)
|
||||
stack.add(system);
|
||||
|
||||
List<ConfigTransformer> transformerStack = new ArrayList<ConfigTransformer>();
|
||||
transformerStack.add(defaultConfigTransformer());
|
||||
ConfigTransformer extraTransformer = configConfig.extraTransformer();
|
||||
if (extraTransformer != null)
|
||||
transformerStack.add(extraTransformer);
|
||||
ConfigTransformer transformer = new StackTransformer(transformerStack);
|
||||
ConfigTransformer transformer = withExtraTransformer(configConfig
|
||||
.extraTransformer());
|
||||
|
||||
StackConfigObject stackConfig = new StackConfigObject(
|
||||
new SimpleConfigOrigin("config for " + configConfig.rootPath()),
|
||||
transformer,
|
||||
stack);
|
||||
AbstractConfigObject merged = AbstractConfigObject
|
||||
.merge(new SimpleConfigOrigin("config for "
|
||||
+ configConfig.rootPath()), stack, transformer);
|
||||
|
||||
return stackConfig;
|
||||
return merged;
|
||||
}
|
||||
|
||||
public static ConfigObject getEnvironmentAsConfig() {
|
||||
return envVariablesConfig();
|
||||
public static ConfigObject getEnvironmentAsConfig(
|
||||
ConfigTransformer extraTransformer) {
|
||||
// This should not need to create a new config object
|
||||
// as long as the transformer is just the default transformer.
|
||||
return AbstractConfigObject.transformed(envVariablesConfig(),
|
||||
withExtraTransformer(extraTransformer));
|
||||
}
|
||||
|
||||
private static ConfigTransformer withExtraTransformer(
|
||||
ConfigTransformer extraTransformer) {
|
||||
// idea is to avoid creating a new, unique transformer if there's no
|
||||
// extraTransformer
|
||||
if (extraTransformer != null) {
|
||||
List<ConfigTransformer> transformerStack = new ArrayList<ConfigTransformer>();
|
||||
transformerStack.add(defaultConfigTransformer());
|
||||
transformerStack.add(extraTransformer);
|
||||
return new StackTransformer(transformerStack);
|
||||
} else {
|
||||
return defaultConfigTransformer();
|
||||
}
|
||||
}
|
||||
|
||||
private static ConfigTransformer defaultTransformer = null;
|
||||
|
@ -11,6 +11,10 @@ import com.typesafe.config.ConfigOrigin;
|
||||
import com.typesafe.config.ConfigTransformer;
|
||||
import com.typesafe.config.ConfigValue;
|
||||
|
||||
/**
|
||||
* This is unused for now, decided that it was too annoying to "lazy merge" and
|
||||
* better to do the full merge up-front.
|
||||
*/
|
||||
final class StackConfigObject extends AbstractConfigObject {
|
||||
|
||||
private List<AbstractConfigObject> stack;
|
||||
|
Loading…
Reference in New Issue
Block a user