This commit is contained in:
Ryan O'Neill 2015-08-05 16:14:36 -07:00
parent f086a0902a
commit 7a423d3824
13 changed files with 134 additions and 63 deletions

28
NEWS.md
View File

@ -1,22 +1,12 @@
# 1.3.0-M3: April 21, 2015 # 1.3.0: May 8, 2015
- this is an ABI-not-guaranteed beta release in advance - no changes from 1.3.0-M3
of 1.3.0. Please see the notes for 1.3.0-M1 below for warnings, - this is an ABI-guaranteed stable release.
caveats, and the bulk of what's changed since 1.2.1. - 1.3.0 should be ABI-compatible with 1.2.x for most applications,
though there are enough changes to corner cases and
API changes (since 1.3.0-M2): implementation that some obscure things could break. Please see
the notes for the 1.3.0 milestones below for details, especially
- renamed some methods in the new ConfigDocument for consistency for 1.3.0-M1.
with Config (breaks ABI vs. 1.3.0-M2, but not vs. any stable
release)
Fixes:
- couple of bugfixes in ConfigDocument
Thank you to contributors with commits since v1.3.0-M2 tag:
- Preben Ingvaldsen
# 1.3.0-M3: April 21, 2015 # 1.3.0-M3: April 21, 2015
@ -392,4 +382,4 @@ Thank you to contributors with commits since v1.2.1 tag:
`${foo}` syntax) without getting confused; just about anything `${foo}` syntax) without getting confused; just about anything
that makes conceptual sense should now work. Only inherently that makes conceptual sense should now work. Only inherently
circular config files should fail. circular config files should fail.
- some minor documentation fixes. - some minor documentation fixes.

View File

@ -93,19 +93,19 @@ The license is Apache 2.0, see LICENSE-2.0.txt.
### Binary Releases ### Binary Releases
Version 1.2.1 and earlier were built for Java 6, while newer Version 1.2.1 and earlier were built for Java 6, while newer
versions (1.3.0-M1 is the current beta) will be built for Java 8. versions (1.3.0 and above) will be built for Java 8.
You can find published releases on Maven Central. You can find published releases on Maven Central.
<dependency> <dependency>
<groupId>com.typesafe</groupId> <groupId>com.typesafe</groupId>
<artifactId>config</artifactId> <artifactId>config</artifactId>
<version>1.2.1</version> <version>1.3.0</version>
</dependency> </dependency>
sbt dependency: sbt dependency:
libraryDependencies += "com.typesafe" % "config" % "1.2.1" libraryDependencies += "com.typesafe" % "config" % "1.3.0"
Link for direct download if you don't use a dependency manager: Link for direct download if you don't use a dependency manager:
@ -135,6 +135,10 @@ Typesafe Contributor License Agreement online, using your GitHub
account - it takes 30 seconds. You can do this at account - it takes 30 seconds. You can do this at
http://www.typesafe.com/contribute/cla http://www.typesafe.com/contribute/cla
Please see
[CONTRIBUTING](https://github.com/typesafehub/config/blob/master/CONTRIBUTING.md)
for more including how to make a release.
### Build ### Build
The build uses sbt and the tests are written in Scala; however, The build uses sbt and the tests are written in Scala; however,
@ -748,6 +752,14 @@ If you have trouble with your configuration, some useful tips.
Currently the library is maintained against Java 8, but Currently the library is maintained against Java 8, but
version 1.2.1 and earlier will work with Java 6. version 1.2.1 and earlier will work with Java 6.
Please use 1.2.1 if you need Java 6 support, though some people
have expressed interest in a branch off of 1.3.0 supporting
Java 7. If you want to work on that branch you might bring it up
on [chat](https://gitter.im/typesafehub/config). We can release a
jar for Java 7 if someone(s) steps up to maintain the branch. The
master branch does not use Java 8 "gratuitously" but some APIs
that use Java 8 types will need to be removed.
### Rationale for Supported File Formats ### Rationale for Supported File Formats
(For the curious.) (For the curious.)
@ -800,6 +812,9 @@ your wrapper, just send a pull request for this README. We would
love to know what you're doing with this library or with the HOCON love to know what you're doing with this library or with the HOCON
format. format.
#### Guice integration
* Typesafe Config Guice https://github.com/racc/typesafeconfig-guice
#### Scala wrappers for the Java library #### Scala wrappers for the Java library
* Ficus https://github.com/ceedubs/ficus * Ficus https://github.com/ceedubs/ficus

View File

@ -193,6 +193,13 @@ public class ConfigBeanImpl {
return config.getObjectList(configPropName); return config.getObjectList(configPropName);
} else if (elementType == ConfigValue.class) { } else if (elementType == ConfigValue.class) {
return config.getList(configPropName); return config.getList(configPropName);
} else if (hasAtLeastOneBeanProperty((Class<?>) elementType)) {
List<Object> beanList = new ArrayList<Object>();
List<? extends Config> configList = config.getConfigList(configPropName);
for (Config listMember : configList) {
beanList.add(createInternal(listMember, (Class<?>) elementType));
}
return beanList;
} else { } else {
throw new ConfigException.BadBean("Bean property '" + configPropName + "' of class " + beanClass.getName() + " has unsupported list element type " + elementType); throw new ConfigException.BadBean("Bean property '" + configPropName + "' of class " + beanClass.getName() + " has unsupported list element type " + elementType);
} }
@ -245,4 +252,4 @@ public class ConfigBeanImpl {
return false; return false;
} }
} }

