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