Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for huge memory units #663

Merged
merged 8 commits into from
Feb 17, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 30 additions & 7 deletions config/src/main/java/com/typesafe/config/ConfigMemorySize.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@
*/
package com.typesafe.config;

import java.math.BigInteger;

/**
* An immutable class representing an amount of memory. Use
* static factory methods such as {@link
* ConfigMemorySize#ofBytes(long)} to create instances.
* ConfigMemorySize#ofBytes(BigInteger)} to create instances.
*
* @since 1.3.0
*/
public final class ConfigMemorySize {
private final long bytes;

private ConfigMemorySize(long bytes) {
if (bytes < 0)
private BigInteger bytes;

private ConfigMemorySize(BigInteger bytes) {
if (bytes.compareTo(BigInteger.ZERO) < 0)
throw new IllegalArgumentException("Attempt to construct ConfigMemorySize with negative number: " + bytes);
this.bytes = bytes;
}
Expand All @@ -26,16 +29,36 @@ private ConfigMemorySize(long bytes) {
* @param bytes a number of bytes
* @return an instance representing the number of bytes
*/
public static ConfigMemorySize ofBytes(long bytes) {
public static ConfigMemorySize ofBytes(BigInteger bytes) {
return new ConfigMemorySize(bytes);
}

/**
* Constructs a ConfigMemorySize representing the given
* number of bytes.
* @param bytes a number of bytes
* @return an instance representing the number of bytes
*/
public static ConfigMemorySize ofBytes(long bytes) {
return new ConfigMemorySize(BigInteger.valueOf(bytes));
}

/**
* Gets the size in bytes.
*
* @deprecated use {@link #getBytes()} to handle to bytes conversion of huge memory units.
mpryahin marked this conversation as resolved.
Show resolved Hide resolved
* @since 1.3.0
* @return how many bytes
*/
public long toBytes() {
return bytes.longValueExact();
}

/**
* Gets the size in bytes.
* @return how many bytes
*/
public BigInteger getBytes() {
return bytes;
}

Expand All @@ -47,7 +70,7 @@ public String toString() {
@Override
public boolean equals(Object other) {
if (other instanceof ConfigMemorySize) {
return ((ConfigMemorySize)other).bytes == this.bytes;
return ((ConfigMemorySize)other).bytes.equals(this.bytes);
} else {
return false;
}
Expand All @@ -56,7 +79,7 @@ public boolean equals(Object other) {
@Override
public int hashCode() {
// in Java 8 this can become Long.hashCode(bytes)
return Long.valueOf(bytes).hashCode();
return bytes.hashCode();
}

}
Expand Down
73 changes: 66 additions & 7 deletions config/src/main/java/com/typesafe/config/impl/SimpleConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,15 @@ public Long getBytes(String path) {

@Override
public ConfigMemorySize getMemorySize(String path) {
return ConfigMemorySize.ofBytes(getBytes(path));
BigInteger size;
try {
size = BigInteger.valueOf(getLong(path));
} catch (ConfigException.WrongType e) {
ConfigValue v = find(path, ConfigValueType.STRING);
size = parseBytesAsBigInteger((String) v.unwrapped(),
v.origin(), path);
}
return ConfigMemorySize.ofBytes(size);
havocp marked this conversation as resolved.
Show resolved Hide resolved
}

@Deprecated
Expand Down Expand Up @@ -502,12 +510,22 @@ public List<Long> getBytesList(String path) {

@Override
public List<ConfigMemorySize> getMemorySizeList(String path) {
List<Long> list = getBytesList(path);
List<ConfigMemorySize> builder = new ArrayList<ConfigMemorySize>();
for (Long v : list) {
builder.add(ConfigMemorySize.ofBytes(v));
List<ConfigMemorySize> l = new ArrayList<>();
List<? extends ConfigValue> list = getList(path);
for (ConfigValue v : list) {
if (v.valueType() == ConfigValueType.NUMBER) {
l.add(ConfigMemorySize.ofBytes(((Number) v.unwrapped()).longValue()));
} else if (v.valueType() == ConfigValueType.STRING) {
String s = (String) v.unwrapped();
BigInteger n = parseBytesAsBigInteger(s, v.origin(), path);
l.add(ConfigMemorySize.ofBytes(n));
} else {
throw new ConfigException.WrongType(v.origin(), path,
"memory size string or number of bytes", v.valueType()
.name());
}
}
return builder;
return l;
havocp marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
Expand Down Expand Up @@ -884,7 +902,48 @@ public static long parseBytes(String input, ConfigOrigin originForException,
return result.longValue();
else
throw new ConfigException.BadValue(originForException, pathForException,
"size-in-bytes value is out of range for a 64-bit long: '" + input + "'");
"size-in-bytes value is out of range for a 64-bit long: '" + input + "'");
mpryahin marked this conversation as resolved.
Show resolved Hide resolved
} catch (NumberFormatException e) {
throw new ConfigException.BadValue(originForException, pathForException,
"Could not parse size-in-bytes number '" + numberString + "'");
}
}


public static BigInteger parseBytesAsBigInteger(String input, ConfigOrigin originForException,
mpryahin marked this conversation as resolved.
Show resolved Hide resolved
String pathForException) {
String s = ConfigImplUtil.unicodeTrim(input);
String unitString = getUnits(s);
String numberString = ConfigImplUtil.unicodeTrim(s.substring(0,
s.length() - unitString.length()));

// this would be caught later anyway, but the error message
// is more helpful if we check it here.
if (numberString.length() == 0) {
throw new ConfigException.BadValue(originForException,
pathForException, "No number in size-in-bytes value '"
+ input + "'");
}

MemoryUnit units = MemoryUnit.parseUnit(unitString);

if (units == null) {
throw new ConfigException.BadValue(originForException, pathForException,
"Could not parse size-in-bytes unit '" + unitString
+ "' (try k, K, kB, KiB, kilobytes, kibibytes)");
}

try {
BigInteger result;
// if the string is purely digits, parse as an integer to avoid
// possible precision loss; otherwise as a double.
if (numberString.matches("[0-9]+")) {
result = units.bytes.multiply(new BigInteger(numberString));
} else {
BigDecimal resultDecimal = (new BigDecimal(units.bytes)).multiply(new BigDecimal(numberString));
result = resultDecimal.toBigInteger();
}
return result;
} catch (NumberFormatException e) {
throw new ConfigException.BadValue(originForException, pathForException,
"Could not parse size-in-bytes number '" + numberString + "'");
Expand Down
2 changes: 2 additions & 0 deletions config/src/test/resources/test01.conf
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@
"megsList" : [1M, 1024K, 1048576],
"megAsNumber" : 1048576,
"halfMeg" : 0.5M
"yottabyte" : 1YB
"yottabyteList" : [1YB, 0.5YB]
},

"system" : {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
*/
package com.typesafe.config.impl

import java.math.BigInteger

import org.junit.Assert._
import org.junit._
import com.typesafe.config.ConfigMemorySize
Expand All @@ -22,4 +24,10 @@ class ConfigMemorySizeTest extends TestUtils {
val kilobyte = ConfigMemorySize.ofBytes(1024)
assertEquals(1024, kilobyte.toBytes)
}

@Test
def testGetBytes() {
val yottabyte = ConfigMemorySize.ofBytes(new BigInteger("1000000000000000000000000"))
assertEquals(new BigInteger("1000000000000000000000000"), yottabyte.getBytes)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
package com.typesafe.config.impl

import java.math.BigInteger
import java.time.temporal.{ ChronoUnit, TemporalUnit }

import org.junit.Assert._
Expand Down Expand Up @@ -831,6 +832,10 @@ class ConfigTest extends TestUtils {
assertEquals(Seq(1024 * 1024L, 1024 * 1024L, 1024L * 1024L),
conf.getMemorySizeList("memsizes.megsList").asScala.map(_.toBytes))
assertEquals(512 * 1024L, conf.getMemorySize("memsizes.halfMeg").toBytes)

assertEquals(new BigInteger("1000000000000000000000000"), conf.getMemorySize("memsizes.yottabyte").getBytes)
assertEquals(Seq(new BigInteger("1000000000000000000000000"), new BigInteger("500000000000000000000000")),
conf.getMemorySizeList("memsizes.yottabyteList").asScala.map(_.getBytes))
}

@Test
Expand Down