Browse Source

Add support for DataSize

This commit provides a data type to represents a size in bytes and other
standard unit.

Issue: SPR-17154
pull/1924/merge
Stephane Nicoll 6 years ago
parent
commit
8a1588ae29
  1. 248
      spring-core/src/main/java/org/springframework/util/unit/DataSize.java
  2. 82
      spring-core/src/main/java/org/springframework/util/unit/DataUnit.java
  3. 25
      spring-core/src/main/java/org/springframework/util/unit/package-info.java
  4. 158
      spring-core/src/test/java/org/springframework/util/unit/DataSizeTests.java

248
spring-core/src/main/java/org/springframework/util/unit/DataSize.java

@ -0,0 +1,248 @@ @@ -0,0 +1,248 @@
/*
* Copyright 2002-2018 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
*
* http://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 org.springframework.util.unit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A data size, such as '12MB'.
* <p>
* This class models a size in terms of bytes and is immutable and thread-safe.
*
* @author Stephane Nicoll
* @since 5.1
*/
public class DataSize implements Comparable<DataSize> {
/**
* The pattern for parsing.
*/
private static final Pattern PATTERN = Pattern.compile("^(\\d+)([a-zA-Z]{0,2})$");
/**
* Bytes per KiloByte.
*/
private static long BYTES_PER_KB = 1024;
/**
* Bytes per MegaByte.
*/
private static long BYTES_PER_MB = BYTES_PER_KB * 1024;
/**
* Bytes per GigaByte.
*/
private static long BYTES_PER_GB = BYTES_PER_MB * 1024;
/**
* Bytes per TeraByte.
*/
private static 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
* @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
* @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
* @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
* @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
* @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
* @return a {@link DataSize}
*/
public static DataSize of(long amount, DataUnit unit) {
Assert.notNull(unit, "Unit must not be null");
return new DataSize(Math.multiplyExact(amount, unit.getSize().toBytes()));
}
/**
* Obtain a {@link DataSize} from a text string such as {@code 12MB} using
* {@link DataUnit#BYTES} if no unit is specified.
* <p>
* Examples:
* <pre>
* "12KB" -- parses as "12 kilobytes"
* "5MB" -- parses as "5 megabytes" (where a minute is 60 seconds)
* "20" -- parses as "20 bytes"
* </pre>
* @param text the text to parse
* @return the parsed {@link DataSize}
* @see #parse(CharSequence, DataUnit)
*/
public static DataSize parse(CharSequence 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.
* <p>
* The string starts with a number followed optionally by a unit matching one of the
* supported {@link DataUnit suffixes}.
* <p>
* Examples:
* <pre>
* "12KB" -- parses as "12 kilobytes"
* "5MB" -- parses as "5 megabytes" (where a minute is 60 seconds)
* "20" -- parses as "20 kilobytes" (where the {@code defaultUnit} is {@link DataUnit#KILOBYTES})
* </pre>
* @param text the text to parse
* @return the parsed {@link DataSize}
*/
public static DataSize parse(CharSequence text, @Nullable DataUnit defaultUnit) {
Assert.notNull(text, "Text must not be null");
try {
Matcher matcher = PATTERN.matcher(text);
Assert.state(matcher.matches(), "Does not match data size pattern");
DataUnit unit = 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);
}
}
private static DataUnit determineDataUnit(String suffix,
@Nullable DataUnit defaultUnit) {
defaultUnit = (defaultUnit != null ? defaultUnit : DataUnit.BYTES);
return (StringUtils.hasLength(suffix) ? DataUnit.fromSuffix(suffix)
: defaultUnit);
}
/**
* 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(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DataSize that = (DataSize) o;
return this.bytes == that.bytes;
}
@Override
public int hashCode() {
return Long.hashCode(this.bytes);
}
}

82
spring-core/src/main/java/org/springframework/util/unit/DataUnit.java

@ -0,0 +1,82 @@ @@ -0,0 +1,82 @@
/*
* Copyright 2002-2018 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
*
* http://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 org.springframework.util.unit;
import java.util.Objects;
/**
* A standard set of data size units.
*
* @author Stephane Nicoll
* @since 5.1
*/
public enum DataUnit {
/**
* Bytes.
*/
BYTES("B", DataSize.ofBytes(1)),
/**
* KiloByte.
*/
KILOBYTES("KB", DataSize.ofKiloBytes(1)),
/**
* MegaByte.
*/
MEGABYTES("MB", DataSize.ofMegaBytes(1)),
/**
* TeraByte.
*/
GIGABYTES("GB", DataSize.ofGigaBytes(1)),
/**
* TeraByte.
*/
TERABYTES("TB", DataSize.ofTeraBytes(1));
private final String suffix;
private final DataSize size;
DataUnit(String suffix, DataSize size) {
this.suffix = suffix;
this.size = size;
}
protected DataSize getSize() {
return this.size;
}
/**
* Return the {@link DataUnit} matching the specified {@code suffix}.
* @param suffix one of the standard suffix
* @return the {@link DataUnit} matching the specified {@code suffix}
* @throws IllegalArgumentException if the suffix does not match any instance
*/
public static DataUnit fromSuffix(String suffix) {
for (DataUnit candidate : values()) {
if (Objects.equals(candidate.suffix, suffix)) {
return candidate;
}
}
throw new IllegalArgumentException("Unknown unit '" + suffix + "'");
}
}

25
spring-core/src/main/java/org/springframework/util/unit/package-info.java

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
/*
* Copyright 2002-2018 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
*
* http://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.
*/
/**
* Useful unit data types.
*/
@NonNullApi
@NonNullFields
package org.springframework.util.unit;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;

158
spring-core/src/test/java/org/springframework/util/unit/DataSizeTests.java

@ -0,0 +1,158 @@ @@ -0,0 +1,158 @@
/*
* Copyright 2002-2018 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
*
* http://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 org.springframework.util.unit;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.junit.Assert.*;
/**
* Tests for {@link DataSize}.
*
* @author Stephane Nicoll
*/
public class DataSizeTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Test
public void ofBytesToBytes() {
assertEquals(1024, DataSize.ofBytes(1024).toBytes());
}
@Test
public void ofBytesToKiloBytes() {
assertEquals(1, DataSize.ofBytes(1024).toKiloBytes());
}
@Test
public void ofKiloBytesToKiloBytes() {
assertEquals(1024, DataSize.ofKiloBytes(1024).toKiloBytes());
}
@Test
public void ofKiloBytesToMegaBytes() {
assertEquals(1, DataSize.ofKiloBytes(1024).toMegaBytes());
}
@Test
public void ofMegaBytesToMegaBytes() {
assertEquals(1024, DataSize.ofMegaBytes(1024).toMegaBytes());
}
@Test
public void ofMegaBytesToGigaBytes() {
assertEquals(2, DataSize.ofMegaBytes(2048).toGigaBytes());
}
@Test
public void ofGigaBytesToGigaBytes() {
assertEquals(4096, DataSize.ofGigaBytes(4096).toGigaBytes());
}
@Test
public void ofGigaBytesToTeraBytes() {
assertEquals(4, DataSize.ofGigaBytes(4096).toTeraBytes());
}
@Test
public void ofTeraBytesToGigaBytes() {
assertEquals(1024, DataSize.ofTeraBytes(1).toGigaBytes());
}
@Test
public void ofWithBytesUnit() {
assertEquals(DataSize.ofBytes(10), DataSize.of(10, DataUnit.BYTES));
}
@Test
public void ofWithKiloBytesUnit() {
assertEquals(DataSize.ofKiloBytes(20), DataSize.of(20, DataUnit.KILOBYTES));
}
@Test
public void ofWithMegaBytesUnit() {
assertEquals(DataSize.ofMegaBytes(30), DataSize.of(30, DataUnit.MEGABYTES));
}
@Test
public void ofWithGigaBytesUnit() {
assertEquals(DataSize.ofGigaBytes(40), DataSize.of(40, DataUnit.GIGABYTES));
}
@Test
public void ofWithTeraBytesUnit() {
assertEquals(DataSize.ofTeraBytes(50), DataSize.of(50, DataUnit.TERABYTES));
}
@Test
public void parseWithDefaultUnitUsesBytes() {
assertEquals(DataSize.ofKiloBytes(1), DataSize.parse("1024"));
}
@Test
public void parseWithNullDefaultUnitUsesBytes() {
assertEquals(DataSize.ofKiloBytes(1), DataSize.parse("1024", null));
}
@Test
public void parseWithCustomDefaultUnit() {
assertEquals(DataSize.ofKiloBytes(1), DataSize.parse("1", DataUnit.KILOBYTES));
}
@Test
public void parseWithBytes() {
assertEquals(DataSize.ofKiloBytes(1), DataSize.parse("1024B"));
}
@Test
public void parseWithKiloBytes() {
assertEquals(DataSize.ofBytes(1024), DataSize.parse("1KB"));
}
@Test
public void parseWithMegaBytes() {
assertEquals(DataSize.ofMegaBytes(4), DataSize.parse("4MB"));
}
@Test
public void parseWithGigaBytes() {
assertEquals(DataSize.ofMegaBytes(1024), DataSize.parse("1GB"));
}
@Test
public void parseWithTeraBytes() {
assertEquals(DataSize.ofTeraBytes(1), DataSize.parse("1TB"));
}
@Test
public void toStringUsesBytes() {
assertEquals("1024B", DataSize.ofKiloBytes(1).toString());
}
@Test
public void parseWithUnsupportedUnit() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("3WB");
this.thrown.expectMessage("is not a valid data size");
DataSize.parse("3WB");
}
}
Loading…
Cancel
Save