001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.util.unit; 018 019import java.io.Serializable; 020import java.util.regex.Matcher; 021import java.util.regex.Pattern; 022 023import org.springframework.lang.Nullable; 024import org.springframework.util.Assert; 025import org.springframework.util.StringUtils; 026 027/** 028 * A data size, such as '12MB'. 029 * 030 * <p>This class models data size in terms of bytes and is immutable and thread-safe. 031 * 032 * <p>The terms and units used in this class are based on 033 * <a href="https://en.wikipedia.org/wiki/Binary_prefix">binary prefixes</a> 034 * indicating multiplication by powers of 2. Consult the following table and 035 * the Javadoc for {@link DataUnit} for details. 036 * 037 * <p> 038 * <table border="1"> 039 * <tr><th>Term</th><th>Data Size</th><th>Size in Bytes</th></tr> 040 * <tr><td>byte</td><td>1B</td><td>1</td></tr> 041 * <tr><td>kilobyte</td><td>1KB</td><td>1,024</td></tr> 042 * <tr><td>megabyte</td><td>1MB</td><td>1,048,576</td></tr> 043 * <tr><td>gigabyte</td><td>1GB</td><td>1,073,741,824</td></tr> 044 * <tr><td>terabyte</td><td>1TB</td><td>1,099,511,627,776</td></tr> 045 * </table> 046 * 047 * @author Stephane Nicoll 048 * @author Sam Brannen 049 * @since 5.1 050 * @see DataUnit 051 */ 052@SuppressWarnings("serial") 053public final class DataSize implements Comparable<DataSize>, Serializable { 054 055 /** 056 * The pattern for parsing. 057 */ 058 private static final Pattern PATTERN = Pattern.compile("^([+\\-]?\\d+)([a-zA-Z]{0,2})$"); 059 060 /** 061 * Bytes per Kilobyte. 062 */ 063 private static final long BYTES_PER_KB = 1024; 064 065 /** 066 * Bytes per Megabyte. 067 */ 068 private static final long BYTES_PER_MB = BYTES_PER_KB * 1024; 069 070 /** 071 * Bytes per Gigabyte. 072 */ 073 private static final long BYTES_PER_GB = BYTES_PER_MB * 1024; 074 075 /** 076 * Bytes per Terabyte. 077 */ 078 private static final long BYTES_PER_TB = BYTES_PER_GB * 1024; 079 080 081 private final long bytes; 082 083 084 private DataSize(long bytes) { 085 this.bytes = bytes; 086 } 087 088 089 /** 090 * Obtain a {@link DataSize} representing the specified number of bytes. 091 * @param bytes the number of bytes, positive or negative 092 * @return a {@link DataSize} 093 */ 094 public static DataSize ofBytes(long bytes) { 095 return new DataSize(bytes); 096 } 097 098 /** 099 * Obtain a {@link DataSize} representing the specified number of kilobytes. 100 * @param kilobytes the number of kilobytes, positive or negative 101 * @return a {@link DataSize} 102 */ 103 public static DataSize ofKilobytes(long kilobytes) { 104 return new DataSize(Math.multiplyExact(kilobytes, BYTES_PER_KB)); 105 } 106 107 /** 108 * Obtain a {@link DataSize} representing the specified number of megabytes. 109 * @param megabytes the number of megabytes, positive or negative 110 * @return a {@link DataSize} 111 */ 112 public static DataSize ofMegabytes(long megabytes) { 113 return new DataSize(Math.multiplyExact(megabytes, BYTES_PER_MB)); 114 } 115 116 /** 117 * Obtain a {@link DataSize} representing the specified number of gigabytes. 118 * @param gigabytes the number of gigabytes, positive or negative 119 * @return a {@link DataSize} 120 */ 121 public static DataSize ofGigabytes(long gigabytes) { 122 return new DataSize(Math.multiplyExact(gigabytes, BYTES_PER_GB)); 123 } 124 125 /** 126 * Obtain a {@link DataSize} representing the specified number of terabytes. 127 * @param terabytes the number of terabytes, positive or negative 128 * @return a {@link DataSize} 129 */ 130 public static DataSize ofTerabytes(long terabytes) { 131 return new DataSize(Math.multiplyExact(terabytes, BYTES_PER_TB)); 132 } 133 134 /** 135 * Obtain a {@link DataSize} representing an amount in the specified {@link DataUnit}. 136 * @param amount the amount of the size, measured in terms of the unit, 137 * positive or negative 138 * @return a corresponding {@link DataSize} 139 */ 140 public static DataSize of(long amount, DataUnit unit) { 141 Assert.notNull(unit, "Unit must not be null"); 142 return new DataSize(Math.multiplyExact(amount, unit.size().toBytes())); 143 } 144 145 /** 146 * Obtain a {@link DataSize} from a text string such as {@code 12MB} using 147 * {@link DataUnit#BYTES} if no unit is specified. 148 * <p> 149 * Examples: 150 * <pre> 151 * "12KB" -- parses as "12 kilobytes" 152 * "5MB" -- parses as "5 megabytes" 153 * "20" -- parses as "20 bytes" 154 * </pre> 155 * @param text the text to parse 156 * @return the parsed {@link DataSize} 157 * @see #parse(CharSequence, DataUnit) 158 */ 159 public static DataSize parse(CharSequence text) { 160 return parse(text, null); 161 } 162 163 /** 164 * Obtain a {@link DataSize} from a text string such as {@code 12MB} using 165 * the specified default {@link DataUnit} if no unit is specified. 166 * <p> 167 * The string starts with a number followed optionally by a unit matching one of the 168 * supported {@linkplain DataUnit suffixes}. 169 * <p> 170 * Examples: 171 * <pre> 172 * "12KB" -- parses as "12 kilobytes" 173 * "5MB" -- parses as "5 megabytes" 174 * "20" -- parses as "20 kilobytes" (where the {@code defaultUnit} is {@link DataUnit#KILOBYTES}) 175 * </pre> 176 * @param text the text to parse 177 * @return the parsed {@link DataSize} 178 */ 179 public static DataSize parse(CharSequence text, @Nullable DataUnit defaultUnit) { 180 Assert.notNull(text, "Text must not be null"); 181 try { 182 Matcher matcher = PATTERN.matcher(text); 183 Assert.state(matcher.matches(), "Does not match data size pattern"); 184 DataUnit unit = determineDataUnit(matcher.group(2), defaultUnit); 185 long amount = Long.parseLong(matcher.group(1)); 186 return DataSize.of(amount, unit); 187 } 188 catch (Exception ex) { 189 throw new IllegalArgumentException("'" + text + "' is not a valid data size", ex); 190 } 191 } 192 193 private static DataUnit determineDataUnit(String suffix, @Nullable DataUnit defaultUnit) { 194 DataUnit defaultUnitToUse = (defaultUnit != null ? defaultUnit : DataUnit.BYTES); 195 return (StringUtils.hasLength(suffix) ? DataUnit.fromSuffix(suffix) : defaultUnitToUse); 196 } 197 198 /** 199 * Checks if this size is negative, excluding zero. 200 * @return true if this size has a size less than zero bytes 201 */ 202 public boolean isNegative() { 203 return this.bytes < 0; 204 } 205 206 /** 207 * Return the number of bytes in this instance. 208 * @return the number of bytes 209 */ 210 public long toBytes() { 211 return this.bytes; 212 } 213 214 /** 215 * Return the number of kilobytes in this instance. 216 * @return the number of kilobytes 217 */ 218 public long toKilobytes() { 219 return this.bytes / BYTES_PER_KB; 220 } 221 222 /** 223 * Return the number of megabytes in this instance. 224 * @return the number of megabytes 225 */ 226 public long toMegabytes() { 227 return this.bytes / BYTES_PER_MB; 228 } 229 230 /** 231 * Return the number of gigabytes in this instance. 232 * @return the number of gigabytes 233 */ 234 public long toGigabytes() { 235 return this.bytes / BYTES_PER_GB; 236 } 237 238 /** 239 * Return the number of terabytes in this instance. 240 * @return the number of terabytes 241 */ 242 public long toTerabytes() { 243 return this.bytes / BYTES_PER_TB; 244 } 245 246 @Override 247 public int compareTo(DataSize other) { 248 return Long.compare(this.bytes, other.bytes); 249 } 250 251 @Override 252 public String toString() { 253 return String.format("%dB", this.bytes); 254 } 255 256 257 @Override 258 public boolean equals(@Nullable Object other) { 259 if (this == other) { 260 return true; 261 } 262 if (other == null || getClass() != other.getClass()) { 263 return false; 264 } 265 DataSize otherSize = (DataSize) other; 266 return (this.bytes == otherSize.bytes); 267 } 268 269 @Override 270 public int hashCode() { 271 return Long.hashCode(this.bytes); 272 } 273 274}