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
of 1.3.0. Please see the notes for 1.3.0-M1 below for warnings,
caveats, and the bulk of what's changed since 1.2.1.
API changes (since 1.3.0-M2):
- renamed some methods in the new ConfigDocument for consistency
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
- no changes from 1.3.0-M3
- this is an ABI-guaranteed stable release.
- 1.3.0 should be ABI-compatible with 1.2.x for most applications,
though there are enough changes to corner cases and
implementation that some obscure things could break. Please see
the notes for the 1.3.0 milestones below for details, especially
for 1.3.0-M1.
# 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
that makes conceptual sense should now work. Only inherently
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
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.
<dependency>
<groupId>com.typesafe</groupId>
<artifactId>config</artifactId>
<version>1.2.1</version>
<version>1.3.0</version>
</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:
@ -135,6 +135,10 @@ Typesafe Contributor License Agreement online, using your GitHub
account - it takes 30 seconds. You can do this at
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
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
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
(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
format.
#### Guice integration
* Typesafe Config Guice https://github.com/racc/typesafeconfig-guice
#### Scala wrappers for the Java library
* Ficus https://github.com/ceedubs/ficus

View File

@ -193,6 +193,13 @@ public class ConfigBeanImpl {
return config.getObjectList(configPropName);
} else if (elementType == ConfigValue.class) {
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 {
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;
}
}
}

View File

@ -270,6 +270,11 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl
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 void render(List<AbstractConfigValue> stack, StringBuilder sb, int indent, boolean atRoot, String atKey,
ConfigRenderOptions options) {
@ -334,4 +339,4 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl
sb.append("# ) end of unresolved merge\n");
}
}
}
}

View File

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

View File

@ -121,6 +121,9 @@ final class ConfigNodeObject extends ConfigNodeComplexValue {
private Collection<AbstractConfigNode> indentation() {
boolean seenNewLine = false;
ArrayList<AbstractConfigNode> indentation = new ArrayList<AbstractConfigNode>();
if (children.isEmpty()) {
return indentation;
}
for (int i = 0; i < children.size(); i++) {
if (!seenNewLine) {
if (children.get(i) instanceof ConfigNodeSingleToken &&
@ -141,19 +144,18 @@ final class ConfigNodeObject extends ConfigNodeComplexValue {
}
if (indentation.isEmpty()) {
indentation.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, " ")));
return indentation;
} else {
// Calculate the indentation of the ending curly-brace to get the indentation of the root object
AbstractConfigNode last = children.get(children.size() - 1);
if (last instanceof ConfigNodeSingleToken && ((ConfigNodeSingleToken) last).token() == Tokens.CLOSE_CURLY) {
AbstractConfigNode beforeLast = children.get(children.size() - 2);
String indent = "";
if (beforeLast instanceof ConfigNodeSingleToken &&
Tokens.isIgnoredWhitespace(((ConfigNodeSingleToken) beforeLast).token())) {
String indent = ((ConfigNodeSingleToken) beforeLast).token().tokenText();
indent += " ";
indentation.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, indent)));
return indentation;
}
Tokens.isIgnoredWhitespace(((ConfigNodeSingleToken) beforeLast).token()))
indent = ((ConfigNodeSingleToken) beforeLast).token().tokenText();
indent += " ";
indentation.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, indent)));
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
AbstractConfigNodeValue indentedValue;
if (value instanceof ConfigNodeComplexValue) {
if (value instanceof ConfigNodeComplexValue && !indentation.isEmpty()) {
indentedValue = ((ConfigNodeComplexValue) value).indentText(indentation.get(indentation.size() - 1));
} else {
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()));
// 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
ArrayList<AbstractConfigNode> newObjectNodes = new ArrayList<AbstractConfigNode>();
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));
ConfigNodeObject newObject = new ConfigNodeObject(newObjectNodes);
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();
return changeValueOnPath(path, null, flavor);
}
}
}

View File

@ -1,9 +1,7 @@
package com.typesafe.config.impl;
import com.typesafe.config.*;
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.util.Iterator;
@ -34,7 +32,9 @@ final class SimpleConfigDocument implements ConfigDocument {
public ConfigDocument withValue(String path, ConfigValue newValue) {
if (newValue == null)
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
@ -60,4 +60,4 @@ final class SimpleConfigDocument implements ConfigDocument {
public int hashCode() {
return render().hashCode();
}
}
}

View File

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

View File

@ -23,6 +23,7 @@ public class ArraysConfig {
List<ConfigValue> ofConfigValue;
List<Duration> ofDuration;
List<ConfigMemorySize> ofMemorySize;
List<StringsConfig> ofStringBean;
public List<Integer> getEmpty() {
return empty;
@ -127,4 +128,12 @@ public class ArraysConfig {
public void setOfMemorySize(List<ConfigMemorySize> 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) {
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])
}
val expecteds = Seq(Missing("propNotListedInConfig", 67, "string"),
WrongType("shouldBeInt", 68, "number", "boolean"),
WrongType("should-be-boolean", 69, "boolean", "number"),
WrongType("should-be-list", 70, "list", "string"))
val expecteds = Seq(Missing("propNotListedInConfig", 77, "string"),
WrongType("shouldBeInt", 78, "number", "boolean"),
WrongType("should-be-boolean", 79, "boolean", "number"),
WrongType("should-be-list", 80, "list", "string"))
checkValidationException(e, expecteds)
}
@ -111,6 +111,15 @@ class ConfigBeanFactoryTest extends TestUtils {
ConfigMemorySize.ofBytes(1048576),
ConfigMemorySize.ofBytes(1073741824)),
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
@ -200,4 +209,4 @@ class ConfigBeanFactoryTest extends TestUtils {
}
}
}
}

View File

@ -171,14 +171,14 @@ class ConfigDocumentTest extends TestUtils {
@Test
def configDocumentSetNewValueMultiLevelConf {
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")
}
@Test
def configDocumentSetNewValueMultiLevelJson {
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")
}
@ -342,13 +342,13 @@ class ConfigDocumentTest extends TestUtils {
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 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"
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 : { 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
@ -435,7 +435,14 @@ class ConfigDocumentTest extends TestUtils {
val 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
@ -445,9 +452,7 @@ class ConfigDocumentTest extends TestUtils {
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)
}
}
}

View File

@ -212,7 +212,7 @@ class ConfigNodeTest extends TestUtils {
nodeCloseBrace))
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" +
"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
// 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())
}
}
}