Merge pull request #243 from acaly/set_origin

Add methods to create and set origin.
This commit is contained in:
Havoc Pennington 2015-01-27 08:58:48 -05:00
commit ce394080a1
15 changed files with 162 additions and 23 deletions

View File

@ -41,4 +41,6 @@ public interface ConfigList extends List<ConfigValue>, ConfigValue {
@Override @Override
List<Object> unwrapped(); List<Object> unwrapped();
@Override
ConfigList withOrigin(ConfigOrigin origin);
} }

View File

@ -129,4 +129,7 @@ public interface ConfigObject extends ConfigValue, Map<String, ConfigValue> {
* @return the new instance with the new map entry * @return the new instance with the new map entry
*/ */
ConfigObject withValue(String key, ConfigValue value); ConfigObject withValue(String key, ConfigValue value);
@Override
ConfigObject withOrigin(ConfigOrigin origin);
} }

View File

@ -79,4 +79,36 @@ public interface ConfigOrigin {
* none * none
*/ */
public List<String> comments(); public List<String> comments();
/**
* Returns a {@code ConfigOrigin} based on this one, but with the given
* comments. Does not modify this instance or any {@code ConfigValue}s with
* this origin (since they are immutable). To set the returned origin to a
* {@code ConfigValue}, use {@link ConfigValue#withOrigin}.
*
* <p>
* Note that when the given comments are equal to the comments on this object,
* a new instance may not be created and {@code this} is returned directly.
*
* @param comments the comments used on the returned origin
* @return the ConfigOrigin with the given comments
*/
public ConfigOrigin withComments(List<String> comments);
/**
* Returns a {@code ConfigOrigin} based on this one, but with the given
* line number. This origin must be a FILE, URL or RESOURCE. Does not modify
* this instance or any {@code ConfigValue}s with this origin (since they are
* immutable). To set the returned origin to a {@code ConfigValue}, use
* {@link ConfigValue#withOrigin}.
*
* <p>
* Note that when the given lineNumber are equal to the lineNumber on this
* object, a new instance may not be created and {@code this} is returned
* directly.
*
* @param comments the comments used on the returned origin
* @return the created ConfigOrigin
*/
public ConfigOrigin withLineNumber(int lineNumber);
} }

View File

@ -0,0 +1,59 @@
package com.typesafe.config;
import java.net.URL;
import com.typesafe.config.impl.ConfigImpl;
/**
* This class contains some static factory methods for building a {@link
* ConfigOrigin}. {@code ConfigOrigin}s are automatically created when you
* call other API methods to get a {@code ConfigValue} or {@code Config}.
* But you can also set the origin of an existing {@code ConfigValue}, using
* {@link ConfigValue#withOrigin(ConfigOrigin)}.
*
*/
public final class ConfigOriginFactory {
private ConfigOriginFactory() {
}
/**
* Returns the default origin for values when no other information is
* provided. This is the origin used in {@link ConfigValueFactory
* #fromAnyRef(Object)}.
*
* @return the default origin
*/
public static ConfigOrigin newSimple() {
return newSimple(null);
}
/**
* Returns a origin with the given description.
*
* @param description brief description of what the origin is
* @return
*/
public static ConfigOrigin newSimple(String description) {
return ConfigImpl.newSimpleOrigin(description);
}
/**
* Creates a file origin with the given filename.
*
* @param filename the filename of this origin
* @return
*/
public static ConfigOrigin newFile(String filename) {
return ConfigImpl.newFileOrigin(filename);
}
/**
* Creates a url origin with the given URL object.
*
* @param url the url of this origin
* @return
*/
public static ConfigOrigin newURL(URL url) {
return ConfigImpl.newURLOrigin(url);
}
}

View File

@ -106,4 +106,14 @@ public interface ConfigValue extends ConfigMergeable {
* @return a {@code Config} instance containing this value at the given key. * @return a {@code Config} instance containing this value at the given key.
*/ */
Config atKey(String key); Config atKey(String key);
/**
* Returns a {@code ConfigValue} based on this one, but with the given
* origin. This is useful when you are parsing a new format of file or setting
* comments for a single ConfigValue.
*
* @param origin the origin set on the returned value
* @return the new ConfigValue with the given origin
*/
ConfigValue withOrigin(ConfigOrigin origin);
} }

