-
Notifications
You must be signed in to change notification settings - Fork 966
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ConfigDocument API #280
Changes from 7 commits
13f2cb3
faf8d42
fa0aaea
3166db4
e695543
cce8c20
0a804de
b19e38f
2e6bc40
4101f94
f115731
d3b33cc
c44ef1c
639a3ea
4b61790
28a096f
c3a2e07
1639a91
b70d4c1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/** | ||
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com> | ||
*/ | ||
package com.typesafe.config; | ||
|
||
/** | ||
* An immutable node that makes up the ConfigDocument AST, and which can be | ||
* used to reproduce part or all of the original text of an input. | ||
* | ||
* <p> | ||
* Because this object is immutable, it is safe to use from multiple threads and | ||
* there's no need for "defensive copies." | ||
* | ||
* <p> | ||
* <em>Do not implement interface {@code ConfigNode}</em>; it should only be | ||
* implemented by the config library. Arbitrary implementations will not work | ||
* because the library internals assume a specific concrete implementation. | ||
* Also, this interface is likely to grow new methods over time, so third-party | ||
* implementations will break. | ||
*/ | ||
public interface ConfigNode { | ||
/** | ||
* The original text of the input which was used to form this particular node. | ||
* @return the original text used to form this node as a String | ||
*/ | ||
public String render(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/** | ||
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com> | ||
*/ | ||
package com.typesafe.config.impl; | ||
|
||
import com.typesafe.config.ConfigNode; | ||
import java.util.Collection; | ||
|
||
abstract class AbstractConfigNode implements ConfigNode { | ||
abstract Collection<Token> tokens(); | ||
final public String render() { | ||
StringBuilder origText = new StringBuilder(); | ||
Iterable<Token> tokens = tokens(); | ||
for (Token t : tokens) { | ||
origText.append(t.tokenText()); | ||
} | ||
return origText.toString(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/** | ||
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com> | ||
*/ | ||
package com.typesafe.config.impl; | ||
|
||
// This is required if we want | ||
// to be referencing the AbstractConfigNode class in implementation rather than the | ||
// ConfigNode interface, as we can't cast an AbstractConfigNode to an interface | ||
abstract class AbstractConfigNodeValue extends AbstractConfigNode { | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
/** | ||
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com> | ||
*/ | ||
package com.typesafe.config.impl; | ||
|
||
import com.typesafe.config.ConfigNode; | ||
|
||
import java.util.*; | ||
|
||
final class ConfigNodeComplexValue extends AbstractConfigNodeValue { | ||
final private ArrayList<AbstractConfigNode> children; | ||
|
||
ConfigNodeComplexValue(Collection<AbstractConfigNode> children) { | ||
this.children = new ArrayList(children); | ||
} | ||
|
||
public Iterable<AbstractConfigNode> children() { | ||
return children; | ||
} | ||
|
||
protected Collection<Token> tokens() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this one should have |
||
ArrayList<Token> tokens = new ArrayList(); | ||
for (AbstractConfigNode child : children) { | ||
tokens.addAll(child.tokens()); | ||
} | ||
return tokens; | ||
} | ||
|
||
protected ConfigNodeComplexValue changeValueOnPath(Path desiredPath, AbstractConfigNodeValue value) { | ||
ArrayList<AbstractConfigNode> childrenCopy = new ArrayList(children); | ||
boolean replaced = value == null; | ||
ConfigNodeKeyValue node; | ||
Path key; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these two vars could be inside the loop right? |
||
for (int i = children.size() - 1; i >= 0; i--) { | ||
if (!(children.get(i) instanceof ConfigNodeKeyValue)) { | ||
continue; | ||
} | ||
node = (ConfigNodeKeyValue)children.get(i); | ||
key = node.key().value(); | ||
if (key.equals(desiredPath)) { | ||
if (!replaced) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's a nice practice to avoid combining negation and if/else (do |
||
childrenCopy.set(i, node.replaceValue(value)); | ||
replaced = true; | ||
} | ||
else | ||
childrenCopy.remove(i); | ||
} else if (desiredPath.startsWith(key)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. below feels like it should be using recursion somehow - maybe it's as simple as |
||
if (node.value() instanceof ConfigNodeComplexValue) { | ||
Path remainingPath = desiredPath.subPath(key.length()); | ||
if (!replaced) { | ||
node = node.replaceValue(((ConfigNodeComplexValue) node.value()).changeValueOnPath(remainingPath, value)); | ||
if (node.render() != children.get(i).render()) | ||
replaced = true; | ||
childrenCopy.set(i, node); | ||
} else { | ||
node = node.replaceValue(((ConfigNodeComplexValue) node.value()).removeValueOnPath(remainingPath)); | ||
childrenCopy.set(i, node); | ||
} | ||
} | ||
} | ||
} | ||
return new ConfigNodeComplexValue(childrenCopy); | ||
} | ||
|
||
public ConfigNodeComplexValue setValueOnPath(Path desiredPath, AbstractConfigNodeValue value) { | ||
ConfigNodeComplexValue node = changeValueOnPath(desiredPath, value); | ||
|
||
// If the desired Path did not exist, add it | ||
if (node.render().equals(render())) { | ||
ArrayList<AbstractConfigNode> childrenCopy = new ArrayList<AbstractConfigNode>(children); | ||
ArrayList<AbstractConfigNode> newNodes = new ArrayList(); | ||
newNodes.add(new ConfigNodeSingleToken(Tokens.newLine(null))); | ||
newNodes.add(new ConfigNodeKey(desiredPath)); | ||
newNodes.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, " "))); | ||
newNodes.add(new ConfigNodeSingleToken(Tokens.COLON)); | ||
newNodes.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, " "))); | ||
newNodes.add(value); | ||
newNodes.add(new ConfigNodeSingleToken(Tokens.newLine(null))); | ||
childrenCopy.add(new ConfigNodeKeyValue(newNodes)); | ||
node = new ConfigNodeComplexValue(childrenCopy); | ||
} | ||
return node; | ||
} | ||
|
||
public ConfigNodeComplexValue removeValueOnPath(Path desiredPath) { | ||
return changeValueOnPath(desiredPath, null); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/** | ||
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com> | ||
*/ | ||
package com.typesafe.config.impl; | ||
|
||
import java.util.Collection; | ||
|
||
final class ConfigNodeKey extends AbstractConfigNode { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there's a convention throughout the lib, or at least in public API, that we use "key" to mean one element of a path; paths have to be parsed, keys are the raw strings in each Path element. ConfigNodeKey should probably be ConfigNodePath to be sure we don't get confused on this point. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good. I think it would also be worth changing |
||
final private Path key; | ||
ConfigNodeKey(Path key) { | ||
this.key = key; | ||
} | ||
|
||
protected Collection<Token> tokens() { | ||
return key.tokens(); | ||
} | ||
|
||
protected Path value() { | ||
return key; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
/** | ||
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com> | ||
*/ | ||
package com.typesafe.config.impl; | ||
|
||
import com.typesafe.config.ConfigException; | ||
import com.typesafe.config.ConfigNode; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
|
||
final class ConfigNodeKeyValue extends AbstractConfigNode { | ||
final private ArrayList<AbstractConfigNode> children; | ||
|
||
public ConfigNodeKeyValue(Collection<AbstractConfigNode> children) { | ||
this.children = new ArrayList(children); | ||
} | ||
|
||
protected Collection<Token> tokens() { | ||
ArrayList<Token> tokens = new ArrayList(); | ||
for (AbstractConfigNode child : children) { | ||
tokens.addAll(child.tokens()); | ||
} | ||
return tokens; | ||
} | ||
|
||
public ConfigNodeKeyValue replaceValue(AbstractConfigNodeValue newValue) { | ||
ArrayList<AbstractConfigNode> childrenCopy = new ArrayList(children); | ||
for (int i = 0; i < childrenCopy.size(); i++) { | ||
if (childrenCopy.get(i) instanceof AbstractConfigNodeValue) { | ||
childrenCopy.set(i, newValue); | ||
return new ConfigNodeKeyValue(childrenCopy); | ||
} | ||
} | ||
throw new ConfigException.BugOrBroken("KeyValue node doesn't have a value"); | ||
} | ||
|
||
public AbstractConfigNodeValue value() { | ||
for (int i = 0; i < children.size(); i++) { | ||
if (children.get(i) instanceof AbstractConfigNodeValue) { | ||
return (AbstractConfigNodeValue)children.get(i); | ||
} | ||
} | ||
throw new ConfigException.BugOrBroken("KeyValue node doesn't have a value"); | ||
} | ||
|
||
public ConfigNodeKey key() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should probably be called path() and the KeyValue class could either be called |
||
for (int i = 0; i < children.size(); i++) { | ||
if (children.get(i) instanceof ConfigNodeKey) { | ||
return (ConfigNodeKey)children.get(i); | ||
} | ||
} | ||
throw new ConfigException.BugOrBroken("KeyValue node doesn't have a key"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/** | ||
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com> | ||
*/ | ||
package com.typesafe.config.impl; | ||
|
||
import java.util.Collection; | ||
import java.util.Collections; | ||
|
||
class ConfigNodeSimpleValue extends AbstractConfigNodeValue { | ||
final Token token; | ||
ConfigNodeSimpleValue(Token value) { | ||
token = value; | ||
} | ||
|
||
protected Collection<Token> tokens() { | ||
return Collections.singletonList(token); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/** | ||
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com> | ||
*/ | ||
package com.typesafe.config.impl; | ||
|
||
import java.util.Collection; | ||
import java.util.Collections; | ||
|
||
final class ConfigNodeSingleToken extends AbstractConfigNode{ | ||
final Token token; | ||
ConfigNodeSingleToken(Token t) { | ||
token = t; | ||
} | ||
|
||
protected Collection<Token> tokens() { | ||
return Collections.singletonList(token); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1063,6 +1063,7 @@ private static Path parsePathExpression(Iterator<Token> expression, | |
private static Path parsePathExpression(Iterator<Token> expression, | ||
ConfigOrigin origin, String originalText) { | ||
// each builder in "buf" is an element in the path. | ||
ArrayList<Token> pathTokens = new ArrayList<Token>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It feels like a somewhat messy mixing of concerns to make
Then you can have the functions which call that either provide the It would be fine to skip the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that's doable, but I think in that case, a |
||
List<Element> buf = new ArrayList<Element>(); | ||
buf.add(new Element("", false)); | ||
|
||
|
@@ -1073,6 +1074,7 @@ private static Path parsePathExpression(Iterator<Token> expression, | |
|
||
while (expression.hasNext()) { | ||
Token t = expression.next(); | ||
pathTokens.add(t); | ||
|
||
// Ignore all IgnoredWhitespace tokens | ||
if (Tokens.isIgnoredWhitespace(t)) | ||
|
@@ -1119,7 +1121,7 @@ private static Path parsePathExpression(Iterator<Token> expression, | |
} | ||
} | ||
|
||
PathBuilder pb = new PathBuilder(); | ||
PathBuilder pb = new PathBuilder(pathTokens); | ||
for (Element e : buf) { | ||
if (e.sb.length() == 0 && !e.canBeEmpty) { | ||
throw new ConfigException.BadPath( | ||
|
@@ -1192,8 +1194,10 @@ private static boolean looksUnsafeForFastParser(String s) { | |
private static Path fastPathBuild(Path tail, String s, int end) { | ||
// lastIndexOf takes last index it should look at, end - 1 not end | ||
int splitAt = s.lastIndexOf('.', end - 1); | ||
ArrayList<Token> tokens = new ArrayList<Token>(); | ||
tokens.add(Tokens.newUnquotedText(null, s)); | ||
// this works even if splitAt is -1; then we start the substring at 0 | ||
Path withOneMoreElement = new Path(s.substring(splitAt + 1, end), tail); | ||
Path withOneMoreElement = new Path(s.substring(splitAt + 1, end), tail, tokens); | ||
if (splitAt < 0) { | ||
return withOneMoreElement; | ||
} else { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,8 @@ | |
*/ | ||
package com.typesafe.config.impl; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
|
||
|
@@ -13,12 +15,22 @@ final class Path { | |
final private String first; | ||
final private Path remainder; | ||
|
||
// We only need to keep track of this for top-level paths created with | ||
// parsePath, so this will be empty or null for all other cases | ||
final private ArrayList<Token> tokens; | ||
|
||
Path(String first, Path remainder) { | ||
this(first, remainder, new ArrayList<Token>()); | ||
} | ||
|
||
Path(String first, Path remainder, Collection<Token> tokens) { | ||
this.first = first; | ||
this.remainder = remainder; | ||
this.tokens = new ArrayList<Token>(tokens); | ||
} | ||
|
||
Path(String... elements) { | ||
this.tokens = null; | ||
if (elements.length == 0) | ||
throw new ConfigException.BugOrBroken("empty path"); | ||
this.first = elements[0]; | ||
|
@@ -40,6 +52,7 @@ final class Path { | |
|
||
// append all the paths in the iterator together into one path | ||
Path(Iterator<Path> i) { | ||
this.tokens = null; | ||
if (!i.hasNext()) | ||
throw new ConfigException.BugOrBroken("empty path"); | ||
|
||
|
@@ -141,6 +154,21 @@ Path subPath(int firstIndex, int lastIndex) { | |
return pb.result(); | ||
} | ||
|
||
boolean startsWith(Path other) { | ||
Path myRemainder = this; | ||
Path otherRemainder = other; | ||
if (otherRemainder.length() <= myRemainder.length()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since length is O(n) it would be a bit nicer to instead check myRemainder for null, below, perhaps. Since paths are generally short it probably doesn't really matter that much. |
||
while(otherRemainder != null) { | ||
if (!otherRemainder.first().equals(myRemainder.first())) | ||
return false; | ||
myRemainder = myRemainder.remainder(); | ||
otherRemainder = otherRemainder.remainder(); | ||
} | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object other) { | ||
if (other instanceof Path) { | ||
|
@@ -189,6 +217,10 @@ private void appendToStringBuilder(StringBuilder sb) { | |
} | ||
} | ||
|
||
protected Collection<Token> tokens() { | ||
return tokens; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
StringBuilder sb = new StringBuilder(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this can be final I guess