diff --git a/spring-core/src/main/java/org/springframework/util/unit/DataSize.java b/spring-core/src/main/java/org/springframework/util/unit/DataSize.java new file mode 100644 index 0000000000..4921de8eba --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/unit/DataSize.java @@ -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'. + *
+ * 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
+ * Examples:
+ *
+ * The string starts with a number followed optionally by a unit matching one of the
+ * supported {@link DataUnit suffixes}.
+ *
+ * Examples:
+ *
+ * "12KB" -- parses as "12 kilobytes"
+ * "5MB" -- parses as "5 megabytes" (where a minute is 60 seconds)
+ * "20" -- parses as "20 bytes"
+ *
+ * @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.
+ *
+ * "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})
+ *
+ * @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);
+ }
+
+}
+
diff --git a/spring-core/src/main/java/org/springframework/util/unit/DataUnit.java b/spring-core/src/main/java/org/springframework/util/unit/DataUnit.java
new file mode 100644
index 0000000000..86158f6ed5
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/unit/DataUnit.java
@@ -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 + "'");
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/unit/package-info.java b/spring-core/src/main/java/org/springframework/util/unit/package-info.java
new file mode 100644
index 0000000000..fded112fe5
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/unit/package-info.java
@@ -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;
\ No newline at end of file
diff --git a/spring-core/src/test/java/org/springframework/util/unit/DataSizeTests.java b/spring-core/src/test/java/org/springframework/util/unit/DataSizeTests.java
new file mode 100644
index 0000000000..091f97ca38
--- /dev/null
+++ b/spring-core/src/test/java/org/springframework/util/unit/DataSizeTests.java
@@ -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");
+ }
+
+}