diff --git a/config/src/main/java/com/typesafe/config/Config.java b/config/src/main/java/com/typesafe/config/Config.java index 2d795243..9526fa1f 100644 --- a/config/src/main/java/com/typesafe/config/Config.java +++ b/config/src/main/java/com/typesafe/config/Config.java @@ -585,6 +585,28 @@ public interface Config extends ConfigMergeable { */ Long getBytes(String path); + /** + * Gets a value as an amount of memory (parses special strings like "128M"). If + * the value is already a number, then it's left alone; if it's a string, + * it's parsed understanding unit suffixes such as "128K", as documented in + * the <a + * href="https://github.com/typesafehub/config/blob/master/HOCON.md">the + * spec</a>. + * + * @since 1.3.0 + * + * @param path + * path expression + * @return the value at the requested path, in bytes + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to Long or String + * @throws ConfigException.BadValue + * if value cannot be parsed as a size in bytes + */ + ConfigMemorySize getMemorySize(String path); + /** * Get value as a duration in milliseconds. If the value is already a * number, then it's left alone; if it's a string, it's parsed understanding @@ -686,6 +708,17 @@ public interface Config extends ConfigMergeable { List<Long> getBytesList(String path); + /** + * Gets a list, converting each value in the list to a memory size, using the + * same rules as {@link #getMemorySize(String)}. + * + * @since 1.3.0 + * @param path + * a path expression + * @return list of memory sizes + */ + List<ConfigMemorySize> getMemorySizeList(String path); + /** * @deprecated As of release 1.1, replaced by {@link #getDurationList(String, TimeUnit)} */ diff --git a/config/src/main/java/com/typesafe/config/ConfigMemorySize.java b/config/src/main/java/com/typesafe/config/ConfigMemorySize.java new file mode 100644 index 00000000..cc9aed47 --- /dev/null +++ b/config/src/main/java/com/typesafe/config/ConfigMemorySize.java @@ -0,0 +1,60 @@ +/** + * Copyright (C) 2015 Typesafe Inc. <http://typesafe.com> + */ +package com.typesafe.config; + +/** + * An immutable class representing an amount of memory. Use + * static factory methods such as {@link + * ConfigMemorySize#ofBytes(long)} to create instances. + * + * @since 1.3.0 + */ +public final class ConfigMemorySize { + private final long bytes; + + private ConfigMemorySize(long bytes) { + if (bytes < 0) + throw new IllegalArgumentException("Attempt to construct ConfigMemorySize with negative number: " + bytes); + this.bytes = bytes; + } + + /** + * Constructs a ConfigMemorySize representing the given + * number of bytes. + * @since 1.3.0 + */ + public static ConfigMemorySize ofBytes(long bytes) { + return new ConfigMemorySize(bytes); + } + + /** + * Gets the size in bytes. + * @since 1.3.0 + */ + public long toBytes() { + return bytes; + } + + @Override + public String toString() { + return "ConfigMemorySize(" + bytes + ")"; + } + + @Override + public boolean equals(Object other) { + if (other instanceof ConfigMemorySize) { + return ((ConfigMemorySize)other).bytes == this.bytes; + } else { + return false; + } + } + + @Override + public int hashCode() { + // in Java 8 this can become Long.hashCode(bytes) + return Long.valueOf(bytes).hashCode(); + } + +} + diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java b/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java index 332a754d..1c9cfc85 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java @@ -18,6 +18,7 @@ import java.util.concurrent.Callable; import com.typesafe.config.Config; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigIncluder; +import com.typesafe.config.ConfigMemorySize; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigParseOptions; @@ -273,6 +274,8 @@ public class ConfigImpl { } return new SimpleConfigList(origin, values); + } else if (object instanceof ConfigMemorySize) { + return new ConfigLong(origin, ((ConfigMemorySize) object).toBytes(), null); } else { throw new ConfigException.BugOrBroken( "bug in method caller: not valid to create ConfigValue from: " diff --git a/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java b/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java index fb6c7053..6b8f8f9f 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java @@ -19,6 +19,7 @@ import java.util.concurrent.TimeUnit; import com.typesafe.config.Config; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigList; +import com.typesafe.config.ConfigMemorySize; import com.typesafe.config.ConfigMergeable; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigOrigin; @@ -245,6 +246,11 @@ final class SimpleConfig implements Config, MergeableValue, Serializable { return size; } + @Override + public ConfigMemorySize getMemorySize(String path) { + return ConfigMemorySize.ofBytes(getBytes(path)); + } + @Deprecated @Override public Long getMilliseconds(String path) { @@ -396,6 +402,16 @@ final class SimpleConfig implements Config, MergeableValue, Serializable { return l; } + @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)); + } + return builder; + } + @Override public List<Long> getDurationList(String path, TimeUnit unit) { List<Long> l = new ArrayList<Long>(); diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigMemorySizeTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigMemorySizeTest.scala new file mode 100644 index 00000000..378c9476 --- /dev/null +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigMemorySizeTest.scala @@ -0,0 +1,25 @@ +/** + * Copyright (C) 2015 Typesafe Inc. <http://typesafe.com> + */ +package com.typesafe.config.impl + +import org.junit.Assert._ +import org.junit._ +import com.typesafe.config.ConfigMemorySize + +class ConfigMemorySizeTest extends TestUtils { + + @Test + def testEquals() { + assertTrue("Equal ConfigMemorySize are equal", + ConfigMemorySize.ofBytes(10).equals(ConfigMemorySize.ofBytes(10))) + assertTrue("Different ConfigMemorySize are not equal", + !ConfigMemorySize.ofBytes(10).equals(ConfigMemorySize.ofBytes(11))) + } + + @Test + def testToUnits() { + val kilobyte = ConfigMemorySize.ofBytes(1024) + assertEquals(1024, kilobyte.toBytes) + } +} diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala index 634c0d69..3d1f0017 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala @@ -645,6 +645,10 @@ class ConfigTest extends TestUtils { conf.getBytes("nulls.null") } + intercept[ConfigException.Null] { + conf.getMemorySize("nulls.null") + } + // should throw WrongType if key is wrong type and not convertible intercept[ConfigException.WrongType] { conf.getInt("booleans.trueAgain") @@ -674,6 +678,10 @@ class ConfigTest extends TestUtils { conf.getBytes("ints") } + intercept[ConfigException.WrongType] { + conf.getMemorySize("ints") + } + // should throw BadPath on various bad paths intercept[ConfigException.BadPath] { conf.getInt(".bad") @@ -700,6 +708,10 @@ class ConfigTest extends TestUtils { intercept[ConfigException.BadValue] { conf.getBytes("strings.a") } + + intercept[ConfigException.BadValue] { + conf.getMemorySize("strings.a") + } } @Test @@ -787,6 +799,13 @@ class ConfigTest extends TestUtils { assertEquals(Seq(1024 * 1024L, 1024 * 1024L, 1024L * 1024L), conf.getBytesList("memsizes.megsList").asScala) assertEquals(512 * 1024L, conf.getBytes("memsizes.halfMeg")) + + // should get size as a ConfigMemorySize + assertEquals(1024 * 1024L, conf.getMemorySize("memsizes.meg").toBytes) + assertEquals(1024 * 1024L, conf.getMemorySize("memsizes.megAsNumber").toBytes) + assertEquals(Seq(1024 * 1024L, 1024 * 1024L, 1024L * 1024L), + conf.getMemorySizeList("memsizes.megsList").asScala.map(_.toBytes)) + assertEquals(512 * 1024L, conf.getMemorySize("memsizes.halfMeg").toBytes) } @Test diff --git a/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala b/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala index 7c9cd48e..ead13c18 100644 --- a/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala @@ -169,6 +169,12 @@ class PublicApiTest extends TestUtils { assertEquals("foo", ConfigValueFactory.fromIterable(treeSet, "foo").origin().description()) } + @Test + def fromConfigMemorySize() { + testFromValue(longValue(1024), ConfigMemorySize.ofBytes(1024)); + testFromValue(longValue(512), ConfigMemorySize.ofBytes(512)); + } + @Test def roundTripUnwrap() { val conf = ConfigFactory.load("test01")