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}