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}