mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-15 10:21:45 -04:00
updated typesafe config (I should probably make this a sub-repo eventually)
This commit is contained in:
parent
3a87ecd6ce
commit
a10d4fdce5
@ -6,6 +6,7 @@ package com.typesafe.config;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* An immutable map from config paths to config values.
|
||||
@ -440,6 +441,8 @@ public interface Config extends ConfigMergeable {
|
||||
* href="https://github.com/typesafehub/config/blob/master/HOCON.md">the
|
||||
* spec</a>.
|
||||
*
|
||||
* @deprecated As of release 1.1, replaced by {@link #getDuration(String, TimeUnit)}
|
||||
*
|
||||
* @param path
|
||||
* path expression
|
||||
* @return the duration value at the requested path, in milliseconds
|
||||
@ -450,13 +453,15 @@ public interface Config extends ConfigMergeable {
|
||||
* @throws ConfigException.BadValue
|
||||
* if value cannot be parsed as a number of milliseconds
|
||||
*/
|
||||
Long getMilliseconds(String path);
|
||||
@Deprecated Long getMilliseconds(String path);
|
||||
|
||||
/**
|
||||
* Get value as a duration in nanoseconds. If the value is already a number
|
||||
* it's taken as milliseconds and converted to nanoseconds. If it's a
|
||||
* string, it's parsed understanding unit suffixes, as for
|
||||
* {@link #getMilliseconds(String)}.
|
||||
* {@link #getDuration(String, TimeUnit)}.
|
||||
*
|
||||
* @deprecated As of release 1.1, replaced by {@link #getDuration(String, TimeUnit)}
|
||||
*
|
||||
* @param path
|
||||
* path expression
|
||||
@ -468,7 +473,32 @@ public interface Config extends ConfigMergeable {
|
||||
* @throws ConfigException.BadValue
|
||||
* if value cannot be parsed as a number of nanoseconds
|
||||
*/
|
||||
Long getNanoseconds(String path);
|
||||
@Deprecated Long getNanoseconds(String path);
|
||||
|
||||
/**
|
||||
* Gets a value as a duration in a specified
|
||||
* {@link java.util.concurrent.TimeUnit TimeUnit}. If the value is already a
|
||||
* number, then it's taken as milliseconds and then converted to the
|
||||
* requested TimeUnit; if it's a string, it's parsed understanding units
|
||||
* suffixes like "10m" or "5ns" as documented in the <a
|
||||
* href="https://github.com/typesafehub/config/blob/master/HOCON.md">the
|
||||
* spec</a>.
|
||||
*
|
||||
* @since 1.1
|
||||
*
|
||||
* @param path
|
||||
* path expression
|
||||
* @param unit
|
||||
* convert the return value to this time unit
|
||||
* @return the duration value at the requested path, in the given TimeUnit
|
||||
* @throws ConfigException.Missing
|
||||
* if value is absent or null
|
||||
* @throws ConfigException.WrongType
|
||||
* if value is not convertible to Long or String
|
||||
* @throws ConfigException.BadValue
|
||||
* if value cannot be parsed as a number of the given TimeUnit
|
||||
*/
|
||||
Long getDuration(String path, TimeUnit unit);
|
||||
|
||||
/**
|
||||
* Gets a list value (with any element type) as a {@link ConfigList}, which
|
||||
@ -505,9 +535,28 @@ public interface Config extends ConfigMergeable {
|
||||
|
||||
List<Long> getBytesList(String path);
|
||||
|
||||
List<Long> getMillisecondsList(String path);
|
||||
/**
|
||||
* @deprecated As of release 1.1, replaced by {@link #getDurationList(String, TimeUnit)}
|
||||
*/
|
||||
@Deprecated List<Long> getMillisecondsList(String path);
|
||||
|
||||
List<Long> getNanosecondsList(String path);
|
||||
/**
|
||||
* @deprecated As of release 1.1, replaced by {@link #getDurationList(String, TimeUnit)}
|
||||
*/
|
||||
@Deprecated List<Long> getNanosecondsList(String path);
|
||||
|
||||
/**
|
||||
* Gets a list, converting each value in the list to a duration, using the
|
||||
* same rules as {@link #getDuration(String, TimeUnit)}.
|
||||
*
|
||||
* @since 1.1
|
||||
* @param path
|
||||
* a path expression
|
||||
* @param unit
|
||||
* time units of the returned values
|
||||
* @return list of durations, in the requested units
|
||||
*/
|
||||
List<Long> getDurationList(String path, TimeUnit unit);
|
||||
|
||||
/**
|
||||
* Clone the config with only the given path (and its children) retained;
|
||||
@ -554,7 +603,7 @@ public interface Config extends ConfigMergeable {
|
||||
* to the given value. Does not modify this instance (since it's immutable).
|
||||
* If the path already has a value, that value is replaced. To remove a
|
||||
* value, use withoutPath().
|
||||
*
|
||||
*
|
||||
* @param path
|
||||
* path to add
|
||||
* @param value
|
||||
|
@ -339,6 +339,11 @@ public abstract class ConfigException extends RuntimeException implements Serial
|
||||
public String problem() {
|
||||
return problem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ValidationProblem(" + path + "," + origin + "," + problem + ")";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -78,8 +78,10 @@ final public class ConfigImplUtil {
|
||||
if (s.length() == 0)
|
||||
return renderJsonString(s);
|
||||
|
||||
// if it starts with a hyphen or number, we have to quote
|
||||
// to ensure we end up with a string and not a number
|
||||
int first = s.codePointAt(0);
|
||||
if (Character.isDigit(first))
|
||||
if (Character.isDigit(first) || first == '-')
|
||||
return renderJsonString(s);
|
||||
|
||||
if (s.startsWith("include") || s.startsWith("true") || s.startsWith("false")
|
||||
@ -89,7 +91,7 @@ final public class ConfigImplUtil {
|
||||
// only unquote if it's pure alphanumeric
|
||||
for (int i = 0; i < s.length(); ++i) {
|
||||
char c = s.charAt(i);
|
||||
if (!(Character.isLetter(c) || Character.isDigit(c)))
|
||||
if (!(Character.isLetter(c) || Character.isDigit(c) || c == '-'))
|
||||
return renderJsonString(s);
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
@ -104,6 +105,10 @@ public abstract class Parseable implements ConfigParseable {
|
||||
return null;
|
||||
}
|
||||
|
||||
ConfigSyntax contentType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
ConfigParseable relativeTo(String filename) {
|
||||
// fall back to classpath; we treat the "filename" as absolute
|
||||
// (don't add a package name in front),
|
||||
@ -173,7 +178,10 @@ public abstract class Parseable implements ConfigParseable {
|
||||
if (finalOptions.getAllowMissing()) {
|
||||
return SimpleConfigObject.emptyMissing(origin);
|
||||
} else {
|
||||
throw new ConfigException.IO(origin, e.getMessage(), e);
|
||||
trace("exception loading " + origin.description() + ": " + e.getClass().getName()
|
||||
+ ": " + e.getMessage());
|
||||
throw new ConfigException.IO(origin,
|
||||
e.getClass().getName() + ": " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -183,8 +191,23 @@ public abstract class Parseable implements ConfigParseable {
|
||||
protected AbstractConfigValue rawParseValue(ConfigOrigin origin, ConfigParseOptions finalOptions)
|
||||
throws IOException {
|
||||
Reader reader = reader();
|
||||
|
||||
// after reader() we will have loaded the Content-Type.
|
||||
ConfigSyntax contentType = contentType();
|
||||
|
||||
ConfigParseOptions optionsWithContentType;
|
||||
if (contentType != null) {
|
||||
if (ConfigImpl.traceLoadsEnabled() && finalOptions.getSyntax() != null)
|
||||
trace("Overriding syntax " + finalOptions.getSyntax()
|
||||
+ " with Content-Type which specified " + contentType);
|
||||
|
||||
optionsWithContentType = finalOptions.setSyntax(contentType);
|
||||
} else {
|
||||
optionsWithContentType = finalOptions;
|
||||
}
|
||||
|
||||
try {
|
||||
return rawParseValue(reader, origin, finalOptions);
|
||||
return rawParseValue(reader, origin, optionsWithContentType);
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
@ -237,12 +260,16 @@ public abstract class Parseable implements ConfigParseable {
|
||||
}
|
||||
|
||||
private static Reader readerFromStream(InputStream input) {
|
||||
return readerFromStream(input, "UTF-8");
|
||||
}
|
||||
|
||||
private static Reader readerFromStream(InputStream input, String encoding) {
|
||||
try {
|
||||
// well, this is messed up. If we aren't going to close
|
||||
// the passed-in InputStream then we have no way to
|
||||
// close these readers. So maybe we should not have an
|
||||
// InputStream version, only a Reader version.
|
||||
Reader reader = new InputStreamReader(input, "UTF-8");
|
||||
Reader reader = new InputStreamReader(input, encoding);
|
||||
return new BufferedReader(reader);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new ConfigException.BugOrBroken("Java runtime does not support UTF-8", e);
|
||||
@ -387,6 +414,7 @@ public abstract class Parseable implements ConfigParseable {
|
||||
|
||||
private final static class ParseableURL extends Parseable {
|
||||
final private URL input;
|
||||
private String contentType = null;
|
||||
|
||||
ParseableURL(URL input, ConfigParseOptions options) {
|
||||
this.input = input;
|
||||
@ -397,7 +425,22 @@ public abstract class Parseable implements ConfigParseable {
|
||||
protected Reader reader() throws IOException {
|
||||
if (ConfigImpl.traceLoadsEnabled())
|
||||
trace("Loading config from a URL: " + input.toExternalForm());
|
||||
InputStream stream = input.openStream();
|
||||
URLConnection connection = input.openConnection();
|
||||
connection.connect();
|
||||
|
||||
// save content type for later
|
||||
contentType = connection.getContentType();
|
||||
if (contentType != null) {
|
||||
if (ConfigImpl.traceLoadsEnabled())
|
||||
trace("URL sets Content-Type: '" + contentType + "'");
|
||||
contentType = contentType.trim();
|
||||
int semi = contentType.indexOf(';');
|
||||
if (semi >= 0)
|
||||
contentType = contentType.substring(0, semi);
|
||||
}
|
||||
|
||||
InputStream stream = connection.getInputStream();
|
||||
|
||||
return readerFromStream(stream);
|
||||
}
|
||||
|
||||
@ -406,6 +449,25 @@ public abstract class Parseable implements ConfigParseable {
|
||||
return syntaxFromExtension(input.getPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
ConfigSyntax contentType() {
|
||||
if (contentType != null) {
|
||||
if (contentType.equals("application/json"))
|
||||
return ConfigSyntax.JSON;
|
||||
else if (contentType.equals("text/x-java-properties"))
|
||||
return ConfigSyntax.PROPERTIES;
|
||||
else if (contentType.equals("application/hocon"))
|
||||
return ConfigSyntax.CONF;
|
||||
else {
|
||||
if (ConfigImpl.traceLoadsEnabled())
|
||||
trace("'" + contentType + "' isn't a known content type");
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
ConfigParseable relativeTo(String filename) {
|
||||
URL url = relativeTo(input, filename);
|
||||
|
@ -41,14 +41,26 @@ final class Parser {
|
||||
TokenWithComments(Token token, List<Token> comments) {
|
||||
this.token = token;
|
||||
this.comments = comments;
|
||||
|
||||
if (Tokens.isComment(token))
|
||||
throw new ConfigException.BugOrBroken("tried to annotate a comment with a comment");
|
||||
}
|
||||
|
||||
TokenWithComments(Token token) {
|
||||
this(token, Collections.<Token> emptyList());
|
||||
}
|
||||
|
||||
TokenWithComments removeAll() {
|
||||
if (comments.isEmpty())
|
||||
return this;
|
||||
else
|
||||
return new TokenWithComments(token);
|
||||
}
|
||||
|
||||
TokenWithComments prepend(List<Token> earlier) {
|
||||
if (this.comments.isEmpty()) {
|
||||
if (earlier.isEmpty()) {
|
||||
return this;
|
||||
} else if (this.comments.isEmpty()) {
|
||||
return new TokenWithComments(token, earlier);
|
||||
} else {
|
||||
List<Token> merged = new ArrayList<Token>();
|
||||
@ -58,7 +70,18 @@ final class Parser {
|
||||
}
|
||||
}
|
||||
|
||||
SimpleConfigOrigin setComments(SimpleConfigOrigin origin) {
|
||||
TokenWithComments add(Token after) {
|
||||
if (this.comments.isEmpty()) {
|
||||
return new TokenWithComments(token, Collections.<Token> singletonList(after));
|
||||
} else {
|
||||
List<Token> merged = new ArrayList<Token>();
|
||||
merged.addAll(comments);
|
||||
merged.add(after);
|
||||
return new TokenWithComments(token, merged);
|
||||
}
|
||||
}
|
||||
|
||||
SimpleConfigOrigin prependComments(SimpleConfigOrigin origin) {
|
||||
if (comments.isEmpty()) {
|
||||
return origin;
|
||||
} else {
|
||||
@ -66,7 +89,19 @@ final class Parser {
|
||||
for (Token c : comments) {
|
||||
newComments.add(Tokens.getCommentText(c));
|
||||
}
|
||||
return origin.setComments(newComments);
|
||||
return origin.prependComments(newComments);
|
||||
}
|
||||
}
|
||||
|
||||
SimpleConfigOrigin appendComments(SimpleConfigOrigin origin) {
|
||||
if (comments.isEmpty()) {
|
||||
return origin;
|
||||
} else {
|
||||
List<String> newComments = new ArrayList<String>();
|
||||
for (Token c : comments) {
|
||||
newComments.add(Tokens.getCommentText(c));
|
||||
}
|
||||
return origin.appendComments(newComments);
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,12 +140,38 @@ final class Parser {
|
||||
this.equalsCount = 0;
|
||||
}
|
||||
|
||||
static private boolean attractsTrailingComments(Token token) {
|
||||
// END can't have a trailing comment; START, OPEN_CURLY, and
|
||||
// OPEN_SQUARE followed by a comment should behave as if the comment
|
||||
// went with the following field or element. Associating a comment
|
||||
// with a newline would mess up all the logic for comment tracking,
|
||||
// so don't do that either.
|
||||
if (Tokens.isNewline(token) || token == Tokens.START || token == Tokens.OPEN_CURLY
|
||||
|| token == Tokens.OPEN_SQUARE || token == Tokens.END)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
static private boolean attractsLeadingComments(Token token) {
|
||||
// a comment just before a close } generally doesn't go with the
|
||||
// value before it, unless it's on the same line as that value
|
||||
if (Tokens.isNewline(token) || token == Tokens.START || token == Tokens.CLOSE_CURLY
|
||||
|| token == Tokens.CLOSE_SQUARE || token == Tokens.END)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
private void consolidateCommentBlock(Token commentToken) {
|
||||
// a comment block "goes with" the following token
|
||||
// unless it's separated from it by a blank line.
|
||||
// we want to build a list of newline tokens followed
|
||||
// by a non-newline non-comment token; with all comments
|
||||
// associated with that final non-newline non-comment token.
|
||||
// a comment AFTER a token, without an intervening newline,
|
||||
// also goes with that token, but isn't handled in this method,
|
||||
// instead we handle it later by peeking ahead.
|
||||
List<Token> newlines = new ArrayList<Token>();
|
||||
List<Token> comments = new ArrayList<Token>();
|
||||
|
||||
@ -128,6 +189,11 @@ final class Parser {
|
||||
comments.add(next);
|
||||
} else {
|
||||
// a non-newline non-comment token
|
||||
|
||||
// comments before a close brace or bracket just get dumped
|
||||
if (!attractsLeadingComments(next))
|
||||
comments.clear();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@ -146,7 +212,7 @@ final class Parser {
|
||||
}
|
||||
}
|
||||
|
||||
private TokenWithComments popToken() {
|
||||
private TokenWithComments popTokenWithoutTrailingComment() {
|
||||
if (buffer.isEmpty()) {
|
||||
Token t = tokens.next();
|
||||
if (Tokens.isComment(t)) {
|
||||
@ -160,6 +226,35 @@ final class Parser {
|
||||
}
|
||||
}
|
||||
|
||||
private TokenWithComments popToken() {
|
||||
TokenWithComments withPrecedingComments = popTokenWithoutTrailingComment();
|
||||
// handle a comment AFTER the other token,
|
||||
// but before a newline. If the next token is not
|
||||
// a comment, then any comment later on the line is irrelevant
|
||||
// since it would end up going with that later token, not
|
||||
// this token. Comments are supposed to be processed prior
|
||||
// to adding stuff to the buffer, so they can only be found
|
||||
// in "tokens" not in "buffer" in theory.
|
||||
if (!attractsTrailingComments(withPrecedingComments.token)) {
|
||||
return withPrecedingComments;
|
||||
} else if (buffer.isEmpty()) {
|
||||
Token after = tokens.next();
|
||||
if (Tokens.isComment(after)) {
|
||||
return withPrecedingComments.add(after);
|
||||
} else {
|
||||
buffer.push(new TokenWithComments(after));
|
||||
return withPrecedingComments;
|
||||
}
|
||||
} else {
|
||||
// comments are supposed to get attached to a token,
|
||||
// not put back in the buffer. Assert this as an invariant.
|
||||
if (Tokens.isComment(buffer.peek().token))
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"comment token should not have been in buffer: " + buffer);
|
||||
return withPrecedingComments;
|
||||
}
|
||||
}
|
||||
|
||||
private TokenWithComments nextToken() {
|
||||
TokenWithComments withComments = null;
|
||||
|
||||
@ -192,6 +287,9 @@ final class Parser {
|
||||
}
|
||||
|
||||
private void putBack(TokenWithComments token) {
|
||||
if (Tokens.isComment(token.token))
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"comment token should have been stripped before it was available to put back");
|
||||
buffer.push(token);
|
||||
}
|
||||
|
||||
@ -216,6 +314,19 @@ final class Parser {
|
||||
return t;
|
||||
}
|
||||
|
||||
private AbstractConfigValue addAnyCommentsAfterAnyComma(AbstractConfigValue v) {
|
||||
TokenWithComments t = nextToken(); // do NOT skip newlines, we only
|
||||
// want same-line comments
|
||||
if (t.token == Tokens.COMMA) {
|
||||
// steal the comments from after the comma
|
||||
putBack(t.removeAll());
|
||||
return v.withOrigin(t.appendComments(v.origin()));
|
||||
} else {
|
||||
putBack(t);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
// In arrays and objects, comma can be omitted
|
||||
// as long as there's at least one newline instead.
|
||||
// this skips any newlines in front of a comma,
|
||||
@ -272,22 +383,14 @@ final class Parser {
|
||||
|
||||
// create only if we have value tokens
|
||||
List<AbstractConfigValue> values = null;
|
||||
TokenWithComments firstValueWithComments = null;
|
||||
|
||||
// ignore a newline up front
|
||||
TokenWithComments t = nextTokenIgnoringNewline();
|
||||
while (true) {
|
||||
AbstractConfigValue v = null;
|
||||
if (Tokens.isValue(t.token)) {
|
||||
// if we consolidateValueTokens() multiple times then
|
||||
// this value could be a concatenation, object, array,
|
||||
// or substitution already.
|
||||
v = Tokens.getValue(t.token);
|
||||
} else if (Tokens.isUnquotedText(t.token)) {
|
||||
v = new ConfigString(t.token.origin(), Tokens.getUnquotedText(t.token));
|
||||
} else if (Tokens.isSubstitution(t.token)) {
|
||||
v = new ConfigReference(t.token.origin(),
|
||||
tokenToSubstitutionExpression(t.token));
|
||||
} else if (t.token == Tokens.OPEN_CURLY || t.token == Tokens.OPEN_SQUARE) {
|
||||
if (Tokens.isValue(t.token) || Tokens.isUnquotedText(t.token)
|
||||
|| Tokens.isSubstitution(t.token) || t.token == Tokens.OPEN_CURLY
|
||||
|| t.token == Tokens.OPEN_SQUARE) {
|
||||
// there may be newlines _within_ the objects and arrays
|
||||
v = parseValue(t);
|
||||
} else {
|
||||
@ -299,7 +402,6 @@ final class Parser {
|
||||
|
||||
if (values == null) {
|
||||
values = new ArrayList<AbstractConfigValue>();
|
||||
firstValueWithComments = t;
|
||||
}
|
||||
values.add(v);
|
||||
|
||||
@ -313,11 +415,10 @@ final class Parser {
|
||||
|
||||
AbstractConfigValue consolidated = ConfigConcatenation.concatenate(values);
|
||||
|
||||
putBack(new TokenWithComments(Tokens.newValue(consolidated),
|
||||
firstValueWithComments.comments));
|
||||
putBack(new TokenWithComments(Tokens.newValue(consolidated)));
|
||||
}
|
||||
|
||||
private ConfigOrigin lineOrigin() {
|
||||
private SimpleConfigOrigin lineOrigin() {
|
||||
return ((SimpleConfigOrigin) baseOrigin).setLineNumber(lineNumber);
|
||||
}
|
||||
|
||||
@ -403,7 +504,14 @@ final class Parser {
|
||||
AbstractConfigValue v;
|
||||
|
||||
if (Tokens.isValue(t.token)) {
|
||||
// if we consolidateValueTokens() multiple times then
|
||||
// this value could be a concatenation, object, array,
|
||||
// or substitution already.
|
||||
v = Tokens.getValue(t.token);
|
||||
} else if (Tokens.isUnquotedText(t.token)) {
|
||||
v = new ConfigString(t.token.origin(), Tokens.getUnquotedText(t.token));
|
||||
} else if (Tokens.isSubstitution(t.token)) {
|
||||
v = new ConfigReference(t.token.origin(), tokenToSubstitutionExpression(t.token));
|
||||
} else if (t.token == Tokens.OPEN_CURLY) {
|
||||
v = parseObject(true);
|
||||
} else if (t.token == Tokens.OPEN_SQUARE) {
|
||||
@ -413,7 +521,7 @@ final class Parser {
|
||||
"Expecting a value but got wrong token: " + t.token));
|
||||
}
|
||||
|
||||
v = v.withOrigin(t.setComments(v.origin()));
|
||||
v = v.withOrigin(t.prependComments(v.origin()));
|
||||
|
||||
return v;
|
||||
}
|
||||
@ -601,7 +709,7 @@ final class Parser {
|
||||
private AbstractConfigObject parseObject(boolean hadOpenCurly) {
|
||||
// invoked just after the OPEN_CURLY (or START, if !hadOpenCurly)
|
||||
Map<String, AbstractConfigValue> values = new HashMap<String, AbstractConfigValue>();
|
||||
ConfigOrigin objectOrigin = lineOrigin();
|
||||
SimpleConfigOrigin objectOrigin = lineOrigin();
|
||||
boolean afterComma = false;
|
||||
Path lastPath = null;
|
||||
boolean lastInsideEquals = false;
|
||||
@ -616,6 +724,9 @@ final class Parser {
|
||||
throw parseError(addQuoteSuggestion(t.toString(),
|
||||
"unbalanced close brace '}' with no open brace"));
|
||||
}
|
||||
|
||||
objectOrigin = t.appendComments(objectOrigin);
|
||||
|
||||
break;
|
||||
} else if (t.token == Tokens.END && !hadOpenCurly) {
|
||||
putBack(t);
|
||||
@ -652,8 +763,11 @@ final class Parser {
|
||||
|
||||
consolidateValueTokens();
|
||||
valueToken = nextTokenIgnoringNewline();
|
||||
// put comments from separator token on the value token
|
||||
valueToken = valueToken.prepend(afterKey.comments);
|
||||
}
|
||||
|
||||
// comments from the key token go to the value token
|
||||
newValue = parseValue(valueToken.prepend(keyToken.comments));
|
||||
|
||||
if (afterKey.token == Tokens.PLUS_EQUALS) {
|
||||
@ -667,6 +781,8 @@ final class Parser {
|
||||
newValue = ConfigConcatenation.concatenate(concat);
|
||||
}
|
||||
|
||||
newValue = addAnyCommentsAfterAnyComma(newValue);
|
||||
|
||||
lastPath = pathStack.pop();
|
||||
if (insideEquals) {
|
||||
equalsCount -= 1;
|
||||
@ -722,6 +838,9 @@ final class Parser {
|
||||
throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals,
|
||||
t.toString(), "unbalanced close brace '}' with no open brace"));
|
||||
}
|
||||
|
||||
objectOrigin = t.appendComments(objectOrigin);
|
||||
|
||||
break;
|
||||
} else if (hadOpenCurly) {
|
||||
throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals,
|
||||
@ -743,7 +862,7 @@ final class Parser {
|
||||
|
||||
private SimpleConfigList parseArray() {
|
||||
// invoked just after the OPEN_SQUARE
|
||||
ConfigOrigin arrayOrigin = lineOrigin();
|
||||
SimpleConfigOrigin arrayOrigin = lineOrigin();
|
||||
List<AbstractConfigValue> values = new ArrayList<AbstractConfigValue>();
|
||||
|
||||
consolidateValueTokens();
|
||||
@ -752,11 +871,13 @@ final class Parser {
|
||||
|
||||
// special-case the first element
|
||||
if (t.token == Tokens.CLOSE_SQUARE) {
|
||||
return new SimpleConfigList(arrayOrigin,
|
||||
return new SimpleConfigList(t.appendComments(arrayOrigin),
|
||||
Collections.<AbstractConfigValue> emptyList());
|
||||
} else if (Tokens.isValue(t.token) || t.token == Tokens.OPEN_CURLY
|
||||
|| t.token == Tokens.OPEN_SQUARE) {
|
||||
values.add(parseValue(t));
|
||||
AbstractConfigValue v = parseValue(t);
|
||||
v = addAnyCommentsAfterAnyComma(v);
|
||||
values.add(v);
|
||||
} else {
|
||||
throw parseError(addKeyName("List should have ] or a first element after the open [, instead had token: "
|
||||
+ t
|
||||
@ -773,7 +894,7 @@ final class Parser {
|
||||
} else {
|
||||
t = nextTokenIgnoringNewline();
|
||||
if (t.token == Tokens.CLOSE_SQUARE) {
|
||||
return new SimpleConfigList(arrayOrigin, values);
|
||||
return new SimpleConfigList(t.appendComments(arrayOrigin), values);
|
||||
} else {
|
||||
throw parseError(addKeyName("List should have ended with ] or had a comma, instead had token: "
|
||||
+ t
|
||||
@ -789,7 +910,9 @@ final class Parser {
|
||||
t = nextTokenIgnoringNewline();
|
||||
if (Tokens.isValue(t.token) || t.token == Tokens.OPEN_CURLY
|
||||
|| t.token == Tokens.OPEN_SQUARE) {
|
||||
values.add(parseValue(t));
|
||||
AbstractConfigValue v = parseValue(t);
|
||||
v = addAnyCommentsAfterAnyComma(v);
|
||||
values.add(v);
|
||||
} else if (flavor != ConfigSyntax.JSON && t.token == Tokens.CLOSE_SQUARE) {
|
||||
// we allow one trailing comma
|
||||
putBack(t);
|
||||
|
@ -14,6 +14,7 @@ import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.io.ObjectStreamException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -199,7 +200,12 @@ class SerializedConfigValue extends AbstractConfigValue implements Externalizabl
|
||||
// not private because we use it to serialize ConfigException
|
||||
static void writeOrigin(DataOutput out, SimpleConfigOrigin origin,
|
||||
SimpleConfigOrigin baseOrigin) throws IOException {
|
||||
Map<SerializedField, Object> m = origin.toFieldsDelta(baseOrigin);
|
||||
Map<SerializedField, Object> m;
|
||||
// to serialize a null origin, we write out no fields at all
|
||||
if (origin != null)
|
||||
m = origin.toFieldsDelta(baseOrigin);
|
||||
else
|
||||
m = Collections.emptyMap();
|
||||
for (Map.Entry<SerializedField, Object> e : m.entrySet()) {
|
||||
FieldOut field = new FieldOut(e.getKey());
|
||||
Object v = e.getValue();
|
||||
|
@ -234,23 +234,25 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public Long getMilliseconds(String path) {
|
||||
long ns = getNanoseconds(path);
|
||||
long ms = TimeUnit.NANOSECONDS.toMillis(ns);
|
||||
return ms;
|
||||
return getDuration(path, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public Long getNanoseconds(String path) {
|
||||
return getDuration(path, TimeUnit.NANOSECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getNanoseconds(String path) {
|
||||
Long ns = null;
|
||||
try {
|
||||
ns = TimeUnit.MILLISECONDS.toNanos(getLong(path));
|
||||
} catch (ConfigException.WrongType e) {
|
||||
ConfigValue v = find(path, ConfigValueType.STRING);
|
||||
ns = parseDuration((String) v.unwrapped(), v.origin(), path);
|
||||
}
|
||||
return ns;
|
||||
public Long getDuration(String path, TimeUnit unit) {
|
||||
ConfigValue v = find(path, ConfigValueType.STRING);
|
||||
Long result = unit.convert(
|
||||
parseDuration((String) v.unwrapped(), v.origin(), path),
|
||||
TimeUnit.NANOSECONDS);
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -384,36 +386,42 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getMillisecondsList(String path) {
|
||||
List<Long> nanos = getNanosecondsList(path);
|
||||
List<Long> l = new ArrayList<Long>();
|
||||
for (Long n : nanos) {
|
||||
l.add(TimeUnit.NANOSECONDS.toMillis(n));
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getNanosecondsList(String path) {
|
||||
public List<Long> getDurationList(String path, TimeUnit unit) {
|
||||
List<Long> l = new ArrayList<Long>();
|
||||
List<? extends ConfigValue> list = getList(path);
|
||||
for (ConfigValue v : list) {
|
||||
if (v.valueType() == ConfigValueType.NUMBER) {
|
||||
l.add(TimeUnit.MILLISECONDS.toNanos(((Number) v.unwrapped())
|
||||
.longValue()));
|
||||
Long n = unit.convert(
|
||||
((Number) v.unwrapped()).longValue(),
|
||||
TimeUnit.MILLISECONDS);
|
||||
l.add(n);
|
||||
} else if (v.valueType() == ConfigValueType.STRING) {
|
||||
String s = (String) v.unwrapped();
|
||||
Long n = parseDuration(s, v.origin(), path);
|
||||
Long n = unit.convert(
|
||||
parseDuration(s, v.origin(), path),
|
||||
TimeUnit.NANOSECONDS);
|
||||
l.add(n);
|
||||
} else {
|
||||
throw new ConfigException.WrongType(v.origin(), path,
|
||||
"duration string or number of nanoseconds", v
|
||||
.valueType().name());
|
||||
"duration string or number of milliseconds",
|
||||
v.valueType().name());
|
||||
}
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public List<Long> getMillisecondsList(String path) {
|
||||
return getDurationList(path, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public List<Long> getNanosecondsList(String path) {
|
||||
return getDurationList(path, TimeUnit.NANOSECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractConfigObject toFallbackValue() {
|
||||
return object;
|
||||
@ -715,7 +723,8 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
return false;
|
||||
}
|
||||
} else if (reference instanceof SimpleConfigList) {
|
||||
if (value instanceof SimpleConfigList) {
|
||||
// objects may be convertible to lists if they have numeric keys
|
||||
if (value instanceof SimpleConfigList || value instanceof SimpleConfigObject) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@ -759,6 +768,25 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkListCompatibility(Path path, SimpleConfigList listRef,
|
||||
SimpleConfigList listValue, List<ConfigException.ValidationProblem> accumulator) {
|
||||
if (listRef.isEmpty() || listValue.isEmpty()) {
|
||||
// can't verify type, leave alone
|
||||
} else {
|
||||
AbstractConfigValue refElement = listRef.get(0);
|
||||
for (ConfigValue elem : listValue) {
|
||||
AbstractConfigValue e = (AbstractConfigValue) elem;
|
||||
if (!haveCompatibleTypes(refElement, e)) {
|
||||
addProblem(accumulator, path, e.origin(), "List at '" + path.render()
|
||||
+ "' contains wrong value type, expecting list of "
|
||||
+ getDesc(refElement) + " but got element of type " + getDesc(e));
|
||||
// don't add a problem for every last array element
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkValid(Path path, ConfigValue reference, AbstractConfigValue value,
|
||||
List<ConfigException.ValidationProblem> accumulator) {
|
||||
// Unmergeable is supposed to be impossible to encounter in here
|
||||
@ -771,22 +799,16 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
} else if (reference instanceof SimpleConfigList && value instanceof SimpleConfigList) {
|
||||
SimpleConfigList listRef = (SimpleConfigList) reference;
|
||||
SimpleConfigList listValue = (SimpleConfigList) value;
|
||||
if (listRef.isEmpty() || listValue.isEmpty()) {
|
||||
// can't verify type, leave alone
|
||||
} else {
|
||||
AbstractConfigValue refElement = listRef.get(0);
|
||||
for (ConfigValue elem : listValue) {
|
||||
AbstractConfigValue e = (AbstractConfigValue) elem;
|
||||
if (!haveCompatibleTypes(refElement, e)) {
|
||||
addProblem(accumulator, path, e.origin(), "List at '" + path.render()
|
||||
+ "' contains wrong value type, expecting list of "
|
||||
+ getDesc(refElement) + " but got element of type "
|
||||
+ getDesc(e));
|
||||
// don't add a problem for every last array element
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
checkListCompatibility(path, listRef, listValue, accumulator);
|
||||
} else if (reference instanceof SimpleConfigList && value instanceof SimpleConfigObject) {
|
||||
// attempt conversion of indexed object to list
|
||||
SimpleConfigList listRef = (SimpleConfigList) reference;
|
||||
AbstractConfigValue listValue = DefaultTransformer.transform(value,
|
||||
ConfigValueType.LIST);
|
||||
if (listValue instanceof SimpleConfigList)
|
||||
checkListCompatibility(path, listRef, (SimpleConfigList) listValue, accumulator);
|
||||
else
|
||||
addWrongType(accumulator, reference, value, path);
|
||||
}
|
||||
} else {
|
||||
addWrongType(accumulator, reference, value, path);
|
||||
|
@ -390,7 +390,9 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
|
||||
if (options.getComments()) {
|
||||
for (String comment : v.origin().comments()) {
|
||||
indent(sb, indent + 1, options);
|
||||
sb.append("# ");
|
||||
sb.append("#");
|
||||
if (!comment.startsWith(" "))
|
||||
sb.append(' ');
|
||||
sb.append(comment);
|
||||
sb.append("\n");
|
||||
}
|
||||
|
@ -93,6 +93,34 @@ final class SimpleConfigOrigin implements ConfigOrigin {
|
||||
}
|
||||
}
|
||||
|
||||
SimpleConfigOrigin prependComments(List<String> comments) {
|
||||
if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull) || comments == null) {
|
||||
return this;
|
||||
} else if (this.commentsOrNull == null) {
|
||||
return setComments(comments);
|
||||
} else {
|
||||
List<String> merged = new ArrayList<String>(comments.size()
|
||||
+ this.commentsOrNull.size());
|
||||
merged.addAll(comments);
|
||||
merged.addAll(this.commentsOrNull);
|
||||
return setComments(merged);
|
||||
}
|
||||
}
|
||||
|
||||
SimpleConfigOrigin appendComments(List<String> comments) {
|
||||
if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull) || comments == null) {
|
||||
return this;
|
||||
} else if (this.commentsOrNull == null) {
|
||||
return setComments(comments);
|
||||
} else {
|
||||
List<String> merged = new ArrayList<String>(comments.size()
|
||||
+ this.commentsOrNull.size());
|
||||
merged.addAll(this.commentsOrNull);
|
||||
merged.addAll(comments);
|
||||
return setComments(merged);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
// not putting the URL in here for files and resources, because people
|
||||
@ -438,6 +466,10 @@ final class SimpleConfigOrigin implements ConfigOrigin {
|
||||
}
|
||||
|
||||
static SimpleConfigOrigin fromFields(Map<SerializedField, Object> m) throws IOException {
|
||||
// we represent a null origin as one with no fields at all
|
||||
if (m.isEmpty())
|
||||
return null;
|
||||
|
||||
String description = (String) m.get(SerializedField.ORIGIN_DESCRIPTION);
|
||||
Integer lineNumber = (Integer) m.get(SerializedField.ORIGIN_LINE_NUMBER);
|
||||
Integer endLineNumber = (Integer) m.get(SerializedField.ORIGIN_END_LINE_NUMBER);
|
||||
|
@ -175,7 +175,7 @@ class SimpleIncluder implements FullIncluder {
|
||||
ConfigParseable jsonHandle = source.nameToParseable(name + ".json", options);
|
||||
ConfigParseable propsHandle = source.nameToParseable(name + ".properties", options);
|
||||
boolean gotSomething = false;
|
||||
List<String> failMessages = new ArrayList<String>();
|
||||
List<ConfigException.IO> fails = new ArrayList<ConfigException.IO>();
|
||||
|
||||
ConfigSyntax syntax = options.getSyntax();
|
||||
|
||||
@ -186,7 +186,7 @@ class SimpleIncluder implements FullIncluder {
|
||||
.setSyntax(ConfigSyntax.CONF));
|
||||
gotSomething = true;
|
||||
} catch (ConfigException.IO e) {
|
||||
failMessages.add(e.getMessage());
|
||||
fails.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,7 +197,7 @@ class SimpleIncluder implements FullIncluder {
|
||||
obj = obj.withFallback(parsed);
|
||||
gotSomething = true;
|
||||
} catch (ConfigException.IO e) {
|
||||
failMessages.add(e.getMessage());
|
||||
fails.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -208,26 +208,39 @@ class SimpleIncluder implements FullIncluder {
|
||||
obj = obj.withFallback(parsed);
|
||||
gotSomething = true;
|
||||
} catch (ConfigException.IO e) {
|
||||
failMessages.add(e.getMessage());
|
||||
fails.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.getAllowMissing() && !gotSomething) {
|
||||
String failMessage;
|
||||
if (failMessages.isEmpty()) {
|
||||
if (ConfigImpl.traceLoadsEnabled()) {
|
||||
// the individual exceptions should have been logged already
|
||||
// with tracing enabled
|
||||
ConfigImpl.trace("Did not find '" + name
|
||||
+ "' with any extension (.conf, .json, .properties); "
|
||||
+ "exceptions should have been logged above.");
|
||||
}
|
||||
|
||||
if (fails.isEmpty()) {
|
||||
// this should not happen
|
||||
throw new ConfigException.BugOrBroken(
|
||||
"should not be reached: nothing found but no exceptions thrown");
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String msg : failMessages) {
|
||||
sb.append(msg);
|
||||
for (Throwable t : fails) {
|
||||
sb.append(t.getMessage());
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.setLength(sb.length() - 2);
|
||||
failMessage = sb.toString();
|
||||
throw new ConfigException.IO(SimpleConfigOrigin.newSimple(name), sb.toString(),
|
||||
fails.get(0));
|
||||
}
|
||||
} else if (!gotSomething) {
|
||||
if (ConfigImpl.traceLoadsEnabled()) {
|
||||
ConfigImpl.trace("Did not find '" + name
|
||||
+ "' with any extension (.conf, .json, .properties); but '" + name
|
||||
+ "' is allowed to be missing. Exceptions from load attempts should have been logged above.");
|
||||
}
|
||||
throw new ConfigException.IO(SimpleConfigOrigin.newSimple(name), failMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -355,7 +355,15 @@ final class Tokenizer {
|
||||
return Tokens.newLong(lineOrigin, Long.parseLong(s), s);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
throw problem(s, "Invalid number: '" + s + "'", true /* suggestQuotes */, e);
|
||||
// not a number after all, see if it's an unquoted string.
|
||||
for (char u : s.toCharArray()) {
|
||||
if (notInUnquotedText.indexOf(u) >= 0)
|
||||
throw problem(asString(u), "Reserved character '" + asString(u)
|
||||
+ "' is not allowed outside quotes", true /* suggestQuotes */);
|
||||
}
|
||||
// no evil chars so we just decide this was a string and
|
||||
// not a number.
|
||||
return Tokens.newUnquotedText(lineOrigin, s);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,8 +182,8 @@ object Settings {
|
||||
val renderSettings = ConfigRenderOptions.defaults.setJson(false).setOriginComments(false)
|
||||
val out = new PrintWriter(file)
|
||||
out.write(config.root.render(renderSettings).lines.
|
||||
// Strip extra spaces in front and fix additional space in of comments.
|
||||
map(_.stripPrefix(" ").replaceAll("^(\\s*)# ", "$1# ")).
|
||||
// Strip extra spaces in front.
|
||||
map(_.stripPrefix(" ")).
|
||||
// Indent two spaces instead of four.
|
||||
map(line => """^(\s*)""".r.replaceAllIn(line, m => m.group(1).replace(" ", " "))).
|
||||
// Finalize the string.
|
||||
|
Loading…
x
Reference in New Issue
Block a user