From 2b8bc459efd8fa29e2f170d97b303dbb1f2cd068 Mon Sep 17 00:00:00 2001 From: Gmugra Date: Thu, 13 May 2021 18:04:12 +0200 Subject: [PATCH] #56 : API: Custom converter ByteSize --- README.md | 48 +++- .../DefaultConvertorValidator.java | 3 + .../core/converter/bytesize/ByteSize.java | 172 +++++++++++++ .../converter/bytesize/ByteSizeStandard.java | 41 ++++ .../core/converter/bytesize/ByteSizeUnit.java | 228 ++++++++++++++++++ .../converter/standard/ByteSizeConverter.java | 53 ++++ .../config/core/util/ApiMessages.java | 2 +- .../config/core/util/ApiMessages.properties | 1 + .../core/converter/bytesize/ByteSizeTest.java | 110 +++++++++ .../standard/ByteSizeConverterTest.java | 56 +++++ 10 files changed, 706 insertions(+), 8 deletions(-) create mode 100644 core/src/main/java/net/cactusthorn/config/core/converter/bytesize/ByteSize.java create mode 100644 core/src/main/java/net/cactusthorn/config/core/converter/bytesize/ByteSizeStandard.java create mode 100644 core/src/main/java/net/cactusthorn/config/core/converter/bytesize/ByteSizeUnit.java create mode 100644 core/src/main/java/net/cactusthorn/config/core/converter/standard/ByteSizeConverter.java create mode 100644 core/src/test/java/net/cactusthorn/config/core/converter/bytesize/ByteSizeTest.java create mode 100644 core/src/test/java/net/cactusthorn/config/core/converter/standard/ByteSizeConverterTest.java diff --git a/README.md b/README.md index 5756238b..3028c10b 100644 --- a/README.md +++ b/README.md @@ -58,18 +58,20 @@ To access properties you need to define a convenient Java interface, e.g. : ```java package my.superapp; -import static net.cactusthorn.config.core.Disable.Feature.* -import net.cactusthorn.config.core.* +import static net.cactusthorn.config.core.Disable.Feature.*; +import net.cactusthorn.config.core.*; +import net.cactusthorn.config.core.converter.*; import java.util.concurrent.TimeUnit; import java.util.Set; import java.util.List; import java.util.Optional; import java.util.UUID; +import java.time.LocalDate; @Config @Prefix("app") -interface MyConfig { +public interface MyConfig { @Default("unknown") String val(); @@ -78,6 +80,8 @@ interface MyConfig { @Disable(PREFIX) Optional> ids(); @Split("[:;]") @Default("DAYS:HOURS") Set units(); + + @ConverterLocalDate({"dd.MM.yyyy", "yyyy-MM-dd"}) LocalDate date(); } ``` - An interface must be annotated with `@Config`. @@ -100,6 +104,7 @@ app.val=ABC app.number=10 ids=f8c3de3d-1fea-4d7c-a8b0-29f63c4c3454,123e4567-e89b-12d3-a456-556642440000 app.units=DAYS:HOURS;MICROSECONDS +app.date=12.11.2005 ``` ### Annotations @@ -179,7 +184,7 @@ The return type of the interface methods must either: 1. e.g. [Integer.valueOf](https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html#valueOf-java.lang.String-) 1. e.g. [UUID.fromString](https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html#fromString-java.lang.String-) 1. If both methods are present then `valueOf` used unless the type is an `enum` in which case `fromString` used. -1. Be `java.net.URL`, `java.net.URI`, `java.time.Instant`, `java.time.Duration`, `java.time.Period`, `java.nio.file.Path` +1. Be `java.net.URL`, `java.net.URI`, `java.time.Instant`, `java.time.Duration`, `java.time.Period`, `java.nio.file.Path`, `net.cactusthorn.config.core.converter.bytesize.ByteSize` 1. Be `List`, `Set` or `SortedSet`, where T satisfies 2, 3 or 4 above. The resulting collection is read-only. 1. Be `Optional`, where T satisfies 2, 3, 4 or 5 above @@ -212,6 +217,35 @@ e.g. `2011-12-03T10:15:30Z` - `m`, `mo`, `month`, `months` - `y`, `year`, `years` +### `net.cactusthorn.config.core.converter.bytesize.ByteSize` format +It based on [OWNER](http://owner.aeonbits.org/docs/type-conversion/) classes to represent data sizes. + +usage: +```java +@Config public interface MyByteSize { + @Default("10 megabytes") + ByteSize size(); +} +``` +The supported unit strings for `ByteSize` are case sensitive and must be lowercase. Exactly these strings are supported: + - `byte`, `bytes`, `b` + - `kilobyte`, `kilobytes`, `k`, `ki`, `kib` + - `kibibyte`, `kibibytes`, `kb` + - `megabyte`, `megabytes`, `m`, `mi`, `mib` + - `mebibyte`, `mebibytes`, `mb` + - `gigabyte`, `gigabytes`, `g`, `gi`, `gib` + - `gibibyte`, `gibibytes`, `gb` + - `terabyte`, `terabytes`, `t`, `ti`, `tib` + - `tebibyte`, `tebibytes`, `tb` + - `petabyte`, `petabytes`, `p`, `pi`, `pib` + - `pebibyte`, `pebibytes`, `pb` + - `exabyte`, `exabytes`, `e`, `ei`, `eib` + - `exbibyte`, `exbibytes`, `eb` + - `zettabyte`, `zettabytes`, `z`, `zi`, `zib` + - `zebibyte`, `zebibytes`, `zb` + - `yottabyte`, `yottabytes`, `y`, `yi`, `yib` + - `yobibyte`, `yobibytes`, `yb` + ### Custom converters If it's need to deal with class which is not supported "by default" (see *Supported method return types*), a custom converter can be implemented and used. ```java @@ -266,9 +300,9 @@ public interface MyConfig { ``` Several such annotation shipped with the library: -`net.cactusthorn.config.core.converter.ConverterLocalDate` -`net.cactusthorn.config.core.converter.ConverterLocalDateTime` -`net.cactusthorn.config.core.converter.ConverterZonedDateTime` +* `net.cactusthorn.config.core.converter.ConverterLocalDate` +* `net.cactusthorn.config.core.converter.ConverterLocalDateTime` +* `net.cactusthorn.config.core.converter.ConverterZonedDateTime` ## Loaders diff --git a/compiler/src/main/java/net/cactusthorn/config/compiler/methodvalidator/DefaultConvertorValidator.java b/compiler/src/main/java/net/cactusthorn/config/compiler/methodvalidator/DefaultConvertorValidator.java index 3b9b13b6..179e7ba9 100644 --- a/compiler/src/main/java/net/cactusthorn/config/compiler/methodvalidator/DefaultConvertorValidator.java +++ b/compiler/src/main/java/net/cactusthorn/config/compiler/methodvalidator/DefaultConvertorValidator.java @@ -39,6 +39,8 @@ import net.cactusthorn.config.compiler.ProcessorException; import net.cactusthorn.config.core.converter.Converter; +import net.cactusthorn.config.core.converter.bytesize.ByteSize; +import net.cactusthorn.config.core.converter.standard.ByteSizeConverter; import net.cactusthorn.config.core.converter.standard.DurationConverter; import net.cactusthorn.config.core.converter.standard.InstantConverter; import net.cactusthorn.config.core.converter.standard.PathConverter; @@ -57,6 +59,7 @@ public class DefaultConvertorValidator extends MethodValidatorAncestor { CONVERTERS.put(Path.class, PathConverter.class.getName()); CONVERTERS.put(Duration.class, DurationConverter.class.getName()); CONVERTERS.put(Period.class, PeriodConverter.class.getName()); + CONVERTERS.put(ByteSize.class, ByteSizeConverter.class.getName()); } private final Map classTypes = new HashMap<>(); diff --git a/core/src/main/java/net/cactusthorn/config/core/converter/bytesize/ByteSize.java b/core/src/main/java/net/cactusthorn/config/core/converter/bytesize/ByteSize.java new file mode 100644 index 00000000..4c3b4030 --- /dev/null +++ b/core/src/main/java/net/cactusthorn/config/core/converter/bytesize/ByteSize.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2012-2016, Luigi R. Viggiano + * All rights reserved. + * + * This software is distributed under the BSD license. + * See the terms of the BSD license in the documentation provided with this software. + */ +package net.cactusthorn.config.core.converter.bytesize; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.util.Objects; + +/** + * A unit of byte size, such as "512 kilobytes". + * + * This class models a two part byte count size, one part being a value and the + * other part being a {@link ByteSizeUnit}. + * + * This class supports converting to another {@link ByteSizeUnit}. + * + * @author Stefan Freyr Stefansson + */ +public class ByteSize { + private final BigDecimal value; + private final ByteSizeUnit unit; + + /** + * Creates a byte size value from two parts, a value and a {@link ByteSizeUnit}. + * + * @param value the value part of this byte size. + * @param unit the unit part of this byte size. + */ + public ByteSize(BigDecimal value, ByteSizeUnit unit) { + this.value = value; + this.unit = unit; + } + + /** + * Creates a byte size value from a long value representing the + * number of bytes. + * + * The unit part of this byte size will be {@link ByteSizeUnit#BYTES}. + * + * @param bytes the number of bytes this {@link ByteSize} instance should + * represent + */ + public ByteSize(long bytes) { + this(bytes, ByteSizeUnit.BYTES); + } + + /** + * Creates a byte size value from a String value and a + * {@link ByteSizeUnit}. + * + * @param value the value part of this byte size + * @param unit the unit part of this byte size + */ + public ByteSize(String value, ByteSizeUnit unit) { + this(new BigDecimal(value), unit); + } + + /** + * Creates a byte size value from a long value and a + * {@link ByteSizeUnit}. + * + * @param value the value part of this byte size + * @param unit the unit part of this byte size + */ + public ByteSize(long value, ByteSizeUnit unit) { + this(BigDecimal.valueOf(value), unit); + } + + /** + * Creates a byte size value from a double value and a + * {@link ByteSizeUnit}. + * + * @param value the value part of this byte size + * @param unit the unit part of this byte size + */ + public ByteSize(double value, ByteSizeUnit unit) { + this(BigDecimal.valueOf(value), unit); + } + + /** + * Returns the number of bytes that this byte size represents after multiplying + * the unit factor with the value. + * + * Since the value part can be a represented by a decimal, there is some + * possibility of a rounding error. Therefore, the result of multiplying the + * value and the unit factor are always rounded towards positive infinity to the + * nearest integer value (see {@link RoundingMode#CEILING}) to make sure that + * this method never gives values that are too small. + * + * @return number of bytes this byte size represents after factoring in the + * unit. + */ + public BigInteger getBytes() { + return value.multiply(unit.getFactor()).setScale(0, RoundingMode.CEILING).toBigIntegerExact(); + } + + /** + * Returns the number of bytes that this byte size represents as a + * long after multiplying the unit factor with the value, throwing + * an exception if the result overflows a long. + * + * @throws ArithmeticException if the result overflows a long + * + * @return the number of bytes that this byte size represents after factoring in + * the unit. + */ + public long getBytesAsLong() { + return getBytes().longValueExact(); + } + + /** + * Returns the number of bytes that this byte size represents as an + * int after multiplying the unit factor with the value, throwing + * an exception if the result overflows an int. + * + * @throws ArithmeticException if the result overflows an int + * + * @return the number of bytes that this byte size represents after factoring in + * the unit. + */ + public int getBytesAsInt() { + return getBytes().intValueExact(); + } + + /** + * Creates a new {@link ByteSize} representing the same byte size but in a + * different unit. + * + * Scale of the value (number of decimal points) is handled automatically but if + * a non-terminating decimal expansion occurs, an {@link ArithmeticException} is + * thrown. + * + * @param unit the unit for the new {@link ByteSize}. + * + * @throws ArithmeticException if a non-terminating decimal expansion occurs + * during calculation. + * + * @return a new {@link ByteSize} instance representing the same byte size as + * this but using the specified unit. + */ + public ByteSize convertTo(ByteSizeUnit byteSizeUnit) { + BigDecimal bytes = this.value.multiply(this.unit.getFactor()).setScale(0, RoundingMode.CEILING); + return new ByteSize(bytes.divide(byteSizeUnit.getFactor()), byteSizeUnit); + } + + @Override public String toString() { + return value.toString() + " " + unit.toStringShortForm(); + } + + @Override public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ByteSize byteSize = (ByteSize) o; + + return getBytes().equals(byteSize.getBytes()); + } + + @Override public int hashCode() { + return Objects.hash(value, unit); + } +} diff --git a/core/src/main/java/net/cactusthorn/config/core/converter/bytesize/ByteSizeStandard.java b/core/src/main/java/net/cactusthorn/config/core/converter/bytesize/ByteSizeStandard.java new file mode 100644 index 00000000..7ee50f62 --- /dev/null +++ b/core/src/main/java/net/cactusthorn/config/core/converter/bytesize/ByteSizeStandard.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2012-2016, Luigi R. Viggiano + * All rights reserved. + * + * This software is distributed under the BSD license. + * See the terms of the BSD license in the documentation provided with this software. + */ +package net.cactusthorn.config.core.converter.bytesize; + +/** + * Represents the possible standards that a {@link ByteSizeUnit} can have. + * Different standards represent different "power of" values for which byte + * sizes are defined in. + * + * @see https://en.wikipedia.org/wiki/Binary_prefix + * + * @author Stefan Freyr Stefansson + */ +public enum ByteSizeStandard { + + /** + * The International System of Units (SI) standard. Base of 1000. + */ + SI(1000), + + /** + * The International Electrotechnical Commission (IEC) standard. Base of 1024. + */ + IEC(1024); + + private final int powerOf; + + ByteSizeStandard(int powerOf) { + this.powerOf = powerOf; + } + + public int powerOf() { + return powerOf; + } +} diff --git a/core/src/main/java/net/cactusthorn/config/core/converter/bytesize/ByteSizeUnit.java b/core/src/main/java/net/cactusthorn/config/core/converter/bytesize/ByteSizeUnit.java new file mode 100644 index 00000000..f6b48249 --- /dev/null +++ b/core/src/main/java/net/cactusthorn/config/core/converter/bytesize/ByteSizeUnit.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2012-2016, Luigi R. Viggiano + * All rights reserved. + * + * This software is distributed under the BSD license. + * See the terms of the BSD license in the documentation provided with this software. + */ + +package net.cactusthorn.config.core.converter.bytesize; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; + +import static net.cactusthorn.config.core.converter.bytesize.ByteSizeStandard.*; + +/** + * Specifies the available byte size units that a {@link ByteSize} can have. + * + * A byte size unit has a {@link ByteSizeStandard} that dictates the base value + * to be raised to a given power as well as the power value that dictates the + * magnitude of the unit. For example, a unit having the SI standard with a + * power value of 4 is known as a terabyte while a unit having the IEC standard + * with a power value of 4 is known as a tebibyte. The former has a factor of + * 1000^4 while the latter has a factor of 1024^4. + * + * @author Stefan Freyr Stefansson + */ +public enum ByteSizeUnit { + /** + * The base unit. Has a factor of 1. + */ + BYTES("", "B", SI, 0), + + /** + * The SI kilobyte. Has a factor of 1000^1. + */ + KILOBYTES("kilo", "KB", SI, 1), + /** + * The IEC kibibyte. Has a factor of 1024^1. + */ + KIBIBYTES("kibi", "KiB", IEC, 1), + /** + * The SI megabyte. Has a factor of 1000^2. + */ + MEGABYTES("mega", "MB", SI, 2), + /** + * The IEC mebibyte. Has a factor of 1024^2. + */ + MEBIBYTES("mebi", "MiB", IEC, 2), + /** + * The SI gigabyte. Has a factor of 1000^3. + */ + GIGABYTES("giga", "GB", SI, 3), + /** + * The IEC gibibyte. Has a factor of 1024^3. + */ + GIBIBYTES("gibi", "GiB", IEC, 3), + /** + * The SI terabyte. Has a factor of 1000^4. + */ + TERABYTES("tera", "TB", SI, 4), + /** + * The IEC tebibyte. Has a factor of 1024^4. + */ + TEBIBYTES("tebi", "TiB", IEC, 4), + /** + * The SI petabyte. Has a factor of 1000^5. + */ + PETABYTES("peta", "PB", SI, 5), + /** + * The IEC pebibyte. Has a factor of 1024^5. + */ + PEBIBYTES("pebi", "PiB", IEC, 5), + /** + * The SI exabyte. Has a factor of 1000^6. + */ + EXABYTES("exa", "EB", SI, 6), + /** + * The IEC exibyte. Has a factor of 1024^6. + */ + EXBIBYTES("exbi", "EiB", IEC, 6), + /** + * The SI zettabyte. Has a factor of 1000^7. + */ + ZETTABYTES("zetta", "ZB", SI, 7), + /** + * The IEC zebibyte. Has a factor of 1024^7. + */ + ZEBIBYTES("zebi", "ZiB", IEC, 7), + /** + * The SI yottabyte. Has a factor of 1000^8. + */ + YOTTABYTES("yotta", "YB", SI, 8), + /** + * The IEC yobibyte. Has a factor of 1024^8. + */ + YOBIBYTES("yobi", "YiB", IEC, 8); + + private final String prefix; + private final String shortLabel; + private final ByteSizeStandard standard; + private final int power; + + ByteSizeUnit(String prefix, String shortLabel, ByteSizeStandard standard, int power) { + this.prefix = prefix; + this.shortLabel = shortLabel; + this.standard = standard; + this.power = power; + } + + private static Map makeUnitsMap() { + Map map = new HashMap(); + for (ByteSizeUnit unit : ByteSizeUnit.values()) { + map.put(unit.prefix + "byte", unit); + map.put(unit.prefix + "bytes", unit); + if (unit.prefix.length() == 0) { + map.put("b", unit); + map.put("", unit); // no unit specified means bytes + } else { + String first = unit.prefix.substring(0, 1); + if (unit.standard == IEC) { + map.put(first, unit); // 512m + map.put(first + "i", unit); // 512mi + map.put(first + "ib", unit); // 512mib + } else { //unit.standard == SI + map.put(first + "b", unit); // 512kb + } + } + } + return map; + } + + private static Map unitsMap = makeUnitsMap(); + + /** + * Parses a string representation of a byte size unit and returns the + * corresponding {@link ByteSizeUnit}. + * + * There is support for various formats. Below is a list describing them where + * [prefix] represents the long form prefix of the unit (such as "kilo", "mega", + * "tera", etc) and [first] represents the first letter in the prefix (such as + * "k", "m", "t", etc): + *
    + *
  • "" (empty string) - refers to the {@link ByteSizeUnit#BYTES}
  • + *
  • "b" - refers to the {@link ByteSizeUnit#BYTES}.
  • + *
  • "[prefix]byte" - the prefix determines what {@link ByteSizeStandard} is + * being used.
  • + *
  • "[prefix]bytes" - the prefix determines what {@link ByteSizeStandard} is + * being used.
  • + *
  • "[first]" - refers to the {@link ByteSizeStandard#IEC} standard for the + * corresponding prefix, eg. "k" is equal to "kibibyte"
  • + *
  • "[first]i" - refers to the {@link ByteSizeStandard#IEC} standard for the + * corresponding prefix, eg. "ki" is equal to "kibibyte"
  • + *
  • "[first]ib" - refers to the {@link ByteSizeStandard#IEC} standard for the + * corresponding prefix, eg. "kib" is equal to "kibibyte"
  • + *
  • "[first]b" - refers to the {@link ByteSizeStandard#SI} standard for the + * corresponding prefix, eg. "kb" is equal to "kilobyte"
  • + *
+ * + * The parsing is case insensitive, meaning that the following strings are + * equivalent: "KiB", "kIb", "KIB", etc. + * + * This method will return null if the specified string is not a + * valid {@link ByteSizeUnit} string as described above. + * + * @param unit the string representation of the desired {@link ByteSizeUnit}. + * + * @return the {@link ByteSizeUnit} represented by the specified string or + * null if the string could not be translated into a known + * unit. + */ + public static ByteSizeUnit parse(String unit) { + return unitsMap.get(unit.toLowerCase()); + } + + /** + * Returns whether this {@link ByteSizeUnit} is an SI unit. + * + * @return true iff this unit is an SI unit. + */ + public boolean isSI() { + return this.standard == SI; + } + + /** + * Returns whether this {@link ByteSizeUnit} is an IEC unit. + * + * @return true iff this unit is an IEC unit. + */ + public boolean isIEC() { + return this.standard == IEC; + } + + /** + * Gets the multiplication factor for this {@link ByteSizeUnit}. + * + * Returns the result of raising the poweOf value of this units standard to the + * power specified in this unit. + * + * @return the factor by which to multiply for this unit. + */ + public BigDecimal getFactor() { + return BigDecimal.valueOf(standard.powerOf()).pow(power); + } + + /** + * Returns the long string representation of this unit, such as "kilobytes", + * "megabytes" or "bytes". + * + * Note that this method always returns the plural form. + * + * @return the plural long string representation of this unit. + */ + public String toStringLongForm() { + return prefix + "bytes"; + } + + /** + * Returns the short string representation of this unit, such as "KiB", "B" or + * "MB". + * + * @return the short string representation of this unit. + */ + public String toStringShortForm() { + return shortLabel; + } +} diff --git a/core/src/main/java/net/cactusthorn/config/core/converter/standard/ByteSizeConverter.java b/core/src/main/java/net/cactusthorn/config/core/converter/standard/ByteSizeConverter.java new file mode 100644 index 00000000..9f8f2b06 --- /dev/null +++ b/core/src/main/java/net/cactusthorn/config/core/converter/standard/ByteSizeConverter.java @@ -0,0 +1,53 @@ +/* +* Copyright (C) 2021, Alexei Khatskevich +* +* Licensed under the BSD 3-Clause license. +* You may obtain a copy of the License at +* +* https://github.com/Gmugra/net.cactusthorn.config/blob/main/LICENSE +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package net.cactusthorn.config.core.converter.standard; + +import static net.cactusthorn.config.core.util.ApiMessages.*; +import static net.cactusthorn.config.core.util.ApiMessages.Key.*; + +import java.math.BigDecimal; + +import net.cactusthorn.config.core.converter.Converter; +import net.cactusthorn.config.core.converter.bytesize.ByteSize; +import net.cactusthorn.config.core.converter.bytesize.ByteSizeUnit; +import net.cactusthorn.config.core.util.NumericAndCharSplitter; + +/** + * @author Stefan Freyr Stefansson, Alexei Khatskevich + */ +public class ByteSizeConverter implements Converter { + + private static final NumericAndCharSplitter SPLITTER = new NumericAndCharSplitter(); + + @Override public ByteSize convert(String input, String[] parameters) { + String[] parts = SPLITTER.split(input); + String value = parts[0]; + String unit = parts[1]; + + BigDecimal bdValue = new BigDecimal(value); + ByteSizeUnit bsuUnit = ByteSizeUnit.parse(unit); + + if (bsuUnit == null) { + throw new IllegalArgumentException(msg(INVALID_UNIT_STRING, unit)); + } + + return new ByteSize(bdValue, bsuUnit); + } +} diff --git a/core/src/main/java/net/cactusthorn/config/core/util/ApiMessages.java b/core/src/main/java/net/cactusthorn/config/core/util/ApiMessages.java index 0053745d..cce0414f 100644 --- a/core/src/main/java/net/cactusthorn/config/core/util/ApiMessages.java +++ b/core/src/main/java/net/cactusthorn/config/core/util/ApiMessages.java @@ -31,7 +31,7 @@ public final class ApiMessages { public enum Key { IS_NULL, IS_EMPTY, CANT_LOAD_RESOURCE, VALUE_NOT_FOUND, LOADER_NOT_FOUND, CANT_INVOKE_CONFIGBUILDER, CANT_FIND_CONFIGBUILDER, WRONG_SOURCE_PARAM, DURATION_NO_NUMBER, DURATION_WRONG_TIME_UNIT, PERIOD_NO_NUMBER, PERIOD_WRONG_TIME_UNIT, MANIFEST_NOT_FOUND_1, - MANIFEST_NOT_FOUND_2 + MANIFEST_NOT_FOUND_2, INVALID_UNIT_STRING } private ApiMessages() { diff --git a/core/src/main/resources/net/cactusthorn/config/core/util/ApiMessages.properties b/core/src/main/resources/net/cactusthorn/config/core/util/ApiMessages.properties index 1a5eb389..fd1954d0 100644 --- a/core/src/main/resources/net/cactusthorn/config/core/util/ApiMessages.properties +++ b/core/src/main/resources/net/cactusthorn/config/core/util/ApiMessages.properties @@ -12,4 +12,5 @@ PERIOD_NO_NUMBER=No number in duration value '{0}' PERIOD_WRONG_TIME_UNIT=Could not parse time unit '{0}' (try d, w, mo, y) MANIFEST_NOT_FOUND_1=Manifest for ''{0}={1}'' NOT found MANIFEST_NOT_FOUND_2="Manifest for ''{0}'' NOT found." +INVALID_UNIT_STRING="Invalid unit string: ''{0}'' diff --git a/core/src/test/java/net/cactusthorn/config/core/converter/bytesize/ByteSizeTest.java b/core/src/test/java/net/cactusthorn/config/core/converter/bytesize/ByteSizeTest.java new file mode 100644 index 00000000..805c3f4b --- /dev/null +++ b/core/src/test/java/net/cactusthorn/config/core/converter/bytesize/ByteSizeTest.java @@ -0,0 +1,110 @@ +/* +* Copyright (C) 2021, Alexei Khatskevich +* +* Licensed under the BSD 3-Clause license. +* You may obtain a copy of the License at +* +* https://github.com/Gmugra/net.cactusthorn.config/blob/main/LICENSE +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package net.cactusthorn.config.core.converter.bytesize; + +import static org.junit.jupiter.api.Assertions.*; + +import java.math.BigInteger; + +import org.junit.jupiter.api.Test; + +public class ByteSizeTest { + + @Test public void basics() { + assertEquals(1, new ByteSize(1, ByteSizeUnit.BYTES).getBytesAsLong()); + + BigInteger siBytes = BigInteger.valueOf(1000); + BigInteger iecBytes = BigInteger.valueOf(1024); + + for (ByteSizeUnit bsu : ByteSizeUnit.values()) { + if (bsu == ByteSizeUnit.BYTES) { + assertEquals(1, new ByteSize(1, bsu).getBytesAsLong()); + } else if (bsu.isIEC()) { + assertEquals(iecBytes, new ByteSize(1, bsu).getBytes()); + iecBytes = iecBytes.multiply(BigInteger.valueOf(1024)); + } else if (bsu.isSI()) { + assertEquals(siBytes, new ByteSize(1, bsu).getBytes()); + siBytes = siBytes.multiply(BigInteger.valueOf(1000)); + } + } + } + + @Test public void conversion() { + assertEquals(new ByteSize(0.5, ByteSizeUnit.GIGABYTES), + new ByteSize(500, ByteSizeUnit.MEGABYTES).convertTo(ByteSizeUnit.GIGABYTES)); + assertEquals(new ByteSize(9.765625, ByteSizeUnit.KIBIBYTES), + new ByteSize(10, ByteSizeUnit.KILOBYTES).convertTo(ByteSizeUnit.KIBIBYTES)); + assertEquals(new ByteSize(10, ByteSizeUnit.MEGABYTES), new ByteSize(10, ByteSizeUnit.MEGABYTES).convertTo(ByteSizeUnit.MEGABYTES)); + ByteSize bs = new ByteSize(1, ByteSizeUnit.BYTES).convertTo(ByteSizeUnit.ZETTABYTES); + assertEquals(1, bs.getBytesAsLong()); + assertEquals(new ByteSize(1, ByteSizeUnit.BYTES), bs.convertTo(ByteSizeUnit.BYTES)); + } + + @Test public void equality() { + assertEquals(new ByteSize(500, ByteSizeUnit.MEGABYTES), new ByteSize(0.5, ByteSizeUnit.GIGABYTES)); + assertEquals(new ByteSize(500, ByteSizeUnit.MEBIBYTES), new ByteSize("0.48828125", ByteSizeUnit.GIBIBYTES)); + } + + @Test public void toStr() { + assertEquals("0.5 GB", new ByteSize(0.5, ByteSizeUnit.GIGABYTES).toString()); + } + + @Test public void getBytesAsInt() { + assertEquals(500000000, new ByteSize(0.5, ByteSizeUnit.GIGABYTES).getBytesAsInt()); + } + + @Test public void hash() { + assertEquals(new ByteSize(0.5, ByteSizeUnit.GIGABYTES).hashCode(), new ByteSize(0.5, ByteSizeUnit.GIGABYTES).hashCode()); + } + + @Test public void bytes() { + assertEquals("1024 B", new ByteSize(1024).toString()); + } + + @Test public void eqSame() { + ByteSize bs = new ByteSize(1024); + assertTrue(bs.equals(bs)); + } + + @Test public void eqNull() { + ByteSize bs = new ByteSize(1024); + assertFalse(bs.equals(null)); + } + + @Test public void eqWrongObject() { + ByteSize bs = new ByteSize(1024); + assertFalse(bs.equals("wrong")); + } + + @Test public void parse() { + ByteSizeUnit unit = ByteSizeUnit.parse("k"); + assertEquals(ByteSizeUnit.KIBIBYTES, unit); + } + + @Test public void toStringLongForm() { + ByteSizeUnit unit = ByteSizeUnit.parse("k"); + assertEquals("kibibytes", unit.toStringLongForm()); + } + + @Test public void isSI() { + ByteSizeUnit unit = ByteSizeUnit.parse("k"); + assertFalse(unit.isSI()); + } +} diff --git a/core/src/test/java/net/cactusthorn/config/core/converter/standard/ByteSizeConverterTest.java b/core/src/test/java/net/cactusthorn/config/core/converter/standard/ByteSizeConverterTest.java new file mode 100644 index 00000000..fcf6c42b --- /dev/null +++ b/core/src/test/java/net/cactusthorn/config/core/converter/standard/ByteSizeConverterTest.java @@ -0,0 +1,56 @@ +/* +* Copyright (C) 2021, Alexei Khatskevich +* +* Licensed under the BSD 3-Clause license. +* You may obtain a copy of the License at +* +* https://github.com/Gmugra/net.cactusthorn.config/blob/main/LICENSE +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package net.cactusthorn.config.core.converter.standard; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import net.cactusthorn.config.core.converter.Converter; +import net.cactusthorn.config.core.converter.bytesize.ByteSize; +import net.cactusthorn.config.core.converter.bytesize.ByteSizeUnit; + +public class ByteSizeConverterTest { + + static Converter converter = new ByteSizeConverter(); + + @ParameterizedTest // + @ValueSource(strings = { "10 bytes", "10byte", "10 byte" }) // + public void bytes(String value) { + assertEquals(new ByteSize(10, ByteSizeUnit.BYTES), converter.convert(value)); + } + + @ParameterizedTest // + @ValueSource(strings = { "10m", "10mi", "10mib" }) // + public void mebibytes(String value) { + assertEquals(new ByteSize(10, ByteSizeUnit.MEBIBYTES), converter.convert(value)); + } + + @Test public void invalidUnit() { + assertThrows(IllegalArgumentException.class, () -> converter.convert("10 sillybyte")); + } + + @Test public void invalidNumber() { + assertThrows(IllegalArgumentException.class, () -> converter.convert("megabyte")); + } +}