View File

@ -18,7 +18,6 @@ import com.typesafe.config.ConfigValue;
import com.typesafe.config.ConfigValueType; import com.typesafe.config.ConfigValueType;
abstract class AbstractConfigObject extends AbstractConfigValue implements ConfigObject, Container { abstract class AbstractConfigObject extends AbstractConfigValue implements ConfigObject, Container {
final private SimpleConfig config; final private SimpleConfig config;
protected AbstractConfigObject(ConfigOrigin origin) { protected AbstractConfigObject(ConfigOrigin origin) {
@ -214,4 +213,9 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements Confi
public ConfigValue remove(Object arg0) { public ConfigValue remove(Object arg0) {
throw weAreImmutable("remove"); throw weAreImmutable("remove");
} }
@Override
public AbstractConfigObject withOrigin(ConfigOrigin origin) {
return (AbstractConfigObject) super.withOrigin(origin);
}
} }

View File

@ -257,6 +257,7 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue {
return mergedWithNonObject(Collections.singletonList(this), fallback); return mergedWithNonObject(Collections.singletonList(this), fallback);
} }
@Override
public AbstractConfigValue withOrigin(ConfigOrigin origin) { public AbstractConfigValue withOrigin(ConfigOrigin origin) {
if (this.origin == origin) if (this.origin == origin)
return this; return this;

View File

@ -5,6 +5,7 @@ package com.typesafe.config.impl;
import java.io.File; import java.io.File;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -461,4 +462,24 @@ public class ConfigImpl {
else else
return new ConfigException.NotResolved(newMessage, original); return new ConfigException.NotResolved(newMessage, original);
} }
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigOrigin newSimpleOrigin(String description) {
if (description == null) {
return defaultValueOrigin;
} else {
return SimpleConfigOrigin.newSimple(description);
}
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigOrigin newFileOrigin(String filename) {
return SimpleConfigOrigin.newFile(filename);
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigOrigin newURLOrigin(URL url) {
return SimpleConfigOrigin.newURL(url);
}
} }

View File

@ -424,7 +424,7 @@ final class Parser {
} }
private SimpleConfigOrigin lineOrigin() { private SimpleConfigOrigin lineOrigin() {
return ((SimpleConfigOrigin) baseOrigin).setLineNumber(lineNumber); return ((SimpleConfigOrigin) baseOrigin).withLineNumber(lineNumber);
} }
private ConfigException parseError(String message) { private ConfigException parseError(String message) {
@ -553,19 +553,19 @@ final class Parser {
} }
} }
// the setComments(null) is to ensure comments are only // the withComments(null) is to ensure comments are only
// on the exact leaf node they apply to. // on the exact leaf node they apply to.
// a comment before "foo.bar" applies to the full setting // a comment before "foo.bar" applies to the full setting
// "foo.bar" not also to "foo" // "foo.bar" not also to "foo"
ListIterator<String> i = keys.listIterator(keys.size()); ListIterator<String> i = keys.listIterator(keys.size());
String deepest = i.previous(); String deepest = i.previous();
AbstractConfigObject o = new SimpleConfigObject(value.origin().setComments(null), AbstractConfigObject o = new SimpleConfigObject(value.origin().withComments(null),
Collections.<String, AbstractConfigValue> singletonMap( Collections.<String, AbstractConfigValue> singletonMap(
deepest, value)); deepest, value));
while (i.hasPrevious()) { while (i.hasPrevious()) {
Map<String, AbstractConfigValue> m = Collections.<String, AbstractConfigValue> singletonMap( Map<String, AbstractConfigValue> m = Collections.<String, AbstractConfigValue> singletonMap(
i.previous(), o); i.previous(), o);
o = new SimpleConfigObject(value.origin().setComments(null), m); o = new SimpleConfigObject(value.origin().withComments(null), m);
} }
return o; return o;

View File

