pass an "includer" object down to the parser

This commit is contained in:
Havoc Pennington 2011-11-09 17:39:07 -05:00
parent 9509876ced
commit cdcde9eebc
8 changed files with 76 additions and 36 deletions

View File

@ -25,7 +25,11 @@ public class ConfigImpl {
if (system != null)
stack.add(system);
stack.add(Loader.load(configConfig.rootPath()));
// this is a conceptual placeholder for a customizable
// object that the app might be able to pass in.
IncludeHandler includer = defaultIncluder();
stack.add(includer.include(configConfig.rootPath()));
ConfigTransformer transformer = withExtraTransformer(null);
@ -76,6 +80,21 @@ public class ConfigImpl {
return defaultTransformer;
}
private static IncludeHandler defaultIncluder = null;
synchronized static IncludeHandler defaultIncluder() {
if (defaultIncluder == null) {
defaultIncluder = new IncludeHandler() {
@Override
public AbstractConfigObject include(String name) {
return Loader.load(name, this);
}
};
}
return defaultIncluder;
}
private static AbstractConfigObject systemProperties = null;
synchronized static AbstractConfigObject systemPropertiesConfig() {

View File

@ -0,0 +1,11 @@
package com.typesafe.config.impl;
/**
* This is sort of a placeholder so that something per-config-load is passed in
* to the parser to handle includes. The eventual idea is to let apps customize
* how an included name gets searched for, which would involve some nicer
* interface in the public API.
*/
interface IncludeHandler {
AbstractConfigObject include(String name);
}

View File

@ -15,19 +15,19 @@ import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigOrigin;
final class Loader {
static AbstractConfigObject load(String name) {
static AbstractConfigObject load(String name, IncludeHandler includer) {
List<AbstractConfigObject> stack = new ArrayList<AbstractConfigObject>();
// if name has an extension, only use that; otherwise merge all three
if (name.endsWith(".conf") || name.endsWith(".json")
|| name.endsWith(".properties")) {
addResource(name, stack);
addResource(name, includer, stack);
} else {
// .conf wins over .json wins over .properties;
// arbitrary, but deterministic
addResource(name + ".conf", stack);
addResource(name + ".json", stack);
addResource(name + ".properties", stack);
addResource(name + ".conf", includer, stack);
addResource(name + ".json", includer, stack);
addResource(name + ".properties", includer, stack);
}
AbstractConfigObject merged = AbstractConfigObject.merge(
@ -37,15 +37,15 @@ final class Loader {
return merged;
}
private static void addResource(String name,
private static void addResource(String name, IncludeHandler includer,
List<AbstractConfigObject> stack) {
URL url = ConfigImpl.class.getResource("/" + name);
if (url != null) {
stack.add(loadURL(url));
stack.add(loadURL(url, includer));
}
}
private static AbstractConfigObject loadURL(URL url) {
private static AbstractConfigObject loadURL(URL url, IncludeHandler includer) {
if (url.getPath().endsWith(".properties")) {
ConfigOrigin origin = new SimpleConfigOrigin(url.toExternalForm());
Properties props = new Properties();
@ -65,7 +65,7 @@ final class Loader {
}
return fromProperties(url.toExternalForm(), props);
} else {
return forceParsedToObject(Parser.parse(url));
return forceParsedToObject(Parser.parse(url, includer));
}
}

View File

@ -29,9 +29,10 @@ final class Parser {
* yourself.
*/
static AbstractConfigValue parse(SyntaxFlavor flavor, ConfigOrigin origin,
InputStream input) {
InputStream input, IncludeHandler includer) {
try {
return parse(flavor, origin, new InputStreamReader(input, "UTF-8"));
return parse(flavor, origin, new InputStreamReader(input, "UTF-8"),
includer);
} catch (UnsupportedEncodingException e) {
throw new ConfigException.BugOrBroken(
"Java runtime does not support UTF-8");
@ -39,14 +40,14 @@ final class Parser {
}
static AbstractConfigValue parse(SyntaxFlavor flavor, ConfigOrigin origin,
Reader input) {
Reader input, IncludeHandler includer) {
Iterator<Token> tokens = Tokenizer.tokenize(origin, input);
return parse(flavor, origin, tokens);
return parse(flavor, origin, tokens, includer);
}
static AbstractConfigValue parse(SyntaxFlavor flavor, ConfigOrigin origin,
String input) {
return parse(flavor, origin, new StringReader(input));
String input, IncludeHandler includer) {
return parse(flavor, origin, new StringReader(input), includer);
}
private static SyntaxFlavor flavorFromExtension(String name,
@ -59,38 +60,41 @@ final class Parser {
throw new ConfigException.IO(origin, "Unknown filename extension");
}
static AbstractConfigValue parse(File f) {
return parse(null, f);
static AbstractConfigValue parse(File f, IncludeHandler includer) {
return parse(null, f, includer);
}
static AbstractConfigValue parse(SyntaxFlavor flavor, File f) {
static AbstractConfigValue parse(SyntaxFlavor flavor, File f,
IncludeHandler includer) {
ConfigOrigin origin = new SimpleConfigOrigin(f.getPath());
try {
return parse(flavor, origin, f.toURI().toURL());
return parse(flavor, origin, f.toURI().toURL(), includer);
} catch (MalformedURLException e) {
throw new ConfigException.IO(origin,
"failed to create url from file path", e);
}
}
static AbstractConfigValue parse(URL url) {
return parse(null, url);
static AbstractConfigValue parse(URL url, IncludeHandler includer) {
return parse(null, url, includer);
}
static AbstractConfigValue parse(SyntaxFlavor flavor, URL url) {
static AbstractConfigValue parse(SyntaxFlavor flavor, URL url,
IncludeHandler includer) {
ConfigOrigin origin = new SimpleConfigOrigin(url.toExternalForm());
return parse(flavor, origin, url);
return parse(flavor, origin, url, includer);
}
static AbstractConfigValue parse(SyntaxFlavor flavor, ConfigOrigin origin,
URL url) {
URL url, IncludeHandler includer) {
AbstractConfigValue result = null;
try {
InputStream stream = new BufferedInputStream(url.openStream());
try {
result = parse(
flavor != null ? flavor : flavorFromExtension(
url.getPath(), origin), origin, stream);
url.getPath(), origin), origin, stream,
includer);
} finally {
stream.close();
}
@ -104,16 +108,18 @@ final class Parser {
private int lineNumber;
private Stack<Token> buffer;
private Iterator<Token> tokens;
private IncludeHandler includer;
private SyntaxFlavor flavor;
private ConfigOrigin baseOrigin;
ParseContext(SyntaxFlavor flavor, ConfigOrigin origin,
Iterator<Token> tokens) {
Iterator<Token> tokens, IncludeHandler includer) {
lineNumber = 0;
buffer = new Stack<Token>();
this.tokens = tokens;
this.flavor = flavor;
this.baseOrigin = origin;
this.includer = includer;
}
private Token nextToken() {
@ -395,9 +401,9 @@ final class Parser {
}
private static AbstractConfigValue parse(SyntaxFlavor flavor,
ConfigOrigin origin,
Iterator<Token> tokens) {
ParseContext context = new ParseContext(flavor, origin, tokens);
ConfigOrigin origin, Iterator<Token> tokens, IncludeHandler includer) {
ParseContext context = new ParseContext(flavor, origin, tokens,
includer);
return context.parse();
}
}

View File

@ -10,7 +10,7 @@ import java.util.HashMap
class ConfParserTest extends TestUtils {
def parse(s: String): ConfigValue = {
Parser.parse(SyntaxFlavor.CONF, new SimpleConfigOrigin("test conf string"), s)
Parser.parse(SyntaxFlavor.CONF, new SimpleConfigOrigin("test conf string"), s, includer())
}
private def addOffendingJsonToException[R](parserName: String, s: String)(body: => R) = {

View File

@ -41,11 +41,11 @@ class EquivalentsTest extends TestUtils {
}
private def parse(flavor: SyntaxFlavor, f: File) = {
postParse(Parser.parse(flavor, f))
postParse(Parser.parse(flavor, f, includer()))
}
private def parse(f: File) = {
postParse(Parser.parse(f))
postParse(Parser.parse(f, includer()))
}
// would like each "equivNN" directory to be a suite and each file in the dir

View File

@ -12,11 +12,11 @@ import java.util.Collections
class JsonTest extends TestUtils {
def parse(s: String): ConfigValue = {
Parser.parse(SyntaxFlavor.JSON, new SimpleConfigOrigin("test json string"), s)
Parser.parse(SyntaxFlavor.JSON, new SimpleConfigOrigin("test json string"), s, includer())
}
def parseAsConf(s: String): ConfigValue = {
Parser.parse(SyntaxFlavor.CONF, new SimpleConfigOrigin("test conf string"), s)
Parser.parse(SyntaxFlavor.CONF, new SimpleConfigOrigin("test conf string"), s, includer())
}
private[this] def toLift(value: ConfigValue): lift.JValue = {

View File

@ -75,6 +75,10 @@ abstract trait TestUtils {
new SimpleConfigOrigin("fake origin")
}
def includer() = {
ConfigImpl.defaultIncluder();
}
case class ParseTest(liftBehaviorUnexpected: Boolean, whitespaceMatters: Boolean, test: String)
object ParseTest {
def apply(liftBehaviorUnexpected: Boolean, test: String): ParseTest = {
@ -209,7 +213,7 @@ abstract trait TestUtils {
protected def doubleValue(d: Double) = new ConfigDouble(fakeOrigin(), d)
protected def parseObject(s: String) = {
Parser.parse(SyntaxFlavor.CONF, new SimpleConfigOrigin("test string"), s).asInstanceOf[AbstractConfigObject]
Parser.parse(SyntaxFlavor.CONF, new SimpleConfigOrigin("test string"), s, includer()).asInstanceOf[AbstractConfigObject]
}
protected def subst(ref: String, style: SubstitutionStyle = SubstitutionStyle.PATH) = {