Convert numbers to ConfigInt when possible, then ConfigLong, and ConfigDouble only when required

Had to overhaul and clean up equality of ConfigNumber as part of this.
This commit is contained in:
Havoc Pennington 2011-11-16 10:05:44 -05:00
parent 054e27ccd0
commit 61b1281e90
8 changed files with 117 additions and 58 deletions

View File

@ -5,7 +5,7 @@ import com.typesafe.config.ConfigResolveOptions;
import com.typesafe.config.ConfigValue;
/**
*
*
* Trying very hard to avoid a parent reference in config values; when you have
* a tree like this, the availability of parent() tends to result in a lot of
* improperly-factored and non-modular code. Please don't add parent().
@ -121,4 +121,22 @@ abstract class AbstractConfigValue implements ConfigValue {
String transformToString() {
return null;
}
static ConfigNumber newNumber(ConfigOrigin origin, long number,
String originalText) {
if (number <= Integer.MAX_VALUE && number >= Integer.MIN_VALUE)
return new ConfigInt(origin, (int) number, originalText);
else
return new ConfigLong(origin, number, originalText);
}
static ConfigNumber newNumber(ConfigOrigin origin, double number,
String originalText) {
long asLong = (long) number;
if (asLong == number) {
return newNumber(origin, asLong, originalText);
} else {
return new ConfigDouble(origin, number, originalText);
}
}
}

View File

@ -30,4 +30,14 @@ final class ConfigDouble extends ConfigNumber {
else
return s;
}
@Override
protected long longValue() {
return (long) value;
}
@Override
protected double doubleValue() {
return value;
}
}

View File

@ -22,30 +22,6 @@ final class ConfigInt extends ConfigNumber {
return value;
}
@Override
protected boolean canEqual(Object other) {
return other instanceof ConfigInt || other instanceof ConfigLong;
}
@Override
public boolean equals(Object other) {
// note that "origin" is deliberately NOT part of equality
if (other instanceof ConfigInt) {
return this.value == ((ConfigInt) other).value;
} else if (other instanceof ConfigLong) {
Long l = ((ConfigLong) other).unwrapped();
return l.intValue() == l && this.value == l.intValue();
} else {
return false;
}
}
@Override
public int hashCode() {
// note that "origin" is deliberately NOT part of equality
return value;
}
@Override
String transformToString() {
String s = super.transformToString();
@ -54,4 +30,14 @@ final class ConfigInt extends ConfigNumber {
else
return s;
}
@Override
protected long longValue() {
return value;
}
@Override
protected double doubleValue() {
return value;
}
}

View File

@ -22,35 +22,6 @@ final class ConfigLong extends ConfigNumber {
return value;
}
@Override
protected boolean canEqual(Object other) {
return other instanceof ConfigInt || other instanceof ConfigLong;
}
@Override
public boolean equals(Object other) {
// note that "origin" is deliberately NOT part of equality
if (other instanceof ConfigLong) {
return this.value == ((ConfigLong) other).value;
} else if (other instanceof ConfigInt) {
Long l = this.unwrapped();
return l.intValue() == l
&& ((ConfigInt) other).unwrapped() == l.intValue();
} else {
return false;
}
}
@Override
public int hashCode() {
// note that "origin" is deliberately NOT part of equality
if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE)
return unwrapped().intValue(); // match the ConfigInt hashCode for
// any valid Integer
else
return unwrapped().hashCode(); // use Long.hashCode()
}
@Override
String transformToString() {
String s = super.transformToString();
@ -59,4 +30,14 @@ final class ConfigLong extends ConfigNumber {
else
return s;
}
@Override
protected long longValue() {
return value;
}
@Override
protected double doubleValue() {
return value;
}
}

View File

@ -18,4 +18,48 @@ abstract class ConfigNumber extends AbstractConfigValue {
String transformToString() {
return originalText;
}
protected abstract long longValue();
protected abstract double doubleValue();
private boolean isWhole() {
long asLong = longValue();
return asLong == doubleValue();
}
@Override
protected boolean canEqual(Object other) {
return other instanceof ConfigNumber;
}
@Override
public boolean equals(Object other) {
// note that "origin" is deliberately NOT part of equality
if (canEqual(other)) {
ConfigNumber n = (ConfigNumber) other;
if (isWhole()) {
return n.isWhole() && this.longValue() == n.longValue();
} else {
return (!n.isWhole()) && this.doubleValue() == n.doubleValue();
}
} else {
return false;
}
}
@Override
public int hashCode() {
// note that "origin" is deliberately NOT part of equality
// this matches what standard Long.hashCode and Double.hashCode
// do, though I don't think it really matters.
long asLong;
if (isWhole()) {
asLong = longValue();
} else {
asLong = Double.doubleToLongBits(doubleValue());
}
return (int) (asLong ^ (asLong >>> 32));
}
}

View File

@ -265,16 +265,19 @@ final class Tokens {
}
static Token newInt(ConfigOrigin origin, int value, String originalText) {
return newValue(new ConfigInt(origin, value, originalText));
return newValue(AbstractConfigValue.newNumber(origin, value,
originalText));
}
static Token newDouble(ConfigOrigin origin, double value,
String originalText) {
return newValue(new ConfigDouble(origin, value, originalText));
return newValue(AbstractConfigValue.newNumber(origin, value,
originalText));
}
static Token newLong(ConfigOrigin origin, long value, String originalText) {
return newValue(new ConfigLong(origin, value, originalText));
return newValue(AbstractConfigValue.newNumber(origin, value,
originalText));
}
static Token newNull(ConfigOrigin origin) {

View File

@ -404,7 +404,7 @@ class ConfigTest extends TestUtils {
assertEquals(stringValue("abcd"), conf.getValue("strings.abcd"))
// get stuff with getAny
assertEquals(42L, conf.getAnyRef("ints.fortyTwo"))
assertEquals(42, conf.getAnyRef("ints.fortyTwo"))
assertEquals("abcd", conf.getAnyRef("strings.abcd"))
assertEquals(false, conf.getAnyRef("booleans.falseAgain"))

View File

@ -405,4 +405,21 @@ class ConfigValueTest extends TestUtils {
}
}
@Test
def newNumberWorks() {
def nL(v: Long) = AbstractConfigValue.newNumber(fakeOrigin(), v, null)
def nD(v: Double) = AbstractConfigValue.newNumber(fakeOrigin(), v, null)
// the general idea is that the destination type should depend
// only on the actual numeric value, not on the type of the source
// value.
assertEquals(3.14, nD(3.14).unwrapped())
assertEquals(1, nL(1).unwrapped())
assertEquals(1, nD(1.0).unwrapped())
assertEquals(Int.MaxValue + 1L, nL(Int.MaxValue + 1L).unwrapped())
assertEquals(Int.MinValue - 1L, nL(Int.MinValue - 1L).unwrapped())
assertEquals(Int.MaxValue + 1L, nD(Int.MaxValue + 1.0).unwrapped())
assertEquals(Int.MinValue - 1L, nD(Int.MinValue - 1.0).unwrapped())
}
}