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