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