Properly track resource file origins

This improves error messages to fix #254.
This commit is contained in:
Havoc Pennington 2015-03-02 15:26:49 -05:00
parent 55bd472c24
commit a528bf104a
6 changed files with 146 additions and 86 deletions

View File

@ -45,6 +45,11 @@ public abstract class Parseable implements ConfigParseable {
private ConfigParseOptions initialOptions; private ConfigParseOptions initialOptions;
private ConfigOrigin initialOrigin; private ConfigOrigin initialOrigin;
protected interface Relativizer {
ConfigParseable relativeTo(String filename);
}
private static final ThreadLocal<LinkedList<Parseable>> parseStack = new ThreadLocal<LinkedList<Parseable>>() { private static final ThreadLocal<LinkedList<Parseable>> parseStack = new ThreadLocal<LinkedList<Parseable>>() {
@Override @Override
protected LinkedList<Parseable> initialValue() { protected LinkedList<Parseable> initialValue() {
@ -213,7 +218,7 @@ public abstract class Parseable implements ConfigParseable {
} }
} }
protected AbstractConfigValue rawParseValue(Reader reader, ConfigOrigin origin, private AbstractConfigValue rawParseValue(Reader reader, ConfigOrigin origin,
ConfigParseOptions finalOptions) throws IOException { ConfigParseOptions finalOptions) throws IOException {
if (finalOptions.getSyntax() == ConfigSyntax.PROPERTIES) { if (finalOptions.getSyntax() == ConfigSyntax.PROPERTIES) {
return PropertiesParser.parse(reader, origin); return PropertiesParser.parse(reader, origin);
@ -412,12 +417,17 @@ public abstract class Parseable implements ConfigParseable {
return new ParseableString(input, options); return new ParseableString(input, options);
} }
private final static class ParseableURL extends Parseable { private static class ParseableURL extends Parseable {
final private URL input; final protected URL input;
private String contentType = null; private String contentType = null;
ParseableURL(URL input, ConfigParseOptions options) { protected ParseableURL(URL input) {
this.input = input; this.input = input;
// does not postConstruct (subclass does it)
}
ParseableURL(URL input, ConfigParseOptions options) {
this(input);
postConstruct(options); postConstruct(options);
} }
@ -551,7 +561,35 @@ public abstract class Parseable implements ConfigParseable {
return new ParseableFile(input, options); return new ParseableFile(input, options);
} }
private final static class ParseableResources extends Parseable {
private final static class ParseableResourceURL extends ParseableURL {
private final Relativizer relativizer;
private final String resource;
ParseableResourceURL(URL input, ConfigParseOptions options, String resource, Relativizer relativizer) {
super(input);
this.relativizer = relativizer;
this.resource = resource;
postConstruct(options);
}
@Override
protected ConfigOrigin createOrigin() {
return SimpleConfigOrigin.newResource(resource, input);
}
@Override
ConfigParseable relativeTo(String filename) {
return relativizer.relativeTo(filename);
}
}
private static Parseable newResourceURL(URL input, ConfigParseOptions options, String resource, Relativizer relativizer) {
return new ParseableResourceURL(input, options, resource, relativizer);
}
private final static class ParseableResources extends Parseable implements Relativizer {
final private String resource; final private String resource;
ParseableResources(String resource, ConfigParseOptions options) { ParseableResources(String resource, ConfigParseOptions options) {
@ -583,31 +621,12 @@ public abstract class Parseable implements ConfigParseable {
URL url = e.nextElement(); URL url = e.nextElement();
if (ConfigImpl.traceLoadsEnabled()) if (ConfigImpl.traceLoadsEnabled())
trace("Loading config from URL " + url.toExternalForm() + " from class loader " trace("Loading config from resource '" + resource + "' URL " + url.toExternalForm() + " from class loader "
+ loader); + loader);
ConfigOrigin elementOrigin = ((SimpleConfigOrigin) origin).addURL(url); Parseable element = newResourceURL(url, finalOptions, resource, this);
AbstractConfigValue v; AbstractConfigValue v = element.parseValue();
// it's tempting to use ParseableURL here but it would be wrong
// because the wrong relativeTo() would be used for includes.
InputStream stream = url.openStream();
try {
Reader reader = readerFromStream(stream);
stream = null; // reader now owns it
try {
// parse in "raw" mode which will throw any IOException
// from here.
v = rawParseValue(reader, elementOrigin, finalOptions);
} finally {
reader.close();
}
} finally {
// stream is null if the reader owns it
if (stream != null)
stream.close();
}
merged = merged.withFallback(v); merged = merged.withFallback(v);
} }
@ -634,7 +653,7 @@ public abstract class Parseable implements ConfigParseable {
} }
@Override @Override
ConfigParseable relativeTo(String sibling) { public ConfigParseable relativeTo(String sibling) {
if (sibling.startsWith("/")) { if (sibling.startsWith("/")) {
// if it starts with "/" then don't make it relative to // if it starts with "/" then don't make it relative to
// the including resource // the including resource

View File

@ -65,7 +65,9 @@ class SerializedConfigValue extends AbstractConfigValue implements Externalizabl
ORIGIN_URL, ORIGIN_URL,
ORIGIN_COMMENTS, ORIGIN_COMMENTS,
ORIGIN_NULL_URL, ORIGIN_NULL_URL,
ORIGIN_NULL_COMMENTS; ORIGIN_NULL_COMMENTS,
ORIGIN_RESOURCE,
ORIGIN_NULL_RESOURCE;
static SerializedField forInt(int b) { static SerializedField forInt(int b) {
if (b < values().length) if (b < values().length)
@ -179,6 +181,9 @@ class SerializedConfigValue extends AbstractConfigValue implements Externalizabl
case ORIGIN_URL: case ORIGIN_URL:
out.writeUTF((String) v); out.writeUTF((String) v);
break; break;
case ORIGIN_RESOURCE:
out.writeUTF((String) v);
break;
case ORIGIN_COMMENTS: case ORIGIN_COMMENTS:
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<String> list = (List<String>) v; List<String> list = (List<String>) v;
@ -189,6 +194,7 @@ class SerializedConfigValue extends AbstractConfigValue implements Externalizabl
} }
break; break;
case ORIGIN_NULL_URL: // FALL THRU case ORIGIN_NULL_URL: // FALL THRU
case ORIGIN_NULL_RESOURCE: // FALL THRU
case ORIGIN_NULL_COMMENTS: case ORIGIN_NULL_COMMENTS:
// nothing to write out besides code and length // nothing to write out besides code and length
break; break;
@ -245,6 +251,10 @@ class SerializedConfigValue extends AbstractConfigValue implements Externalizabl
in.readInt(); // discard length in.readInt(); // discard length
v = in.readUTF(); v = in.readUTF();
break; break;
case ORIGIN_RESOURCE:
in.readInt(); // discard length
v = in.readUTF();
break;
case ORIGIN_COMMENTS: case ORIGIN_COMMENTS:
in.readInt(); // discard length in.readInt(); // discard length
int size = in.readInt(); int size = in.readInt();
@ -255,6 +265,7 @@ class SerializedConfigValue extends AbstractConfigValue implements Externalizabl
v = list; v = list;
break; break;
case ORIGIN_NULL_URL: // FALL THRU case ORIGIN_NULL_URL: // FALL THRU
case ORIGIN_NULL_RESOURCE: // FALL THRU
case ORIGIN_NULL_COMMENTS: case ORIGIN_NULL_COMMENTS:
// nothing to read besides code and length // nothing to read besides code and length
in.readInt(); // discard length in.readInt(); // discard length

View File

@ -28,10 +28,11 @@ final class SimpleConfigOrigin implements ConfigOrigin {
final private int endLineNumber; final private int endLineNumber;
final private OriginType originType; final private OriginType originType;
final private String urlOrNull; final private String urlOrNull;
final private String resourceOrNull;
final private List<String> commentsOrNull; final private List<String> commentsOrNull;
protected SimpleConfigOrigin(String description, int lineNumber, int endLineNumber, protected SimpleConfigOrigin(String description, int lineNumber, int endLineNumber, OriginType originType,
OriginType originType, String urlOrNull, List<String> commentsOrNull) { String urlOrNull, String resourceOrNull, List<String> commentsOrNull) {
if (description == null) if (description == null)
throw new ConfigException.BugOrBroken("description may not be null"); throw new ConfigException.BugOrBroken("description may not be null");
this.description = description; this.description = description;
@ -39,11 +40,12 @@ final class SimpleConfigOrigin implements ConfigOrigin {
this.endLineNumber = endLineNumber; this.endLineNumber = endLineNumber;
this.originType = originType; this.originType = originType;
this.urlOrNull = urlOrNull; this.urlOrNull = urlOrNull;
this.resourceOrNull = resourceOrNull;
this.commentsOrNull = commentsOrNull; this.commentsOrNull = commentsOrNull;
} }
static SimpleConfigOrigin newSimple(String description) { static SimpleConfigOrigin newSimple(String description) {
return new SimpleConfigOrigin(description, -1, -1, OriginType.GENERIC, null, null); return new SimpleConfigOrigin(description, -1, -1, OriginType.GENERIC, null, null, null);
} }
static SimpleConfigOrigin newFile(String filename) { static SimpleConfigOrigin newFile(String filename) {
@ -53,17 +55,22 @@ final class SimpleConfigOrigin implements ConfigOrigin {
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
url = null; url = null;
} }
return new SimpleConfigOrigin(filename, -1, -1, OriginType.FILE, url, null); return new SimpleConfigOrigin(filename, -1, -1, OriginType.FILE, url, null, null);
} }
static SimpleConfigOrigin newURL(URL url) { static SimpleConfigOrigin newURL(URL url) {
String u = url.toExternalForm(); String u = url.toExternalForm();
return new SimpleConfigOrigin(u, -1, -1, OriginType.URL, u, null); return new SimpleConfigOrigin(u, -1, -1, OriginType.URL, u, null, null);
} }
static SimpleConfigOrigin newResource(String resource, URL url) { static SimpleConfigOrigin newResource(String resource, URL url) {
return new SimpleConfigOrigin(resource, -1, -1, OriginType.RESOURCE, String desc;
url != null ? url.toExternalForm() : null, null); if (url != null)
desc = resource + " @ " + url.toExternalForm();
else
desc = resource;
return new SimpleConfigOrigin(desc, -1, -1, OriginType.RESOURCE, url != null ? url.toExternalForm() : null,
resource, null);
} }
static SimpleConfigOrigin newResource(String resource) { static SimpleConfigOrigin newResource(String resource) {
@ -75,14 +82,14 @@ final class SimpleConfigOrigin implements ConfigOrigin {
if (lineNumber == this.lineNumber && lineNumber == this.endLineNumber) { if (lineNumber == this.lineNumber && lineNumber == this.endLineNumber) {
return this; return this;
} else { } else {
return new SimpleConfigOrigin(this.description, lineNumber, lineNumber, return new SimpleConfigOrigin(this.description, lineNumber, lineNumber, this.originType, this.urlOrNull,
this.originType, this.urlOrNull, this.commentsOrNull); this.resourceOrNull, this.commentsOrNull);
} }
} }
SimpleConfigOrigin addURL(URL url) { SimpleConfigOrigin addURL(URL url) {
return new SimpleConfigOrigin(this.description, this.lineNumber, this.endLineNumber, return new SimpleConfigOrigin(this.description, this.lineNumber, this.endLineNumber, this.originType,
this.originType, url != null ? url.toExternalForm() : null, this.commentsOrNull); url != null ? url.toExternalForm() : null, this.resourceOrNull, this.commentsOrNull);
} }
@Override @Override
@ -90,8 +97,8 @@ final class SimpleConfigOrigin implements ConfigOrigin {
if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull)) { if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull)) {
return this; return this;
} else { } else {
return new SimpleConfigOrigin(this.description, this.lineNumber, this.endLineNumber, return new SimpleConfigOrigin(this.description, this.lineNumber, this.endLineNumber, this.originType,
this.originType, this.urlOrNull, comments); this.urlOrNull, this.resourceOrNull, comments);
} }
} }
@ -101,8 +108,7 @@ final class SimpleConfigOrigin implements ConfigOrigin {
} else if (this.commentsOrNull == null) { } else if (this.commentsOrNull == null) {
return withComments(comments); return withComments(comments);
} else { } else {
List<String> merged = new ArrayList<String>(comments.size() List<String> merged = new ArrayList<String>(comments.size() + this.commentsOrNull.size());
+ this.commentsOrNull.size());
merged.addAll(comments); merged.addAll(comments);
merged.addAll(this.commentsOrNull); merged.addAll(this.commentsOrNull);
return withComments(merged); return withComments(merged);
@ -115,8 +121,7 @@ final class SimpleConfigOrigin implements ConfigOrigin {
} else if (this.commentsOrNull == null) { } else if (this.commentsOrNull == null) {
return withComments(comments); return withComments(comments);
} else { } else {
List<String> merged = new ArrayList<String>(comments.size() List<String> merged = new ArrayList<String>(comments.size() + this.commentsOrNull.size());
+ this.commentsOrNull.size());
merged.addAll(this.commentsOrNull); merged.addAll(this.commentsOrNull);
merged.addAll(comments); merged.addAll(comments);
return withComments(merged); return withComments(merged);
@ -125,8 +130,6 @@ final class SimpleConfigOrigin implements ConfigOrigin {
@Override @Override
public String description() { public String description() {
// not putting the URL in here for files and resources, because people
// parsing "file: line" syntax would hit the ":" in the URL.
if (lineNumber < 0) { if (lineNumber < 0) {
return description; return description;
} else if (endLineNumber == lineNumber) { } else if (endLineNumber == lineNumber) {
@ -141,11 +144,10 @@ final class SimpleConfigOrigin implements ConfigOrigin {
if (other instanceof SimpleConfigOrigin) { if (other instanceof SimpleConfigOrigin) {
SimpleConfigOrigin otherOrigin = (SimpleConfigOrigin) other; SimpleConfigOrigin otherOrigin = (SimpleConfigOrigin) other;
return this.description.equals(otherOrigin.description) return this.description.equals(otherOrigin.description) && this.lineNumber == otherOrigin.lineNumber
&& this.lineNumber == otherOrigin.lineNumber && this.endLineNumber == otherOrigin.endLineNumber && this.originType == otherOrigin.originType
&& this.endLineNumber == otherOrigin.endLineNumber && ConfigImplUtil.equalsHandlingNull(this.urlOrNull, otherOrigin.urlOrNull)
&& this.originType == otherOrigin.originType && ConfigImplUtil.equalsHandlingNull(this.resourceOrNull, otherOrigin.resourceOrNull);
&& ConfigImplUtil.equalsHandlingNull(this.urlOrNull, otherOrigin.urlOrNull);
} else { } else {
return false; return false;
} }
@ -159,17 +161,14 @@ final class SimpleConfigOrigin implements ConfigOrigin {
h = 41 * (h + originType.hashCode()); h = 41 * (h + originType.hashCode());
if (urlOrNull != null) if (urlOrNull != null)
h = 41 * (h + urlOrNull.hashCode()); h = 41 * (h + urlOrNull.hashCode());
if (resourceOrNull != null)
h = 41 * (h + resourceOrNull.hashCode());
return h; return h;
} }
@Override @Override
public String toString() { public String toString() {
// the url is only really useful on top of description for resources return "ConfigOrigin(" + description + ")";
if (originType == OriginType.RESOURCE && urlOrNull != null) {
return "ConfigOrigin(" + description + "," + urlOrNull + ")";
} else {
return "ConfigOrigin(" + description + ")";
}
} }
@Override @Override
@ -208,11 +207,7 @@ final class SimpleConfigOrigin implements ConfigOrigin {
@Override @Override
public String resource() { public String resource() {
if (originType == OriginType.RESOURCE) { return resourceOrNull;
return description;
} else {
return null;
}
} }
@Override @Override
@ -291,6 +286,13 @@ final class SimpleConfigOrigin implements ConfigOrigin {
mergedURL = null; mergedURL = null;
} }
String mergedResource;
if (ConfigImplUtil.equalsHandlingNull(a.resourceOrNull, b.resourceOrNull)) {
mergedResource = a.resourceOrNull;
} else {
mergedResource = null;
}
if (ConfigImplUtil.equalsHandlingNull(a.commentsOrNull, b.commentsOrNull)) { if (ConfigImplUtil.equalsHandlingNull(a.commentsOrNull, b.commentsOrNull)) {
mergedComments = a.commentsOrNull; mergedComments = a.commentsOrNull;
} else { } else {
@ -301,8 +303,8 @@ final class SimpleConfigOrigin implements ConfigOrigin {
mergedComments.addAll(b.commentsOrNull); mergedComments.addAll(b.commentsOrNull);
} }
return new SimpleConfigOrigin(mergedDesc, mergedStartLine, mergedEndLine, mergedType, return new SimpleConfigOrigin(mergedDesc, mergedStartLine, mergedEndLine, mergedType, mergedURL,
mergedURL, mergedComments); mergedResource, mergedComments);
} }
private static int similarity(SimpleConfigOrigin a, SimpleConfigOrigin b) { private static int similarity(SimpleConfigOrigin a, SimpleConfigOrigin b) {
@ -322,6 +324,8 @@ final class SimpleConfigOrigin implements ConfigOrigin {
count += 1; count += 1;
if (ConfigImplUtil.equalsHandlingNull(a.urlOrNull, b.urlOrNull)) if (ConfigImplUtil.equalsHandlingNull(a.urlOrNull, b.urlOrNull))
count += 1; count += 1;
if (ConfigImplUtil.equalsHandlingNull(a.resourceOrNull, b.resourceOrNull))
count += 1;
} }
return count; return count;
@ -331,8 +335,7 @@ final class SimpleConfigOrigin implements ConfigOrigin {
// common. we want to merge two lines in the same file rather than something // common. we want to merge two lines in the same file rather than something
// else with one of the lines; because two lines in the same file can be // else with one of the lines; because two lines in the same file can be
// better consolidated. // better consolidated.
private static SimpleConfigOrigin mergeThree(SimpleConfigOrigin a, SimpleConfigOrigin b, private static SimpleConfigOrigin mergeThree(SimpleConfigOrigin a, SimpleConfigOrigin b, SimpleConfigOrigin c) {
SimpleConfigOrigin c) {
if (similarity(a, b) >= similarity(b, c)) { if (similarity(a, b) >= similarity(b, c)) {
return mergeTwo(mergeTwo(a, b), c); return mergeTwo(mergeTwo(a, b), c);
} else { } else {
@ -397,6 +400,8 @@ final class SimpleConfigOrigin implements ConfigOrigin {
if (urlOrNull != null) if (urlOrNull != null)
m.put(SerializedField.ORIGIN_URL, urlOrNull); m.put(SerializedField.ORIGIN_URL, urlOrNull);
if (resourceOrNull != null)
m.put(SerializedField.ORIGIN_RESOURCE, resourceOrNull);
if (commentsOrNull != null) if (commentsOrNull != null)
m.put(SerializedField.ORIGIN_COMMENTS, commentsOrNull); m.put(SerializedField.ORIGIN_COMMENTS, commentsOrNull);
@ -422,16 +427,14 @@ final class SimpleConfigOrigin implements ConfigOrigin {
for (Map.Entry<SerializedField, Object> baseEntry : base.entrySet()) { for (Map.Entry<SerializedField, Object> baseEntry : base.entrySet()) {
SerializedField f = baseEntry.getKey(); SerializedField f = baseEntry.getKey();
if (m.containsKey(f) if (m.containsKey(f) && ConfigImplUtil.equalsHandlingNull(baseEntry.getValue(), m.get(f))) {
&& ConfigImplUtil.equalsHandlingNull(baseEntry.getValue(), m.get(f))) {
// if field is unchanged, just remove it so we inherit // if field is unchanged, just remove it so we inherit
m.remove(f); m.remove(f);
} else if (!m.containsKey(f)) { } else if (!m.containsKey(f)) {
// if field has been removed, we have to add a deletion entry // if field has been removed, we have to add a deletion entry
switch (f) { switch (f) {
case ORIGIN_DESCRIPTION: case ORIGIN_DESCRIPTION:
throw new ConfigException.BugOrBroken("origin missing description field? " throw new ConfigException.BugOrBroken("origin missing description field? " + child);
+ child);
case ORIGIN_LINE_NUMBER: case ORIGIN_LINE_NUMBER:
m.put(SerializedField.ORIGIN_LINE_NUMBER, -1); m.put(SerializedField.ORIGIN_LINE_NUMBER, -1);
break; break;
@ -443,14 +446,17 @@ final class SimpleConfigOrigin implements ConfigOrigin {
case ORIGIN_URL: case ORIGIN_URL:
m.put(SerializedField.ORIGIN_NULL_URL, ""); m.put(SerializedField.ORIGIN_NULL_URL, "");
break; break;
case ORIGIN_RESOURCE:
m.put(SerializedField.ORIGIN_NULL_RESOURCE, "");
break;
case ORIGIN_COMMENTS: case ORIGIN_COMMENTS:
m.put(SerializedField.ORIGIN_NULL_COMMENTS, ""); m.put(SerializedField.ORIGIN_NULL_COMMENTS, "");
break; break;
case ORIGIN_NULL_URL: // FALL THRU case ORIGIN_NULL_URL: // FALL THRU
case ORIGIN_NULL_RESOURCE: // FALL THRU
case ORIGIN_NULL_COMMENTS: case ORIGIN_NULL_COMMENTS:
throw new ConfigException.BugOrBroken( throw new ConfigException.BugOrBroken("computing delta, base object should not contain " + f + " "
"computing delta, base object should not contain " + f + base);
+ " " + base);
case END_MARKER: case END_MARKER:
case ROOT_VALUE: case ROOT_VALUE:
case ROOT_WAS_CONFIG: case ROOT_WAS_CONFIG:
@ -480,10 +486,16 @@ final class SimpleConfigOrigin implements ConfigOrigin {
throw new IOException("Missing ORIGIN_TYPE field"); throw new IOException("Missing ORIGIN_TYPE field");
OriginType originType = OriginType.values()[originTypeOrdinal.byteValue()]; OriginType originType = OriginType.values()[originTypeOrdinal.byteValue()];
String urlOrNull = (String) m.get(SerializedField.ORIGIN_URL); String urlOrNull = (String) m.get(SerializedField.ORIGIN_URL);
String resourceOrNull = (String) m.get(SerializedField.ORIGIN_RESOURCE);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<String> commentsOrNull = (List<String>) m.get(SerializedField.ORIGIN_COMMENTS); List<String> commentsOrNull = (List<String>) m.get(SerializedField.ORIGIN_COMMENTS);
// Older versions did not have a resource field, they stuffed it into
// the description.
if (originType == OriginType.RESOURCE && resourceOrNull == null) {
resourceOrNull = description;
}
return new SimpleConfigOrigin(description, lineNumber != null ? lineNumber : -1, return new SimpleConfigOrigin(description, lineNumber != null ? lineNumber : -1,
endLineNumber != null ? endLineNumber : -1, originType, urlOrNull, commentsOrNull); endLineNumber != null ? endLineNumber : -1, originType, urlOrNull, resourceOrNull, commentsOrNull);
} }
static Map<SerializedField, Object> applyFieldsDelta(Map<SerializedField, Object> base, static Map<SerializedField, Object> applyFieldsDelta(Map<SerializedField, Object> base,
@ -510,6 +522,13 @@ final class SimpleConfigOrigin implements ConfigOrigin {
m.put(f, base.get(f)); m.put(f, base.get(f));
} }
break; break;
case ORIGIN_RESOURCE:
if (delta.containsKey(SerializedField.ORIGIN_NULL_RESOURCE)) {
m.remove(SerializedField.ORIGIN_NULL_RESOURCE);
} else {
m.put(f, base.get(f));
}
break;
case ORIGIN_COMMENTS: case ORIGIN_COMMENTS:
if (delta.containsKey(SerializedField.ORIGIN_NULL_COMMENTS)) { if (delta.containsKey(SerializedField.ORIGIN_NULL_COMMENTS)) {
m.remove(SerializedField.ORIGIN_NULL_COMMENTS); m.remove(SerializedField.ORIGIN_NULL_COMMENTS);
@ -518,11 +537,12 @@ final class SimpleConfigOrigin implements ConfigOrigin {
} }
break; break;
case ORIGIN_NULL_URL: // FALL THRU case ORIGIN_NULL_URL: // FALL THRU
case ORIGIN_NULL_RESOURCE: // FALL THRU
case ORIGIN_NULL_COMMENTS: // FALL THRU case ORIGIN_NULL_COMMENTS: // FALL THRU
// base objects shouldn't contain these, should just // base objects shouldn't contain these, should just
// lack the field. these are only in deltas. // lack the field. these are only in deltas.
throw new ConfigException.BugOrBroken( throw new ConfigException.BugOrBroken("applying fields, base object should not contain " + f + " "
"applying fields, base object should not contain " + f + " " + base); + base);
case ORIGIN_END_LINE_NUMBER: // FALL THRU case ORIGIN_END_LINE_NUMBER: // FALL THRU
case ORIGIN_LINE_NUMBER: // FALL THRU case ORIGIN_LINE_NUMBER: // FALL THRU
case ORIGIN_TYPE: case ORIGIN_TYPE:
@ -542,8 +562,8 @@ final class SimpleConfigOrigin implements ConfigOrigin {
return m; return m;
} }
static SimpleConfigOrigin fromBase(SimpleConfigOrigin baseOrigin, static SimpleConfigOrigin fromBase(SimpleConfigOrigin baseOrigin, Map<SerializedField, Object> delta)
Map<SerializedField, Object> delta) throws IOException { throws IOException {
Map<SerializedField, Object> baseFields; Map<SerializedField, Object> baseFields;
if (baseOrigin != null) if (baseOrigin != null)
baseFields = baseOrigin.toFields(); baseFields = baseOrigin.toFields();

View File

@ -860,18 +860,27 @@ class ConfigTest extends TestUtils {
val conf = ConfigFactory.load("test01") val conf = ConfigFactory.load("test01")
val o1 = conf.getValue("ints.fortyTwo").origin() val o1 = conf.getValue("ints.fortyTwo").origin()
assertEquals("test01.conf: 3", o1.description) // the checkout directory would be in between this startsWith and endsWith
assertTrue("description starts with resource '" + o1.description + "'", o1.description.startsWith("test01.conf @"))
assertTrue("description ends with url and line '" + o1.description + "'", o1.description.endsWith("/config/target/test-classes/test01.conf: 3"))
assertEquals("test01.conf", o1.resource) assertEquals("test01.conf", o1.resource)
assertTrue("url ends with resource file", o1.url.getPath.endsWith("/config/target/test-classes/test01.conf"))
assertEquals(3, o1.lineNumber) assertEquals(3, o1.lineNumber)
val o2 = conf.getValue("fromJson1").origin() val o2 = conf.getValue("fromJson1").origin()
assertEquals("test01.json: 2", o2.description) // the checkout directory would be in between this startsWith and endsWith
assertTrue("description starts with json resource '" + o2.description + "'", o2.description.startsWith("test01.json @"))
assertTrue("description of json resource ends with url and line '" + o2.description + "'", o2.description.endsWith("/config/target/test-classes/test01.json: 2"))
assertEquals("test01.json", o2.resource) assertEquals("test01.json", o2.resource)
assertTrue("url ends with json resource file", o2.url.getPath.endsWith("/config/target/test-classes/test01.json"))
assertEquals(2, o2.lineNumber) assertEquals(2, o2.lineNumber)
val o3 = conf.getValue("fromProps.bool").origin() val o3 = conf.getValue("fromProps.bool").origin()
assertEquals("test01.properties", o3.description) // the checkout directory would be in between this startsWith and endsWith
assertTrue("description starts with props resource '" + o3.description + "'", o3.description.startsWith("test01.properties @"))
assertTrue("description of props resource ends with url '" + o3.description + "'", o3.description.endsWith("/config/target/test-classes/test01.properties"))
assertEquals("test01.properties", o3.resource) assertEquals("test01.properties", o3.resource)
assertTrue("url ends with props resource file", o3.url.getPath.endsWith("/config/target/test-classes/test01.properties"))
// we don't have line numbers for properties files // we don't have line numbers for properties files
assertEquals(-1, o3.lineNumber) assertEquals(-1, o3.lineNumber)
} }

View File

@ -896,7 +896,8 @@ class ConfigValueTest extends TestUtils {
SimpleConfigOrigin.newSimple("foo"), SimpleConfigOrigin.newSimple("foo"),
SimpleConfigOrigin.newFile("/tmp/blahblah"), SimpleConfigOrigin.newFile("/tmp/blahblah"),
SimpleConfigOrigin.newURL(new URL("http://example.com")), SimpleConfigOrigin.newURL(new URL("http://example.com")),
SimpleConfigOrigin.newResource("myresource")) SimpleConfigOrigin.newResource("myresource"),
SimpleConfigOrigin.newResource("myresource", new URL("file://foo/bar")))
val combos = bases.flatMap({ val combos = bases.flatMap({
base => base =>
Seq( Seq(

View File

@ -561,8 +561,8 @@ class PublicApiTest extends TestUtils {
// check that each value has its own ConfigOrigin // check that each value has its own ConfigOrigin
val v1 = conf.getValue("ints.fortyTwo") val v1 = conf.getValue("ints.fortyTwo")
val v2 = conf.getValue("test-lib.fromTestLib") val v2 = conf.getValue("test-lib.fromTestLib")
assertEquals("test01.conf", v1.origin.resource) assertEquals("v1 has right origin resource", "test01.conf", v1.origin.resource)
assertEquals("test01.conf", v2.origin.resource) assertEquals("v2 has right origin resource", "test01.conf", v2.origin.resource)
assertEquals(v1.origin.resource, v2.origin.resource) assertEquals(v1.origin.resource, v2.origin.resource)
assertFalse("same urls in " + v1.origin + " " + v2.origin, v1.origin.url == v2.origin.url) assertFalse("same urls in " + v1.origin + " " + v2.origin, v1.origin.url == v2.origin.url)
assertFalse(v1.origin.filename == v2.origin.filename) assertFalse(v1.origin.filename == v2.origin.filename)