View File

@ -270,6 +270,11 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl
render(stack, sb, indent, atRoot, atKey, options); render(stack, sb, indent, atRoot, atKey, options);
} }
@Override
protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) {
render(sb, indent, atRoot, null, options);
}
// static method also used by ConfigDelayedMergeObject. // static method also used by ConfigDelayedMergeObject.
static void render(List<AbstractConfigValue> stack, StringBuilder sb, int indent, boolean atRoot, String atKey, static void render(List<AbstractConfigValue> stack, StringBuilder sb, int indent, boolean atRoot, String atKey,
ConfigRenderOptions options) { ConfigRenderOptions options) {
@ -334,4 +339,4 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl
sb.append("# ) end of unresolved merge\n"); sb.append("# ) end of unresolved merge\n");
} }
} }
} }

View File

@ -32,6 +32,10 @@ final public class ConfigImplUtil {
return a.equals(b); return a.equals(b);
} }
static boolean isC0Control(int codepoint) {
return (codepoint >= 0x0000 && codepoint <= 0x001F);
}
public static String renderJsonString(String s) { public static String renderJsonString(String s) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append('"'); sb.append('"');
@ -60,7 +64,7 @@ final public class ConfigImplUtil {
sb.append("\\t"); sb.append("\\t");
break; break;
default: default:
if (Character.isISOControl(c)) if (isC0Control(c))
sb.append(String.format("\\u%04x", (int) c)); sb.append(String.format("\\u%04x", (int) c));
else else
sb.append(c); sb.append(c);
@ -229,4 +233,4 @@ final public class ConfigImplUtil {
} }
return nameBuilder.toString(); return nameBuilder.toString();
} }
} }

View File