@ -451,4 +451,9 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList,
private Object writeReplace() throws ObjectStreamException { private Object writeReplace() throws ObjectStreamException {
return new SerializedConfigValue(this); return new SerializedConfigValue(this);
} }
@Override
public SimpleConfigList withOrigin(ConfigOrigin origin) {
return (SimpleConfigList) super.withOrigin(origin);
}
} }

View File

@ -70,7 +70,8 @@ final class SimpleConfigOrigin implements ConfigOrigin {
return newResource(resource, null); return newResource(resource, null);
} }
SimpleConfigOrigin setLineNumber(int lineNumber) { @Override
public SimpleConfigOrigin withLineNumber(int lineNumber) {
if (lineNumber == this.lineNumber && lineNumber == this.endLineNumber) { if (lineNumber == this.lineNumber && lineNumber == this.endLineNumber) {
return this; return this;
} else { } else {
@ -84,7 +85,8 @@ final class SimpleConfigOrigin implements ConfigOrigin {
this.originType, url != null ? url.toExternalForm() : null, this.commentsOrNull); this.originType, url != null ? url.toExternalForm() : null, this.commentsOrNull);
} }
SimpleConfigOrigin setComments(List<String> comments) { @Override
public SimpleConfigOrigin withComments(List<String> comments) {
if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull)) { if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull)) {
return this; return this;
} else { } else {
@ -97,13 +99,13 @@ final class SimpleConfigOrigin implements ConfigOrigin {
if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull) || comments == null) { if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull) || comments == null) {
return this; return this;
} else if (this.commentsOrNull == null) { } else if (this.commentsOrNull == null) {
return setComments(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 setComments(merged); return withComments(merged);
} }
} }
@ -111,13 +113,13 @@ final class SimpleConfigOrigin implements ConfigOrigin {
if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull) || comments == null) { if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull) || comments == null) {
return this; return this;
} else if (this.commentsOrNull == null) { } else if (this.commentsOrNull == null) {
return setComments(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 setComments(merged); return withComments(merged);
} }
} }

View File

