Merge remote-tracking branch 'origin/master' into wip/havocp-resolve-fixes

Conflicts:
	config/src/main/java/com/typesafe/config/impl/SimpleConfigList.java
This commit is contained in:
Havoc Pennington 2014-12-27 16:45:44 -05:00
commit 1afaea55a8
11 changed files with 144 additions and 33 deletions

View File

@ -863,7 +863,7 @@ usual the comma may be omitted if there's a newline).
If an unquoted `include` at the start of a key is followed by If an unquoted `include` at the start of a key is followed by
anything other than a single quoted string or the anything other than a single quoted string or the
`url("")`/`file("")/`classpath("")` syntax, it is invalid and an `url("")`/`file("")`/`classpath("")` syntax, it is invalid and an
error should be generated. error should be generated.
There can be any amount of whitespace, including newlines, between There can be any amount of whitespace, including newlines, between
@ -1209,9 +1209,9 @@ parsed as a number plus an optional unit string.
The supported unit strings for duration are case sensitive and The supported unit strings for duration are case sensitive and
must be lowercase. Exactly these strings are supported: must be lowercase. Exactly these strings are supported:
- `ns`, `nanosecond`, `nanoseconds` - `ns`, `nano`, `nanos`, `nanosecond`, `nanoseconds`
- `us`, `microsecond`, `microseconds` - `us`, `micro`, `micros`, `microsecond`, `microseconds`
- `ms`, `millisecond`, `milliseconds` - `ms`, `milli`, `millis`, `millisecond`, `milliseconds`
- `s`, `second`, `seconds` - `s`, `second`, `seconds`
- `m`, `minute`, `minutes` - `m`, `minute`, `minutes`
- `h`, `hour`, `hours` - `h`, `hour`, `hours`
@ -1274,6 +1274,13 @@ spec copies that. You can certainly find examples of mapping these
to powers of ten, though. If you don't like ambiguity, don't use to powers of ten, though. If you don't like ambiguity, don't use
the single-letter abbreviations. the single-letter abbreviations.
Note: any value in zetta/zebi or yotta/yobi will overflow a 64-bit
integer, and of course large-enough values in any of the units may
overflow. Most real-world APIs and apps will not support byte
counts that overflow a 64-bit integer. The huge units are provided
just to be complete but probably aren't useful in practice. At
least not in 2014.
### Config object merging and file merging ### Config object merging and file merging
It may be useful to offer a method to merge two objects. If such a It may be useful to offer a method to merge two objects. If such a

View File

@ -92,6 +92,10 @@ Maven Central.
<version>1.2.1</version> <version>1.2.1</version>
</dependency> </dependency>
sbt dependency:
libraryDependencies += "com.typesafe" % "config" % "1.2.1"
Link for direct download if you don't use a dependency manager: Link for direct download if you don't use a dependency manager:
- http://central.maven.org/maven2/com/typesafe/config/ - http://central.maven.org/maven2/com/typesafe/config/
@ -743,3 +747,7 @@ format.
#### Ruby port #### Ruby port
* https://github.com/cprice404/ruby-hocon * https://github.com/cprice404/ruby-hocon
#### Python port
* pyhocon https://github.com/chimpler/pyhocon

View File

@ -16,7 +16,7 @@ crossPaths := false
libraryDependencies += "net.liftweb" %% "lift-json" % "2.5" % "test" libraryDependencies += "net.liftweb" %% "lift-json" % "2.5" % "test"
libraryDependencies += "com.novocode" % "junit-interface" % "0.10-M4" % "test" libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test"
externalResolvers += "Scala Tools Snapshots" at "http://scala-tools.org/repo-snapshots/" externalResolvers += "Scala Tools Snapshots" at "http://scala-tools.org/repo-snapshots/"

View File

@ -591,10 +591,35 @@ public final class ConfigFactory {
return parseURL(url, ConfigParseOptions.defaults()); return parseURL(url, ConfigParseOptions.defaults());
} }
/**
* Parses a file into a Config instance. Does not call
* {@link Config#resolve} or merge the file with any other
* configuration; this method parses a single file and does
* nothing else. It does process "include" statements in the
* parsed file, and may end up doing other IO due to those
* statements.
*
* @param file
* the file to parse
* @param options
* parse options to control how the file is interpreted
* @return the parsed configuration
* @throws ConfigException on IO or parse errors
*/
public static Config parseFile(File file, ConfigParseOptions options) { public static Config parseFile(File file, ConfigParseOptions options) {
return Parseable.newFile(file, options).parse().toConfig(); return Parseable.newFile(file, options).parse().toConfig();
} }
/**
* Parses a file into a Config instance as with
* {@link #parseFile(File,ConfigParseOptions)} but always uses the
* default parse options.
*
* @param file
* the file to parse
* @return the parsed configuration
* @throws ConfigException on IO or parse errors
*/
public static Config parseFile(File file) { public static Config parseFile(File file) {
return parseFile(file, ConfigParseOptions.defaults()); return parseFile(file, ConfigParseOptions.defaults());
} }
@ -637,6 +662,14 @@ public final class ConfigFactory {
return ConfigImpl.parseFileAnySyntax(fileBasename, options).toConfig(); return ConfigImpl.parseFileAnySyntax(fileBasename, options).toConfig();
} }
/**
* Like {@link #parseFileAnySyntax(File,ConfigParseOptions)} but always uses
* default parse options.
*
* @param fileBasename
* a filename with or without extension
* @return the parsed configuration
*/
public static Config parseFileAnySyntax(File fileBasename) { public static Config parseFileAnySyntax(File fileBasename) {
return parseFileAnySyntax(fileBasename, ConfigParseOptions.defaults()); return parseFileAnySyntax(fileBasename, ConfigParseOptions.defaults());
} }

View File

@ -5,6 +5,8 @@ package com.typesafe.config.impl;
import java.io.ObjectStreamException; import java.io.ObjectStreamException;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.AbstractMap; import java.util.AbstractMap;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -512,12 +514,12 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
unitString = unitString + "s"; unitString = unitString + "s";
// note that this is deliberately case-sensitive // note that this is deliberately case-sensitive
if (unitString.equals("") || unitString.equals("ms") if (unitString.equals("") || unitString.equals("ms") || unitString.equals("millis")
|| unitString.equals("milliseconds")) { || unitString.equals("milliseconds")) {
units = TimeUnit.MILLISECONDS; units = TimeUnit.MILLISECONDS;
} else if (unitString.equals("us") || unitString.equals("microseconds")) { } else if (unitString.equals("us") || unitString.equals("micros") || unitString.equals("microseconds")) {
units = TimeUnit.MICROSECONDS; units = TimeUnit.MICROSECONDS;
} else if (unitString.equals("ns") || unitString.equals("nanoseconds")) { } else if (unitString.equals("ns") || unitString.equals("nanos") || unitString.equals("nanoseconds")) {
units = TimeUnit.NANOSECONDS; units = TimeUnit.NANOSECONDS;
} else if (unitString.equals("d") || unitString.equals("days")) { } else if (unitString.equals("d") || unitString.equals("days")) {
units = TimeUnit.DAYS; units = TimeUnit.DAYS;
@ -575,19 +577,13 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
final String prefix; final String prefix;
final int powerOf; final int powerOf;
final int power; final int power;
final long bytes; final BigInteger bytes;
MemoryUnit(String prefix, int powerOf, int power) { MemoryUnit(String prefix, int powerOf, int power) {
this.prefix = prefix; this.prefix = prefix;
this.powerOf = powerOf; this.powerOf = powerOf;
this.power = power; this.power = power;
int i = power; this.bytes = BigInteger.valueOf(powerOf).pow(power);
long bytes = 1;
while (i > 0) {
bytes *= powerOf;
--i;
}
this.bytes = bytes;
} }
private static Map<String, MemoryUnit> makeUnitsMap() { private static Map<String, MemoryUnit> makeUnitsMap() {
@ -667,13 +663,20 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
} }
try { try {
BigInteger result;
// if the string is purely digits, parse as an integer to avoid // if the string is purely digits, parse as an integer to avoid
// possible precision loss; otherwise as a double. // possible precision loss; otherwise as a double.
if (numberString.matches("[0-9]+")) { if (numberString.matches("[0-9]+")) {
return Long.parseLong(numberString) * units.bytes; result = units.bytes.multiply(new BigInteger(numberString));
} else { } else {
return (long) (Double.parseDouble(numberString) * units.bytes); BigDecimal resultDecimal = (new BigDecimal(units.bytes)).multiply(new BigDecimal(numberString));
result = resultDecimal.toBigInteger();
} }
if (result.bitLength() < 64)
return result.longValue();
else
throw new ConfigException.BadValue(originForException, pathForException,
"size-in-bytes value is out of range for a 64-bit long: '" + input + "'");
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new ConfigException.BadValue(originForException, pathForException, throw new ConfigException.BadValue(originForException, pathForException,
"Could not parse size-in-bytes number '" + numberString + "'"); "Could not parse size-in-bytes number '" + numberString + "'");

View File

@ -115,7 +115,11 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList,
} }
if (changed != null) { if (changed != null) {
return new SimpleConfigList(origin(), changed, newResolveStatus); if (newResolveStatus != null) {
return new SimpleConfigList(origin(), changed, newResolveStatus);
} else {
return new SimpleConfigList(origin(), changed);
}
} else { } else {
return this; return this;
} }
@ -151,7 +155,7 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList,
} else { } else {
try { try {
ResolveModifier modifier = new ResolveModifier(context, source.pushParent(this)); ResolveModifier modifier = new ResolveModifier(context, source.pushParent(this));
SimpleConfigList value = modifyMayThrow(modifier, ResolveStatus.RESOLVED); SimpleConfigList value = modifyMayThrow(modifier, context.options().getAllowUnresolved() ? null : ResolveStatus.RESOLVED);
return ResolveResult.make(modifier.context, value); return ResolveResult.make(modifier.context, value);
} catch (NotPossibleToResolve e) { } catch (NotPossibleToResolve e) {
throw e; throw e;

View File

@ -457,23 +457,22 @@ final class Tokenizer {
private Token pullQuotedString() throws ProblemException { private Token pullQuotedString() throws ProblemException {
// the open quote has already been consumed // the open quote has already been consumed
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
int c = '\0'; // value doesn't get used while (true) {
do { int c = nextCharRaw();
c = nextCharRaw();
if (c == -1) if (c == -1)
throw problem("End of input but string quote was still open"); throw problem("End of input but string quote was still open");
if (c == '\\') { if (c == '\\') {
pullEscapeSequence(sb); pullEscapeSequence(sb);
} else if (c == '"') { } else if (c == '"') {
// end the loop, done! break;
} else if (Character.isISOControl(c)) { } else if (Character.isISOControl(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 {
sb.appendCodePoint(c); sb.appendCodePoint(c);
} }
} while (c != '"'); }
// maybe switch to triple-quoted string, sort of hacky... // maybe switch to triple-quoted string, sort of hacky...
if (sb.length() == 0) { if (sb.length() == 0) {

View File

@ -54,7 +54,9 @@
"second" : 1s, "second" : 1s,
"secondsList" : [1s,2seconds,3 s, 4000], "secondsList" : [1s,2seconds,3 s, 4000],
"secondAsNumber" : 1000, "secondAsNumber" : 1000,
"halfSecond" : 0.5s "halfSecond" : 0.5s,
"millis" : 1 milli,
"micros" : 2000 micros
}, },
"memsizes" : { "memsizes" : {

View File

@ -769,6 +769,8 @@ class ConfigTest extends TestUtils {
assertEquals(Seq(1, 2, 3, 4) map s2unit, assertEquals(Seq(1, 2, 3, 4) map s2unit,
conf.getDurationList("durations.secondsList", unit).asScala) conf.getDurationList("durations.secondsList", unit).asScala)
assertEquals(ms2unit(500L), conf.getDuration("durations.halfSecond", unit)) assertEquals(ms2unit(500L), conf.getDuration("durations.halfSecond", unit))
assertEquals(ms2unit(1L), conf.getDuration("durations.millis", unit))
assertEquals(ms2unit(2L), conf.getDuration("durations.micros", unit))
} }
assertDurationAsTimeUnit(NANOSECONDS) assertDurationAsTimeUnit(NANOSECONDS)
@ -1118,6 +1120,15 @@ class ConfigTest extends TestUtils {
assertTrue("after resolution, config is now resolved", resolved2.isResolved) assertTrue("after resolution, config is now resolved", resolved2.isResolved)
} }
@Test
def allowUnresolvedDoesAllowUnresolvedArrayElements() {
val values = ConfigFactory.parseString("unknown = [someVal], known = 42")
val unresolved = ConfigFactory.parseString("concat = [${unknown}[]], sibling = [${unknown}, ${known}]")
unresolved.resolve(ConfigResolveOptions.defaults().setAllowUnresolved(true))
unresolved.withFallback(values).resolve()
unresolved.resolveWith(values)
}
@Test @Test
def allowUnresolvedDoesAllowUnresolved() { def allowUnresolvedDoesAllowUnresolved() {
val values = ConfigFactory.parseString("{ foo = 1, bar = 2, m = 3, n = 4}") val values = ConfigFactory.parseString("{ foo = 1, bar = 2, m = 3, n = 4}")

View File

@ -50,16 +50,19 @@ class UnitParserTest extends TestUtils {
val conf = parseConfig("foo = 1d") val conf = parseConfig("foo = 1d")
assertEquals("could get 1d from conf as days", assertEquals("could get 1d from conf as days",
1L, conf.getDuration("foo", TimeUnit.DAYS)) 1L, conf.getDuration("foo", TimeUnit.DAYS))
assertEquals("could get 1d from conf as nanos", assertEquals("could get 1d from conf as nanos",
dayInNanos, conf.getNanoseconds("foo")) dayInNanos, conf.getNanoseconds("foo"))
assertEquals("could get 1d from conf as millis", assertEquals("could get 1d from conf as millis",
TimeUnit.DAYS.toMillis(1), conf.getMilliseconds("foo")) TimeUnit.DAYS.toMillis(1), conf.getMilliseconds("foo"))
} }
@Test @Test
def parseMemorySizeInBytes(): Unit = { def parseMemorySizeInBytes(): Unit = {
def parseMem(s: String) = SimpleConfig.parseBytes(s, fakeOrigin(), "test") def parseMem(s: String): Long = SimpleConfig.parseBytes(s, fakeOrigin(), "test")
assertEquals(Long.MaxValue, parseMem(s"${Long.MaxValue} bytes"))
assertEquals(Long.MinValue, parseMem(s"${Long.MinValue} bytes"))
val oneMebis = List("1048576", "1048576b", "1048576bytes", "1048576byte", val oneMebis = List("1048576", "1048576b", "1048576bytes", "1048576byte",
"1048576 b", "1048576 bytes", "1048576 b", "1048576 bytes",
@ -88,7 +91,7 @@ class UnitParserTest extends TestUtils {
} }
var result = 1024L * 1024 * 1024 var result = 1024L * 1024 * 1024
for (unit <- Seq("tebi", "pebi", "exbi", "zebi", "yobi")) { for (unit <- Seq("tebi", "pebi", "exbi")) {
val first = unit.substring(0, 1).toUpperCase() val first = unit.substring(0, 1).toUpperCase()
result = result * 1024; result = result * 1024;
assertEquals(result, parseMem("1" + first)) assertEquals(result, parseMem("1" + first))
@ -99,7 +102,7 @@ class UnitParserTest extends TestUtils {
} }
result = 1000L * 1000 * 1000 result = 1000L * 1000 * 1000
for (unit <- Seq("tera", "peta", "exa", "zetta", "yotta")) { for (unit <- Seq("tera", "peta", "exa")) {
val first = unit.substring(0, 1).toUpperCase() val first = unit.substring(0, 1).toUpperCase()
result = result * 1000; result = result * 1000;
assertEquals(result, parseMem("1" + first + "B")) assertEquals(result, parseMem("1" + first + "B"))
@ -119,4 +122,45 @@ class UnitParserTest extends TestUtils {
} }
assertTrue(e2.getMessage().contains("size-in-bytes number")) assertTrue(e2.getMessage().contains("size-in-bytes number"))
} }
// later on we'll want to check this with BigInteger version of getBytes
@Test
def parseHugeMemorySizes(): Unit = {
def parseMem(s: String): Long = SimpleConfig.parseBytes(s, fakeOrigin(), "test")
def assertOutOfRange(s: String) = {
val fail = intercept[ConfigException.BadValue] {
parseMem(s)
}
assertTrue("number was too big", fail.getMessage.contains("out of range"))
}
import java.math.BigInteger
assertOutOfRange(s"${BigInteger.valueOf(Long.MaxValue).add(BigInteger.valueOf(1)).toString} bytes")
assertOutOfRange(s"${BigInteger.valueOf(Long.MinValue).subtract(BigInteger.valueOf(1)).toString} bytes")
var result = 1024L * 1024 * 1024
for (unit <- Seq("zebi", "yobi")) {
val first = unit.substring(0, 1).toUpperCase()
assertOutOfRange("1" + first)
assertOutOfRange("1" + first + "i")
assertOutOfRange("1" + first + "iB")
assertOutOfRange("1" + unit + "byte")
assertOutOfRange("1" + unit + "bytes")
assertOutOfRange("1.1" + first)
assertOutOfRange("-1" + first)
}
result = 1000L * 1000 * 1000
for (unit <- Seq("zetta", "yotta")) {
val first = unit.substring(0, 1).toUpperCase()
assertOutOfRange("1" + first + "B")
assertOutOfRange("1" + unit + "byte")
assertOutOfRange("1" + unit + "bytes")
assertOutOfRange("1.1" + first + "B")
assertOutOfRange("-1" + first + "B")
}
assertOutOfRange("1000 exabytes")
assertOutOfRange("10000000 petabytes")
}
} }

View File

@ -1 +1 @@
sbt.version=0.13.1 sbt.version=0.13.7