@ -121,6 +121,9 @@ final class ConfigNodeObject extends ConfigNodeComplexValue {
private Collection<AbstractConfigNode> indentation() { private Collection<AbstractConfigNode> indentation() {
boolean seenNewLine = false; boolean seenNewLine = false;
ArrayList<AbstractConfigNode> indentation = new ArrayList<AbstractConfigNode>(); ArrayList<AbstractConfigNode> indentation = new ArrayList<AbstractConfigNode>();
if (children.isEmpty()) {
return indentation;
}
for (int i = 0; i < children.size(); i++) { for (int i = 0; i < children.size(); i++) {
if (!seenNewLine) { if (!seenNewLine) {
if (children.get(i) instanceof ConfigNodeSingleToken && if (children.get(i) instanceof ConfigNodeSingleToken &&
@ -141,19 +144,18 @@ final class ConfigNodeObject extends ConfigNodeComplexValue {
} }
if (indentation.isEmpty()) { if (indentation.isEmpty()) {
indentation.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, " "))); indentation.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, " ")));
return indentation;
} else { } else {
// Calculate the indentation of the ending curly-brace to get the indentation of the root object // Calculate the indentation of the ending curly-brace to get the indentation of the root object
AbstractConfigNode last = children.get(children.size() - 1); AbstractConfigNode last = children.get(children.size() - 1);
if (last instanceof ConfigNodeSingleToken && ((ConfigNodeSingleToken) last).token() == Tokens.CLOSE_CURLY) { if (last instanceof ConfigNodeSingleToken && ((ConfigNodeSingleToken) last).token() == Tokens.CLOSE_CURLY) {
AbstractConfigNode beforeLast = children.get(children.size() - 2); AbstractConfigNode beforeLast = children.get(children.size() - 2);
String indent = "";
if (beforeLast instanceof ConfigNodeSingleToken && if (beforeLast instanceof ConfigNodeSingleToken &&
Tokens.isIgnoredWhitespace(((ConfigNodeSingleToken) beforeLast).token())) { Tokens.isIgnoredWhitespace(((ConfigNodeSingleToken) beforeLast).token()))
String indent = ((ConfigNodeSingleToken) beforeLast).token().tokenText(); indent = ((ConfigNodeSingleToken) beforeLast).token().tokenText();
indent += " "; indent += " ";
indentation.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, indent))); indentation.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, indent)));
return indentation; return indentation;
}
} }
} }
@ -168,12 +170,12 @@ final class ConfigNodeObject extends ConfigNodeComplexValue {
// If the value we're inserting is a complex value, we'll need to indent it for insertion // If the value we're inserting is a complex value, we'll need to indent it for insertion
AbstractConfigNodeValue indentedValue; AbstractConfigNodeValue indentedValue;
if (value instanceof ConfigNodeComplexValue) { if (value instanceof ConfigNodeComplexValue && !indentation.isEmpty()) {
indentedValue = ((ConfigNodeComplexValue) value).indentText(indentation.get(indentation.size() - 1)); indentedValue = ((ConfigNodeComplexValue) value).indentText(indentation.get(indentation.size() - 1));
} else { } else {
indentedValue = value; indentedValue = value;
} }
boolean sameLine = !(indentation.get(0) instanceof ConfigNodeSingleToken && boolean sameLine = !(indentation.size() > 0 && indentation.get(0) instanceof ConfigNodeSingleToken &&
Tokens.isNewline(((ConfigNodeSingleToken) indentation.get(0)).token())); Tokens.isNewline(((ConfigNodeSingleToken) indentation.get(0)).token()));
// If the path is of length greater than one, see if the value needs to be added further down // If the path is of length greater than one, see if the value needs to be added further down
@ -209,7 +211,10 @@ final class ConfigNodeObject extends ConfigNodeComplexValue {
// If the path is of length greater than one add the required new objects along the path // If the path is of length greater than one add the required new objects along the path
ArrayList<AbstractConfigNode> newObjectNodes = new ArrayList<AbstractConfigNode>(); ArrayList<AbstractConfigNode> newObjectNodes = new ArrayList<AbstractConfigNode>();
newObjectNodes.add(new ConfigNodeSingleToken(Tokens.OPEN_CURLY)); newObjectNodes.add(new ConfigNodeSingleToken(Tokens.OPEN_CURLY));
newObjectNodes.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, " "))); if (indentation.isEmpty()) {
newObjectNodes.add(new ConfigNodeSingleToken(Tokens.newLine(null)));
}
newObjectNodes.addAll(indentation);
newObjectNodes.add(new ConfigNodeSingleToken(Tokens.CLOSE_CURLY)); newObjectNodes.add(new ConfigNodeSingleToken(Tokens.CLOSE_CURLY));
ConfigNodeObject newObject = new ConfigNodeObject(newObjectNodes); ConfigNodeObject newObject = new ConfigNodeObject(newObjectNodes);
newNodes.add(newObject.addValueOnPath(desiredPath.subPath(1), indentedValue, flavor)); newNodes.add(newObject.addValueOnPath(desiredPath.subPath(1), indentedValue, flavor));
@ -273,4 +278,4 @@ final class ConfigNodeObject extends ConfigNodeComplexValue {
Path path = PathParser.parsePathNode(desiredPath, flavor).value(); Path path = PathParser.parsePathNode(desiredPath, flavor).value();
return changeValueOnPath(path, null, flavor); return changeValueOnPath(path, null, flavor);
} }
} }

