Updated to latest config lib 38fb8d6

This commit is contained in:
Patrik Nordwall 2011-12-02 12:08:28 +01:00
parent eebe068aa5
commit db075d094a
14 changed files with 299 additions and 123 deletions

View file

@ -87,9 +87,6 @@ public interface Config extends ConfigMergeable {
@Override
Config withFallback(ConfigMergeable other);
@Override
ConfigObject toValue();
/**
* Returns a replacement config with all substitutions (the
* <code>${foo.bar}</code> syntax, see <a
@ -163,11 +160,11 @@ public interface Config extends ConfigMergeable {
* with your module. It's best to allow the modules owning those paths to
* validate them. Also, if every module validates only its own stuff, there
* isn't as much redundant work being done.
*
*
* <p>
* If no paths are specified in <code>checkValid()</code>'s parameter list,
* validation is for the entire config.
*
*
* <p>
* If you specify paths that are not in the reference config, those paths
* are ignored. (There's nothing to validate.)
@ -465,7 +462,7 @@ public interface Config extends ConfigMergeable {
List<? extends Object> getAnyRefList(String path);
List<Long> getMemorySizeInBytesList(String path);
List<Long> getBytesList(String path);
List<Long> getMillisecondsList(String path);

View file

@ -7,7 +7,7 @@ package com.typesafe.config;
/**
* All exceptions thrown by the library are subclasses of ConfigException.
*/
public class ConfigException extends RuntimeException {
public abstract class ConfigException extends RuntimeException {
private static final long serialVersionUID = 1L;
final private ConfigOrigin origin;
@ -152,6 +152,11 @@ public class ConfigException extends RuntimeException {
}
}
/**
* Exception indicating that a path expression was invalid. Try putting
* double quotes around path elements that contain "special" characters.
*
*/
public static class BadPath extends ConfigException {
private static final long serialVersionUID = 1L;
@ -267,6 +272,11 @@ public class ConfigException extends RuntimeException {
}
}
/**
* Information about a problem that occurred in {@link Config#checkValid}. A
* {@link ConfigException.ValidationFailed} exception thrown from
* <code>checkValid()</code> includes a list of problems encountered.
*/
public static class ValidationProblem {
final private String path;
@ -279,19 +289,31 @@ public class ConfigException extends RuntimeException {
this.problem = problem;
}
/** Returns the config setting causing the problem. */
public String path() {
return path;
}
/**
* Returns where the problem occurred (origin may include info on the
* file, line number, etc.).
*/
public ConfigOrigin origin() {
return origin;
}
/** Returns a description of the problem. */
public String problem() {
return problem;
}
}
/**
* Exception indicating that {@link Config#checkValid} found validity
* problems. The problems are available via the {@link #problems()} method.
* The <code>getMessage()</code> of this exception is a potentially very
* long string listing all the problems found.
*/
public static class ValidationFailed extends ConfigException {
private static final long serialVersionUID = 1L;
@ -321,4 +343,20 @@ public class ConfigException extends RuntimeException {
return sb.toString();
}
}
/**
* Exception that doesn't fall into any other category.
*/
public static class Generic extends ConfigException {
private static final long serialVersionUID = 1L;
public Generic(String message, Throwable cause) {
super(message, cause);
}
public Generic(String message) {
this(message, null);
}
}
}

View file

@ -5,11 +5,13 @@ package com.typesafe.config;
import java.io.File;
import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.Properties;
import com.typesafe.config.impl.ConfigImpl;
import com.typesafe.config.impl.ConfigUtil;
import com.typesafe.config.impl.Parseable;
/**
@ -57,16 +59,6 @@ public final class ConfigFactory {
/**
* Like {@link #load(String)} but allows you to specify parse and resolve
* options.
*
* <p>
* To be aware of: using
* {@link ConfigResolveOptions#setUseSystemProperties(boolean)
* setUseSystemProperties(false)} with this method has no meaningful effect,
* because the system properties are merged into the config as overrides
* anyway. <code>setUseSystemProperties</code> affects whether to fall back
* to system properties when they are not found in the config, but with
* <code>load()</code>, they will be in the config.
*
* @param resourceBasename
* the classpath resource name with optional extension
* @param parseOptions
@ -100,15 +92,6 @@ public final class ConfigFactory {
* Like {@link #load(Config)} but allows you to specify
* {@link ConfigResolveOptions}.
*
* <p>
* To be aware of: using
* {@link ConfigResolveOptions#setUseSystemProperties(boolean)
* setUseSystemProperties(false)} with this method has no meaningful effect,
* because the system properties are merged into the config as overrides
* anyway. <code>setUseSystemProperties</code> affects whether to fall back
* to system properties when they are not found in the config, but with
* <code>load()</code>, they will be in the config.
*
* @param config
* the application's portion of the configuration
* @param resolveOptions
@ -121,20 +104,83 @@ public final class ConfigFactory {
}
private static class DefaultConfigHolder {
static final Config defaultConfig = load("application");
private static Config loadDefaultConfig() {
int specified = 0;
// override application.conf with config.file, config.resource,
// config.url if requested.
String resource = System.getProperty("config.resource");
if (resource != null)
specified += 1;
String file = System.getProperty("config.file");
if (file != null)
specified += 1;
String url = System.getProperty("config.url");
if (url != null)
specified += 1;
if (specified == 0) {
return load("application");
} else if (specified > 1) {
throw new ConfigException.Generic("You set more than one of config.file='" + file
+ "', config.url='" + url + "', config.resource='" + resource
+ "'; don't know which one to use!");
} else {
if (resource != null) {
// this deliberately does not parseResourcesAnySyntax; if
// people want that they can use an include statement.
return load(parseResources(ConfigFactory.class, resource));
} else if (file != null) {
return load(parseFile(new File(file)));
} else {
try {
return load(parseURL(new URL(url)));
} catch (MalformedURLException e) {
throw new ConfigException.Generic(
"Bad URL in config.url system property: '" + url + "': "
+ e.getMessage(), e);
}
}
}
}
static final Config defaultConfig = loadDefaultConfig();
}
/**
* Loads a default configuration, equivalent to {@link #load(String)
* load("application")}. This configuration should be used by libraries and
* frameworks unless an application provides a different one.
* load("application")} in most cases. This configuration should be used by
* libraries and frameworks unless an application provides a different one.
* <p>
* This method may return a cached singleton.
* <p>
* If the system properties <code>config.resource</code>,
* <code>config.file</code>, or <code>config.url</code> are set, then the
* classpath resource, file, or URL specified in those properties will be
* used rather than the default
* <code>application.{conf,json,properties}</code> classpath resources.
* These system properties should not be set in code (after all, you can
* just parse whatever you want manually and then use {@link #load(Config)
* if you don't want to use <code>application.conf</code>}). The properties
* are intended for use by the person or script launching the application.
* For example someone might have a <code>production.conf</code> that
* include <code>application.conf</code> but then change a couple of values.
* When launching the app they could specify
* <code>-Dconfig.resource=production.conf</code> to get production mode.
* <p>
* If no system properties are set to change the location of the default
* configuration, <code>ConfigFactory.load()</code> is equivalent to
* <code>ConfigFactory.load("application")</code>.
*
* @return configuration for an application
*/
public static Config load() {
return DefaultConfigHolder.defaultConfig;
try {
return DefaultConfigHolder.defaultConfig;
} catch (ExceptionInInitializerError e) {
throw ConfigUtil.extractInitializerError(e);
}
}
/**

View file

@ -17,15 +17,6 @@ package com.typesafe.config;
* implementations will break.
*/
public interface ConfigMergeable {
/**
* Converts this instance to a {@link ConfigValue}. If called on a
* {@code ConfigValue} it returns {@code this}, if called on a
* {@link Config} it's equivalent to {@link Config#root()}.
*
* @return this instance as a {@code ConfigValue}
*/
ConfigValue toValue();
/**
* Returns a new value computed by merging this value with another, with
* keys in this value "winning" over the other one. Only

View file

@ -7,31 +7,24 @@ package com.typesafe.config;
* A set of options related to resolving substitutions. Substitutions use the
* <code>${foo.bar}</code> syntax and are documented in the <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">HOCON</a> spec.
*
* <p>
* This object is immutable, so the "setters" return a new object.
*
* <p>
* Here is an example of creating a custom {@code ConfigResolveOptions}:
*
* <pre>
* ConfigResolveOptions options = ConfigResolveOptions.defaults()
* .setUseSystemProperties(false)
* .setUseSystemEnvironment(false)
* </pre>
*
* <p>
* In addition to {@link ConfigResolveOptions#defaults}, there's a prebuilt
* {@link ConfigResolveOptions#noSystem} which avoids looking at any system
* properties or environment variables.
* environment variables or other external system information. (Right now,
* environment variables are the only example.)
*/
public final class ConfigResolveOptions {
private final boolean useSystemProperties;
private final boolean useSystemEnvironment;
private ConfigResolveOptions(boolean useSystemProperties,
boolean useSystemEnvironment) {
this.useSystemProperties = useSystemProperties;
private ConfigResolveOptions(boolean useSystemEnvironment) {
this.useSystemEnvironment = useSystemEnvironment;
}
@ -41,31 +34,17 @@ public final class ConfigResolveOptions {
* @return the default resolve options
*/
public static ConfigResolveOptions defaults() {
return new ConfigResolveOptions(true, true);
return new ConfigResolveOptions(true);
}
/**
* Returns resolve options that disable any reference to "system" data
* (system properties or environment variables).
* (currently, this means environment variables).
*
* @return the resolve options with system properties and env variables
* disabled
* @return the resolve options with env variables disabled
*/
public static ConfigResolveOptions noSystem() {
return defaults().setUseSystemEnvironment(false).setUseSystemProperties(false);
}
/**
* Returns options with use of Java system properties set to the given
* value.
*
* @param value
* true to resolve substitutions falling back to Java system
* properties.
* @return options with requested setting for use of system properties
*/
public ConfigResolveOptions setUseSystemProperties(boolean value) {
return new ConfigResolveOptions(value, useSystemEnvironment);
return defaults().setUseSystemEnvironment(false);
}
/**
@ -76,18 +55,9 @@ public final class ConfigResolveOptions {
* variables.
* @return options with requested setting for use of environment variables
*/
@SuppressWarnings("static-method")
public ConfigResolveOptions setUseSystemEnvironment(boolean value) {
return new ConfigResolveOptions(useSystemProperties, value);
}
/**
* Returns whether the options enable use of system properties. This method
* is mostly used by the config lib internally, not by applications.
*
* @return true if system properties should be used
*/
public boolean getUseSystemProperties() {
return useSystemProperties;
return new ConfigResolveOptions(value);
}
/**

View file

@ -35,6 +35,11 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
return config;
}
@Override
public AbstractConfigObject toFallbackValue() {
return this;
}
/**
* This looks up the key with no transformation or type conversion of any
* kind, and returns null if the key is not present.
@ -135,6 +140,7 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
if (ignoresFallbacks())
throw new ConfigException.BugOrBroken("should not be reached");
boolean changed = false;
boolean allResolved = true;
Map<String, AbstractConfigValue> merged = new HashMap<String, AbstractConfigValue>();
Set<String> allKeys = new HashSet<String>();
@ -150,12 +156,26 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
kept = first;
else
kept = first.withFallback(second);
merged.put(key, kept);
if (first != kept)
changed = true;
if (kept.resolveStatus() == ResolveStatus.UNRESOLVED)
allResolved = false;
}
return new SimpleConfigObject(mergeOrigins(this, fallback), merged,
ResolveStatus.fromBoolean(allResolved), fallback.ignoresFallbacks());
ResolveStatus newResolveStatus = ResolveStatus.fromBoolean(allResolved);
boolean newIgnoresFallbacks = fallback.ignoresFallbacks();
if (changed)
return new SimpleConfigObject(mergeOrigins(this, fallback), merged, newResolveStatus,
newIgnoresFallbacks);
else if (newResolveStatus != resolveStatus() || newIgnoresFallbacks != ignoresFallbacks())
return newCopy(newResolveStatus, newIgnoresFallbacks);
else
return this;
}
@Override

View file

@ -16,7 +16,7 @@ import com.typesafe.config.ConfigValue;
* improperly-factored and non-modular code. Please don't add parent().
*
*/
abstract class AbstractConfigValue implements ConfigValue {
abstract class AbstractConfigValue implements ConfigValue, MergeableValue {
final private ConfigOrigin origin;
@ -72,7 +72,7 @@ abstract class AbstractConfigValue implements ConfigValue {
}
@Override
public AbstractConfigValue toValue() {
public AbstractConfigValue toFallbackValue() {
return this;
}
@ -110,7 +110,7 @@ abstract class AbstractConfigValue implements ConfigValue {
if (ignoresFallbacks()) {
return this;
} else {
ConfigValue other = mergeable.toValue();
ConfigValue other = ((MergeableValue) mergeable).toFallbackValue();
if (other instanceof Unmergeable) {
return mergedWithTheUnmergeable((Unmergeable) other);

View file

@ -305,7 +305,11 @@ public class ConfigImpl {
}
static ConfigIncluder defaultIncluder() {
return DefaultIncluderHolder.defaultIncluder;
try {
return DefaultIncluderHolder.defaultIncluder;
} catch (ExceptionInInitializerError e) {
throw ConfigUtil.extractInitializerError(e);
}
}
private static AbstractConfigObject loadSystemProperties() {
@ -319,7 +323,11 @@ public class ConfigImpl {
}
static AbstractConfigObject systemPropertiesAsConfigObject() {
return SystemPropertiesHolder.systemProperties;
try {
return SystemPropertiesHolder.systemProperties;
} catch (ExceptionInInitializerError e) {
throw ConfigUtil.extractInitializerError(e);
}
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
@ -351,7 +359,11 @@ public class ConfigImpl {
}
static AbstractConfigObject envVariablesAsConfigObject() {
return EnvVariablesHolder.envVariables;
try {
return EnvVariablesHolder.envVariables;
} catch (ExceptionInInitializerError e) {
throw ConfigUtil.extractInitializerError(e);
}
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
@ -369,6 +381,10 @@ public class ConfigImpl {
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static Config defaultReference() {
return ReferenceHolder.referenceConfig;
try {
return ReferenceHolder.referenceConfig;
} catch (ExceptionInInitializerError e) {
throw ConfigUtil.extractInitializerError(e);
}
}
}

View file

@ -131,22 +131,25 @@ final class ConfigSubstitution extends AbstractConfigValue implements
private ConfigValue resolve(SubstitutionResolver resolver, SubstitutionExpression subst,
int depth, ConfigResolveOptions options) {
// First we look up the full path, which means relative to the
// included file if we were not a root file
ConfigValue result = findInObject(resolver.root(), resolver, subst.path(),
depth, options);
// when looking up system props and env variables,
// we don't want the prefix that was added when
// we were included in another file.
Path unprefixed = subst.path().subPath(prefixLength);
if (result == null) {
// Then we want to check relative to the root file. We don't
// want the prefix we were included at to be used when looking up
// env variables either.
Path unprefixed = subst.path().subPath(prefixLength);
if (result == null && options.getUseSystemProperties()) {
result = findInObject(ConfigImpl.systemPropertiesAsConfigObject(), null,
unprefixed, depth, options);
}
if (result == null && prefixLength > 0) {
result = findInObject(resolver.root(), resolver, unprefixed, depth, options);
}
if (result == null && options.getUseSystemEnvironment()) {
result = findInObject(ConfigImpl.envVariablesAsConfigObject(), null,
unprefixed, depth, options);
if (result == null && options.getUseSystemEnvironment()) {
result = findInObject(ConfigImpl.envVariablesAsConfigObject(), null, unprefixed,
depth, options);
}
}
return result;

View file

@ -3,6 +3,12 @@
*/
package com.typesafe.config.impl;
import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import com.typesafe.config.ConfigException;
/** This is public just for the "config" package to use, don't touch it */
final public class ConfigUtil {
@ -118,4 +124,26 @@ final public class ConfigUtil {
}
return s.substring(start, end);
}
/** This is public just for the "config" package to use, don't touch it! */
public static ConfigException extractInitializerError(ExceptionInInitializerError e) {
Throwable cause = e.getCause();
if (cause != null && cause instanceof ConfigException) {
return (ConfigException) cause;
} else {
throw e;
}
}
static File urlToFile(URL url) {
// this isn't really right, clearly, but not sure what to do.
try {
// this will properly handle hex escapes, etc.
return new File(url.toURI());
} catch (URISyntaxException e) {
// this handles some stuff like file:///c:/Whatever/
// apparently but mangles handling of hex escapes
return new File(url.getPath());
}
}
}

View file

@ -0,0 +1,9 @@
package com.typesafe.config.impl;
import com.typesafe.config.ConfigMergeable;
import com.typesafe.config.ConfigValue;
interface MergeableValue extends ConfigMergeable {
// converts a Config to its root object and a ConfigValue to itself
ConfigValue toFallbackValue();
}

View file

@ -247,6 +247,20 @@ public abstract class Parseable implements ConfigParseable {
}
}
static File relativeTo(File file, String filename) {
File child = new File(filename);
if (child.isAbsolute())
return null;
File parent = file.getParentFile();
if (parent == null)
return null;
else
return new File(parent, filename);
}
private final static class ParseableReader extends Parseable {
final private Reader reader;
@ -338,7 +352,13 @@ public abstract class Parseable implements ConfigParseable {
}
public static Parseable newURL(URL input, ConfigParseOptions options) {
return new ParseableURL(input, options);
// we want file: URLs and files to always behave the same, so switch
// to a file if it's a file: URL
if (input.getProtocol().equals("file")) {
return newFile(ConfigUtil.urlToFile(input), options);
} else {
return new ParseableURL(input, options);
}
}
private final static class ParseableFile extends Parseable {
@ -362,13 +382,27 @@ public abstract class Parseable implements ConfigParseable {
@Override
ConfigParseable relativeTo(String filename) {
try {
URL url = relativeTo(input.toURI().toURL(), filename);
if (url == null)
return null;
return newURL(url, options().setOriginDescription(null));
} catch (MalformedURLException e) {
File sibling;
if ((new File(filename)).isAbsolute()) {
sibling = new File(filename);
} else {
// this may return null
sibling = relativeTo(input, filename);
}
if (sibling == null)
return null;
if (sibling.exists()) {
return newFile(sibling, options().setOriginDescription(null));
} else {
// fall back to classpath; we treat the "filename" as absolute
// (don't add a package name in front),
// if it starts with "/" then remove the "/", for consistency
// with ParseableResources.relativeTo
String resource = filename;
if (filename.startsWith("/"))
resource = filename.substring(1);
return newResources(this.getClass().getClassLoader(), resource, options()
.setOriginDescription(null));
}
}
@ -450,6 +484,10 @@ public abstract class Parseable implements ConfigParseable {
}
static String parent(String resource) {
// the "resource" is not supposed to begin with a "/"
// because it's supposed to be the raw resource
// (ClassLoader#getResource), not the
// resource "syntax" (Class#getResource)
int i = resource.lastIndexOf('/');
if (i < 0) {
return null;
@ -460,18 +498,24 @@ public abstract class Parseable implements ConfigParseable {
@Override
ConfigParseable relativeTo(String sibling) {
// here we want to build a new resource name and let
// the class loader have it, rather than getting the
// url with getResource() and relativizing to that url.
// This is needed in case the class loader is going to
// search a classpath.
String parent = parent(resource);
if (parent == null)
return newResources(loader, sibling, options()
.setOriginDescription(null));
else
return newResources(loader, parent + "/" + sibling,
if (sibling.startsWith("/")) {
// if it starts with "/" then don't make it relative to
// the including resource
return newResources(loader, sibling.substring(1),
options().setOriginDescription(null));
} else {
// here we want to build a new resource name and let
// the class loader have it, rather than getting the
// url with getResource() and relativizing to that url.
// This is needed in case the class loader is going to
// search a classpath.
String parent = parent(resource);
if (parent == null)
return newResources(loader, sibling, options().setOriginDescription(null));
else
return newResources(loader, parent + "/" + sibling, options()
.setOriginDescription(null));
}
}
@Override

View file

@ -139,11 +139,25 @@ final class Path {
}
// this doesn't have a very precise meaning, just to reduce
// noise from quotes in the rendered path
// noise from quotes in the rendered path for average cases
static boolean hasFunkyChars(String s) {
for (int i = 0; i < s.length(); ++i) {
int length = s.length();
if (length == 0)
return false;
// if the path starts with something that could be a number,
// we need to quote it because the number could be invalid,
// for example it could be a hyphen with no digit afterward
// or the exponent "e" notation could be mangled.
char first = s.charAt(0);
if (!(Character.isLetter(first)))
return true;
for (int i = 1; i < length; ++i) {
char c = s.charAt(i);
if (Character.isLetterOrDigit(c) || c == ' ')
if (Character.isLetterOrDigit(c) || c == '-' || c == '_')
continue;
else
return true;

View file

@ -27,9 +27,9 @@ import com.typesafe.config.ConfigValueType;
* key-value pairs would be all the tree's leaf values, in a big flat list with
* their full paths.
*/
class SimpleConfig implements Config {
final class SimpleConfig implements Config, MergeableValue {
AbstractConfigObject object;
final private AbstractConfigObject object;
SimpleConfig(AbstractConfigObject object) {
this.object = object;
@ -327,7 +327,7 @@ class SimpleConfig implements Config {
}
@Override
public List<Long> getMemorySizeInBytesList(String path) {
public List<Long> getBytesList(String path) {
List<Long> l = new ArrayList<Long>();
List<? extends ConfigValue> list = getList(path);
for (ConfigValue v : list) {
@ -378,7 +378,7 @@ class SimpleConfig implements Config {
}
@Override
public AbstractConfigObject toValue() {
public AbstractConfigObject toFallbackValue() {
return object;
}