@ -128,7 +128,7 @@ final class Tokenizer {
this.allowComments = allowComments; this.allowComments = allowComments;
this.buffer = new LinkedList<Integer>(); this.buffer = new LinkedList<Integer>();
lineNumber = 1; lineNumber = 1;
lineOrigin = this.origin.setLineNumber(lineNumber); lineOrigin = this.origin.withLineNumber(lineNumber);
tokens = new LinkedList<Token>(); tokens = new LinkedList<Token>();
tokens.add(Tokens.START); tokens.add(Tokens.START);
whitespaceSaver = new WhitespaceSaver(); whitespaceSaver = new WhitespaceSaver();
@ -254,7 +254,7 @@ final class Tokenizer {
private static ConfigOrigin lineOrigin(ConfigOrigin baseOrigin, private static ConfigOrigin lineOrigin(ConfigOrigin baseOrigin,
int lineNumber) { int lineNumber) {
return ((SimpleConfigOrigin) baseOrigin).setLineNumber(lineNumber); return ((SimpleConfigOrigin) baseOrigin).withLineNumber(lineNumber);
} }
// ONE char has always been consumed, either the # or the first /, but // ONE char has always been consumed, either the # or the first /, but
@ -446,7 +446,7 @@ final class Tokenizer {
else if (c == '\n') { else if (c == '\n') {
// keep the line number accurate // keep the line number accurate
lineNumber += 1; lineNumber += 1;
lineOrigin = origin.setLineNumber(lineNumber); lineOrigin = origin.withLineNumber(lineNumber);
} }
} }
@ -549,7 +549,7 @@ final class Tokenizer {
// newline tokens have the just-ended line number // newline tokens have the just-ended line number
Token line = Tokens.newLine(lineOrigin); Token line = Tokens.newLine(lineOrigin);
lineNumber += 1; lineNumber += 1;
lineOrigin = origin.setLineNumber(lineNumber); lineOrigin = origin.withLineNumber(lineNumber);
return line; return line;
} else { } else {
Token t; Token t;

View File

@ -663,8 +663,8 @@ class ConfigValueTest extends TestUtils {
def configOriginFileAndLine() { def configOriginFileAndLine() {
val hasFilename = SimpleConfigOrigin.newFile("foo") val hasFilename = SimpleConfigOrigin.newFile("foo")
val noFilename = SimpleConfigOrigin.newSimple("bar") val noFilename = SimpleConfigOrigin.newSimple("bar")
val filenameWithLine = hasFilename.setLineNumber(3) val filenameWithLine = hasFilename.withLineNumber(3)
val noFilenameWithLine = noFilename.setLineNumber(4) val noFilenameWithLine = noFilename.withLineNumber(4)
assertEquals("foo", hasFilename.filename()) assertEquals("foo", hasFilename.filename())
assertEquals("foo", filenameWithLine.filename()) assertEquals("foo", filenameWithLine.filename())
@ -866,10 +866,10 @@ class ConfigValueTest extends TestUtils {
val combos = bases.flatMap({ val combos = bases.flatMap({
base => base =>
Seq( Seq(
(base, base.setComments(Seq("this is a comment", "another one").asJava)), (base, base.withComments(Seq("this is a comment", "another one").asJava)),
(base, base.setComments(null)), (base, base.withComments(null)),
(base, base.setLineNumber(41)), (base, base.withLineNumber(41)),
(base, SimpleConfigOrigin.mergeOrigins(base.setLineNumber(10), base.setLineNumber(20)))) (base, SimpleConfigOrigin.mergeOrigins(base.withLineNumber(10), base.withLineNumber(20))))
}) ++ }) ++
bases.sliding(2).map({ seq => (seq.head, seq.tail.head) }) ++ bases.sliding(2).map({ seq => (seq.head, seq.tail.head) }) ++
bases.sliding(3).map({ seq => (seq.head, seq.tail.tail.head) }) ++ bases.sliding(3).map({ seq => (seq.head, seq.tail.tail.head) }) ++

View File

@ -888,7 +888,7 @@ class PublicApiTest extends TestUtils {
def exceptionSerializable() { def exceptionSerializable() {
// ArrayList is a serialization problem so we want to cover it in tests // ArrayList is a serialization problem so we want to cover it in tests
val comments = new java.util.ArrayList(List("comment 1", "comment 2").asJava) val comments = new java.util.ArrayList(List("comment 1", "comment 2").asJava)
val e = new ConfigException.WrongType(SimpleConfigOrigin.newSimple("an origin").setComments(comments), val e = new ConfigException.WrongType(SimpleConfigOrigin.newSimple("an origin").withComments(comments),
"this is a message", new RuntimeException("this is a cause")) "this is a message", new RuntimeException("this is a cause"))
val eCopy = checkSerializableNoMeaningfulEquals(e) val eCopy = checkSerializableNoMeaningfulEquals(e)
assertTrue("messages equal after deserialize", e.getMessage.equals(eCopy.getMessage)) assertTrue("messages equal after deserialize", e.getMessage.equals(eCopy.getMessage))

View File

@ -614,7 +614,7 @@ abstract trait TestUtils {
def tokenDouble(d: Double) = Tokens.newDouble(fakeOrigin(), d, null) def tokenDouble(d: Double) = Tokens.newDouble(fakeOrigin(), d, null)
def tokenInt(i: Int) = Tokens.newInt(fakeOrigin(), i, null) def tokenInt(i: Int) = Tokens.newInt(fakeOrigin(), i, null)
def tokenLong(l: Long) = Tokens.newLong(fakeOrigin(), l, null) def tokenLong(l: Long) = Tokens.newLong(fakeOrigin(), l, null)
def tokenLine(line: Int) = Tokens.newLine(fakeOrigin.setLineNumber(line)) def tokenLine(line: Int) = Tokens.newLine(fakeOrigin.withLineNumber(line))
def tokenComment(text: String) = Tokens.newComment(fakeOrigin(), text) def tokenComment(text: String) = Tokens.newComment(fakeOrigin(), text)
private def tokenMaybeOptionalSubstitution(optional: Boolean, expression: Token*) = { private def tokenMaybeOptionalSubstitution(optional: Boolean, expression: Token*) = {