View File

@ -1,9 +1,7 @@
package com.typesafe.config.impl; package com.typesafe.config.impl;
import com.typesafe.config.*;
import com.typesafe.config.parser.ConfigDocument; import com.typesafe.config.parser.ConfigDocument;
import com.typesafe.config.ConfigParseOptions;
import com.typesafe.config.ConfigValue;
import com.typesafe.config.ConfigException;
import java.io.StringReader; import java.io.StringReader;
import java.util.Iterator; import java.util.Iterator;
@ -34,7 +32,9 @@ final class SimpleConfigDocument implements ConfigDocument {
public ConfigDocument withValue(String path, ConfigValue newValue) { public ConfigDocument withValue(String path, ConfigValue newValue) {
if (newValue == null) if (newValue == null)
throw new ConfigException.BugOrBroken("null value for " + path + " passed to withValue"); throw new ConfigException.BugOrBroken("null value for " + path + " passed to withValue");
return withValueText(path, newValue.render().trim()); ConfigRenderOptions options = ConfigRenderOptions.defaults();
options = options.setOriginComments(false);
return withValueText(path, newValue.render(options).trim());
} }
@Override @Override
@ -60,4 +60,4 @@ final class SimpleConfigDocument implements ConfigDocument {
public int hashCode() { public int hashCode() {
return render().hashCode(); return render().hashCode();
} }
} }

View File

@ -38,7 +38,7 @@ final class Tokenizer {
return "tab"; return "tab";
else if (codepoint == -1) else if (codepoint == -1)
return "end of file"; return "end of file";
else if (Character.isISOControl(codepoint)) else if (ConfigImplUtil.isC0Control(codepoint))
return String.format("control character 0x%x", codepoint); return String.format("control character 0x%x", codepoint);
else else
return String.format("%c", codepoint); return String.format("%c", codepoint);
@ -498,7 +498,7 @@ final class Tokenizer {
} else if (c == '"') { } else if (c == '"') {
sbOrig.appendCodePoint(c); sbOrig.appendCodePoint(c);
break; break;
} else if (Character.isISOControl(c)) { } else if (ConfigImplUtil.isC0Control(c)) {
throw problem(asString(c), "JSON does not allow unescaped " + asString(c) throw problem(asString(c), "JSON does not allow unescaped " + asString(c)
+ " in quoted strings, use a backslash escape"); + " in quoted strings, use a backslash escape");
} else { } else {
@ -692,4 +692,4 @@ final class Tokenizer {
"Does not make sense to remove items from token stream"); "Does not make sense to remove items from token stream");
} }
} }
} }

View File

