Skip to content

Commit 8a1588a

Browse files
committed
Add support for DataSize
This commit provides a data type to represents a size in bytes and other standard unit. Issue: SPR-17154
1 parent 85ad2a5 commit 8a1588a

File tree

4 files changed

+513
-0
lines changed

4 files changed

+513
-0
lines changed
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.util.unit;
18+
19+
import java.util.regex.Matcher;
20+
import java.util.regex.Pattern;
21+
22+
import org.springframework.lang.Nullable;
23+
import org.springframework.util.Assert;
24+
import org.springframework.util.StringUtils;
25+
26+
/**
27+
* A data size, such as '12MB'.
28+
* <p>
29+
* This class models a size in terms of bytes and is immutable and thread-safe.
30+
*
31+
* @author Stephane Nicoll
32+
* @since 5.1
33+
*/
34+
public class DataSize implements Comparable<DataSize> {
35+
36+
/**
37+
* The pattern for parsing.
38+
*/
39+
private static final Pattern PATTERN = Pattern.compile("^(\\d+)([a-zA-Z]{0,2})$");
40+
41+
/**
42+
* Bytes per KiloByte.
43+
*/
44+
private static long BYTES_PER_KB = 1024;
45+
46+
/**
47+
* Bytes per MegaByte.
48+
*/
49+
private static long BYTES_PER_MB = BYTES_PER_KB * 1024;
50+
51+
/**
52+
* Bytes per GigaByte.
53+
*/
54+
private static long BYTES_PER_GB = BYTES_PER_MB * 1024;
55+
56+
/**
57+
* Bytes per TeraByte.
58+
*/
59+
private static long BYTES_PER_TB = BYTES_PER_GB * 1024;
60+
61+
62+
private final long bytes;
63+
64+
private DataSize(long bytes) {
65+
this.bytes = bytes;
66+
}
67+
68+
69+
/**
70+
* Obtain a {@link DataSize} representing the specified number of bytes.
71+
* @param bytes the number of bytes
72+
* @return a {@link DataSize}
73+
*/
74+
public static DataSize ofBytes(long bytes) {
75+
return new DataSize(bytes);
76+
}
77+
78+
/**
79+
* Obtain a {@link DataSize} representing the specified number of kilobytes.
80+
* @param kiloBytes the number of kilobytes
81+
* @return a {@link DataSize}
82+
*/
83+
public static DataSize ofKiloBytes(long kiloBytes) {
84+
return new DataSize(Math.multiplyExact(kiloBytes, BYTES_PER_KB));
85+
}
86+
87+
/**
88+
* Obtain a {@link DataSize} representing the specified number of megabytes.
89+
* @param megaBytes the number of megabytes
90+
* @return a {@link DataSize}
91+
*/
92+
public static DataSize ofMegaBytes(long megaBytes) {
93+
return new DataSize(Math.multiplyExact(megaBytes, BYTES_PER_MB));
94+
}
95+
96+
/**
97+
* Obtain a {@link DataSize} representing the specified number of gigabytes.
98+
* @param gigaBytes the number of gigabytes
99+
* @return a {@link DataSize}
100+
*/
101+
public static DataSize ofGigaBytes(long gigaBytes) {
102+
return new DataSize(Math.multiplyExact(gigaBytes, BYTES_PER_GB));
103+
}
104+
105+
/**
106+
* Obtain a {@link DataSize} representing the specified number of terabytes.
107+
* @param teraBytes the number of terabytes
108+
* @return a {@link DataSize}
109+
*/
110+
public static DataSize ofTeraBytes(long teraBytes) {
111+
return new DataSize(Math.multiplyExact(teraBytes, BYTES_PER_TB));
112+
}
113+
114+
/**
115+
* Obtain a {@link DataSize} representing an amount in the specified {@link DataUnit}.
116+
* @param amount the amount of the size, measured in terms of the unit
117+
* @return a {@link DataSize}
118+
*/
119+
public static DataSize of(long amount, DataUnit unit) {
120+
Assert.notNull(unit, "Unit must not be null");
121+
return new DataSize(Math.multiplyExact(amount, unit.getSize().toBytes()));
122+
}
123+
124+
/**
125+
* Obtain a {@link DataSize} from a text string such as {@code 12MB} using
126+
* {@link DataUnit#BYTES} if no unit is specified.
127+
* <p>
128+
* Examples:
129+
* <pre>
130+
* "12KB" -- parses as "12 kilobytes"
131+
* "5MB" -- parses as "5 megabytes" (where a minute is 60 seconds)
132+
* "20" -- parses as "20 bytes"
133+
* </pre>
134+
* @param text the text to parse
135+
* @return the parsed {@link DataSize}
136+
* @see #parse(CharSequence, DataUnit)
137+
*/
138+
public static DataSize parse(CharSequence text) {
139+
return parse(text, null);
140+
}
141+
142+
/**
143+
* Obtain a {@link DataSize} from a text string such as {@code 12MB} using
144+
* the specified default {@link DataUnit} if no unit is specified.
145+
* <p>
146+
* The string starts with a number followed optionally by a unit matching one of the
147+
* supported {@link DataUnit suffixes}.
148+
* <p>
149+
* Examples:
150+
* <pre>
151+
* "12KB" -- parses as "12 kilobytes"
152+
* "5MB" -- parses as "5 megabytes" (where a minute is 60 seconds)
153+
* "20" -- parses as "20 kilobytes" (where the {@code defaultUnit} is {@link DataUnit#KILOBYTES})
154+
* </pre>
155+
* @param text the text to parse
156+
* @return the parsed {@link DataSize}
157+
*/
158+
public static DataSize parse(CharSequence text, @Nullable DataUnit defaultUnit) {
159+
Assert.notNull(text, "Text must not be null");
160+
try {
161+
Matcher matcher = PATTERN.matcher(text);
162+
Assert.state(matcher.matches(), "Does not match data size pattern");
163+
DataUnit unit = determineDataUnit(matcher.group(2), defaultUnit);
164+
Long amount = Long.parseLong(matcher.group(1));
165+
return DataSize.of(amount, unit);
166+
}
167+
catch (Exception ex) {
168+
throw new IllegalArgumentException(
169+
"'" + text + "' is not a valid data size", ex);
170+
}
171+
}
172+
173+
private static DataUnit determineDataUnit(String suffix,
174+
@Nullable DataUnit defaultUnit) {
175+
defaultUnit = (defaultUnit != null ? defaultUnit : DataUnit.BYTES);
176+
return (StringUtils.hasLength(suffix) ? DataUnit.fromSuffix(suffix)
177+
: defaultUnit);
178+
}
179+
180+
/**
181+
* Return the number of bytes in this instance.
182+
* @return the number of bytes
183+
*/
184+
public long toBytes() {
185+
return this.bytes;
186+
}
187+
188+
/**
189+
* Return the number of kilobytes in this instance.
190+
* @return the number of kilobytes
191+
*/
192+
public long toKiloBytes() {
193+
return this.bytes / BYTES_PER_KB;
194+
}
195+
196+
/**
197+
* Return the number of megabytes in this instance.
198+
* @return the number of megabytes
199+
*/
200+
public long toMegaBytes() {
201+
return this.bytes / BYTES_PER_MB;
202+
}
203+
204+
/**
205+
* Return the number of gigabytes in this instance.
206+
* @return the number of gigabytes
207+
*/
208+
public long toGigaBytes() {
209+
return this.bytes / BYTES_PER_GB;
210+
}
211+
212+
/**
213+
* Return the number of terabytes in this instance.
214+
* @return the number of terabytes
215+
*/
216+
public long toTeraBytes() {
217+
return this.bytes / BYTES_PER_TB;
218+
}
219+
220+
@Override
221+
public int compareTo(DataSize other) {
222+
return Long.compare(this.bytes, other.bytes);
223+
}
224+
225+
@Override
226+
public String toString() {
227+
return String.format("%dB", this.bytes);
228+
}
229+
230+
@Override
231+
public boolean equals(Object o) {
232+
if (this == o) {
233+
return true;
234+
}
235+
if (o == null || getClass() != o.getClass()) {
236+
return false;
237+
}
238+
DataSize that = (DataSize) o;
239+
return this.bytes == that.bytes;
240+
}
241+
242+
@Override
243+
public int hashCode() {
244+
return Long.hashCode(this.bytes);
245+
}
246+
247+
}
248+
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.util.unit;
18+
19+
import java.util.Objects;
20+
21+
/**
22+
* A standard set of data size units.
23+
*
24+
* @author Stephane Nicoll
25+
* @since 5.1
26+
*/
27+
public enum DataUnit {
28+
29+
/**
30+
* Bytes.
31+
*/
32+
BYTES("B", DataSize.ofBytes(1)),
33+
34+
/**
35+
* KiloByte.
36+
*/
37+
KILOBYTES("KB", DataSize.ofKiloBytes(1)),
38+
39+
/**
40+
* MegaByte.
41+
*/
42+
MEGABYTES("MB", DataSize.ofMegaBytes(1)),
43+
44+
/**
45+
* TeraByte.
46+
*/
47+
GIGABYTES("GB", DataSize.ofGigaBytes(1)),
48+
49+
/**
50+
* TeraByte.
51+
*/
52+
TERABYTES("TB", DataSize.ofTeraBytes(1));
53+
54+
private final String suffix;
55+
56+
private final DataSize size;
57+
58+
DataUnit(String suffix, DataSize size) {
59+
this.suffix = suffix;
60+
this.size = size;
61+
}
62+
63+
protected DataSize getSize() {
64+
return this.size;
65+
}
66+
67+
/**
68+
* Return the {@link DataUnit} matching the specified {@code suffix}.
69+
* @param suffix one of the standard suffix
70+
* @return the {@link DataUnit} matching the specified {@code suffix}
71+
* @throws IllegalArgumentException if the suffix does not match any instance
72+
*/
73+
public static DataUnit fromSuffix(String suffix) {
74+
for (DataUnit candidate : values()) {
75+
if (Objects.equals(candidate.suffix, suffix)) {
76+
return candidate;
77+
}
78+
}
79+
throw new IllegalArgumentException("Unknown unit '" + suffix + "'");
80+
}
81+
82+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Useful unit data types.
19+
*/
20+
@NonNullApi
21+
@NonNullFields
22+
package org.springframework.util.unit;
23+
24+
import org.springframework.lang.NonNullApi;
25+
import org.springframework.lang.NonNullFields;

0 commit comments

Comments
 (0)