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 }