@ -23,6 +23,7 @@ public class ArraysConfig {
List<ConfigValue> ofConfigValue; List<ConfigValue> ofConfigValue;
List<Duration> ofDuration; List<Duration> ofDuration;
List<ConfigMemorySize> ofMemorySize; List<ConfigMemorySize> ofMemorySize;
List<StringsConfig> ofStringBean;
public List<Integer> getEmpty() { public List<Integer> getEmpty() {
return empty; return empty;
@ -127,4 +128,12 @@ public class ArraysConfig {
public void setOfMemorySize(List<ConfigMemorySize> ofMemorySize) { public void setOfMemorySize(List<ConfigMemorySize> ofMemorySize) {
this.ofMemorySize = ofMemorySize; this.ofMemorySize = ofMemorySize;
} }
}
public List<StringsConfig> getOfStringBean() {
return ofStringBean;
}
public void setOfStringBean(List<StringsConfig> ofStringBean) {
this.ofStringBean = ofStringBean;
}
}

View File

@ -20,4 +20,26 @@ public class StringsConfig {
public void setYes(String s) { public void setYes(String s) {
yes = s; yes = s;
} }
}
@Override
public boolean equals(Object o) {
if (o instanceof StringsConfig) {
StringsConfig sc = (StringsConfig) o;
return sc.abcd.equals(abcd) &&
sc.yes.equals(yes);
} else {
return false;
}
}
@Override
public int hashCode() {
int h = 41 * (41 + abcd.hashCode());
return h + yes.hashCode();
}
@Override
public String toString() {
return "StringsConfig(" + abcd + "," + yes + ")";
}
}

View File

@ -46,10 +46,10 @@ class ConfigBeanFactoryTest extends TestUtils {
ConfigBeanFactory.create(config, classOf[ValidationBeanConfig]) ConfigBeanFactory.create(config, classOf[ValidationBeanConfig])
} }
val expecteds = Seq(Missing("propNotListedInConfig", 67, "string"), val expecteds = Seq(Missing("propNotListedInConfig", 77, "string"),
WrongType("shouldBeInt", 68, "number", "boolean"), WrongType("shouldBeInt", 78, "number", "boolean"),
WrongType("should-be-boolean", 69, "boolean", "number"), WrongType("should-be-boolean", 79, "boolean", "number"),
WrongType("should-be-list", 70, "list", "string")) WrongType("should-be-list", 80, "list", "string"))
checkValidationException(e, expecteds) checkValidationException(e, expecteds)
} }
@ -111,6 +111,15 @@ class ConfigBeanFactoryTest extends TestUtils {
ConfigMemorySize.ofBytes(1048576), ConfigMemorySize.ofBytes(1048576),
ConfigMemorySize.ofBytes(1073741824)), ConfigMemorySize.ofBytes(1073741824)),
beanConfig.getOfMemorySize.asScala) beanConfig.getOfMemorySize.asScala)
val stringsConfigOne = new StringsConfig();
stringsConfigOne.setAbcd("testAbcdOne")
stringsConfigOne.setYes("testYesOne")
val stringsConfigTwo = new StringsConfig();
stringsConfigTwo.setAbcd("testAbcdTwo")
stringsConfigTwo.setYes("testYesTwo")
assertEquals(List(stringsConfigOne, stringsConfigTwo).asJava, beanConfig.getOfStringBean)
} }
@Test @Test
@ -200,4 +209,4 @@ class ConfigBeanFactoryTest extends TestUtils {
} }
} }
} }

View File

