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 duration units.
040     */
041    public enum DurationUnit {
042    
043      /**
044       * A day unit.
045       */
046      DAYS((long) 24 * 60 * 60 * 1000, "d", "days"),
047    
048      /**
049       * An hour unit.
050       */
051      HOURS((long) 60 * 60 * 1000, "h", "hours"),
052    
053      /**
054       * A millisecond unit.
055       */
056      MILLI_SECONDS(1L, "ms", "milliseconds"),
057    
058      /**
059       * A minute unit.
060       */
061      MINUTES((long) 60 * 1000, "m", "minutes"),
062    
063      /**
064       * A second unit.
065       */
066      SECONDS(1000L, "s", "seconds"),
067    
068      /**
069       * A week unit.
070       */
071      WEEKS((long) 7 * 24 * 60 * 60 * 1000, "w", "weeks");
072    
073      // A lookup table for resolving a unit from its name.
074      private static final Map<String, DurationUnit> nameToUnit;
075      static {
076        nameToUnit = new HashMap<String, DurationUnit>();
077    
078        for (DurationUnit unit : DurationUnit.values()) {
079          nameToUnit.put(unit.shortName, unit);
080          nameToUnit.put(unit.longName, unit);
081        }
082      }
083    
084    
085    
086      /**
087       * Get the unit corresponding to the provided unit name.
088       *
089       * @param s
090       *          The name of the unit. Can be the abbreviated or long
091       *          name and can contain white space and mixed case
092       *          characters.
093       * @return Returns the unit corresponding to the provided unit name.
094       * @throws IllegalArgumentException
095       *           If the provided name did not correspond to a known
096       *           duration unit.
097       */
098      public static DurationUnit getUnit(String s) throws IllegalArgumentException {
099        DurationUnit unit = nameToUnit.get(s.trim().toLowerCase());
100        if (unit == null) {
101          throw new IllegalArgumentException("Illegal duration unit \"" + s + "\"");
102        }
103        return unit;
104      }
105    
106    
107    
108      /**
109       * Parse the provided duration string and return its equivalent
110       * duration in milliseconds. The duration string must specify the
111       * unit e.g. "10s". This method will parse duration string
112       * representations produced from the {@link #toString(long)} method.
113       * Therefore, a duration can comprise of multiple duration
114       * specifiers, for example <code>1d15m25s</code>.
115       *
116       * @param s
117       *          The duration string to be parsed.
118       * @return Returns the parsed duration in milliseconds.
119       * @throws NumberFormatException
120       *           If the provided duration string could not be parsed.
121       * @see #toString(long)
122       */
123      public static long parseValue(String s) throws NumberFormatException {
124        return parseValue(s, null);
125      }
126    
127    
128    
129      /**
130       * Parse the provided duration string and return its equivalent
131       * duration in milliseconds. This method will parse duration string
132       * representations produced from the {@link #toString(long)} method.
133       * Therefore, a duration can comprise of multiple duration
134       * specifiers, for example <code>1d15m25s</code>.
135       *
136       * @param s
137       *          The duration string to be parsed.
138       * @param defaultUnit
139       *          The default unit to use if there is no unit specified in
140       *          the duration string, or <code>null</code> if the
141       *          string must always contain a unit.
142       * @return Returns the parsed duration in milliseconds.
143       * @throws NumberFormatException
144       *           If the provided duration string could not be parsed.
145       * @see #toString(long)
146       */
147      public static long parseValue(String s, DurationUnit defaultUnit)
148          throws NumberFormatException {
149        String ns = s.trim();
150        if (ns.length() == 0) {
151          throw new NumberFormatException("Empty duration value \"" + s + "\"");
152        }
153    
154        Pattern p1 = Pattern.compile("^\\s*((\\d+)\\s*w)?" + "\\s*((\\d+)\\s*d)?"
155            + "\\s*((\\d+)\\s*h)?" + "\\s*((\\d+)\\s*m)?" + "\\s*((\\d+)\\s*s)?"
156            + "\\s*((\\d+)\\s*ms)?\\s*$", Pattern.CASE_INSENSITIVE);
157        Matcher m1 = p1.matcher(ns);
158        if (m1.matches()) {
159          // Value must be of the form produced by toString(long).
160          String weeks = m1.group(2);
161          String days = m1.group(4);
162          String hours = m1.group(6);
163          String minutes = m1.group(8);
164          String seconds = m1.group(10);
165          String ms = m1.group(12);
166    
167          long duration = 0;
168    
169          try {
170            if (weeks != null) {
171              duration += Long.valueOf(weeks) * WEEKS.getDuration();
172            }
173    
174            if (days != null) {
175              duration += Long.valueOf(days) * DAYS.getDuration();
176            }
177    
178            if (hours != null) {
179              duration += Long.valueOf(hours) * HOURS.getDuration();
180            }
181    
182            if (minutes != null) {
183              duration += Long.valueOf(minutes) * MINUTES.getDuration();
184            }
185    
186            if (seconds != null) {
187              duration += Long.valueOf(seconds) * SECONDS.getDuration();
188            }
189    
190            if (ms != null) {
191              duration += Long.valueOf(ms) * MILLI_SECONDS.getDuration();
192            }
193          } catch (NumberFormatException e) {
194            throw new NumberFormatException("Invalid duration value \"" + s + "\"");
195          }
196    
197          return duration;
198        } else {
199          // Value must be a floating point number followed by a unit.
200          Pattern p2 = Pattern.compile("^\\s*(\\d+(\\.\\d+)?)\\s*(\\w+)?\\s*$");
201          Matcher m2 = p2.matcher(ns);
202    
203          if (!m2.matches()) {
204            throw new NumberFormatException("Invalid duration value \"" + s + "\"");
205          }
206    
207          // Group 1 is the float.
208          double d;
209          try {
210            d = Double.valueOf(m2.group(1));
211          } catch (NumberFormatException e) {
212            throw new NumberFormatException("Invalid duration value \"" + s + "\"");
213          }
214    
215          // Group 3 is the unit.
216          String unitString = m2.group(3);
217          DurationUnit unit;
218          if (unitString == null) {
219            if (defaultUnit == null) {
220              throw new NumberFormatException("Invalid duration value \"" + s
221                  + "\"");
222            } else {
223              unit = defaultUnit;
224            }
225          } else {
226            try {
227              unit = getUnit(unitString);
228            } catch (IllegalArgumentException e) {
229              throw new NumberFormatException("Invalid duration value \"" + s
230                  + "\"");
231            }
232          }
233    
234          return unit.toMilliSeconds(d);
235        }
236      }
237    
238    
239    
240      /**
241       * Returns a string representation of the provided duration. The
242       * string representation can be parsed using the
243       * {@link #parseValue(String)} method. The string representation is
244       * comprised of one or more of the number of weeks, days, hours,
245       * minutes, seconds, and milliseconds. Here are some examples:
246       *
247       * <pre>
248       * toString(0)       // 0 ms
249       * toString(999)     // 999 ms
250       * toString(1000)    // 1 s
251       * toString(1500)    // 1 s 500 ms
252       * toString(3650000) // 1 h 50 s
253       * toString(3700000) // 1 h 1 m 40 s
254       * </pre>
255       *
256       * @param duration
257       *          The duration in milliseconds.
258       * @return Returns a string representation of the provided duration.
259       * @throws IllegalArgumentException
260       *           If the provided duration is negative.
261       * @see #parseValue(String)
262       * @see #parseValue(String, DurationUnit)
263       */
264      public static String toString(long duration) throws IllegalArgumentException {
265        if (duration < 0) {
266          throw new IllegalArgumentException("Negative duration " + duration);
267        }
268    
269        if (duration == 0) {
270          return "0 ms";
271        }
272    
273        DurationUnit[] units = new DurationUnit[] { WEEKS, DAYS, HOURS, MINUTES,
274            SECONDS, MILLI_SECONDS };
275        long remainder = duration;
276        StringBuilder builder = new StringBuilder();
277        boolean isFirst = true;
278        for (DurationUnit unit : units) {
279          long count = remainder / unit.getDuration();
280          if (count > 0) {
281            if (!isFirst) {
282              builder.append(' ');
283            }
284            builder.append(count);
285            builder.append(' ');
286            builder.append(unit.getShortName());
287            remainder = remainder - (count * unit.getDuration());
288            isFirst = false;
289          }
290        }
291        return builder.toString();
292      }
293    
294      // The long name of the unit.
295      private final String longName;
296    
297      // The abbreviation of the unit.
298      private final String shortName;
299    
300      // The size of the unit in milliseconds.
301      private final long sz;
302    
303    
304    
305      // Private constructor.
306      private DurationUnit(long sz, String shortName, String longName) {
307        this.sz = sz;
308        this.shortName = shortName;
309        this.longName = longName;
310      }
311    
312    
313    
314      /**
315       * Converts the specified duration in milliseconds to this unit.
316       *
317       * @param duration
318       *          The duration in milliseconds.
319       * @return Returns milliseconds in this unit.
320       */
321      public double fromMilliSeconds(long duration) {
322        return ((double) duration / sz);
323      }
324    
325    
326    
327      /**
328       * Get the number of milliseconds that this unit represents.
329       *
330       * @return Returns the number of milliseconds that this unit
331       *         represents.
332       */
333      public long getDuration() {
334        return sz;
335      }
336    
337    
338    
339      /**
340       * Get the long name of this unit.
341       *
342       * @return Returns the long name of this unit.
343       */
344      public String getLongName() {
345        return longName;
346      }
347    
348    
349    
350      /**
351       * Get the abbreviated name of this unit.
352       *
353       * @return Returns the abbreviated name of this unit.
354       */
355      public String getShortName() {
356        return shortName;
357      }
358    
359    
360    
361      /**
362       * Converts the specified duration in this unit to milliseconds.
363       *
364       * @param duration
365       *          The duration as a quantity of this unit.
366       * @return Returns the number of milliseconds that the duration
367       *         represents.
368       */
369      public long toMilliSeconds(double duration) {
370        return (long) (sz * duration);
371      }
372    
373    
374    
375      /**
376       * {@inheritDoc}
377       * <p>
378       * This implementation returns the abbreviated name of this duration
379       * unit.
380       */
381      @Override
382      public String toString() {
383        return shortName;
384      }
385    }