View Javadoc

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