Groundwork for the new file() url() classpath() include syntax

This patch doesn't have the parser just the implementation
This commit is contained in:
Havoc Pennington 2012-04-09 10:31:27 -04:00
parent cdd3e127fc
commit 3b7d99a797
12 changed files with 336 additions and 62 deletions

View File

@ -34,4 +34,13 @@ public interface ConfigIncludeContext {
* null
*/
ConfigParseable relativeTo(String filename);
/**
* Parse options to use (if you use another method to get a
* {@link ConfigParseable} then use {@link ConfigParseable#options()}
* instead though).
*
* @return the parse options
*/
ConfigParseOptions parseOptions();
}

View File

@ -6,7 +6,9 @@ package com.typesafe.config;
/**
* Implement this interface and provide an instance to
* {@link ConfigParseOptions#setIncluder ConfigParseOptions.setIncluder()} to
* customize handling of {@code include} statements in config files.
* customize handling of {@code include} statements in config files. You may
* also want to implement {@link ConfigIncluderClasspath},
* {@link ConfigIncluderFile}, and {@link ConfigIncluderURL}, or not.
*/
public interface ConfigIncluder {
/**
@ -30,7 +32,14 @@ public interface ConfigIncluder {
* Parses another item to be included. The returned object typically would
* not have substitutions resolved. You can throw a ConfigException here to
* abort parsing, or return an empty object, but may not return null.
*
*
* This method is used for a "heuristic" include statement that does not
* specify file, URL, or classpath resource. If the include statement does
* specify, then the same class implementing {@link ConfigIncluder} must
* also implement {@link ConfigIncluderClasspath},
* {@link ConfigIncluderFile}, or {@link ConfigIncluderURL} as needed, or a
* default includer will be used.
*
* @param context
* some info about the include context
* @param what

View File

@ -0,0 +1,25 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
package com.typesafe.config;
/**
* Implement this <em>in addition to</em> {@link ConfigIncluder} if you want to
* support inclusion of files with the {@code include classpath("resource")}
* syntax. If you do not implement this but do implement {@link ConfigIncluder},
* attempts to load classpath resources will use the default includer.
*/
public interface ConfigIncluderClasspath {
/**
* Parses another item to be included. The returned object typically would
* not have substitutions resolved. You can throw a ConfigException here to
* abort parsing, or return an empty object, but may not return null.
*
* @param context
* some info about the include context
* @param what
* the include statement's argument
* @return a non-null ConfigObject
*/
ConfigObject includeResources(ConfigIncludeContext context, String what);
}

View File

@ -0,0 +1,27 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
package com.typesafe.config;
import java.io.File;
/**
* Implement this <em>in addition to</em> {@link ConfigIncluder} if you want to
* support inclusion of files with the {@code include file("filename")} syntax.
* If you do not implement this but do implement {@link ConfigIncluder},
* attempts to load files will use the default includer.
*/
public interface ConfigIncluderFile {
/**
* Parses another item to be included. The returned object typically would
* not have substitutions resolved. You can throw a ConfigException here to
* abort parsing, or return an empty object, but may not return null.
*
* @param context
* some info about the include context
* @param what
* the include statement's argument
* @return a non-null ConfigObject
*/
ConfigObject includeFile(ConfigIncludeContext context, File what);
}

View File

@ -0,0 +1,27 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
package com.typesafe.config;
import java.net.URL;
/**
* Implement this <em>in addition to</em> {@link ConfigIncluder} if you want to
* support inclusion of files with the {@code include url("http://example.com")}
* syntax. If you do not implement this but do implement {@link ConfigIncluder},
* attempts to load URLs will use the default includer.
*/
public interface ConfigIncluderURL {
/**
* Parses another item to be included. The returned object typically would
* not have substitutions resolved. You can throw a ConfigException here to
* abort parsing, or return an empty object, but may not return null.
*
* @param context
* some info about the include context
* @param what
* the include statement's argument
* @return a non-null ConfigObject
*/
ConfigObject includeURL(ConfigIncludeContext context, URL what);
}

View File

@ -123,8 +123,7 @@ public final class ConfigParseOptions {
if (this.includer == includer)
return this;
else
return new ConfigParseOptions(this.syntax, this.originDescription,
this.allowMissing,
return new ConfigParseOptions(this.syntax, this.originDescription, this.allowMissing,
includer, this.classLoader);
}
@ -153,7 +152,7 @@ public final class ConfigParseOptions {
/**
* Set the class loader. If set to null,
* <code>Thread.currentThread().getContextClassLoader()</code> will be used.
*
*
* @param loader
* a class loader or {@code null} to use thread context class
* loader

View File

@ -77,39 +77,51 @@ public class ConfigImpl {
return cache.getOrElseUpdate(loader, key, updater);
}
static class FileNameSource implements SimpleIncluder.NameSource {
@Override
public ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions) {
return Parseable.newFile(new File(name), parseOptions);
}
};
static class ClasspathNameSource implements SimpleIncluder.NameSource {
@Override
public ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions) {
return Parseable.newResources(name, parseOptions);
}
};
static class ClasspathNameSourceWithClass implements SimpleIncluder.NameSource {
final private Class<?> klass;
public ClasspathNameSourceWithClass(Class<?> klass) {
this.klass = klass;
}
@Override
public ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions) {
return Parseable.newResources(klass, name, parseOptions);
}
};
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigObject parseResourcesAnySyntax(final Class<?> klass,
String resourceBasename, final ConfigParseOptions baseOptions) {
NameSource source = new NameSource() {
@Override
public ConfigParseable nameToParseable(String name) {
return Parseable.newResources(klass, name, baseOptions);
}
};
public static ConfigObject parseResourcesAnySyntax(Class<?> klass, String resourceBasename,
ConfigParseOptions baseOptions) {
NameSource source = new ClasspathNameSourceWithClass(klass);
return SimpleIncluder.fromBasename(source, resourceBasename, baseOptions);
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigObject parseResourcesAnySyntax(String resourceBasename,
final ConfigParseOptions baseOptions) {
NameSource source = new NameSource() {
@Override
public ConfigParseable nameToParseable(String name) {
return Parseable.newResources(name, baseOptions);
}
};
ConfigParseOptions baseOptions) {
NameSource source = new ClasspathNameSource();
return SimpleIncluder.fromBasename(source, resourceBasename, baseOptions);
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigObject parseFileAnySyntax(final File basename,
final ConfigParseOptions baseOptions) {
NameSource source = new NameSource() {
@Override
public ConfigParseable nameToParseable(String name) {
return Parseable.newFile(new File(name), baseOptions);
}
};
public static ConfigObject parseFileAnySyntax(File basename, ConfigParseOptions baseOptions) {
NameSource source = new FileNameSource();
return SimpleIncluder.fromBasename(source, basename.getPath(), baseOptions);
}

View File

@ -0,0 +1,14 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
package com.typesafe.config.impl;
import com.typesafe.config.ConfigIncluder;
import com.typesafe.config.ConfigIncluderClasspath;
import com.typesafe.config.ConfigIncluderFile;
import com.typesafe.config.ConfigIncluderURL;
interface FullIncluder extends ConfigIncluder, ConfigIncluderFile, ConfigIncluderURL,
ConfigIncluderClasspath {
}

View File

@ -57,7 +57,10 @@ public abstract class Parseable implements ConfigParseable {
}
ConfigParseOptions modified = baseOptions.setSyntax(syntax);
// make sure the app-provided includer falls back to default
modified = modified.appendIncluder(ConfigImpl.defaultIncluder());
// make sure the app-provided includer is complete
modified = modified.setIncluder(SimpleIncluder.makeFull(modified.getIncluder()));
return modified;
}

View File

@ -16,7 +16,6 @@ import java.util.Stack;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigIncludeContext;
import com.typesafe.config.ConfigIncluder;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigParseOptions;
import com.typesafe.config.ConfigSyntax;
@ -27,8 +26,8 @@ final class Parser {
static AbstractConfigValue parse(Iterator<Token> tokens,
ConfigOrigin origin, ConfigParseOptions options,
ConfigIncludeContext includeContext) {
ParseContext context = new ParseContext(options.getSyntax(), origin,
tokens, options.getIncluder(), includeContext);
ParseContext context = new ParseContext(options.getSyntax(), origin, tokens,
SimpleIncluder.makeFull(options.getIncluder()), includeContext);
return context.parse();
}
@ -80,7 +79,7 @@ final class Parser {
private int lineNumber;
final private Stack<TokenWithComments> buffer;
final private Iterator<Token> tokens;
final private ConfigIncluder includer;
final private FullIncluder includer;
final private ConfigIncludeContext includeContext;
final private ConfigSyntax flavor;
final private ConfigOrigin baseOrigin;
@ -90,9 +89,8 @@ final class Parser {
// someone may think this is .properties format.
int equalsCount;
ParseContext(ConfigSyntax flavor, ConfigOrigin origin,
Iterator<Token> tokens, ConfigIncluder includer,
ConfigIncludeContext includeContext) {
ParseContext(ConfigSyntax flavor, ConfigOrigin origin, Iterator<Token> tokens,
FullIncluder includer, ConfigIncludeContext includeContext) {
lineNumber = 1;
buffer = new Stack<TokenWithComments>();
this.tokens = tokens;

View File

@ -4,6 +4,7 @@
package com.typesafe.config.impl;
import com.typesafe.config.ConfigIncludeContext;
import com.typesafe.config.ConfigParseOptions;
import com.typesafe.config.ConfigParseable;
class SimpleIncludeContext implements ConfigIncludeContext {
@ -14,12 +15,11 @@ class SimpleIncludeContext implements ConfigIncludeContext {
this.parseable = parseable;
}
SimpleIncludeContext() {
this(null);
}
SimpleIncludeContext withParseable(Parseable parseable) {
return new SimpleIncludeContext(parseable);
if (parseable == this.parseable)
return this;
else
return new SimpleIncludeContext(parseable);
}
@Override
@ -29,4 +29,9 @@ class SimpleIncludeContext implements ConfigIncludeContext {
else
return null;
}
@Override
public ConfigParseOptions parseOptions() {
return SimpleIncluder.clearForInclude(parseable.options());
}
}

View File

@ -3,18 +3,25 @@
*/
package com.typesafe.config.impl;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigIncludeContext;
import com.typesafe.config.ConfigIncluder;
import com.typesafe.config.ConfigIncluderClasspath;
import com.typesafe.config.ConfigIncluderFile;
import com.typesafe.config.ConfigIncluderURL;
import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigParseOptions;
import com.typesafe.config.ConfigParseable;
import com.typesafe.config.ConfigSyntax;
class SimpleIncluder implements ConfigIncluder {
class SimpleIncluder implements FullIncluder {
private ConfigIncluder fallback;
@ -22,24 +29,17 @@ class SimpleIncluder implements ConfigIncluder {
this.fallback = fallback;
}
// ConfigIncludeContext does this for us on its options
static ConfigParseOptions clearForInclude(ConfigParseOptions options) {
// the class loader and includer are inherited, but not this other
// stuff.
return options.setSyntax(null).setOriginDescription(null).setAllowMissing(true);
}
// this is the heuristic includer
@Override
public ConfigObject include(final ConfigIncludeContext context, String name) {
NameSource source = new NameSource() {
@Override
public ConfigParseable nameToParseable(String name) {
ConfigParseable p = context.relativeTo(name);
if (p == null) {
// avoid returning null
return Parseable.newNotFound(name, "include was not found: '" + name + "'",
ConfigParseOptions.defaults());
} else {
return p;
}
}
};
ConfigObject obj = fromBasename(source, name, ConfigParseOptions.defaults()
.setAllowMissing(true));
ConfigObject obj = includeWithoutFallback(context, name);
// now use the fallback includer if any and merge
// its result.
@ -50,6 +50,79 @@ class SimpleIncluder implements ConfigIncluder {
}
}
// the heuristic includer in static form
static ConfigObject includeWithoutFallback(final ConfigIncludeContext context, String name) {
// the heuristic is valid URL then URL, else relative to including file;
// relativeTo in a file falls back to classpath inside relativeTo().
URL url;
try {
url = new URL(name);
} catch (MalformedURLException e) {
url = null;
}
if (url != null) {
return includeURLWithoutFallback(context, url);
} else {
NameSource source = new RelativeNameSource(context);
return fromBasename(source, name, context.parseOptions());
}
}
@Override
public ConfigObject includeURL(ConfigIncludeContext context, URL url) {
ConfigObject obj = includeURLWithoutFallback(context, url);
// now use the fallback includer if any and merge
// its result.
if (fallback != null && fallback instanceof ConfigIncluderURL) {
return obj.withFallback(((ConfigIncluderURL) fallback).includeURL(context, url));
} else {
return obj;
}
}
static ConfigObject includeURLWithoutFallback(final ConfigIncludeContext context, URL url) {
return ConfigFactory.parseURL(url, context.parseOptions()).root();
}
@Override
public ConfigObject includeFile(ConfigIncludeContext context, File file) {
ConfigObject obj = includeFileWithoutFallback(context, file);
// now use the fallback includer if any and merge
// its result.
if (fallback != null && fallback instanceof ConfigIncluderFile) {
return obj.withFallback(((ConfigIncluderFile) fallback).includeFile(context, file));
} else {
return obj;
}
}
static ConfigObject includeFileWithoutFallback(final ConfigIncludeContext context, File file) {
return ConfigFactory.parseFileAnySyntax(file, context.parseOptions()).root();
}
@Override
public ConfigObject includeResources(ConfigIncludeContext context, String resource) {
ConfigObject obj = includeResourceWithoutFallback(context, resource);
// now use the fallback includer if any and merge
// its result.
if (fallback != null && fallback instanceof ConfigIncluderClasspath) {
return obj.withFallback(((ConfigIncluderClasspath) fallback).includeResources(context,
resource));
} else {
return obj;
}
}
static ConfigObject includeResourceWithoutFallback(final ConfigIncludeContext context,
String resource) {
return ConfigFactory.parseResourcesAnySyntax(resource, context.parseOptions()).root();
}
@Override
public ConfigIncluder withFallback(ConfigIncluder fallback) {
if (this == fallback) {
@ -64,9 +137,29 @@ class SimpleIncluder implements ConfigIncluder {
}
interface NameSource {
ConfigParseable nameToParseable(String name);
ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions);
}
static private class RelativeNameSource implements NameSource {
final private ConfigIncludeContext context;
RelativeNameSource(ConfigIncludeContext context) {
this.context = context;
}
@Override
public ConfigParseable nameToParseable(String name, ConfigParseOptions options) {
ConfigParseable p = context.relativeTo(name);
if (p == null) {
// avoid returning null
return Parseable
.newNotFound(name, "include was not found: '" + name + "'", options);
} else {
return p;
}
}
};
// this function is a little tricky because there are three places we're
// trying to use it; for 'include "basename"' in a .conf file, for
// loading app.{conf,json,properties} from classpath, and for
@ -74,13 +167,13 @@ class SimpleIncluder implements ConfigIncluder {
static ConfigObject fromBasename(NameSource source, String name, ConfigParseOptions options) {
ConfigObject obj;
if (name.endsWith(".conf") || name.endsWith(".json") || name.endsWith(".properties")) {
ConfigParseable p = source.nameToParseable(name);
ConfigParseable p = source.nameToParseable(name, options);
obj = p.parse(p.options().setAllowMissing(options.getAllowMissing()));
} else {
ConfigParseable confHandle = source.nameToParseable(name + ".conf");
ConfigParseable jsonHandle = source.nameToParseable(name + ".json");
ConfigParseable propsHandle = source.nameToParseable(name + ".properties");
ConfigParseable confHandle = source.nameToParseable(name + ".conf", options);
ConfigParseable jsonHandle = source.nameToParseable(name + ".json", options);
ConfigParseable propsHandle = source.nameToParseable(name + ".properties", options);
boolean gotSomething = false;
List<String> failMessages = new ArrayList<String>();
@ -140,4 +233,57 @@ class SimpleIncluder implements ConfigIncluder {
return obj;
}
// the Proxy is a proxy for an application-provided includer that uses our
// default implementations when the application-provided includer doesn't
// have an implementation.
static private class Proxy implements FullIncluder {
final ConfigIncluder delegate;
Proxy(ConfigIncluder delegate) {
this.delegate = delegate;
}
@Override
public ConfigIncluder withFallback(ConfigIncluder fallback) {
// we never fall back
return this;
}
@Override
public ConfigObject include(ConfigIncludeContext context, String what) {
return delegate.include(context, what);
}
@Override
public ConfigObject includeResources(ConfigIncludeContext context, String what) {
if (delegate instanceof ConfigIncluderClasspath)
return ((ConfigIncluderClasspath) delegate).includeResources(context, what);
else
return includeResourceWithoutFallback(context, what);
}
@Override
public ConfigObject includeURL(ConfigIncludeContext context, URL what) {
if (delegate instanceof ConfigIncluderURL)
return ((ConfigIncluderURL) delegate).includeURL(context, what);
else
return includeURLWithoutFallback(context, what);
}
@Override
public ConfigObject includeFile(ConfigIncludeContext context, File what) {
if (delegate instanceof ConfigIncluderFile)
return ((ConfigIncluderFile) delegate).includeFile(context, what);
else
return includeFileWithoutFallback(context, what);
}
}
static FullIncluder makeFull(ConfigIncluder includer) {
if (includer instanceof FullIncluder)
return (FullIncluder) includer;
else
return new Proxy(includer);
}
}