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