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