001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.util.time;
018
019import java.util.Locale;
020import java.util.Locale.Category;
021import java.util.regex.Matcher;
022import java.util.regex.Pattern;
023import org.apache.wicket.util.string.StringValue;
024import org.apache.wicket.util.string.StringValueConversionException;
025import org.apache.wicket.util.thread.ICode;
026import org.slf4j.Logger;
027
028
029/**
030 * A <code>Duration</code> is an immutable length of time stored as a number of milliseconds.
031 * Various factory and conversion methods are available for convenience.
032 * <p>
033 * These static factory methods allow easy construction of value objects using either long values
034 * like <code>seconds(2034)</code> or <code>hours(3)</code>:
035 * <p>
036 * <ul>
037 * <li><code>Duration.milliseconds(long)</code>
038 * <li><code>Duration.seconds(int)</code>
039 * <li><code>Duration.minutes(int)</code>
040 * <li><code>Duration.hours(int)</code>
041 * <li><code>Duration.days(int)</code>
042 * </ul>
043 * <p>
044 * ...or double-precision floating point values like <code>days(3.2)</code>:
045 * <p>
046 * <ul>
047 * <li><code>Duration.milliseconds(double)</code>
048 * <li><code>Duration.seconds(double)</code>
049 * <li><code>Duration.minutes(double)</code>
050 * <li><code>Duration.hours(double)</code>
051 * <li><code>Duration.days(double)</code>
052 * </ul>
053 * <p>
054 * In the case of <code>milliseconds(double)</code>, the value will be rounded off to the nearest
055 * integral millisecond using <code>Math.round()</code>.
056 * <p>
057 * The precise number of milliseconds represented by a <code>Duration</code> object can be retrieved
058 * by calling the <code>getMilliseconds</code> method. The value of a <code>Duration</code> object
059 * in a given unit like days or hours can be retrieved by calling one of the following unit methods,
060 * each of which returns a double-precision floating point number:
061 * <p>
062 * <ul>
063 * <li><code>seconds()</code>
064 * <li><code>minutes()</code>
065 * <li><code>hours()</code>
066 * <li><code>days()</code>
067 * </ul>
068 * <p>
069 * Values can be added and subtracted using the <code>add(Duration)</code> and
070 * <code>subtract(Duration)</code> methods, each of which returns a new immutable
071 * <code>Duration</code> object.
072 * <p>
073 * <code>String</code> values can be converted to <code>Duration</code> objects using the static
074 * <code>valueOf</code> factory methods. The <code>String</code> format is the opposite of the one
075 * created by <code>toString()</code>, which converts a <code>Duration</code> object to a readable
076 * form, such as "3.2 hours" or "32.5 minutes". Valid units are: milliseconds, seconds, minutes
077 * hours and days. Correct English plural forms are used in creating <code>String</code> values and
078 * are parsed as well. The <code>Locale</code> is respected and "," will be used instead of "." in
079 * the Eurozone.
080 * <p>
081 * The benchmark method will "benchmark" a <code>Runnable</code> or an {@link ICode} implementing
082 * object, returning a <code>Duration</code> object that represents the amount of time elapsed in
083 * running the code.
084 * <p>
085 * Finally, the <code>sleep</code> method will sleep for the value of a <code>Duration</code>.
086 * 
087 * @author Jonathan Locke
088 * @since 1.2.6
089 * 
090 * @deprecated Since Wicket 9 this class is obsolete and no more used. It will be removed in Wicket 10. Use {@link java.time.Duration} instead
091 */
092@Deprecated
093public class Duration extends AbstractTimeValue
094{
095        private static final long serialVersionUID = 1L;
096
097        /** Constant for maximum duration. */
098        public static final Duration MAXIMUM = milliseconds(Long.MAX_VALUE);
099
100        /** Constant for no duration. */
101        public static final Duration NONE = milliseconds(0);
102
103        /** Constant for one day. */
104        public static final Duration ONE_DAY = days(1);
105
106        /** Constant for one hour. */
107        public static final Duration ONE_HOUR = hours(1);
108
109        /** Constant for on minute. */
110        public static final Duration ONE_MINUTE = minutes(1);
111
112        /** Constant for one second. */
113        public static final Duration ONE_SECOND = seconds(1);
114
115        /** Constant for one week. */
116        public static final Duration ONE_WEEK = days(7);
117
118        /** pattern to match strings */
119        private static final Pattern pattern = Pattern.compile(
120                "([0-9]+([.,][0-9]+)?)\\s+(millisecond|second|minute|hour|day)s?", Pattern.CASE_INSENSITIVE);
121
122        /**
123         * Benchmark the given command.
124         * 
125         * @param code
126         *            an <code>ICode</code>
127         * @param log
128         *            optional logger to use with errors and exceptions
129         * @return the <code>Time</code> value it took to run the code
130         */
131        public static Duration benchmark(final ICode code, final Logger log)
132        {
133                // Get time before running code
134                final Time start = Time.now();
135
136                // Run the code
137                code.run(log);
138
139                // Return the difference
140                return Time.now().subtract(start);
141        }
142
143        /**
144         * Benchmark the given command.
145         * 
146         * @param code
147         *            a <code>Runnable</code>
148         * @return the <code>Time</code> value it took to run the code
149         */
150        public static Duration benchmark(final Runnable code)
151        {
152                // Get time before running code
153                final Time start = Time.now();
154
155                // Run code
156                code.run();
157
158                // Return the difference
159                return Time.now().subtract(start);
160        }
161
162        /**
163         * Retrieves the <code>Duration</code> based on days.
164         * 
165         * @param days
166         *            days <code>double</code> value
167         * @return the <code>Duration</code> based on days
168         */
169        public static Duration days(final double days)
170        {
171                return hours(24.0 * days);
172        }
173
174        /**
175         * Retrieves the <code>Duration</code> based on days.
176         * 
177         * @param days
178         *            days <code>int</code> value
179         * @return the <code>Duration</code> based on days
180         */
181        public static Duration days(final int days)
182        {
183                return hours(24 * days);
184        }
185
186        /**
187         * Calculates the amount of time elapsed since start time.
188         * 
189         * @param start
190         *            the start <code>Time</code>
191         * @return the elapsed period as a <code>Duration</code>
192         * @throws IllegalStateException
193         *             if start <code>Time</code> is in the future
194         */
195        public static Duration elapsed(final Time start)
196        {
197                return start.elapsedSince();
198        }
199
200        /**
201         * Retrieves the <code>Duration</code> based on hours.
202         * 
203         * @param hours
204         *            hours <code>double</code> value
205         * @return the <code>Duration</code> based on hours
206         */
207        public static Duration hours(final double hours)
208        {
209                return minutes(60.0 * hours);
210        }
211
212        /**
213         * Retrieves the <code>Duration</code> based on hours.
214         * 
215         * @param hours
216         *            hours <code>int</code> value
217         * @return the <code>Duration</code> based on hours
218         */
219        public static Duration hours(final int hours)
220        {
221                return minutes(60 * hours);
222        }
223
224        /**
225         * Retrieves the <code>Duration</code> based on milliseconds.
226         * 
227         * @param milliseconds
228         *            milliseconds <code>double</code> value
229         * @return the <code>Duration</code> based on milliseconds
230         */
231        public static Duration milliseconds(final double milliseconds)
232        {
233                return milliseconds(Math.round(milliseconds));
234        }
235
236        /**
237         * Retrieves the <code>Duration</code> based on milliseconds.
238         * 
239         * @param milliseconds
240         *            milliseconds <code>long</code> value
241         * @return the <code>Duration</code> based on milliseconds
242         */
243        public static Duration milliseconds(final long milliseconds)
244        {
245                return new Duration(milliseconds);
246        }
247
248        /**
249         * Retrieves the <code>Duration</code> based on minutes.
250         * 
251         * @param minutes
252         *            minutes <code>double</code> value
253         * @return the <code>Duration</code> based on minutes
254         */
255        public static Duration minutes(final double minutes)
256        {
257                return seconds(60.0 * minutes);
258        }
259
260        /**
261         * Retrieves the <code>Duration</code> based on minutes.
262         * 
263         * @param minutes
264         *            minutes <code>int</code> value
265         * @return the <code>Duration</code> based on minutes
266         */
267        public static Duration minutes(final int minutes)
268        {
269                return seconds(60 * minutes);
270        }
271
272        /**
273         * Retrieves the <code>Duration</code> based on seconds.
274         * 
275         * @param seconds
276         *            seconds <code>double</code> value
277         * @return the <code>Duration</code> based on seconds
278         */
279        public static Duration seconds(final double seconds)
280        {
281                return milliseconds(seconds * 1000.0);
282        }
283
284        /**
285         * Retrieves the <code>Duration</code> based on seconds.
286         * 
287         * @param seconds
288         *            seconds <code>int</code> value
289         * @return the <code>Duration</code> based on seconds
290         */
291        public static Duration seconds(final int seconds)
292        {
293                return milliseconds(seconds * 1000L);
294        }
295
296        /**
297         * Retrieves the given <code>long</code> as a <code>Duration</code>.
298         * 
299         * @param time
300         *            the duration <code>long</code> value in milliseconds
301         * @return the <code>Duration</code> value
302         */
303        public static Duration valueOf(final long time)
304        {
305                return new Duration(time);
306        }
307
308        /**
309         * Converts the given <code>String</code> to a new <code>Duration</code> object. The string can
310         * take the form of a floating point number followed by a number of milliseconds, seconds,
311         * minutes, hours or days. For example "6 hours" or "3.4 days". Parsing is case-insensitive.
312         * 
313         * @param string
314         *            a <code>String</code> to parse
315         * @return the <code>Duration</code> value of the given <code>String</code>
316         * @throws StringValueConversionException
317         */
318        public static Duration valueOf(final String string) throws StringValueConversionException
319        {
320                return valueOf(string, Locale.getDefault(Locale.Category.FORMAT));
321        }
322
323        /**
324         * Converts the given <code>String</code> to a new <code>Duration</code> object. The string can
325         * take the form of a floating point number followed by a number of milliseconds, seconds,
326         * minutes, hours or days. For example "6 hours" or "3.4 days". Parsing is case-insensitive.
327         * 
328         * @param string
329         *            a <code>String</code> to parse
330         * @param locale
331         *            the <code>Locale</code> used for parsing
332         * @return the <code>Duration</code> value of the given <code>String</code>
333         * @throws StringValueConversionException
334         */
335        public static Duration valueOf(final String string, final Locale locale)
336                throws StringValueConversionException
337        {
338                final Matcher matcher = pattern.matcher(string);
339
340                if (matcher.matches())
341                {
342                        final double value = StringValue.valueOf(matcher.group(1), locale).toDouble();
343                        final String units = matcher.group(3);
344
345                        if (units.equalsIgnoreCase("millisecond"))
346                        {
347                                return milliseconds(value);
348                        }
349                        else if (units.equalsIgnoreCase("second"))
350                        {
351                                return seconds(value);
352                        }
353                        else if (units.equalsIgnoreCase("minute"))
354                        {
355                                return minutes(value);
356                        }
357                        else if (units.equalsIgnoreCase("hour"))
358                        {
359                                return hours(value);
360                        }
361                        else if (units.equalsIgnoreCase("day"))
362                        {
363                                return days(value);
364                        }
365                        else
366                        {
367                                throw new StringValueConversionException("Unrecognized units: " + string);
368                        }
369                }
370                else
371                {
372                        throw new StringValueConversionException("Unable to parse duration: " + string);
373                }
374        }
375
376        /**
377         * Private constructor forces use of static factory methods.
378         * 
379         * @param milliseconds
380         *            number of milliseconds in this <code>Duration</code>
381         */
382        protected Duration(final long milliseconds)
383        {
384                super(milliseconds);
385        }
386
387        /**
388         * Adds a given <code>Duration</code> to this <code>Duration</code>.
389         * 
390         * @param duration
391         *            the <code>Duration</code> to add
392         * @return the sum of the <code>Duration</code>s
393         */
394        public Duration add(final Duration duration)
395        {
396                return valueOf(getMilliseconds() + duration.getMilliseconds());
397        }
398
399        /**
400         * Retrieves the number of days of the current <code>Duration</code>.
401         * 
402         * @return number of days of the current <code>Duration</code>
403         */
404        public final double days()
405        {
406                return hours() / 24.0;
407        }
408
409        /**
410         * Retrieves the number of hours of the current <code>Duration</code>.
411         * 
412         * @return number of hours of the current <code>Duration</code>
413         */
414        public final double hours()
415        {
416                return minutes() / 60.0;
417        }
418
419        /**
420         * Retrieves the number of minutes of the current <code>Duration</code>.
421         * 
422         * @return number of minutes of the current <code>Duration</code>
423         */
424        public final double minutes()
425        {
426                return seconds() / 60.0;
427        }
428
429        /**
430         * Retrieves the number of seconds of the current <code>Duration</code>.
431         * 
432         * @return number of seconds of the current <code>Duration</code>
433         */
434        public final double seconds()
435        {
436                return getMilliseconds() / 1000.0;
437        }
438
439        /**
440         * Sleeps for the current <code>Duration</code>.
441         */
442        public final void sleep()
443        {
444                if (getMilliseconds() > 0)
445                {
446                        try
447                        {
448                                Thread.sleep(getMilliseconds());
449                        }
450                        catch (InterruptedException e)
451                        {
452                                // Ignored
453                        }
454                }
455        }
456
457        /**
458         * Subtracts a given <code>Duration</code> from this <code>Duration</code>.
459         * 
460         * @param that
461         *            the <code>Duration</code> to subtract
462         * @return this <code>Duration</code> minus that <code>Duration</code>
463         */
464        public Duration subtract(final Duration that)
465        {
466                return valueOf(getMilliseconds() - that.getMilliseconds());
467        }
468
469        /**
470         * Wait for this duration on the given monitor
471         * 
472         * @param object
473         *            The monitor to wait on
474         */
475        public void wait(final Object object)
476        {
477                try
478                {
479                        object.wait(getMilliseconds());
480                }
481                catch (InterruptedException e)
482                {
483                        throw new RuntimeException(e);
484                }
485        }
486
487        /**
488         * Retrieves the <code>String</code> representation of this <code>Duration</code> in days,
489         * hours, minutes, seconds or milliseconds, as appropriate. Uses the default <code>Locale</code>
490         * .
491         * 
492         * @return a <code>String</code> representation
493         */
494        @Override
495        public String toString()
496        {
497                return toString(Locale.getDefault(Category.FORMAT));
498        }
499
500        /**
501         * Retrieves the <code>String</code> representation of this <code>Duration</code> in days,
502         * hours, minutes, seconds or milliseconds, as appropriate.
503         * 
504         * @param locale
505         *            a <code>Locale</code>
506         * @return a <code>String</code> representation
507         */
508        public String toString(final Locale locale)
509        {
510                if (getMilliseconds() >= 0)
511                {
512                        if (days() >= 1.0)
513                        {
514                                return unitString(days(), "day", locale);
515                        }
516
517                        if (hours() >= 1.0)
518                        {
519                                return unitString(hours(), "hour", locale);
520                        }
521
522                        if (minutes() >= 1.0)
523                        {
524                                return unitString(minutes(), "minute", locale);
525                        }
526
527                        if (seconds() >= 1.0)
528                        {
529                                return unitString(seconds(), "second", locale);
530                        }
531
532                        return unitString(getMilliseconds(), "millisecond", locale);
533                }
534                else
535                {
536                        return "N/A";
537                }
538        }
539
540        /**
541         * Builds a {@link java.time.Duration} out of a wicket {@link Duration}.
542         *
543         * @return returns a {@link java.time.Duration}
544         */
545        public java.time.Duration toJavaDuration()
546        {
547                return java.time.Duration.ofMillis(getMilliseconds());
548        }
549
550        /**
551         * Converts a value to a unit-suffixed value, taking care of English singular/plural suffix.
552         * 
553         * @param value
554         *            a <code>double</code> value to format
555         * @param units
556         *            the units to apply singular or plural suffix to
557         * @param locale
558         *            the <code>Locale</code>
559         * @return a <code>String</code> representation
560         */
561        private String unitString(final double value, final String units, final Locale locale)
562        {
563                return StringValue.valueOf(value, locale) + " " + units + ((value > 1.0) ? "s" : "");
564        }
565}