com.wl4g.infra.common.io.DataSize Maven / Gradle / Ivy
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wl4g.infra.common.io;
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.io.Serializable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import com.wl4g.infra.common.lang.Assert2;
import com.wl4g.infra.common.lang.StringUtils2;
/**
* A data size, such as '12MB'.
*
*
* This class models data size in terms of bytes and is immutable and
* thread-safe.
*
*
* The terms and units used in this class are based on
* binary prefixes
* indicating multiplication by powers of 2. Consult the following table and the
* Javadoc for {@link DataUnit} for details.
*
*
*
*
* Term
* Data Size
* Size in Bytes
*
*
* byte
* 1B
* 1
*
*
* kilobyte
* 1KB
* 1,024
*
*
* megabyte
* 1MB
* 1,048,576
*
*
* gigabyte
* 1GB
* 1,073,741,824
*
*
* terabyte
* 1TB
* 1,099,511,627,776
*
*
*
* @author Stephane Nicoll
* @author Sam Brannen
* @since 5.1
* @see DataUnit
* @see Copy of {@link org.springframework.util.unit.DataSize}
*/
@SuppressWarnings("serial")
public final class DataSize implements Comparable, Serializable {
/**
* Bytes per Kilobyte.
*/
private static final long BYTES_PER_KB = 1024;
/**
* Bytes per Megabyte.
*/
private static final long BYTES_PER_MB = BYTES_PER_KB * 1024;
/**
* Bytes per Gigabyte.
*/
private static final long BYTES_PER_GB = BYTES_PER_MB * 1024;
/**
* Bytes per Terabyte.
*/
private static final long BYTES_PER_TB = BYTES_PER_GB * 1024;
private final long bytes;
private DataSize(long bytes) {
this.bytes = bytes;
}
/**
* Obtain a {@link DataSize} representing the specified number of bytes.
*
* @param bytes
* the number of bytes, positive or negative
* @return a {@link DataSize}
*/
public static DataSize ofBytes(long bytes) {
return new DataSize(bytes);
}
/**
* Obtain a {@link DataSize} representing the specified number of kilobytes.
*
* @param kilobytes
* the number of kilobytes, positive or negative
* @return a {@link DataSize}
*/
public static DataSize ofKilobytes(long kilobytes) {
return new DataSize(Math.multiplyExact(kilobytes, BYTES_PER_KB));
}
/**
* Obtain a {@link DataSize} representing the specified number of megabytes.
*
* @param megabytes
* the number of megabytes, positive or negative
* @return a {@link DataSize}
*/
public static DataSize ofMegabytes(long megabytes) {
return new DataSize(Math.multiplyExact(megabytes, BYTES_PER_MB));
}
/**
* Obtain a {@link DataSize} representing the specified number of gigabytes.
*
* @param gigabytes
* the number of gigabytes, positive or negative
* @return a {@link DataSize}
*/
public static DataSize ofGigabytes(long gigabytes) {
return new DataSize(Math.multiplyExact(gigabytes, BYTES_PER_GB));
}
/**
* Obtain a {@link DataSize} representing the specified number of terabytes.
*
* @param terabytes
* the number of terabytes, positive or negative
* @return a {@link DataSize}
*/
public static DataSize ofTerabytes(long terabytes) {
return new DataSize(Math.multiplyExact(terabytes, BYTES_PER_TB));
}
/**
* Obtain a {@link DataSize} representing an amount in the specified
* {@link DataUnit}.
*
* @param amount
* the amount of the size, measured in terms of the unit,
* positive or negative
* @return a corresponding {@link DataSize}
*/
public static DataSize of(long amount, DataUnit unit) {
Assert2.notNull(unit, "Unit must not be null");
return new DataSize(Math.multiplyExact(amount, unit.size().toBytes()));
}
/**
* Obtain a {@link DataSize} from a text string such as {@code 12MB} using
* {@link DataUnit#BYTES} if no unit is specified.
*
* Examples:
*
*
* "12KB" -- parses as "12 kilobytes"
* "5MB" -- parses as "5 megabytes"
* "20" -- parses as "20 bytes"
*
*
* @param text
* the text to parse
* @return the parsed {@link DataSize}
* @see #parse(CharSequence, DataUnit)
*/
public static DataSize parse(String text) {
return parse(text, null);
}
/**
* Obtain a {@link DataSize} from a text string such as {@code 12MB} using
* the specified default {@link DataUnit} if no unit is specified.
*
* The string starts with a number followed optionally by a unit matching
* one of the supported {@linkplain DataUnit suffixes}.
*
* Examples:
*
*
* "12KB" -- parses as "12 kilobytes"
* "5MB" -- parses as "5 megabytes"
* "20" -- parses as "20 kilobytes" (where the {@code
* defaultUnit
* } is {@link DataUnit#KILOBYTES})
*
*
* @param text
* the text to parse
* @return the parsed {@link DataSize}
*/
public static DataSize parse(String text, @Nullable DataUnit defaultUnit) {
Assert2.notNull(text, "Text must not be null");
try {
Matcher matcher = DataSizeUtils.PATTERN.matcher(StringUtils2.trimAllWhitespace(text));
Assert2.state(matcher.matches(), "Does not match data size pattern");
DataUnit unit = DataSizeUtils.determineDataUnit(matcher.group(2), defaultUnit);
long amount = Long.parseLong(matcher.group(1));
return DataSize.of(amount, unit);
} catch (Exception ex) {
throw new IllegalArgumentException("'" + text + "' is not a valid data size", ex);
}
}
/**
* Checks if this size is negative, excluding zero.
*
* @return true if this size has a size less than zero bytes
*/
public boolean isNegative() {
return this.bytes < 0;
}
/**
* Return the number of bytes in this instance.
*
* @return the number of bytes
*/
public long toBytes() {
return this.bytes;
}
/**
* Return the number of kilobytes in this instance.
*
* @return the number of kilobytes
*/
public long toKilobytes() {
return this.bytes / BYTES_PER_KB;
}
/**
* Return the number of megabytes in this instance.
*
* @return the number of megabytes
*/
public long toMegabytes() {
return this.bytes / BYTES_PER_MB;
}
/**
* Return the number of gigabytes in this instance.
*
* @return the number of gigabytes
*/
public long toGigabytes() {
return this.bytes / BYTES_PER_GB;
}
/**
* Return the number of terabytes in this instance.
*
* @return the number of terabytes
*/
public long toTerabytes() {
return this.bytes / BYTES_PER_TB;
}
@Override
public int compareTo(DataSize other) {
return Long.compare(this.bytes, other.bytes);
}
@Override
public String toString() {
return String.format("%dB", this.bytes);
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
DataSize otherSize = (DataSize) other;
return (this.bytes == otherSize.bytes);
}
@Override
public int hashCode() {
return Long.hashCode(this.bytes);
}
/**
* Static nested class to support lazy loading of the {@link #PATTERN}.
*
* @since 5.3.21
*/
private static class DataSizeUtils {
/**
* The pattern for parsing.
*/
private static final Pattern PATTERN = Pattern.compile("^([+\\-]?\\d+)([a-zA-Z]{0,2})$");
private static DataUnit determineDataUnit(String suffix, @Nullable DataUnit defaultUnit) {
DataUnit defaultUnitToUse = (defaultUnit != null ? defaultUnit : DataUnit.BYTES);
return (!isBlank(suffix) ? DataUnit.fromSuffix(suffix) : defaultUnitToUse);
}
}
/**
* A standard set of {@link DataSize} units.
*
*
* The unit prefixes used in this class are
* binary prefixes
* indicating multiplication by powers of 2. The following table displays
* the enum constants defined in this class and corresponding values.
*
*
*
*
* Constant
* Data Size
* Power of 2
* Size in Bytes
*
*
* {@link #BYTES}
* 1B
* 2^0
* 1
*
*
* {@link #KILOBYTES}
* 1KB
* 2^10
* 1,024
*
*
* {@link #MEGABYTES}
* 1MB
* 2^20
* 1,048,576
*
*
* {@link #GIGABYTES}
* 1GB
* 2^30
* 1,073,741,824
*
*
* {@link #TERABYTES}
* 1TB
* 2^40
* 1,099,511,627,776
*
*
*
* @author Stephane Nicoll
* @author Sam Brannen
* @since 5.1
* @see DataSize
*/
public static enum DataUnit {
/**
* Bytes, represented by suffix {@code B}.
*/
BYTES("B", DataSize.ofBytes(1)),
/**
* Kilobytes, represented by suffix {@code KB}.
*/
KILOBYTES("KB", DataSize.ofKilobytes(1)),
/**
* Megabytes, represented by suffix {@code MB}.
*/
MEGABYTES("MB", DataSize.ofMegabytes(1)),
/**
* Gigabytes, represented by suffix {@code GB}.
*/
GIGABYTES("GB", DataSize.ofGigabytes(1)),
/**
* Terabytes, represented by suffix {@code TB}.
*/
TERABYTES("TB", DataSize.ofTerabytes(1));
private final String suffix;
private final DataSize size;
DataUnit(String suffix, DataSize size) {
this.suffix = suffix;
this.size = size;
}
DataSize size() {
return this.size;
}
/**
* Return the {@link DataUnit} matching the specified {@code suffix}.
*
* @param suffix
* one of the standard suffixes
* @return the {@link DataUnit} matching the specified {@code suffix}
* @throws IllegalArgumentException
* if the suffix does not match the suffix of any of this
* enum's constants
*/
public static DataUnit fromSuffix(String suffix) {
for (DataUnit candidate : values()) {
if (candidate.suffix.equals(suffix)) {
return candidate;
}
}
throw new IllegalArgumentException("Unknown data unit suffix '" + suffix + "'");
}
}
}