001 /*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License"). You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at
010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 * Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 * Copyright 2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.admin;
028
029
030
031 import java.util.HashMap;
032 import java.util.Map;
033 import java.util.regex.Matcher;
034 import java.util.regex.Pattern;
035
036
037
038 /**
039 * This enumeration defines various memory size units.
040 */
041 public enum SizeUnit {
042
043 /**
044 * A byte unit.
045 */
046 BYTES(1L, "b", "bytes"),
047
048 /**
049 * A gibi-byte unit.
050 */
051 GIBI_BYTES((long) 1024 * 1024 * 1024, "gib", "gibibytes"),
052
053 /**
054 * A giga-byte unit.
055 */
056 GIGA_BYTES((long) 1000 * 1000 * 1000, "gb", "gigabytes"),
057
058 /**
059 * A kibi-byte unit.
060 */
061 KIBI_BYTES(1024L, "kib", "kibibytes"),
062
063 /**
064 * A kilo-byte unit.
065 */
066 KILO_BYTES(1000L, "kb", "kilobytes"),
067
068 /**
069 * A mebi-byte unit.
070 */
071 MEBI_BYTES((long) 1024 * 1024, "mib", "mebibytes"),
072
073 /**
074 * A mega-byte unit.
075 */
076 MEGA_BYTES((long) 1000 * 1000, "mb", "megabytes"),
077
078 /**
079 * A tebi-byte unit.
080 */
081 TEBI_BYTES((long) 1024 * 1024 * 1024 * 1024, "tib", "tebibytes"),
082
083 /**
084 * A tera-byte unit.
085 */
086 TERA_BYTES((long) 1000 * 1000 * 1000 * 1000, "tb", "terabytes");
087
088 // A lookup table for resolving a unit from its name.
089 private static final Map<String, SizeUnit> nameToUnit;
090 static {
091 nameToUnit = new HashMap<String, SizeUnit>();
092
093 for (SizeUnit unit : SizeUnit.values()) {
094 nameToUnit.put(unit.shortName, unit);
095 nameToUnit.put(unit.longName, unit);
096 }
097 }
098
099
100
101 /**
102 * Gets the best-fit unit for the specified number of bytes. The
103 * returned unit will be able to represent the number of bytes using
104 * a decimal number comprising of an integer part which is greater
105 * than zero. Bigger units are chosen in preference to smaller units
106 * and binary units are only returned if they are an exact fit. If
107 * the number of bytes is zero then the {@link #BYTES} unit is
108 * always returned. For example:
109 *
110 * <pre>
111 * getBestFitUnit(0) // BYTES
112 * getBestFitUnit(999) // BYTES
113 * getBestFitUnit(1000) // KILO_BYTES
114 * getBestFitUnit(1024) // KIBI_BYTES
115 * getBestFitUnit(1025) // KILO_BYTES
116 * getBestFitUnit(999999) // KILO_BYTES
117 * getBestFitUnit(1000000) // MEGA_BYTES
118 * </pre>
119 *
120 * @param bytes
121 * The number of bytes.
122 * @return Returns the best fit unit.
123 * @throws IllegalArgumentException
124 * If <code>bytes</code> is negative.
125 * @see #getBestFitUnitExact(long)
126 */
127 public static SizeUnit getBestFitUnit(long bytes)
128 throws IllegalArgumentException {
129 if (bytes < 0) {
130 throw new IllegalArgumentException("negative number of bytes: " + bytes);
131 } else if (bytes == 0) {
132 // Always use bytes for zero values.
133 return BYTES;
134 } else {
135 // Determine best fit: prefer non-binary units unless binary
136 // fits exactly.
137 SizeUnit[] nonBinary = new SizeUnit[] { TERA_BYTES, GIGA_BYTES,
138 MEGA_BYTES, KILO_BYTES };
139 SizeUnit[] binary = new SizeUnit[] { TEBI_BYTES, GIBI_BYTES, MEBI_BYTES,
140 KIBI_BYTES };
141
142 for (int i = 0; i < nonBinary.length; i++) {
143 if ((bytes % binary[i].getSize()) == 0) {
144 return binary[i];
145 } else if ((bytes / nonBinary[i].getSize()) > 0) {
146 return nonBinary[i];
147 }
148 }
149
150 return BYTES;
151 }
152 }
153
154
155
156 /**
157 * Gets the best-fit unit for the specified number of bytes which
158 * can represent the provided value using an integral value. Bigger
159 * units are chosen in preference to smaller units. If the number of
160 * bytes is zero then the {@link #BYTES} unit is always returned.
161 * For example:
162 *
163 * <pre>
164 * getBestFitUnitExact(0) // BYTES
165 * getBestFitUnitExact(999) // BYTES
166 * getBestFitUnitExact(1000) // KILO_BYTES
167 * getBestFitUnitExact(1024) // KIBI_BYTES
168 * getBestFitUnitExact(1025) // BYTES
169 * getBestFitUnitExact(999999) // BYTES
170 * getBestFitUnitExact(1000000) // MEGA_BYTES
171 * </pre>
172 *
173 * @param bytes
174 * The number of bytes.
175 * @return Returns the best fit unit can represent the provided
176 * value using an integral value.
177 * @throws IllegalArgumentException
178 * If <code>bytes</code> is negative.
179 * @see #getBestFitUnit(long)
180 */
181 public static SizeUnit getBestFitUnitExact(long bytes)
182 throws IllegalArgumentException {
183 if (bytes < 0) {
184 throw new IllegalArgumentException("negative number of bytes: " + bytes);
185 } else if (bytes == 0) {
186 // Always use bytes for zero values.
187 return BYTES;
188 } else {
189 // Determine best fit.
190 SizeUnit[] units = new SizeUnit[] { TEBI_BYTES, TERA_BYTES, GIBI_BYTES,
191 GIGA_BYTES, MEBI_BYTES, MEGA_BYTES, KIBI_BYTES, KILO_BYTES };
192
193 for (SizeUnit unit : units) {
194 if ((bytes % unit.getSize()) == 0) {
195 return unit;
196 }
197 }
198
199 return BYTES;
200 }
201 }
202
203
204
205 /**
206 * Get the unit corresponding to the provided unit name.
207 *
208 * @param s
209 * The name of the unit. Can be the abbreviated or long
210 * name and can contain white space and mixed case
211 * characters.
212 * @return Returns the unit corresponding to the provided unit name.
213 * @throws IllegalArgumentException
214 * If the provided name did not correspond to a known
215 * memory size unit.
216 */
217 public static SizeUnit getUnit(String s) throws IllegalArgumentException {
218 SizeUnit unit = nameToUnit.get(s.trim().toLowerCase());
219 if (unit == null) {
220 throw new IllegalArgumentException("Illegal memory size unit \"" + s
221 + "\"");
222 }
223 return unit;
224 }
225
226
227
228 /**
229 * Parse the provided size string and return its equivalent size in
230 * bytes. The size string must specify the unit e.g. "10kb".
231 *
232 * @param s
233 * The size string to be parsed.
234 * @return Returns the parsed duration in bytes.
235 * @throws NumberFormatException
236 * If the provided size string could not be parsed.
237 */
238 public static long parseValue(String s) throws NumberFormatException {
239 return parseValue(s, null);
240 }
241
242
243
244 /**
245 * Parse the provided size string and return its equivalent size in
246 * bytes.
247 *
248 * @param s
249 * The size string to be parsed.
250 * @param defaultUnit
251 * The default unit to use if there is no unit specified in
252 * the size string, or <code>null</code> if the string
253 * must always contain a unit.
254 * @return Returns the parsed size in bytes.
255 * @throws NumberFormatException
256 * If the provided size string could not be parsed.
257 */
258 public static long parseValue(String s, SizeUnit defaultUnit)
259 throws NumberFormatException {
260 // Value must be a floating point number followed by a unit.
261 Pattern p = Pattern.compile("^\\s*(\\d+(\\.\\d+)?)\\s*(\\w+)?\\s*$");
262 Matcher m = p.matcher(s);
263
264 if (!m.matches()) {
265 throw new NumberFormatException("Invalid size value \"" + s + "\"");
266 }
267
268 // Group 1 is the float.
269 double d;
270 try {
271 d = Double.valueOf(m.group(1));
272 } catch (NumberFormatException e) {
273 throw new NumberFormatException("Invalid size value \"" + s + "\"");
274 }
275
276 // Group 3 is the unit.
277 String unitString = m.group(3);
278 SizeUnit unit;
279 if (unitString == null) {
280 if (defaultUnit == null) {
281 throw new NumberFormatException("Invalid size value \"" + s + "\"");
282 } else {
283 unit = defaultUnit;
284 }
285 } else {
286 try {
287 unit = getUnit(unitString);
288 } catch (IllegalArgumentException e) {
289 throw new NumberFormatException("Invalid size value \"" + s + "\"");
290 }
291 }
292
293 return unit.toBytes(d);
294 }
295
296 // The long name of the unit.
297 private final String longName;
298
299 // The abbreviation of the unit.
300 private final String shortName;
301
302 // The size of the unit in bytes.
303 private final long sz;
304
305
306
307 // Private constructor.
308 private SizeUnit(long sz, String shortName, String longName) {
309 this.sz = sz;
310 this.shortName = shortName;
311 this.longName = longName;
312 }
313
314
315
316 /**
317 * Converts the specified size in bytes to this unit.
318 *
319 * @param amount
320 * The size in bytes.
321 * @return Returns size in this unit.
322 */
323 public double fromBytes(long amount) {
324 return ((double) amount / sz);
325 }
326
327
328
329 /**
330 * Get the long name of this unit.
331 *
332 * @return Returns the long name of this unit.
333 */
334 public String getLongName() {
335 return longName;
336 }
337
338
339
340 /**
341 * Get the abbreviated name of this unit.
342 *
343 * @return Returns the abbreviated name of this unit.
344 */
345 public String getShortName() {
346 return shortName;
347 }
348
349
350
351 /**
352 * Get the number of bytes that this unit represents.
353 *
354 * @return Returns the number of bytes that this unit represents.
355 */
356 public long getSize() {
357 return sz;
358 }
359
360
361
362 /**
363 * Converts the specified size in this unit to bytes.
364 *
365 * @param amount
366 * The size as a quantity of this unit.
367 * @return Returns the number of bytes that the size represents.
368 *
369 * @throws NumberFormatException
370 * If the provided size exceeded long.MAX_VALUE.
371 */
372 public long toBytes(double amount) throws NumberFormatException {
373 double value = sz * amount;
374 if (value > Long.MAX_VALUE)
375 {
376 throw new NumberFormatException
377 ("number too big (exceeded long.MAX_VALUE");
378 }
379 return (long) (value);
380 }
381
382
383
384 /**
385 * {@inheritDoc}
386 * <p>
387 * This implementation returns the abbreviated name of this size
388 * unit.
389 */
390 @Override
391 public String toString() {
392 return shortName;
393 }
394 }