diff --git a/src/main/java/com/typesafe/config/impl/ConfigDouble.java b/src/main/java/com/typesafe/config/impl/ConfigDouble.java index 105811f6..db810177 100644 --- a/src/main/java/com/typesafe/config/impl/ConfigDouble.java +++ b/src/main/java/com/typesafe/config/impl/ConfigDouble.java @@ -3,12 +3,12 @@ package com.typesafe.config.impl; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigValueType; -final class ConfigDouble extends AbstractConfigValue { +final class ConfigDouble extends ConfigNumber { final private double value; - ConfigDouble(ConfigOrigin origin, double value) { - super(origin); + ConfigDouble(ConfigOrigin origin, double value, String originalText) { + super(origin, originalText); this.value = value; } @@ -24,6 +24,10 @@ final class ConfigDouble extends AbstractConfigValue { @Override String transformToString() { - return Double.toString(value); + String s = super.transformToString(); + if (s == null) + return Double.toString(value); + else + return s; } } diff --git a/src/main/java/com/typesafe/config/impl/ConfigInt.java b/src/main/java/com/typesafe/config/impl/ConfigInt.java index 63699363..ce505950 100644 --- a/src/main/java/com/typesafe/config/impl/ConfigInt.java +++ b/src/main/java/com/typesafe/config/impl/ConfigInt.java @@ -3,12 +3,12 @@ package com.typesafe.config.impl; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigValueType; -final class ConfigInt extends AbstractConfigValue { +final class ConfigInt extends ConfigNumber { final private int value; - ConfigInt(ConfigOrigin origin, int value) { - super(origin); + ConfigInt(ConfigOrigin origin, int value, String originalText) { + super(origin, originalText); this.value = value; } @@ -48,6 +48,10 @@ final class ConfigInt extends AbstractConfigValue { @Override String transformToString() { - return Integer.toString(value); + String s = super.transformToString(); + if (s == null) + return Integer.toString(value); + else + return s; } } diff --git a/src/main/java/com/typesafe/config/impl/ConfigLong.java b/src/main/java/com/typesafe/config/impl/ConfigLong.java index 593b9b60..b3a82c81 100644 --- a/src/main/java/com/typesafe/config/impl/ConfigLong.java +++ b/src/main/java/com/typesafe/config/impl/ConfigLong.java @@ -3,12 +3,12 @@ package com.typesafe.config.impl; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigValueType; -final class ConfigLong extends AbstractConfigValue { +final class ConfigLong extends ConfigNumber { final private long value; - ConfigLong(ConfigOrigin origin, long value) { - super(origin); + ConfigLong(ConfigOrigin origin, long value, String originalText) { + super(origin, originalText); this.value = value; } @@ -53,6 +53,10 @@ final class ConfigLong extends AbstractConfigValue { @Override String transformToString() { - return Long.toString(value); + String s = super.transformToString(); + if (s == null) + return Long.toString(value); + else + return s; } } diff --git a/src/main/java/com/typesafe/config/impl/ConfigNumber.java b/src/main/java/com/typesafe/config/impl/ConfigNumber.java new file mode 100644 index 00000000..f150245f --- /dev/null +++ b/src/main/java/com/typesafe/config/impl/ConfigNumber.java @@ -0,0 +1,21 @@ +package com.typesafe.config.impl; + +import com.typesafe.config.ConfigOrigin; + +abstract class ConfigNumber extends AbstractConfigValue { + // This is so when we concatenate a number into a string (say it appears in + // a sentence) we always have it exactly as the person typed it into the + // config file. It's purely cosmetic; equals/hashCode don't consider this + // for example. + final private String originalText; + + protected ConfigNumber(ConfigOrigin origin, String originalText) { + super(origin); + this.originalText = originalText; + } + + @Override + String transformToString() { + return originalText; + } +} diff --git a/src/main/java/com/typesafe/config/impl/DefaultTransformer.java b/src/main/java/com/typesafe/config/impl/DefaultTransformer.java index c7bc1815..db04f6e4 100644 --- a/src/main/java/com/typesafe/config/impl/DefaultTransformer.java +++ b/src/main/java/com/typesafe/config/impl/DefaultTransformer.java @@ -16,13 +16,13 @@ final class DefaultTransformer implements ConfigTransformer { case NUMBER: try { Long v = Long.parseLong(s); - return new ConfigLong(value.origin(), v); + return new ConfigLong(value.origin(), v, s); } catch (NumberFormatException e) { // try Double } try { Double v = Double.parseDouble(s); - return new ConfigDouble(value.origin(), v); + return new ConfigDouble(value.origin(), v, s); } catch (NumberFormatException e) { // oh well. } diff --git a/src/main/java/com/typesafe/config/impl/Tokenizer.java b/src/main/java/com/typesafe/config/impl/Tokenizer.java index d7895090..b0b28c7e 100644 --- a/src/main/java/com/typesafe/config/impl/Tokenizer.java +++ b/src/main/java/com/typesafe/config/impl/Tokenizer.java @@ -271,11 +271,11 @@ final class Tokenizer { try { if (containedDecimalOrE) { // force floating point representation - return Tokens - .newDouble(lineOrigin(), Double.parseDouble(s)); + return Tokens.newDouble(lineOrigin(), + Double.parseDouble(s), s); } else { // this should throw if the integer is too large for Long - return Tokens.newLong(lineOrigin(), Long.parseLong(s)); + return Tokens.newLong(lineOrigin(), Long.parseLong(s), s); } } catch (NumberFormatException e) { throw parseError("Invalid number: '" + s diff --git a/src/main/java/com/typesafe/config/impl/Tokens.java b/src/main/java/com/typesafe/config/impl/Tokens.java index a2315cd2..a478f9f6 100644 --- a/src/main/java/com/typesafe/config/impl/Tokens.java +++ b/src/main/java/com/typesafe/config/impl/Tokens.java @@ -264,16 +264,17 @@ final class Tokens { return newValue(new ConfigString(origin, value)); } - static Token newInt(ConfigOrigin origin, int value) { - return newValue(new ConfigInt(origin, value)); + static Token newInt(ConfigOrigin origin, int value, String originalText) { + return newValue(new ConfigInt(origin, value, originalText)); } - static Token newDouble(ConfigOrigin origin, double value) { - return newValue(new ConfigDouble(origin, value)); + static Token newDouble(ConfigOrigin origin, double value, + String originalText) { + return newValue(new ConfigDouble(origin, value, originalText)); } - static Token newLong(ConfigOrigin origin, long value) { - return newValue(new ConfigLong(origin, value)); + static Token newLong(ConfigOrigin origin, long value, String originalText) { + return newValue(new ConfigLong(origin, value, originalText)); } static Token newNull(ConfigOrigin origin) { diff --git a/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala b/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala index fd347705..82781f36 100644 --- a/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala +++ b/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala @@ -14,9 +14,9 @@ class ConfigValueTest extends TestUtils { @Test def configIntEquality() { - val a = new ConfigInt(fakeOrigin(), 42) - val sameAsA = new ConfigInt(fakeOrigin(), 42) - val b = new ConfigInt(fakeOrigin(), 43) + val a = intValue(42) + val sameAsA = intValue(42) + val b = intValue(43) checkEqualObjects(a, a) checkEqualObjects(a, sameAsA) @@ -25,9 +25,9 @@ class ConfigValueTest extends TestUtils { @Test def configLongEquality() { - val a = new ConfigLong(fakeOrigin(), Integer.MAX_VALUE + 42L) - val sameAsA = new ConfigLong(fakeOrigin(), Integer.MAX_VALUE + 42L) - val b = new ConfigLong(fakeOrigin(), Integer.MAX_VALUE + 43L) + val a = longValue(Integer.MAX_VALUE + 42L) + val sameAsA = longValue(Integer.MAX_VALUE + 42L) + val b = longValue(Integer.MAX_VALUE + 43L) checkEqualObjects(a, a) checkEqualObjects(a, sameAsA) @@ -36,21 +36,21 @@ class ConfigValueTest extends TestUtils { @Test def configIntAndLongEquality() { - val longValue = new ConfigLong(fakeOrigin(), 42L) - val intValue = new ConfigLong(fakeOrigin(), 42) - val longValueB = new ConfigLong(fakeOrigin(), 43L) - val intValueB = new ConfigLong(fakeOrigin(), 43) + val longVal = longValue(42L) + val intValue = longValue(42) + val longValueB = longValue(43L) + val intValueB = longValue(43) - checkEqualObjects(intValue, longValue) + checkEqualObjects(intValue, longVal) checkEqualObjects(intValueB, longValueB) checkNotEqualObjects(intValue, longValueB) - checkNotEqualObjects(intValueB, longValue) + checkNotEqualObjects(intValueB, longVal) } private def configMap(pairs: (String, Int)*): java.util.Map[String, AbstractConfigValue] = { val m = new java.util.HashMap[String, AbstractConfigValue]() for (p <- pairs) { - m.put(p._1, new ConfigInt(fakeOrigin(), p._2)) + m.put(p._1, intValue(p._2)) } m } @@ -309,4 +309,25 @@ class ConfigValueTest extends TestUtils { unresolved { dmo.values() } unresolved { dmo.getInt("foo") } } + + @Test + def roundTripNumbersThroughString() { + // formats rounded off with E notation + val a = "132454454354353245.3254652656454808909932874873298473298472" + // formats as 100000.0 + val b = "1e6" + // formats as 5.0E-5 + val c = "0.00005" + // formats as 1E100 (capital E) + val d = "1e100" + + val obj = parseObject("{ a : " + a + ", b : " + b + ", c : " + c + ", d : " + d + "}") + assertEquals(Seq(a, b, c, d), + Seq("a", "b", "c", "d") map { obj.getString(_) }) + + // make sure it still works if we're doing concatenation + val obj2 = parseObject("{ a : xx " + a + " yy, b : xx " + b + " yy, c : xx " + c + " yy, d : xx " + d + " yy}") + assertEquals(Seq(a, b, c, d) map { "xx " + _ + " yy" }, + Seq("a", "b", "c", "d") map { obj2.getString(_) }) + } } diff --git a/src/test/scala/com/typesafe/config/impl/JsonTest.scala b/src/test/scala/com/typesafe/config/impl/JsonTest.scala index b013aaf1..c85d22ee 100644 --- a/src/test/scala/com/typesafe/config/impl/JsonTest.scala +++ b/src/test/scala/com/typesafe/config/impl/JsonTest.scala @@ -55,11 +55,11 @@ class JsonTest extends TestUtils { case lift.JField(name, value) => throw new IllegalStateException("either JField was a toplevel from lift-json or this function is buggy") case lift.JInt(i) => - if (i.isValidInt) new ConfigInt(fakeOrigin(), i.intValue) else new ConfigLong(fakeOrigin(), i.longValue) + if (i.isValidInt) intValue(i.intValue) else longValue(i.longValue) case lift.JBool(b) => new ConfigBoolean(fakeOrigin(), b) case lift.JDouble(d) => - new ConfigDouble(fakeOrigin(), d) + doubleValue(d) case lift.JString(s) => new ConfigString(fakeOrigin(), s) case lift.JNull => diff --git a/src/test/scala/com/typesafe/config/impl/TestUtils.scala b/src/test/scala/com/typesafe/config/impl/TestUtils.scala index 1d63221a..ad5d8ce4 100644 --- a/src/test/scala/com/typesafe/config/impl/TestUtils.scala +++ b/src/test/scala/com/typesafe/config/impl/TestUtils.scala @@ -311,12 +311,12 @@ abstract trait TestUtils { } } - protected def intValue(i: Int) = new ConfigInt(fakeOrigin(), i) - protected def longValue(l: Long) = new ConfigLong(fakeOrigin(), l) + protected def intValue(i: Int) = new ConfigInt(fakeOrigin(), i, null) + protected def longValue(l: Long) = new ConfigLong(fakeOrigin(), l, null) protected def boolValue(b: Boolean) = new ConfigBoolean(fakeOrigin(), b) protected def nullValue() = new ConfigNull(fakeOrigin()) protected def stringValue(s: String) = new ConfigString(fakeOrigin(), s) - protected def doubleValue(d: Double) = new ConfigDouble(fakeOrigin(), d) + protected def doubleValue(d: Double) = new ConfigDouble(fakeOrigin(), d, null) protected def parseObject(s: String) = { Parser.parse(SyntaxFlavor.CONF, new SimpleConfigOrigin("test string"), s, includer()).asInstanceOf[AbstractConfigObject] @@ -338,9 +338,9 @@ abstract trait TestUtils { def tokenNull = Tokens.newNull(fakeOrigin()) def tokenUnquoted(s: String) = Tokens.newUnquotedText(fakeOrigin(), s) def tokenString(s: String) = Tokens.newString(fakeOrigin(), s) - def tokenDouble(d: Double) = Tokens.newDouble(fakeOrigin(), d) - def tokenInt(i: Int) = Tokens.newInt(fakeOrigin(), i) - def tokenLong(l: Long) = Tokens.newLong(fakeOrigin(), l) + def tokenDouble(d: Double) = Tokens.newDouble(fakeOrigin(), d, null) + def tokenInt(i: Int) = Tokens.newInt(fakeOrigin(), i, null) + def tokenLong(l: Long) = Tokens.newLong(fakeOrigin(), l, null) def tokenSubstitution(expression: Token*) = { val l = new java.util.ArrayList[Token]