View Javadoc

1   /*
2    * Copyright (C) 2003-2004 Christian Siefkes <christian@siefkes.net>.
3    * Development of this software is supported by the German Research Society,
4    * Berlin-Brandenburg Graduate School in Distributed Information Systems
5    * (DFG grant no. GRK 316).
6    *
7    * This library is free software; you can redistribute it and/or
8    * modify it under the terms of the GNU Lesser General Public
9    * License as published by the Free Software Foundation; either
10   * version 2.1 of the License, or (at your option) any later version.
11   *
12   * This library is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15   * Lesser General Public License for more details.
16   *
17   * You should have received a copy of the GNU Lesser General Public
18   * License along with this library; if not, visit
19   * http://www.gnu.org/licenses/lgpl.html or write to the Free Software
20   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
21   */
22  package de.fu_berlin.ties.util;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.lang.reflect.Constructor;
27  import java.lang.reflect.InvocationTargetException;
28  import java.text.NumberFormat;
29  import java.util.Arrays;
30  import java.util.Locale;
31  import java.util.Random;
32  
33  import org.apache.commons.lang.BooleanUtils;
34  import org.apache.commons.lang.StringUtils;
35  import org.apache.commons.lang.builder.StandardToStringStyle;
36  import org.apache.commons.lang.builder.ToStringBuilder;
37  import org.apache.log.Hierarchy;
38  import org.apache.log.LogTarget;
39  import org.apache.log.Logger;
40  import org.apache.log.Priority;
41  import org.apache.log.filter.PriorityFilter;
42  import org.apache.log.format.ExtendedPatternFormatter;
43  import org.apache.log.output.io.FileTarget;
44  import org.apache.log.output.io.StreamTarget;
45  
46  import de.fu_berlin.ties.TiesConfiguration;
47  
48  /***
49   * A static class that provides general utility methods.
50   * No instances of this class can be created, only the static members
51   * should be used.
52   *
53   * @author Christian Siefkes
54   * @version $Revision: 1.27 $, $Date: 2004/12/06 17:59:41 $, $Author: siefkes $
55   */
56  public final class Util {
57  
58      /***
59       * Filter used by the logger to write messages to standard output.
60       */
61      private static final PriorityFilter STD_FILTERED;
62  
63      /***
64       * The logger used in the TIE system. The
65       * <a href="http://avalon.apache.org/logkit/">Avalon LogKit</a> (bundled
66       * with Velocity) is used for logging. All log messages are written to a log
67       * file; message with priority <code>INFO</code> or higher are also written
68       * to standard out.
69       */
70      public static final Logger LOG =
71          Hierarchy.getDefaultHierarchy().getLoggerFor("ties");
72  
73      /***
74       * Configuration key: Only messages with this priority or higher are logged.
75       */
76      public static final String CONFIG_LOGGER_LOG = "log.log";
77  
78      /***
79       * Configuration key: Only messages  with this priority or higher are
80       * written to standard output (but only if covered by
81       * {@link #CONFIG_LOGGER_LOG}).
82       */
83      public static final String CONFIG_LOGGER_SHOW = "log.show";
84  
85      /***
86       * String recognized as Not-a-Number when parsing floating-point numbers:
87       * {@value} (ignoring case).
88       */
89      public static final String NAN = "NaN";
90  
91      /***
92       * Number format used by {@link #showDuration(long)} to format seconds.
93       * This is meant for log methods so we always use the English locale.
94       * Should be synchronized on itself.
95       */
96      private static final NumberFormat SEC_FORMAT =
97          NumberFormat.getNumberInstance(Locale.ENGLISH);
98  
99      /***
100      * Number format used by the {@link #format(double) format} methods.
101      * Uses the English locale. Should be synchronized on itself.
102      */
103     private static final NumberFormat NUM_FORMAT =
104         NumberFormat.getNumberInstance(Locale.ENGLISH);
105 
106     /***
107      * Static initializer: modify style used by {@link ToStringBuilder} and
108      * configure number format + logger.
109      */
110     static {
111         // modify ToStringStyle: short class names, no hash code, separator: ";"
112         final StandardToStringStyle tiesStyle = new StandardToStringStyle();
113         tiesStyle.setUseShortClassName(true);
114         tiesStyle.setUseIdentityHashCode(false);
115         tiesStyle.setFieldSeparator(";");
116         ToStringBuilder.setDefaultStyle(tiesStyle);
117 
118         // configure number format: round to milliseconds
119         SEC_FORMAT.setMaximumFractionDigits(3);
120 
121         // configure logger:
122         // currently no category is printed as "ties" is used globally
123         final String basePattern =
124             "%{priority}: %{message}//n%{throwable}";
125         // full pattern also showns time + thread name
126         final String fullPattern =
127             "%{time:yyyy-MM-dd HH:mm:ss.SSS} [%{thread}] " + basePattern;
128 
129         // log everything to "ties.log" in full format
130         final String logFileName = "ties.log";
131         LogTarget fileLog;
132         IOException caughtException = null;
133         Util.LOG.setPriority(Priority.DEBUG);
134 
135         // INFO and higher goes to standard out in compact format
136         final LogTarget stdRaw = new StreamTarget(System.out,
137             new ExtendedPatternFormatter(basePattern));
138         STD_FILTERED = new PriorityFilter(Priority.INFO);
139         STD_FILTERED.addTarget(stdRaw);
140 
141         try {
142             fileLog = new FileTarget(new File(logFileName), false,
143                 new ExtendedPatternFormatter(fullPattern));
144         } catch (IOException ioe) {
145             // delay logging until format is ok
146             caughtException = ioe;
147             fileLog = null;
148         }
149 
150         final LogTarget[] targets;
151         if (fileLog != null) {
152             targets = new LogTarget[] {fileLog, STD_FILTERED};
153         } else {
154             // file cannot be written -- log everything to stdout instead
155             targets = new LogTarget[] {stdRaw};
156         }
157 
158         Util.LOG.setLogTargets(targets);
159 
160         if (caughtException != null) {
161             Util.LOG.error("Log initialization: could not open " + logFileName
162                 + " -- will log to standard out only: " + caughtException);
163         }
164     }
165 
166     /***
167      * Appends a non-negative number to a string buffer.
168      *
169      * @param appender the string buffer to append to
170      * @param number the number to append; must not be negative
171      * @param minDigits the minimum number of digits to print
172      * @param prefix a prefix to print in front of the number, if not
173      * <code>null</code>
174      * @param appendAlways if <code>true</code>, the number is always printed;
175      * otherwise only positive numbers are printed -- in this case the appender
176      * is not modified if the number is 0
177      * @throws IllegalArgumentException if <code>number &lt; 0</code>
178      */
179     public static void appendPositiveNumber(final StringBuffer appender,
180             final long number, final int minDigits, final String prefix,
181             final boolean appendAlways)
182     throws IllegalArgumentException {
183         // no support for negative numbers
184         ensureNonNegative(number, "Number");
185 
186         if (appendAlways || (number != 0)) {
187             // append prefix if given
188             if (StringUtils.isNotEmpty(prefix)) {
189                 appender.append(prefix);
190             }
191 
192             // convert number to a string
193             final String numberString = Long.toString(number);
194             final int lengthDiff = minDigits - numberString.length();
195 
196             // left-pad with zeros, if necessary
197             for (int i = 0; i < lengthDiff; i++) {
198                 appender.append('0');
199             }
200 
201             // append actual number
202             appender.append(numberString);
203         }
204     }
205 
206     /***
207      * Converts an object into a boolean primitive. For a {@link Boolean}
208      * object, the contained boolean primitive is returned.
209      *
210      * <p>Otherwise the object's <code>toString()</code> output is parsed
211      * calling {@link BooleanUtils#toBooleanObject(java.lang.String)}, i.e.
212      * The strings "true", "on", and "yes" are accepted for a <code>true</code>
213      * value; "false", "off", and "no" are accepted for <code>false</code>;
214      * case and surrounding whitespace are ignored. An exception is thrown for
215      * all other strings.
216      *
217      * <p>If the specified object is <code>null</code>, <code>false</code>
218      * is returned.
219      *
220      * @param obj the object to check
221      * @return the converted boolean
222      * @throws IllegalArgumentException if the object's <code>toString()</code>
223      * output cannot be parsed as a boolean
224      */
225     public static boolean asBoolean(final Object obj)
226             throws IllegalArgumentException {
227         final boolean result;
228         if (obj instanceof Boolean) {
229             // use contained value
230             result = ((Boolean) obj).booleanValue();
231         } else if (obj != null) {
232             // parse string value
233             final String raw = obj.toString().trim();
234             final Boolean parsed = BooleanUtils.toBooleanObject(raw);
235 
236             if (parsed == null) {
237                 // invalid string value
238                 throw new IllegalArgumentException("String '" + raw
239                         + "' does not specify a boolean value");
240             } else {
241                 result = parsed.booleanValue();
242             }
243         } else {
244             // null object: return false
245             result = false;
246         }
247         return result;
248     }
249 
250     /***
251      * Converts an object into a byte primitive. For a {@link Number}
252      * object, the contained number is converted to a byte -- this may involve
253      * rounding or truncation.
254      * <p>Otherwise the object's <code>toString()</code> output is parsed
255      * using the {@link Byte#decode(java.lang.String)} method.
256      * <p>If the specified object is <code>null</code>, -1 is returned.
257      *
258      * @param obj the object to check
259      * @return the converted byte
260      * @throws NumberFormatException if the object's <code>toString()</code>
261      * output does not contain a parsable byte
262      */
263     public static byte asByte(final Object obj) throws NumberFormatException {
264         final byte result;
265         if (obj instanceof Number) {
266             // use contained number
267             result = ((Number) obj).byteValue();
268         } else if (obj != null) {
269             // parse string value
270             final String raw = obj.toString().trim();
271             result = Byte.decode(raw).byteValue();
272         } else {
273             // null object
274             result = -1;
275         }
276         return result;
277     }
278 
279     /***
280      * Converts an object into a char primitive. For a {@link Character}
281      * object, the contained char primitive is returned.
282      * <p>Otherwise the first non-whitespace character of the
283      * object's <code>toString()</code> output is returned. An exception is
284      * thrown if the string is empty or contains only whitespace.
285      * <p>If the specified object is <code>null</code>,
286      * {@link Character#MIN_VALUE} (the smallest character value) is returned.
287      *
288      * @param obj the object to check
289      * @return the converted char
290      * @throws IndexOutOfBoundsException if the object's <code>toString()</code>
291      * output is the empty string (after trimming outer whitespace)
292      */
293     public static char asChar(final Object obj)
294             throws IndexOutOfBoundsException {
295         final char result;
296         if (obj instanceof Character) {
297             // use contained character
298             result = ((Character) obj).charValue();
299         } else if (obj != null) {
300             // return first character of trimmed string value
301             final String raw = obj.toString().trim();
302             result = raw.charAt(0);
303         } else {
304             // null object: return smallest character value
305             result = Character.MIN_VALUE;
306         }
307         return result;
308     }
309 
310     /***
311      * Converts an object into a double primitive. For a {@link Number}
312      * object, the contained number is converted to a double -- this may involve
313      * rounding.
314      *
315      * <p>Otherwise the object's <code>toString()</code> output is parsed
316      * using the {@link Double#parseDouble(java.lang.String)} method.
317      * If the string value to equal to {@link #NAN} (ignoring case),
318      * {@link Double#NaN} is returned.
319      *
320      * <p>If the specified object is <code>null</code>, -1.0 is returned.
321      *
322      * @param obj the object to check
323      * @return the converted double
324      * @throws NumberFormatException if the object's <code>toString()</code>
325      * output does not contain a parsable double
326      */
327     public static double asDouble(final Object obj)
328             throws NumberFormatException {
329         final double result;
330         if (obj instanceof Number) {
331             // use contained number
332             result = ((Number) obj).doubleValue();
333         } else if (obj != null) {
334             final String raw = obj.toString().trim();
335 
336             if (NAN.equalsIgnoreCase(raw)) {
337                 // Not-a-Number
338                 result = Double.NaN;
339             } else {
340                 // parse string value
341                 result = Double.parseDouble(raw);
342             }
343         } else {
344             // null object
345             result = -1.0;
346         }
347         return result;
348     }
349 
350     /***
351      * Converts an object into a float primitive. For a {@link Number}
352      * object, the contained number is converted to a float -- this may involve
353      * rounding.
354      *
355      * <p>Otherwise the object's <code>toString()</code> output is parsed
356      * using the {@link Float#parseFloat(java.lang.String)} method.
357      * If the string value to equal to {@link #NAN} (ignoring case),
358      * {@link Float#NaN} is returned.
359      *
360      * <p>If the specified object is <code>null</code>, -1 is returned.
361      *
362      * @param obj the object to check
363      * @return the converted float
364      * @throws NumberFormatException if the object's <code>toString()</code>
365      * output does not contain a parsable float
366      */
367     public static float asFloat(final Object obj)
368             throws NumberFormatException {
369         final float result;
370         if (obj instanceof Number) {
371             // use contained number
372             result = ((Number) obj).floatValue();
373         } else if (obj != null) {
374             final String raw = obj.toString().trim();
375 
376             if (NAN.equalsIgnoreCase(raw)) {
377                 // Not-a-Number
378                 result = Float.NaN;
379             } else {
380                 // parse string value
381                 result = Float.parseFloat(raw);
382             }
383         } else {
384             // null object
385             result = -1.0f;
386         }
387         return result;
388     }
389 
390     /***
391      * Converts an object into an integer primitive. For a {@link Number}
392      * object, the contained number is converted to a int -- this may involve
393      * rounding or truncation.
394      * <p>Otherwise the object's <code>toString()</code> output is parsed
395      * using the {@link Integer#decode(java.lang.String)} method.
396      * <p>If the specified object is <code>null</code>, -1 is returned.
397      *
398      * @param obj the object to check
399      * @return the converted int
400      * @throws NumberFormatException if the object's <code>toString()</code>
401      * output does not contain a parsable int
402      */
403     public static int asInt(final Object obj) throws NumberFormatException {
404         final int result;
405         if (obj instanceof Number) {
406             // use contained number
407             result = ((Number) obj).intValue();
408         } else if (obj != null) {
409             // parse string value
410             final String raw = obj.toString().trim();
411             result = Integer.decode(raw).intValue();
412         } else {
413             // null object
414             result = -1;
415         }
416         return result;
417     }
418 
419     /***
420      * Converts an object into a long primitive. For a {@link Number}
421      * object, the contained number is converted to a long -- this may involve
422      * rounding or truncation.
423      * <p>Otherwise the object's <code>toString()</code> output is parsed
424      * using the {@link Long#decode(java.lang.String)} method.
425      * <p>If the specified object is <code>null</code>, -1 is returned.
426      *
427      * @param obj the object to check
428      * @return the converted long
429      * @throws NumberFormatException if the object's <code>toString()</code>
430      * output does not contain a parsable long
431      */
432     public static long asLong(final Object obj) throws NumberFormatException {
433         final long result;
434         if (obj instanceof Number) {
435             // use contained number
436             result = ((Number) obj).longValue();
437         } else if (obj != null) {
438             // parse string value
439             final String raw = obj.toString().trim();
440             result = Long.decode(raw).longValue();
441         } else {
442             // null object
443             result = -1;
444         }
445         return result;
446     }
447 
448     /***
449      * Converts an object into a short primitive. For a {@link Number}
450      * object, the contained number is converted to a short -- this may involve
451      * rounding or truncation.
452      * <p>Otherwise the object's <code>toString()</code> output is parsed
453      * using the {@link Short#decode(java.lang.String)} method.
454      * <p>If the specified object is <code>null</code>, -1 is returned.
455      *
456      * @param obj the object to check
457      * @return the converted short
458      * @throws NumberFormatException if the object's <code>toString()</code>
459      * output does not contain a parsable short
460      */
461     public static short asShort(final Object obj) throws NumberFormatException {
462         final short result;
463         if (obj instanceof Number) {
464             // use contained number
465             result = ((Number) obj).shortValue();
466         } else if (obj != null) {
467             // parse string value
468             final String raw = obj.toString().trim();
469             result = Short.decode(raw).shortValue();
470         } else {
471             // null object
472             result = -1;
473         }
474         return result;
475     }
476 
477     /***
478      * Converts an object into a String. For non-null objects, the output
479      * of the {@link Object#toString()} is returned; otherwise,
480      * <code>null</code> is returned.
481      *
482      * @param obj the object to check
483      * @return the output of the object's {@link Object#toString()}; or
484      * <code>null</code> if <code>obj</code> is <code>null</code>
485      */
486     public static String asString(final Object obj) {
487         if (obj != null) {
488             return obj.toString();
489         } else {
490             return null;
491         }
492     }
493 
494     /***
495      * Reconfigures the {@link #LOG logger} from the {@link #CONFIG_LOGGER_LOG}
496      * and {@link #CONFIG_LOGGER_SHOW} values in the provided configuration.
497      * This method should be used with care since it affects the global logging.
498      * It is called by {@link de.fu_berlin.ties.Main#main(String[])} after
499      * configurations and command-line propertis have been read.
500      *
501      * @param config the configuration to use
502      */
503     public static void configureLog(final TiesConfiguration config) {
504         // ignoring case of configured values by converting them to upper case
505         LOG.setPriority(Priority.getPriorityForName(config.getString(
506             CONFIG_LOGGER_LOG).toUpperCase()));
507         STD_FILTERED.setPriority(Priority.getPriorityForName(config.getString(
508         CONFIG_LOGGER_SHOW).toUpperCase()));
509     }
510 
511     /***
512      * Delegates to {@link Util#createObject(Class, String[])}, reading the
513      * class name from the first element in the array.
514      *
515      * @param classDef defines the class to create: the first element must be
516      * a string giving the fully qualified class name; any further elements
517      * are passed as constructor parameters
518      * @return the created object; the returned object will be an instance of
519      * the specified type
520      * @throws IllegalArgumentException if the class name is missing (array is
521      * empty)
522      * @throws ClassNotFoundException if the class cannot be located
523      * @throws InstantiationException if instantiation failed
524      * @throws SecurityException if access to the required reflection
525      * information is denied
526      */
527     public static Object createObject(final String[] classDef)
528             throws IllegalArgumentException, ClassNotFoundException,
529             InstantiationException, SecurityException {
530         final int length = classDef.length;
531 
532         // check arguments
533         if (length == 0) {
534             throw new IllegalArgumentException(
535                 "Class name missing (empty class definition)");
536         }
537 
538         // first element is class name, further elements are constructor args
539         final String type = classDef[0];
540         final String[] params = new String[length - 1];
541 
542         for (int i = 0; i < params.length; i++) {
543             params[i] = classDef[i + 1];
544         }
545 
546         return createObject(Class.forName(type), params);
547     }
548 
549     /***
550      * Delegates to {@link Util#createObject(Class, Object[], Class)}, setting
551      * the <code>paramType</code> to the {@link String} class.
552      *
553      * @param type the class of the object to create
554      * @param params the objects in this array are passed to the constructor;
555      * if <code>null</code> or empty, the default constructor is used
556      * @return the created object; the returned object will be an instance of
557      * <code>type</code>
558      * @throws InstantiationException if instantiation failed
559      * @throws SecurityException if access to the required reflection
560      * information is denied
561      */
562     public static Object createObject(final Class type, final String[] params)
563             throws InstantiationException, SecurityException {
564         return createObject(type, params, String.class);
565     }
566 
567     /***
568      * Creates an object of a specified type. The object must have a public
569      * constructor that accepts <code>params.length</code> arguments of the
570      * specified <code>paramType</code>.
571      *
572      * @param type the class of the object to create
573      * @param params the objects in this array are passed to the constructor;
574      * if <code>null</code> or empty, the default constructor is used
575      * @param paramType the type of the elements stored in the
576      * <code>params</code> array
577      * @return the created object; the returned object will be an instance of
578      * <code>type</code>
579      * @throws InstantiationException if instantiation failed
580      * @throws SecurityException if access to the required reflection
581      * information is denied
582      */
583     public static Object createObject(final Class type, final Object[] params,
584             final Class paramType)
585             throws InstantiationException, SecurityException {
586         final Class[] paramClasses = new Class[params.length];
587         Arrays.fill(paramClasses, paramType);
588         return createObject(type, params, paramClasses);
589     }
590 
591     /***
592      * Creates an object of a specified type. The object must have a public
593      * constructor that accepts <code>params.length</code> arguments of the
594      * specified types.
595      *
596      * @param type the class of the object to create
597      * @param params the objects in this array are passed to the constructor;
598      * if <code>null</code> or empty, the default constructor is used
599      * @param paramTypes the types of the elements stored in the
600      * <code>params</code> list
601      * @return the created object; the returned object will be an instance of
602      * <code>type</code>
603      * @throws IllegalArgumentException if <code>params</code> and
604      * <code>paramTypes</code> are of different lengths
605      * @throws InstantiationException if instantiation failed
606      * @throws SecurityException if access to the required reflection
607      * information is denied
608      */
609     public static Object createObject(final Class type, final Object[] params,
610             final Class[] paramTypes) throws IllegalArgumentException,
611             InstantiationException, SecurityException {
612         // check that array lengths match
613         if ((params != null) && (params.length != paramTypes.length)) {
614             throw new IllegalArgumentException("Lengths of given arrays of "
615                 + " parameters and parameter types differ: " + params.length
616                 + ", " + paramTypes.length);
617         }
618 
619         Object result;
620         try {
621             if ((params == null) || (params.length == 0)) {
622                 // try default constructor
623                 result = type.newInstance();
624             } else {
625                 final Constructor cons = type.getConstructor(paramTypes);
626 
627                 // pass each of the list elements as constructor argument
628                 result = cons.newInstance(params);
629             }
630         } catch (IllegalAccessException iae) {        // repackage exception
631             final InstantiationException ie =
632                 new InstantiationException(iae.toString());
633             ie.initCause(iae);
634             throw ie;
635         } catch (InvocationTargetException ite) {     // repackage exception
636             final InstantiationException ie =
637                 new InstantiationException(ite.toString());
638             ie.initCause(ite);
639             throw ie;
640         } catch (ExceptionInInitializerError eiie) {  // repackage exception
641             final InstantiationException ie =
642                 new InstantiationException(eiie.toString());
643             ie.initCause(eiie);
644             throw ie;
645         } catch (NoSuchMethodException nsme) {        // repackage exception
646             final InstantiationException ie =
647                 new InstantiationException(nsme.toString());
648             ie.initCause(nsme);
649             throw ie;
650         }
651         return result;
652     }
653 
654     /***
655      * Formats a duration. The generated string has the form
656      * "<i>days</i>D+<i>hours</i>:<i>minutes</i>:<i>secs</i>.<i>millisecs</i>".
657      * Days and milliseconds are omitted if 0; the time fields are omitted if
658      * all of them are 0, but only if days are present (to avoid returning an
659      * empty string). Samples: 2D+03:02:43.666, 01:00:00, 1D, 00:05:00.123,
660      * 1D+04:19:03, 00:00:00.
661      *
662      * @param msecs the number of milliseconds
663      * @return a formatted representation as specified above
664      */
665     public static String formatDurationInMillisecs(final long msecs) {
666         // check argument
667         if (msecs < 0.0) {
668             throw new IllegalArgumentException(
669                 "Negative durations are not supported: " + msecs);
670         }
671 
672         final int milliRange = 1000;
673         final int secMinRange = 60;
674         final int hourRange = 24;
675 
676         final int millis = (int) (msecs % milliRange);
677         long rest = msecs / milliRange;
678         final int seconds = (int) (rest % secMinRange);
679         rest = rest / secMinRange;
680         final int minutes = (int) (rest % secMinRange);
681         rest /= secMinRange;
682         final int hours = (int) (rest % hourRange);
683         rest /= hourRange;
684 
685         final StringBuffer result = new StringBuffer();
686 
687         // start with days, if any
688         if (rest > 0) {
689             result.append(rest).append("D");
690         }
691 
692         // don't print anything else if days are present and all time fields
693         // are 0
694         if ((rest == 0) || (hours != 0) || (minutes != 0) || (seconds != 0)
695                 || (millis != 0)) {
696             // constant for padding + printing
697             // append "+" separator if days are present
698             if (rest > 0) {
699                 result.append("+");
700             }
701 
702             // always append hours:minutes:secs
703             appendPositiveNumber(result, hours, 2, null, true);
704             appendPositiveNumber(result, minutes, 2, ":", true);
705             appendPositiveNumber(result, seconds, 2, ":", true);
706 
707             // append milliseconds if not 0
708             appendPositiveNumber(result, millis, 3, ".", false);
709         }
710 
711         return result.toString();
712     }
713 
714     /***
715      * Formats a duration. The generated string has the form
716      * "<i>days</i>D+<i>hours</i>:<i>minutes</i>:<i>secs</i>.<i>millisecs</i>".
717      * Days and milliseconds are omitted if 0; the time fields are omitted if
718      * all of them are 0, but only if days are present (to avoid returning an
719      * empty string). Samples: 2D+03:02:43.666, 01:00:00, 1D, 00:05:00.123,
720      * 1D+04:19:03, 00:00:00.
721      *
722      * @param secs the number of seconds; must be nonnegative
723      * @return a formatted representation as specified above
724      * @throws IllegalArgumentException if <code>secs &lt; 0</code>
725      */
726     public static String formatDurationInSeconds(final double secs)
727             throws IllegalArgumentException {
728         return formatDurationInMillisecs(Math.round(secs * 1000));
729     }
730 
731     /***
732      * Ensures that a number is positive or 0, throwing an exception if this
733      * condition is violated.
734      *
735      * @param number the number to check
736      * @param identifier string used to describe the number in the thrown
737      * exception
738      * @throws IllegalArgumentException if <code>number &lt; 0.0</code>
739      */
740     public static void ensureNonNegative(final double number,
741             final String identifier) throws IllegalArgumentException {
742         if (number < 0.0) {
743             throw new IllegalArgumentException(identifier
744                 + " must not be negative: " + number);
745         }
746     }
747 
748     /***
749      * Ensures that a number is positive or 0, throwing an exception if this
750      * condition is violated.
751      *
752      * @param number the number to check
753      * @param identifier string used to describe the number in the thrown
754      * exception
755      * @throws IllegalArgumentException if <code>number &lt; 0</code>
756      */
757     public static void ensureNonNegative(final long number,
758             final String identifier) throws IllegalArgumentException {
759         if (number < 0) {
760             throw new IllegalArgumentException(identifier
761                 + " must not be negative: " + number);
762         }
763     }
764 
765     /***
766      * Formats the given number, using at most 4 fraction digits.
767      *
768      * @param number the number for format
769      * @return the formatted number
770      */
771     public static String format(final double number) {
772         return format(number, 4);
773     }
774 
775     /***
776      * Formats the given number, using at most the specified number of
777      * fraction digits.
778      *
779      * @param number the number for format
780      * @param maxFractDigits maximum number of digits allowed in the fraction
781      * portion of the number
782      * @return the formatted number
783      */
784     public static String format(final double number, final int maxFractDigits) {
785         synchronized (NUM_FORMAT) {
786             NUM_FORMAT.setMaximumFractionDigits(maxFractDigits);
787             return NUM_FORMAT.format(number);
788         }
789     }
790 
791     /***
792      * Returns an instance of a pseudo-random number generator that uses a
793      * fixed seed, so the same sequence of calls to each object created
794      * by this method will yield the same sequence of pseudo-random numbers.
795      *
796      * @return a new source of pseudo-randomness that will return
797      * reproducable pseudo-random numbers
798      */
799     public static Random reproducibleRandom() {
800         // using a fixed seed which was randomly generated and will never change
801         return new Random(4127208836813110743L);
802     }
803 
804     /***
805      * Calculated and prints the time passed since a given start time
806      * (in English).
807      *
808      * @param startTime the start time in milliseconds
809      * @return a formatted string containing the time difference between the
810      * start time and the {@linkplain System#currentTimeMillis() current time}
811      */
812     public static String showDuration(final long startTime) {
813         final long timeInMS = System.currentTimeMillis() - startTime;
814         final double timeInSecs = ((double) timeInMS) / 1000;
815         String result;
816 
817         synchronized (SEC_FORMAT) {
818             result = "elapsed time: " + SEC_FORMAT.format(timeInSecs) + "s";
819         }
820 
821         // also convert to XML Schema duration etc. if 5min or more
822         if (timeInSecs >= 300) {
823             result += " = " + formatDurationInSeconds(timeInSecs);
824         }
825 
826         return result;
827     }
828 
829     /***
830      * <code>Util</code> instances should NOT be constructed in standard
831      * programming. Instead, the method of this class should be called in a
832      * static way. This constructor is public to permit tools that require a
833      * JavaBean instance to operate.
834      */
835     public Util() {
836         super();
837     }
838 
839 }