mirror of
https://github.com/lightbend/config.git
synced 2025-03-23 07:40:25 +08:00
Save the original text of parsed numbers
Value concatenation is supposed to provide an illusion that you can just type long unquoted sentences, but really they are a series of tokens. Be sure we don't weirdly reformat any numbers that happen to appear in unquoted text.
This commit is contained in:
parent
52f34925dc
commit
31a8e9ee57
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
21
src/main/java/com/typesafe/config/impl/ConfigNumber.java
Normal file
21
src/main/java/com/typesafe/config/impl/ConfigNumber.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -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.
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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(_) })
|
||||
}
|
||||
}
|
||||
|
@ -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 =>
|
||||
|
@ -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]
|
||||
|
Loading…
Reference in New Issue
Block a user