Browse Source
This commit provides a data type to represents a size in bytes and other standard unit. Issue: SPR-17154pull/1924/merge
Stephane Nicoll
6 years ago
4 changed files with 513 additions and 0 deletions
@ -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); |
||||
} |
||||
|
||||
} |
||||
|
@ -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 + "'"); |
||||
} |
||||
|
||||
} |
@ -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; |
@ -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…
Reference in new issue