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