@ -171,14 +171,14 @@ class ConfigDocumentTest extends TestUtils {
@Test @Test
def configDocumentSetNewValueMultiLevelConf { def configDocumentSetNewValueMultiLevelConf {
val origText = "a:b\nc:d" val origText = "a:b\nc:d"
val finalText = "a:b\nc:d\ne : { f : { g : 12 } }" val finalText = "a:b\nc:d\ne : {\n f : {\n g : 12\n }\n}"
configDocumentReplaceConfTest(origText, finalText, "12", "e.f.g") configDocumentReplaceConfTest(origText, finalText, "12", "e.f.g")
} }
@Test @Test
def configDocumentSetNewValueMultiLevelJson { def configDocumentSetNewValueMultiLevelJson {
val origText = "{\"a\":\"b\",\n\"c\":\"d\"}" val origText = "{\"a\":\"b\",\n\"c\":\"d\"}"
val finalText = "{\"a\":\"b\",\n\"c\":\"d\",\n\"e\" : { \"f\" : { \"g\" : 12 } }}" val finalText = "{\"a\":\"b\",\n\"c\":\"d\",\n \"e\" : {\n \"f\" : {\n \"g\" : 12\n }\n }}"
configDocumentReplaceJsonTest(origText, finalText, "12", "e.f.g") configDocumentReplaceJsonTest(origText, finalText, "12", "e.f.g")
} }
@ -342,13 +342,13 @@ class ConfigDocumentTest extends TestUtils {
var configDocument = ConfigDocumentFactory.parseString(origText) var configDocument = ConfigDocumentFactory.parseString(origText)
assertEquals("a {\n b: c\n e : f\n}", configDocument.withValueText("a.e", "f").render()) assertEquals("a {\n b: c\n e : f\n}", configDocument.withValueText("a.e", "f").render())
assertEquals("a {\n b: c\n d : { e : { f : g } }\n}", configDocument.withValueText("a.d.e.f", "g").render()) assertEquals("a {\n b: c\n d : {\n e : {\n f : g\n }\n }\n}", configDocument.withValueText("a.d.e.f", "g").render())
origText = "a {\n b: c\n}\n" origText = "a {\n b: c\n}\n"
configDocument = ConfigDocumentFactory.parseString(origText) configDocument = ConfigDocumentFactory.parseString(origText)
assertEquals("a {\n b: c\n}\nd : e\n", configDocument.withValueText("d", "e").render()) assertEquals("a {\n b: c\n}\nd : e\n", configDocument.withValueText("d", "e").render())
assertEquals("a {\n b: c\n}\nd : { e : { f : g } }\n", configDocument.withValueText("d.e.f", "g").render()) assertEquals("a {\n b: c\n}\nd : {\n e : {\n f : g\n }\n}\n", configDocument.withValueText("d.e.f", "g").render())
} }
@Test @Test
@ -435,7 +435,14 @@ class ConfigDocumentTest extends TestUtils {
val origText = "" val origText = ""
val configDocument = ConfigDocumentFactory.parseString(origText) val configDocument = ConfigDocumentFactory.parseString(origText)
assertEquals(" a : 1", configDocument.withValueText("a", "1").render) assertEquals("a : 1", configDocument.withValueText("a", "1").render)
val mapVal = ConfigValueFactory.fromAnyRef(Map("a" -> 1, "b" -> 2).asJava)
assertEquals("a : {\n \"a\" : 1,\n \"b\" : 2\n}",
configDocument.withValue("a", mapVal).render)
val arrayVal = ConfigValueFactory.fromAnyRef(List(1, 2).asJava)
assertEquals("a : [\n 1,\n 2\n]", configDocument.withValue("a", arrayVal).render)
} }
@Test @Test
@ -445,9 +452,7 @@ class ConfigDocumentTest extends TestUtils {
val configVal = ConfigValueFactory.fromAnyRef(Map("a" -> 1, "b" -> 2).asJava) val configVal = ConfigValueFactory.fromAnyRef(Map("a" -> 1, "b" -> 2).asJava)
assertEquals("{ a : {\n # hardcoded value\n \"a\" : 1,\n # hardcoded value\n \"b\" : 2\n } }", assertEquals("{ a : {\n \"a\" : 1,\n \"b\" : 2\n } }",
configDocument.withValue("a", configVal).render) configDocument.withValue("a", configVal).render)
} }
}
}

View File

@ -212,7 +212,7 @@ class ConfigNodeTest extends TestUtils {
nodeCloseBrace)) nodeCloseBrace))
assertEquals(origText, origNode.render()) assertEquals(origText, origNode.render())
val finalText = "foo : bar\nbaz : {\n\t\"abc.def\" : true\n\t//This is a comment about the below setting\n\n\tabc : {\n\t\t" + val finalText = "foo : bar\nbaz : {\n\t\"abc.def\" : true\n\t//This is a comment about the below setting\n\n\tabc : {\n\t\t" +
"def : false\n\t\t\n\t\t\"this.does.not.exist@@@+$#\" : { end : doesnotexist }\n\t}\n}\n\nbaz.abc.ghi : randomunquotedString\n}" "def : false\n\t\t\n\t\t\"this.does.not.exist@@@+$#\" : {\n\t\t end : doesnotexist\n\t\t}\n\t}\n}\n\nbaz.abc.ghi : randomunquotedString\n}"
//Can replace settings in nested maps //Can replace settings in nested maps
// Paths with quotes in the name are treated as a single Path, rather than multiple sub-paths // Paths with quotes in the name are treated as a single Path, rather than multiple sub-paths
@ -229,4 +229,4 @@ class ConfigNodeTest extends TestUtils {
assertEquals(finalText, newNode.render()) assertEquals(finalText, newNode.render())
} }
} }