org.conqat.lib.commons.filesystem.ByteUnit Maven / Gradle / Ivy
Show all versions of teamscale-lib-commons Show documentation
package org.conqat.lib.commons.filesystem;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.conqat.lib.commons.collections.Pair;
/**
* A {@code ByteUnit} represents amounts of bytes at a given unit of granularity and provides
* utility methods to convert across units. A {@code ByteUnit} does not maintain actual size
* information, but only helps organize and use size representations that may be maintained
* separately across various contexts.
*
* This class performs computations based on the powers of 2 (e.g. 1024 bytes form 1 kibibyte) and
* thus also uses the respective terms and {@link #getAbbreviation() abbreviations} (e.g. MiB
* instead of MB) to avoid confusions with the SI units. The SI units are based on the powers of 10
* (e.g. 1000 bytes form 1 kilobyte) and use different abbreviations (e.g. MB, GB, etc.).
*/
public enum ByteUnit {
/**
* Byte unit representing one single byte (8 bits).
*/
BYTES("B"),
/**
* Byte unit representing one kibibyte (1024 {@link #BYTES bytes}).
*/
KIBIBYTES("KiB"),
/**
* Byte unit representing one mibibyte (1024 {@link #KIBIBYTES kibibytes}).
*/
MEBIBYTES("MiB"),
/**
* Byte unit representing one gibibyte (1024 {@link #MEBIBYTES mebibytes}).
*/
GIBIBYTES("GiB"),
/**
* Byte unit representing one tebibyte (1024 {@link #GIBIBYTES gibibytes}).
*/
TEBIBYTES("TiB"),
/**
* Byte unit representing one pebibyte (1024 {@link #TEBIBYTES tebibytes}).
*/
PEBIBYTES("PiB"),
/**
* Byte unit representing one exbibyte (1024 {@link #PEBIBYTES pebibytes}).
*/
EXBIBYTES("EiB");
private static final Map UNIT_FOR_ABBREVIATION = Collections.unmodifiableMap(
Arrays.stream(values()).collect(Collectors.toMap(ByteUnit::getAbbreviation, Function.identity())));
private static final Pattern VALUE_WITH_ABBREVIATION = Pattern.compile("^(\\d+)\\s*"
+ Arrays.stream(values()).map(ByteUnit::getAbbreviation).collect(Collectors.joining("|", "(", ")")) + "$");
private final String abbreviation;
ByteUnit(String abbreviation) {
this.abbreviation = abbreviation;
}
/**
* @return The IEC abbreviation. E.g. {@code MiB} for {@link #MEBIBYTES}.
*/
public String getAbbreviation() {
return abbreviation;
}
@Override
public String toString() {
return abbreviation;
}
/**
* Equivalent to {@link #convert(long, ByteUnit) BYTES.convert(size, this)}.
*
* @param size
* the size
* @return the converted size, or {@code Long.MIN_VALUE} if conversion would negatively overflow, or
* {@code Long.MAX_VALUE} if it would positively overflow.
*/
public long toBytes(long size) {
return BYTES.convert(size, this);
}
/**
* Equivalent to {@link #convert(long, ByteUnit) KIBIBYTES.convert(size, this)}.
*
* @param size
* the size
* @return the converted size, or {@code Long.MIN_VALUE} if conversion would negatively overflow, or
* {@code Long.MAX_VALUE} if it would positively overflow.
*/
public long toKibiBytes(long size) {
return KIBIBYTES.convert(size, this);
}
/**
* Equivalent to {@link #convert(long, ByteUnit) MEBIBYTES.convert(size, this)}.
*
* @param size
* the size
* @return the converted size, or {@code Long.MIN_VALUE} if conversion would negatively overflow, or
* {@code Long.MAX_VALUE} if it would positively overflow.
*/
public long toMebiBytes(long size) {
return MEBIBYTES.convert(size, this);
}
/**
* Equivalent to {@link #convert(long, ByteUnit) GIBIBYTES.convert(size, this)}.
*
* @param size
* the size
* @return the converted size, or {@code Long.MIN_VALUE} if conversion would negatively overflow, or
* {@code Long.MAX_VALUE} if it would positively overflow.
*/
public long toGibiBytes(long size) {
return GIBIBYTES.convert(size, this);
}
/**
* Equivalent to {@link #convert(long, ByteUnit) TEBIBYTES.convert(size, this)}.
*
* @param size
* the size
* @return the converted size, or {@code Long.MIN_VALUE} if conversion would negatively overflow, or
* {@code Long.MAX_VALUE} if it would positively overflow.
*/
public long toTebiBytes(long size) {
return TEBIBYTES.convert(size, this);
}
/**
* Converts the given {@code sourceSize} in the {@code sourceUnit} into this unit. Conversions from
* finer to coarser granularities truncate, so lose precision. For example, converting {@code 1023}
* bytes to kibibytes results in {@code 0}. Conversions from coarser to finer granularities with
* arguments that would numerically overflow saturate to {@code Long.MIN_VALUE} if negative or
* {@code Long.MAX_VALUE} if positive.
*
* For example, to convert 10 GiB to MiB, use:
* {@code ByteUnit.MIBIBYTES.convert(10L, ByteUnit.GIBIBYTES)}
*
* @param sourceSize
* the size in the given {@code sourceUnit}
* @param sourceUnit
* the unit of the {@code sourceSize} argument
* @return the converted size in this unit, or {@code Long.MIN_VALUE} if conversion would negatively
* overflow, or {@code Long.MAX_VALUE} if it would positively overflow.
*/
public long convert(long sourceSize, ByteUnit sourceUnit) {
return convert(sourceSize, sourceUnit, this);
}
/**
* Returns the multiplier between this instance (as source unit) and the {@code targetUnit}. The
* multiplier can be used in the way:
*
*
* ByteUnit sourceUnit = ...;
* ByteUnit targetUnit = ...;
* long sourceValue = ...;
* long targetValue = sourceValue * sourceUnit.getMultiplier(targetUnit);
*
*
* @param targetUnit
* {@code ByteUnit} into which the value should be multiplied
* @return The multiplier to compute values in the {@code targetUnit} from the source unit.
* @throws IllegalArgumentException
* Thrown then {@code targetUnit} was greater than the source unit.
*/
public long getMultiplier(ByteUnit targetUnit) {
return getMultiplier(this, targetUnit);
}
/**
* Returns the multiplier between the {@code sourceUnit} and the {@code targetUnit}. The multiplier
* can be used in the way:
*
*
* ByteUnit sourceUnit = ...;
* ByteUnit targetUnit = ...;
* long sourceValue = ...;
* long targetValue = sourceValue * ByteUnit.getMultiplier(sourceUnit, targetUnit);
*
*
* @param sourceUnit
* {@code ByteUnit} from which the value should be multiplied
* @param targetUnit
* {@code ByteUnit} into which the value should be multiplied
* @return The multiplier to compute values in the {@code targetUnit} from the {@code sourceUnit}.
* @throws IllegalArgumentException
* Thrown then {@code targetUnit} was greater than the {@code sourceUnit}.
*/
public static long getMultiplier(ByteUnit sourceUnit, ByteUnit targetUnit) {
int scaleDifference = sourceUnit.ordinal() - targetUnit.ordinal();
if (scaleDifference < 0) {
throw new IllegalArgumentException(
String.format("Cannot compute multiplier from %s to %s", sourceUnit.name(), targetUnit.name()));
}
return computeScale(scaleDifference);
}
private static long convert(long size, ByteUnit sourceUnit, ByteUnit targetUnit) {
if (sourceUnit == targetUnit || size == 0L) {
return size;
}
long result = size;
int scaleDifference = sourceUnit.ordinal() - targetUnit.ordinal();
if (scaleDifference > 0) {
result = boundedMultiply(result, computeScale(scaleDifference));
} else {
result /= computeScale(scaleDifference * -1);
}
return result;
}
private static long computeScale(int steps) {
if (steps > 6) {
// Guard against future, where zebibyte and yobibyte may be present
throw new ArithmeticException("Unable to compute conversions larger than the size of long");
}
return 1L << (10 * steps);
}
/**
* Same as {@link Math#multiplyExact(int, int)}, but instead of throwing an exception,
* {@link Long#MAX_VALUE} or {@link Long#MIN_VALUE} is returned.
*
* @return The result of the multiplication of {@code x} and {@code y}.
* {@link Long#MAX_VALUE}/{@link Long#MIN_VALUE} if the resulting value overflowed.
*/
private static long boundedMultiply(long x, long y) {
long r = x * y;
long ax = Math.abs(x);
long ay = Math.abs(y);
if (((ax | ay) >>> 31 != 0)) {
// Some bits greater than 2^31 that might cause overflow
// Check the result using the divide operator
// and check for the special case of Long.MIN_VALUE * -1
if (((y != 0) && (r / y != x)) || (x == Long.MIN_VALUE && y == -1)) {
// here the ArithmeticException is thrown by Math.multiplyExact
return getOverflowValue(x, y);
}
}
return r;
}
private static long getOverflowValue(long x, long y) {
if (x < 0 ^ y < 0) {
// result should be negative as input was positive * negative
return Long.MIN_VALUE;
}
return Long.MAX_VALUE;
}
/**
* Parses the {@link ByteUnit} from the provided {@code abbreviation} (e.g. {@code GiB}).
*
* @throws IllegalArgumentException
* Thrown if the provided abbreviation could not be parsed
*
* @see #getAbbreviation()
*/
public static ByteUnit fromAbbreviation(String abbreviation) {
ByteUnit unit = UNIT_FOR_ABBREVIATION.get(abbreviation);
if (unit == null) {
throw new IllegalArgumentException(String.format("Unknown abbreviation: %s", abbreviation));
}
return unit;
}
/**
* Parses the unit measurement (e.g. {@code 3 GiB}) into the numeric value and {@link ByteUnit}.
*/
public static Pair parseUnitMeasurement(String value) {
Matcher matcher = VALUE_WITH_ABBREVIATION.matcher(value);
if (matcher.matches()) {
long amount = Long.parseLong(matcher.group(1));
ByteUnit unit = ByteUnit.fromAbbreviation(matcher.group(2));
return new Pair<>(amount, unit);
} else {
throw new IllegalArgumentException(String
.format("Provided argument does not match the expected pattern: %s", VALUE_WITH_ABBREVIATION));
}
}
/**
* Parses the unit measurement (e.g. {@code 3 GiB}) into the numeric value of the provided
* {@code targetUnit}.
*/
public static long parseUnitMeasurementAs(String value, ByteUnit targetUnit) {
Pair valueAndUnit = parseUnitMeasurement(value);
return targetUnit.convert(valueAndUnit.getFirst(), valueAndUnit.getSecond());
}
}