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