001    /*
002     * Copyright 2007-2016 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2016 UnboundID Corp.
007     *
008     * This program is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License (GPLv2 only)
010     * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011     * as published by the Free Software Foundation.
012     *
013     * This program is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program; if not, see <http://www.gnu.org/licenses>.
020     */
021    package com.unboundid.util;
022    
023    
024    
025    import java.lang.reflect.Constructor;
026    import java.io.IOException;
027    import java.text.DecimalFormat;
028    import java.text.ParseException;
029    import java.text.SimpleDateFormat;
030    import java.util.ArrayList;
031    import java.util.Arrays;
032    import java.util.Collection;
033    import java.util.Collections;
034    import java.util.Date;
035    import java.util.HashSet;
036    import java.util.Iterator;
037    import java.util.LinkedHashSet;
038    import java.util.List;
039    import java.util.Set;
040    import java.util.StringTokenizer;
041    import java.util.TimeZone;
042    import java.util.UUID;
043    
044    import com.unboundid.ldap.sdk.Attribute;
045    import com.unboundid.ldap.sdk.Control;
046    import com.unboundid.ldap.sdk.Version;
047    
048    import static com.unboundid.util.Debug.*;
049    import static com.unboundid.util.UtilityMessages.*;
050    import static com.unboundid.util.Validator.*;
051    
052    
053    
054    /**
055     * This class provides a number of static utility functions.
056     */
057    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
058    public final class StaticUtils
059    {
060      /**
061       * A pre-allocated byte array containing zero bytes.
062       */
063      public static final byte[] NO_BYTES = new byte[0];
064    
065    
066    
067      /**
068       * A pre-allocated empty control array.
069       */
070      public static final Control[] NO_CONTROLS = new Control[0];
071    
072    
073    
074      /**
075       * A pre-allocated empty string array.
076       */
077      public static final String[] NO_STRINGS = new String[0];
078    
079    
080    
081      /**
082       * The end-of-line marker for this platform.
083       */
084      public static final String EOL = System.getProperty("line.separator");
085    
086    
087    
088      /**
089       * A byte array containing the end-of-line marker for this platform.
090       */
091      public static final byte[] EOL_BYTES = getBytes(EOL);
092    
093    
094    
095      /**
096       * The width of the terminal window, in columns.
097       */
098      public static final int TERMINAL_WIDTH_COLUMNS;
099      static
100      {
101        // Try to dynamically determine the size of the terminal window using the
102        // COLUMNS environment variable.
103        int terminalWidth = 80;
104        final String columnsEnvVar = System.getenv("COLUMNS");
105        if (columnsEnvVar != null)
106        {
107          try
108          {
109            terminalWidth = Integer.parseInt(columnsEnvVar);
110          }
111          catch (final Exception e)
112          {
113            Debug.debugException(e);
114          }
115        }
116    
117        TERMINAL_WIDTH_COLUMNS = terminalWidth;
118      }
119    
120    
121    
122      /**
123       * The thread-local date formatter used to encode generalized time values.
124       */
125      private static final ThreadLocal<SimpleDateFormat> DATE_FORMATTERS =
126           new ThreadLocal<SimpleDateFormat>();
127    
128    
129    
130      /**
131       * A set containing the names of attributes that will be considered sensitive
132       * by the {@code toCode} methods of various request and data structure types.
133       */
134      private static volatile Set<String> TO_CODE_SENSITIVE_ATTRIBUTE_NAMES;
135      static
136      {
137        final LinkedHashSet<String> nameSet = new LinkedHashSet<String>(4);
138    
139        // Add userPassword by name and OID.
140        nameSet.add("userpassword");
141        nameSet.add("2.5.4.35");
142    
143        // add authPassword by name and OID.
144        nameSet.add("authpassword");
145        nameSet.add("1.3.6.1.4.1.4203.1.3.4");
146    
147        TO_CODE_SENSITIVE_ATTRIBUTE_NAMES = Collections.unmodifiableSet(nameSet);
148      }
149    
150    
151    
152      /**
153       * Prevent this class from being instantiated.
154       */
155      private StaticUtils()
156      {
157        // No implementation is required.
158      }
159    
160    
161    
162      /**
163       * Retrieves a UTF-8 byte representation of the provided string.
164       *
165       * @param  s  The string for which to retrieve the UTF-8 byte representation.
166       *
167       * @return  The UTF-8 byte representation for the provided string.
168       */
169      public static byte[] getBytes(final String s)
170      {
171        final int length;
172        if ((s == null) || ((length = s.length()) == 0))
173        {
174          return NO_BYTES;
175        }
176    
177        final byte[] b = new byte[length];
178        for (int i=0; i < length; i++)
179        {
180          final char c = s.charAt(i);
181          if (c <= 0x7F)
182          {
183            b[i] = (byte) (c & 0x7F);
184          }
185          else
186          {
187            try
188            {
189              return s.getBytes("UTF-8");
190            }
191            catch (Exception e)
192            {
193              // This should never happen.
194              debugException(e);
195              return s.getBytes();
196            }
197          }
198        }
199    
200        return b;
201      }
202    
203    
204    
205      /**
206       * Indicates whether the contents of the provided byte array represent an
207       * ASCII string, which is also known in LDAP terminology as an IA5 string.
208       * An ASCII string is one that contains only bytes in which the most
209       * significant bit is zero.
210       *
211       * @param  b  The byte array for which to make the determination.  It must
212       *            not be {@code null}.
213       *
214       * @return  {@code true} if the contents of the provided array represent an
215       *          ASCII string, or {@code false} if not.
216       */
217      public static boolean isASCIIString(final byte[] b)
218      {
219        for (final byte by : b)
220        {
221          if ((by & 0x80) == 0x80)
222          {
223            return false;
224          }
225        }
226    
227        return true;
228      }
229    
230    
231    
232      /**
233       * Indicates whether the provided character is a printable ASCII character, as
234       * per RFC 4517 section 3.2.  The only printable characters are:
235       * <UL>
236       *   <LI>All uppercase and lowercase ASCII alphabetic letters</LI>
237       *   <LI>All ASCII numeric digits</LI>
238       *   <LI>The following additional ASCII characters:  single quote, left
239       *       parenthesis, right parenthesis, plus, comma, hyphen, period, equals,
240       *       forward slash, colon, question mark, space.</LI>
241       * </UL>
242       *
243       * @param  c  The character for which to make the determination.
244       *
245       * @return  {@code true} if the provided character is a printable ASCII
246       *          character, or {@code false} if not.
247       */
248      public static boolean isPrintable(final char c)
249      {
250        if (((c >= 'a') && (c <= 'z')) ||
251            ((c >= 'A') && (c <= 'Z')) ||
252            ((c >= '0') && (c <= '9')))
253        {
254          return true;
255        }
256    
257        switch (c)
258        {
259          case '\'':
260          case '(':
261          case ')':
262          case '+':
263          case ',':
264          case '-':
265          case '.':
266          case '=':
267          case '/':
268          case ':':
269          case '?':
270          case ' ':
271            return true;
272          default:
273            return false;
274        }
275      }
276    
277    
278    
279      /**
280       * Indicates whether the contents of the provided byte array represent a
281       * printable LDAP string, as per RFC 4517 section 3.2.  The only characters
282       * allowed in a printable string are:
283       * <UL>
284       *   <LI>All uppercase and lowercase ASCII alphabetic letters</LI>
285       *   <LI>All ASCII numeric digits</LI>
286       *   <LI>The following additional ASCII characters:  single quote, left
287       *       parenthesis, right parenthesis, plus, comma, hyphen, period, equals,
288       *       forward slash, colon, question mark, space.</LI>
289       * </UL>
290       * If the provided array contains anything other than the above characters
291       * (i.e., if the byte array contains any non-ASCII characters, or any ASCII
292       * control characters, or if it contains excluded ASCII characters like
293       * the exclamation point, double quote, octothorpe, dollar sign, etc.), then
294       * it will not be considered printable.
295       *
296       * @param  b  The byte array for which to make the determination.  It must
297       *            not be {@code null}.
298       *
299       * @return  {@code true} if the contents of the provided byte array represent
300       *          a printable LDAP string, or {@code false} if not.
301       */
302      public static boolean isPrintableString(final byte[] b)
303      {
304        for (final byte by : b)
305        {
306          if ((by & 0x80) == 0x80)
307          {
308            return false;
309          }
310    
311          if (((by >= 'a') && (by <= 'z')) ||
312              ((by >= 'A') && (by <= 'Z')) ||
313              ((by >= '0') && (by <= '9')))
314          {
315            continue;
316          }
317    
318          switch (by)
319          {
320            case '\'':
321            case '(':
322            case ')':
323            case '+':
324            case ',':
325            case '-':
326            case '.':
327            case '=':
328            case '/':
329            case ':':
330            case '?':
331            case ' ':
332              continue;
333            default:
334              return false;
335          }
336        }
337    
338        return true;
339      }
340    
341    
342    
343      /**
344       * Retrieves a string generated from the provided byte array using the UTF-8
345       * encoding.
346       *
347       * @param  b  The byte array for which to return the associated string.
348       *
349       * @return  The string generated from the provided byte array using the UTF-8
350       *          encoding.
351       */
352      public static String toUTF8String(final byte[] b)
353      {
354        try
355        {
356          return new String(b, "UTF-8");
357        }
358        catch (Exception e)
359        {
360          // This should never happen.
361          debugException(e);
362          return new String(b);
363        }
364      }
365    
366    
367    
368      /**
369       * Retrieves a string generated from the specified portion of the provided
370       * byte array using the UTF-8 encoding.
371       *
372       * @param  b       The byte array for which to return the associated string.
373       * @param  offset  The offset in the array at which the value begins.
374       * @param  length  The number of bytes in the value to convert to a string.
375       *
376       * @return  The string generated from the specified portion of the provided
377       *          byte array using the UTF-8 encoding.
378       */
379      public static String toUTF8String(final byte[] b, final int offset,
380                                        final int length)
381      {
382        try
383        {
384          return new String(b, offset, length, "UTF-8");
385        }
386        catch (Exception e)
387        {
388          // This should never happen.
389          debugException(e);
390          return new String(b, offset, length);
391        }
392      }
393    
394    
395    
396      /**
397       * Retrieves a version of the provided string with the first character
398       * converted to lowercase but all other characters retaining their original
399       * capitalization.
400       *
401       * @param  s  The string to be processed.
402       *
403       * @return  A version of the provided string with the first character
404       *          converted to lowercase but all other characters retaining their
405       *          original capitalization.
406       */
407      public static String toInitialLowerCase(final String s)
408      {
409        if ((s == null) || (s.length() == 0))
410        {
411          return s;
412        }
413        else if (s.length() == 1)
414        {
415          return toLowerCase(s);
416        }
417        else
418        {
419          final char c = s.charAt(0);
420          if (((c >= 'A') && (c <= 'Z')) || (c < ' ') || (c > '~'))
421          {
422            final StringBuilder b = new StringBuilder(s);
423            b.setCharAt(0, Character.toLowerCase(c));
424            return b.toString();
425          }
426          else
427          {
428            return s;
429          }
430        }
431      }
432    
433    
434    
435      /**
436       * Retrieves an all-lowercase version of the provided string.
437       *
438       * @param  s  The string for which to retrieve the lowercase version.
439       *
440       * @return  An all-lowercase version of the provided string.
441       */
442      public static String toLowerCase(final String s)
443      {
444        if (s == null)
445        {
446          return null;
447        }
448    
449        final int length = s.length();
450        final char[] charArray = s.toCharArray();
451        for (int i=0; i < length; i++)
452        {
453          switch (charArray[i])
454          {
455            case 'A':
456              charArray[i] = 'a';
457              break;
458            case 'B':
459              charArray[i] = 'b';
460              break;
461            case 'C':
462              charArray[i] = 'c';
463              break;
464            case 'D':
465              charArray[i] = 'd';
466              break;
467            case 'E':
468              charArray[i] = 'e';
469              break;
470            case 'F':
471              charArray[i] = 'f';
472              break;
473            case 'G':
474              charArray[i] = 'g';
475              break;
476            case 'H':
477              charArray[i] = 'h';
478              break;
479            case 'I':
480              charArray[i] = 'i';
481              break;
482            case 'J':
483              charArray[i] = 'j';
484              break;
485            case 'K':
486              charArray[i] = 'k';
487              break;
488            case 'L':
489              charArray[i] = 'l';
490              break;
491            case 'M':
492              charArray[i] = 'm';
493              break;
494            case 'N':
495              charArray[i] = 'n';
496              break;
497            case 'O':
498              charArray[i] = 'o';
499              break;
500            case 'P':
501              charArray[i] = 'p';
502              break;
503            case 'Q':
504              charArray[i] = 'q';
505              break;
506            case 'R':
507              charArray[i] = 'r';
508              break;
509            case 'S':
510              charArray[i] = 's';
511              break;
512            case 'T':
513              charArray[i] = 't';
514              break;
515            case 'U':
516              charArray[i] = 'u';
517              break;
518            case 'V':
519              charArray[i] = 'v';
520              break;
521            case 'W':
522              charArray[i] = 'w';
523              break;
524            case 'X':
525              charArray[i] = 'x';
526              break;
527            case 'Y':
528              charArray[i] = 'y';
529              break;
530            case 'Z':
531              charArray[i] = 'z';
532              break;
533            default:
534              if (charArray[i] > 0x7F)
535              {
536                return s.toLowerCase();
537              }
538              break;
539          }
540        }
541    
542        return new String(charArray);
543      }
544    
545    
546    
547      /**
548       * Indicates whether the provided character is a valid hexadecimal digit.
549       *
550       * @param  c  The character for which to make the determination.
551       *
552       * @return  {@code true} if the provided character does represent a valid
553       *          hexadecimal digit, or {@code false} if not.
554       */
555      public static boolean isHex(final char c)
556      {
557        switch (c)
558        {
559          case '0':
560          case '1':
561          case '2':
562          case '3':
563          case '4':
564          case '5':
565          case '6':
566          case '7':
567          case '8':
568          case '9':
569          case 'a':
570          case 'A':
571          case 'b':
572          case 'B':
573          case 'c':
574          case 'C':
575          case 'd':
576          case 'D':
577          case 'e':
578          case 'E':
579          case 'f':
580          case 'F':
581            return true;
582    
583          default:
584            return false;
585        }
586      }
587    
588    
589    
590      /**
591       * Retrieves a hexadecimal representation of the provided byte.
592       *
593       * @param  b  The byte to encode as hexadecimal.
594       *
595       * @return  A string containing the hexadecimal representation of the provided
596       *          byte.
597       */
598      public static String toHex(final byte b)
599      {
600        final StringBuilder buffer = new StringBuilder(2);
601        toHex(b, buffer);
602        return buffer.toString();
603      }
604    
605    
606    
607      /**
608       * Appends a hexadecimal representation of the provided byte to the given
609       * buffer.
610       *
611       * @param  b       The byte to encode as hexadecimal.
612       * @param  buffer  The buffer to which the hexadecimal representation is to be
613       *                 appended.
614       */
615      public static void toHex(final byte b, final StringBuilder buffer)
616      {
617        switch (b & 0xF0)
618        {
619          case 0x00:
620            buffer.append('0');
621            break;
622          case 0x10:
623            buffer.append('1');
624            break;
625          case 0x20:
626            buffer.append('2');
627            break;
628          case 0x30:
629            buffer.append('3');
630            break;
631          case 0x40:
632            buffer.append('4');
633            break;
634          case 0x50:
635            buffer.append('5');
636            break;
637          case 0x60:
638            buffer.append('6');
639            break;
640          case 0x70:
641            buffer.append('7');
642            break;
643          case 0x80:
644            buffer.append('8');
645            break;
646          case 0x90:
647            buffer.append('9');
648            break;
649          case 0xA0:
650            buffer.append('a');
651            break;
652          case 0xB0:
653            buffer.append('b');
654            break;
655          case 0xC0:
656            buffer.append('c');
657            break;
658          case 0xD0:
659            buffer.append('d');
660            break;
661          case 0xE0:
662            buffer.append('e');
663            break;
664          case 0xF0:
665            buffer.append('f');
666            break;
667        }
668    
669        switch (b & 0x0F)
670        {
671          case 0x00:
672            buffer.append('0');
673            break;
674          case 0x01:
675            buffer.append('1');
676            break;
677          case 0x02:
678            buffer.append('2');
679            break;
680          case 0x03:
681            buffer.append('3');
682            break;
683          case 0x04:
684            buffer.append('4');
685            break;
686          case 0x05:
687            buffer.append('5');
688            break;
689          case 0x06:
690            buffer.append('6');
691            break;
692          case 0x07:
693            buffer.append('7');
694            break;
695          case 0x08:
696            buffer.append('8');
697            break;
698          case 0x09:
699            buffer.append('9');
700            break;
701          case 0x0A:
702            buffer.append('a');
703            break;
704          case 0x0B:
705            buffer.append('b');
706            break;
707          case 0x0C:
708            buffer.append('c');
709            break;
710          case 0x0D:
711            buffer.append('d');
712            break;
713          case 0x0E:
714            buffer.append('e');
715            break;
716          case 0x0F:
717            buffer.append('f');
718            break;
719        }
720      }
721    
722    
723    
724      /**
725       * Retrieves a hexadecimal representation of the contents of the provided byte
726       * array.  No delimiter character will be inserted between the hexadecimal
727       * digits for each byte.
728       *
729       * @param  b  The byte array to be represented as a hexadecimal string.  It
730       *            must not be {@code null}.
731       *
732       * @return  A string containing a hexadecimal representation of the contents
733       *          of the provided byte array.
734       */
735      public static String toHex(final byte[] b)
736      {
737        ensureNotNull(b);
738    
739        final StringBuilder buffer = new StringBuilder(2 * b.length);
740        toHex(b, buffer);
741        return buffer.toString();
742      }
743    
744    
745    
746      /**
747       * Retrieves a hexadecimal representation of the contents of the provided byte
748       * array.  No delimiter character will be inserted between the hexadecimal
749       * digits for each byte.
750       *
751       * @param  b       The byte array to be represented as a hexadecimal string.
752       *                 It must not be {@code null}.
753       * @param  buffer  A buffer to which the hexadecimal representation of the
754       *                 contents of the provided byte array should be appended.
755       */
756      public static void toHex(final byte[] b, final StringBuilder buffer)
757      {
758        toHex(b, null, buffer);
759      }
760    
761    
762    
763      /**
764       * Retrieves a hexadecimal representation of the contents of the provided byte
765       * array.  No delimiter character will be inserted between the hexadecimal
766       * digits for each byte.
767       *
768       * @param  b          The byte array to be represented as a hexadecimal
769       *                    string.  It must not be {@code null}.
770       * @param  delimiter  A delimiter to be inserted between bytes.  It may be
771       *                    {@code null} if no delimiter should be used.
772       * @param  buffer     A buffer to which the hexadecimal representation of the
773       *                    contents of the provided byte array should be appended.
774       */
775      public static void toHex(final byte[] b, final String delimiter,
776                               final StringBuilder buffer)
777      {
778        boolean first = true;
779        for (final byte bt : b)
780        {
781          if (first)
782          {
783            first = false;
784          }
785          else if (delimiter != null)
786          {
787            buffer.append(delimiter);
788          }
789    
790          toHex(bt, buffer);
791        }
792      }
793    
794    
795    
796      /**
797       * Retrieves a hex-encoded representation of the contents of the provided
798       * array, along with an ASCII representation of its contents next to it.  The
799       * output will be split across multiple lines, with up to sixteen bytes per
800       * line.  For each of those sixteen bytes, the two-digit hex representation
801       * will be appended followed by a space.  Then, the ASCII representation of
802       * those sixteen bytes will follow that, with a space used in place of any
803       * byte that does not have an ASCII representation.
804       *
805       * @param  array   The array whose contents should be processed.
806       * @param  indent  The number of spaces to insert on each line prior to the
807       *                 first hex byte.
808       *
809       * @return  A hex-encoded representation of the contents of the provided
810       *          array, along with an ASCII representation of its contents next to
811       *          it.
812       */
813      public static String toHexPlusASCII(final byte[] array, final int indent)
814      {
815        final StringBuilder buffer = new StringBuilder();
816        toHexPlusASCII(array, indent, buffer);
817        return buffer.toString();
818      }
819    
820    
821    
822      /**
823       * Appends a hex-encoded representation of the contents of the provided array
824       * to the given buffer, along with an ASCII representation of its contents
825       * next to it.  The output will be split across multiple lines, with up to
826       * sixteen bytes per line.  For each of those sixteen bytes, the two-digit hex
827       * representation will be appended followed by a space.  Then, the ASCII
828       * representation of those sixteen bytes will follow that, with a space used
829       * in place of any byte that does not have an ASCII representation.
830       *
831       * @param  array   The array whose contents should be processed.
832       * @param  indent  The number of spaces to insert on each line prior to the
833       *                 first hex byte.
834       * @param  buffer  The buffer to which the encoded data should be appended.
835       */
836      public static void toHexPlusASCII(final byte[] array, final int indent,
837                                        final StringBuilder buffer)
838      {
839        if ((array == null) || (array.length == 0))
840        {
841          return;
842        }
843    
844        for (int i=0; i < indent; i++)
845        {
846          buffer.append(' ');
847        }
848    
849        int pos = 0;
850        int startPos = 0;
851        while (pos < array.length)
852        {
853          toHex(array[pos++], buffer);
854          buffer.append(' ');
855    
856          if ((pos % 16) == 0)
857          {
858            buffer.append("  ");
859            for (int i=startPos; i < pos; i++)
860            {
861              if ((array[i] < ' ') || (array[i] > '~'))
862              {
863                buffer.append(' ');
864              }
865              else
866              {
867                buffer.append((char) array[i]);
868              }
869            }
870            buffer.append(EOL);
871            startPos = pos;
872    
873            if (pos < array.length)
874            {
875              for (int i=0; i < indent; i++)
876              {
877                buffer.append(' ');
878              }
879            }
880          }
881        }
882    
883        // If the last line isn't complete yet, then finish it off.
884        if ((array.length % 16) != 0)
885        {
886          final int missingBytes = (16 - (array.length % 16));
887          if (missingBytes > 0)
888          {
889            for (int i=0; i < missingBytes; i++)
890            {
891              buffer.append("   ");
892            }
893            buffer.append("  ");
894            for (int i=startPos; i < array.length; i++)
895            {
896              if ((array[i] < ' ') || (array[i] > '~'))
897              {
898                buffer.append(' ');
899              }
900              else
901              {
902                buffer.append((char) array[i]);
903              }
904            }
905            buffer.append(EOL);
906          }
907        }
908      }
909    
910    
911    
912      /**
913       * Appends a hex-encoded representation of the provided character to the given
914       * buffer.  Each byte of the hex-encoded representation will be prefixed with
915       * a backslash.
916       *
917       * @param  c       The character to be encoded.
918       * @param  buffer  The buffer to which the hex-encoded representation should
919       *                 be appended.
920       */
921      public static void hexEncode(final char c, final StringBuilder buffer)
922      {
923        final byte[] charBytes;
924        if (c <= 0x7F)
925        {
926          charBytes = new byte[] { (byte) (c & 0x7F) };
927        }
928        else
929        {
930          charBytes = getBytes(String.valueOf(c));
931        }
932    
933        for (final byte b : charBytes)
934        {
935          buffer.append('\\');
936          toHex(b, buffer);
937        }
938      }
939    
940    
941    
942      /**
943       * Appends the Java code that may be used to create the provided byte
944       * array to the given buffer.
945       *
946       * @param  array   The byte array containing the data to represent.  It must
947       *                 not be {@code null}.
948       * @param  buffer  The buffer to which the code should be appended.
949       */
950      public static void byteArrayToCode(final byte[] array,
951                                         final StringBuilder buffer)
952      {
953        buffer.append("new byte[] {");
954        for (int i=0; i < array.length; i++)
955        {
956          if (i > 0)
957          {
958            buffer.append(',');
959          }
960    
961          buffer.append(" (byte) 0x");
962          toHex(array[i], buffer);
963        }
964        buffer.append(" }");
965      }
966    
967    
968    
969      /**
970       * Retrieves a single-line string representation of the stack trace for the
971       * provided {@code Throwable}.  It will include the unqualified name of the
972       * {@code Throwable} class, a list of source files and line numbers (if
973       * available) for the stack trace, and will also include the stack trace for
974       * the cause (if present).
975       *
976       * @param  t  The {@code Throwable} for which to retrieve the stack trace.
977       *
978       * @return  A single-line string representation of the stack trace for the
979       *          provided {@code Throwable}.
980       */
981      public static String getStackTrace(final Throwable t)
982      {
983        final StringBuilder buffer = new StringBuilder();
984        getStackTrace(t, buffer);
985        return buffer.toString();
986      }
987    
988    
989    
990      /**
991       * Appends a single-line string representation of the stack trace for the
992       * provided {@code Throwable} to the given buffer.  It will include the
993       * unqualified name of the {@code Throwable} class, a list of source files and
994       * line numbers (if available) for the stack trace, and will also include the
995       * stack trace for the cause (if present).
996       *
997       * @param  t       The {@code Throwable} for which to retrieve the stack
998       *                 trace.
999       * @param  buffer  The buffer to which the information should be appended.
1000       */
1001      public static void getStackTrace(final Throwable t,
1002                                       final StringBuilder buffer)
1003      {
1004        buffer.append(getUnqualifiedClassName(t.getClass()));
1005        buffer.append('(');
1006    
1007        final String message = t.getMessage();
1008        if (message != null)
1009        {
1010          buffer.append("message='");
1011          buffer.append(message);
1012          buffer.append("', ");
1013        }
1014    
1015        buffer.append("trace='");
1016        getStackTrace(t.getStackTrace(), buffer);
1017        buffer.append('\'');
1018    
1019        final Throwable cause = t.getCause();
1020        if (cause != null)
1021        {
1022          buffer.append(", cause=");
1023          getStackTrace(cause, buffer);
1024        }
1025        buffer.append(", revision=");
1026        buffer.append(Version.REVISION_NUMBER);
1027        buffer.append(')');
1028      }
1029    
1030    
1031    
1032      /**
1033       * Returns a single-line string representation of the stack trace.  It will
1034       * include a list of source files and line numbers (if available) for the
1035       * stack trace.
1036       *
1037       * @param  elements  The stack trace.
1038       *
1039       * @return  A single-line string representation of the stack trace.
1040       */
1041      public static String getStackTrace(final StackTraceElement[] elements)
1042      {
1043        final StringBuilder buffer = new StringBuilder();
1044        getStackTrace(elements, buffer);
1045        return buffer.toString();
1046      }
1047    
1048    
1049    
1050      /**
1051       * Appends a single-line string representation of the stack trace to the given
1052       * buffer.  It will include a list of source files and line numbers
1053       * (if available) for the stack trace.
1054       *
1055       * @param  elements  The stack trace.
1056       * @param  buffer  The buffer to which the information should be appended.
1057       */
1058      public static void getStackTrace(final StackTraceElement[] elements,
1059                                       final StringBuilder buffer)
1060      {
1061        for (int i=0; i < elements.length; i++)
1062        {
1063          if (i > 0)
1064          {
1065            buffer.append(" / ");
1066          }
1067    
1068          buffer.append(elements[i].getMethodName());
1069          buffer.append('(');
1070          buffer.append(elements[i].getFileName());
1071    
1072          final int lineNumber = elements[i].getLineNumber();
1073          if (lineNumber > 0)
1074          {
1075            buffer.append(':');
1076            buffer.append(lineNumber);
1077          }
1078          buffer.append(')');
1079        }
1080      }
1081    
1082    
1083    
1084      /**
1085       * Retrieves a string representation of the provided {@code Throwable} object
1086       * suitable for use in a message.  For runtime exceptions and errors, then a
1087       * full stack trace for the exception will be provided.  For exception types
1088       * defined in the LDAP SDK, then its {@code getExceptionMessage} method will
1089       * be used to get the string representation.  For all other types of
1090       * exceptions, then the standard string representation will be used.
1091       * <BR><BR>
1092       * For all types of exceptions, the message will also include the cause if one
1093       * exists.
1094       *
1095       * @param  t  The {@code Throwable} for which to generate the exception
1096       *            message.
1097       *
1098       * @return  A string representation of the provided {@code Throwable} object
1099       *          suitable for use in a message.
1100       */
1101      public static String getExceptionMessage(final Throwable t)
1102      {
1103        if (t == null)
1104        {
1105          return ERR_NO_EXCEPTION.get();
1106        }
1107    
1108        final StringBuilder buffer = new StringBuilder();
1109        if (t instanceof LDAPSDKException)
1110        {
1111          buffer.append(((LDAPSDKException) t).getExceptionMessage());
1112        }
1113        else if (t instanceof LDAPSDKRuntimeException)
1114        {
1115          buffer.append(((LDAPSDKRuntimeException) t).getExceptionMessage());
1116        }
1117        if ((t instanceof RuntimeException) || (t instanceof Error))
1118        {
1119          return getStackTrace(t);
1120        }
1121        else
1122        {
1123          buffer.append(String.valueOf(t));
1124        }
1125    
1126        final Throwable cause = t.getCause();
1127        if (cause != null)
1128        {
1129          buffer.append(" caused by ");
1130          buffer.append(getExceptionMessage(cause));
1131        }
1132    
1133        return buffer.toString();
1134      }
1135    
1136    
1137    
1138      /**
1139       * Retrieves the unqualified name (i.e., the name without package information)
1140       * for the provided class.
1141       *
1142       * @param  c  The class for which to retrieve the unqualified name.
1143       *
1144       * @return  The unqualified name for the provided class.
1145       */
1146      public static String getUnqualifiedClassName(final Class<?> c)
1147      {
1148        final String className     = c.getName();
1149        final int    lastPeriodPos = className.lastIndexOf('.');
1150    
1151        if (lastPeriodPos > 0)
1152        {
1153          return className.substring(lastPeriodPos+1);
1154        }
1155        else
1156        {
1157          return className;
1158        }
1159      }
1160    
1161    
1162    
1163      /**
1164       * Encodes the provided timestamp in generalized time format.
1165       *
1166       * @param  timestamp  The timestamp to be encoded in generalized time format.
1167       *                    It should use the same format as the
1168       *                    {@code System.currentTimeMillis()} method (i.e., the
1169       *                    number of milliseconds since 12:00am UTC on January 1,
1170       *                    1970).
1171       *
1172       * @return  The generalized time representation of the provided date.
1173       */
1174      public static String encodeGeneralizedTime(final long timestamp)
1175      {
1176        return encodeGeneralizedTime(new Date(timestamp));
1177      }
1178    
1179    
1180    
1181      /**
1182       * Encodes the provided date in generalized time format.
1183       *
1184       * @param  d  The date to be encoded in generalized time format.
1185       *
1186       * @return  The generalized time representation of the provided date.
1187       */
1188      public static String encodeGeneralizedTime(final Date d)
1189      {
1190        SimpleDateFormat dateFormat = DATE_FORMATTERS.get();
1191        if (dateFormat == null)
1192        {
1193          dateFormat = new SimpleDateFormat("yyyyMMddHHmmss.SSS'Z'");
1194          dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
1195          DATE_FORMATTERS.set(dateFormat);
1196        }
1197    
1198        return dateFormat.format(d);
1199      }
1200    
1201    
1202    
1203      /**
1204       * Decodes the provided string as a timestamp in generalized time format.
1205       *
1206       * @param  t  The timestamp to be decoded.  It must not be {@code null}.
1207       *
1208       * @return  The {@code Date} object decoded from the provided timestamp.
1209       *
1210       * @throws  ParseException  If the provided string could not be decoded as a
1211       *                          timestamp in generalized time format.
1212       */
1213      public static Date decodeGeneralizedTime(final String t)
1214             throws ParseException
1215      {
1216        ensureNotNull(t);
1217    
1218        // Extract the time zone information from the end of the value.
1219        int tzPos;
1220        final TimeZone tz;
1221        if (t.endsWith("Z"))
1222        {
1223          tz = TimeZone.getTimeZone("UTC");
1224          tzPos = t.length() - 1;
1225        }
1226        else
1227        {
1228          tzPos = t.lastIndexOf('-');
1229          if (tzPos < 0)
1230          {
1231            tzPos = t.lastIndexOf('+');
1232            if (tzPos < 0)
1233            {
1234              throw new ParseException(ERR_GENTIME_DECODE_CANNOT_PARSE_TZ.get(t),
1235                                       0);
1236            }
1237          }
1238    
1239          tz = TimeZone.getTimeZone("GMT" + t.substring(tzPos));
1240          if (tz.getRawOffset() == 0)
1241          {
1242            // This is the default time zone that will be returned if the value
1243            // cannot be parsed.  If it's valid, then it will end in "+0000" or
1244            // "-0000".  Otherwise, it's invalid and GMT was just a fallback.
1245            if (! (t.endsWith("+0000") || t.endsWith("-0000")))
1246            {
1247              throw new ParseException(ERR_GENTIME_DECODE_CANNOT_PARSE_TZ.get(t),
1248                                       tzPos);
1249            }
1250          }
1251        }
1252    
1253    
1254        // See if the timestamp has a sub-second portion.  Note that if there is a
1255        // sub-second portion, then we may need to massage the value so that there
1256        // are exactly three sub-second characters so that it can be interpreted as
1257        // milliseconds.
1258        final String subSecFormatStr;
1259        final String trimmedTimestamp;
1260        int periodPos = t.lastIndexOf('.', tzPos);
1261        if (periodPos > 0)
1262        {
1263          final int subSecondLength = tzPos - periodPos - 1;
1264          switch (subSecondLength)
1265          {
1266            case 0:
1267              subSecFormatStr  = "";
1268              trimmedTimestamp = t.substring(0, periodPos);
1269              break;
1270            case 1:
1271              subSecFormatStr  = ".SSS";
1272              trimmedTimestamp = t.substring(0, (periodPos+2)) + "00";
1273              break;
1274            case 2:
1275              subSecFormatStr  = ".SSS";
1276              trimmedTimestamp = t.substring(0, (periodPos+3)) + '0';
1277              break;
1278            default:
1279              subSecFormatStr  = ".SSS";
1280              trimmedTimestamp = t.substring(0, periodPos+4);
1281              break;
1282          }
1283        }
1284        else
1285        {
1286          subSecFormatStr  = "";
1287          periodPos        = tzPos;
1288          trimmedTimestamp = t.substring(0, tzPos);
1289        }
1290    
1291    
1292        // Look at where the period is (or would be if it existed) to see how many
1293        // characters are in the integer portion.  This will give us what we need
1294        // for the rest of the format string.
1295        final String formatStr;
1296        switch (periodPos)
1297        {
1298          case 10:
1299            formatStr = "yyyyMMddHH" + subSecFormatStr;
1300            break;
1301          case 12:
1302            formatStr = "yyyyMMddHHmm" + subSecFormatStr;
1303            break;
1304          case 14:
1305            formatStr = "yyyyMMddHHmmss" + subSecFormatStr;
1306            break;
1307          default:
1308            throw new ParseException(ERR_GENTIME_CANNOT_PARSE_INVALID_LENGTH.get(t),
1309                                     periodPos);
1310        }
1311    
1312    
1313        // We should finally be able to create an appropriate date format object
1314        // to parse the trimmed version of the timestamp.
1315        final SimpleDateFormat dateFormat = new SimpleDateFormat(formatStr);
1316        dateFormat.setTimeZone(tz);
1317        dateFormat.setLenient(false);
1318        return dateFormat.parse(trimmedTimestamp);
1319      }
1320    
1321    
1322    
1323      /**
1324       * Trims only leading spaces from the provided string, leaving any trailing
1325       * spaces intact.
1326       *
1327       * @param  s  The string to be processed.  It must not be {@code null}.
1328       *
1329       * @return  The original string if no trimming was required, or a new string
1330       *          without leading spaces if the provided string had one or more.  It
1331       *          may be an empty string if the provided string was an empty string
1332       *          or contained only spaces.
1333       */
1334      public static String trimLeading(final String s)
1335      {
1336        ensureNotNull(s);
1337    
1338        int nonSpacePos = 0;
1339        final int length = s.length();
1340        while ((nonSpacePos < length) && (s.charAt(nonSpacePos) == ' '))
1341        {
1342          nonSpacePos++;
1343        }
1344    
1345        if (nonSpacePos == 0)
1346        {
1347          // There were no leading spaces.
1348          return s;
1349        }
1350        else if (nonSpacePos >= length)
1351        {
1352          // There were no non-space characters.
1353          return "";
1354        }
1355        else
1356        {
1357          // There were leading spaces, so return the string without them.
1358          return s.substring(nonSpacePos, length);
1359        }
1360      }
1361    
1362    
1363    
1364      /**
1365       * Trims only trailing spaces from the provided string, leaving any leading
1366       * spaces intact.
1367       *
1368       * @param  s  The string to be processed.  It must not be {@code null}.
1369       *
1370       * @return  The original string if no trimming was required, or a new string
1371       *          without trailing spaces if the provided string had one or more.
1372       *          It may be an empty string if the provided string was an empty
1373       *          string or contained only spaces.
1374       */
1375      public static String trimTrailing(final String s)
1376      {
1377        ensureNotNull(s);
1378    
1379        final int lastPos = s.length() - 1;
1380        int nonSpacePos = lastPos;
1381        while ((nonSpacePos >= 0) && (s.charAt(nonSpacePos) == ' '))
1382        {
1383          nonSpacePos--;
1384        }
1385    
1386        if (nonSpacePos < 0)
1387        {
1388          // There were no non-space characters.
1389          return "";
1390        }
1391        else if (nonSpacePos == lastPos)
1392        {
1393          // There were no trailing spaces.
1394          return s;
1395        }
1396        else
1397        {
1398          // There were trailing spaces, so return the string without them.
1399          return s.substring(0, (nonSpacePos+1));
1400        }
1401      }
1402    
1403    
1404    
1405      /**
1406       * Wraps the contents of the specified line using the given width.  It will
1407       * attempt to wrap at spaces to preserve words, but if that is not possible
1408       * (because a single "word" is longer than the maximum width), then it will
1409       * wrap in the middle of the word at the specified maximum width.
1410       *
1411       * @param  line      The line to be wrapped.  It must not be {@code null}.
1412       * @param  maxWidth  The maximum width for lines in the resulting list.  A
1413       *                   value less than or equal to zero will cause no wrapping
1414       *                   to be performed.
1415       *
1416       * @return  A list of the wrapped lines.  It may be empty if the provided line
1417       *          contained only spaces.
1418       */
1419      public static List<String> wrapLine(final String line, final int maxWidth)
1420      {
1421        return wrapLine(line, maxWidth, maxWidth);
1422      }
1423    
1424    
1425    
1426      /**
1427       * Wraps the contents of the specified line using the given width.  It will
1428       * attempt to wrap at spaces to preserve words, but if that is not possible
1429       * (because a single "word" is longer than the maximum width), then it will
1430       * wrap in the middle of the word at the specified maximum width.
1431       *
1432       * @param  line                    The line to be wrapped.  It must not be
1433       *                                 {@code null}.
1434       * @param  maxFirstLineWidth       The maximum length for the first line in
1435       *                                 the resulting list.  A value less than or
1436       *                                 equal to zero will cause no wrapping to be
1437       *                                 performed.
1438       * @param  maxSubsequentLineWidth  The maximum length for all lines except the
1439       *                                 first line.  This must be greater than zero
1440       *                                 unless {@code maxFirstLineWidth} is less
1441       *                                 than or equal to zero.
1442       *
1443       * @return  A list of the wrapped lines.  It may be empty if the provided line
1444       *          contained only spaces.
1445       */
1446      public static List<String> wrapLine(final String line,
1447                                          final int maxFirstLineWidth,
1448                                          final int maxSubsequentLineWidth)
1449      {
1450        if (maxFirstLineWidth > 0)
1451        {
1452          Validator.ensureTrue(maxSubsequentLineWidth > 0);
1453        }
1454    
1455        // See if the provided string already contains line breaks.  If so, then
1456        // treat it as multiple lines rather than a single line.
1457        final int breakPos = line.indexOf('\n');
1458        if (breakPos >= 0)
1459        {
1460          final ArrayList<String> lineList = new ArrayList<String>(10);
1461          final StringTokenizer tokenizer = new StringTokenizer(line, "\r\n");
1462          while (tokenizer.hasMoreTokens())
1463          {
1464            lineList.addAll(wrapLine(tokenizer.nextToken(), maxFirstLineWidth,
1465                 maxSubsequentLineWidth));
1466          }
1467    
1468          return lineList;
1469        }
1470    
1471        final int length = line.length();
1472        if ((maxFirstLineWidth <= 0) || (length < maxFirstLineWidth))
1473        {
1474          return Arrays.asList(line);
1475        }
1476    
1477    
1478        int wrapPos = maxFirstLineWidth;
1479        int lastWrapPos = 0;
1480        final ArrayList<String> lineList = new ArrayList<String>(5);
1481        while (true)
1482        {
1483          final int spacePos = line.lastIndexOf(' ', wrapPos);
1484          if (spacePos > lastWrapPos)
1485          {
1486            // We found a space in an acceptable location, so use it after trimming
1487            // any trailing spaces.
1488            final String s = trimTrailing(line.substring(lastWrapPos, spacePos));
1489    
1490            // Don't bother adding the line if it contained only spaces.
1491            if (s.length() > 0)
1492            {
1493              lineList.add(s);
1494            }
1495    
1496            wrapPos = spacePos;
1497          }
1498          else
1499          {
1500            // We didn't find any spaces, so we'll have to insert a hard break at
1501            // the specified wrap column.
1502            lineList.add(line.substring(lastWrapPos, wrapPos));
1503          }
1504    
1505          // Skip over any spaces before the next non-space character.
1506          while ((wrapPos < length) && (line.charAt(wrapPos) == ' '))
1507          {
1508            wrapPos++;
1509          }
1510    
1511          lastWrapPos = wrapPos;
1512          wrapPos += maxSubsequentLineWidth;
1513          if (wrapPos >= length)
1514          {
1515            // The last fragment can fit on the line, so we can handle that now and
1516            // break.
1517            if (lastWrapPos >= length)
1518            {
1519              break;
1520            }
1521            else
1522            {
1523              final String s = line.substring(lastWrapPos);
1524              if (s.length() > 0)
1525              {
1526                lineList.add(s);
1527              }
1528              break;
1529            }
1530          }
1531        }
1532    
1533        return lineList;
1534      }
1535    
1536    
1537    
1538      /**
1539       * This method returns a form of the provided argument that is safe to
1540       * use on the command line for the local platform. This method is provided as
1541       * a convenience wrapper around {@link ExampleCommandLineArgument}.  Calling
1542       * this method is equivalent to:
1543       *
1544       * <PRE>
1545       *  return ExampleCommandLineArgument.getCleanArgument(s).getLocalForm();
1546       * </PRE>
1547       *
1548       * For getting direct access to command line arguments that are safe to
1549       * use on other platforms, call
1550       * {@link ExampleCommandLineArgument#getCleanArgument}.
1551       *
1552       * @param  s  The string to be processed.  It must not be {@code null}.
1553       *
1554       * @return  A cleaned version of the provided string in a form that will allow
1555       *          it to be displayed as the value of a command-line argument on.
1556       */
1557      public static String cleanExampleCommandLineArgument(final String s)
1558      {
1559        return ExampleCommandLineArgument.getCleanArgument(s).getLocalForm();
1560      }
1561    
1562    
1563    
1564      /**
1565       * Retrieves a single string which is a concatenation of all of the provided
1566       * strings.
1567       *
1568       * @param  a  The array of strings to concatenate.  It must not be
1569       *            {@code null}.
1570       *
1571       * @return  A string containing a concatenation of all of the strings in the
1572       *          provided array.
1573       */
1574      public static String concatenateStrings(final String... a)
1575      {
1576        return concatenateStrings(null, null, "  ", null, null, a);
1577      }
1578    
1579    
1580    
1581      /**
1582       * Retrieves a single string which is a concatenation of all of the provided
1583       * strings.
1584       *
1585       * @param  l  The list of strings to concatenate.  It must not be
1586       *            {@code null}.
1587       *
1588       * @return  A string containing a concatenation of all of the strings in the
1589       *          provided list.
1590       */
1591      public static String concatenateStrings(final List<String> l)
1592      {
1593        return concatenateStrings(null, null, "  ", null, null, l);
1594      }
1595    
1596    
1597    
1598      /**
1599       * Retrieves a single string which is a concatenation of all of the provided
1600       * strings.
1601       *
1602       * @param  beforeList       A string that should be placed at the beginning of
1603       *                          the list.  It may be {@code null} or empty if
1604       *                          nothing should be placed at the beginning of the
1605       *                          list.
1606       * @param  beforeElement    A string that should be placed before each element
1607       *                          in the list.  It may be {@code null} or empty if
1608       *                          nothing should be placed before each element.
1609       * @param  betweenElements  The separator that should be placed between
1610       *                          elements in the list.  It may be {@code null} or
1611       *                          empty if no separator should be placed between
1612       *                          elements.
1613       * @param  afterElement     A string that should be placed after each element
1614       *                          in the list.  It may be {@code null} or empty if
1615       *                          nothing should be placed after each element.
1616       * @param  afterList        A string that should be placed at the end of the
1617       *                          list.  It may be {@code null} or empty if nothing
1618       *                          should be placed at the end of the list.
1619       * @param  a                The array of strings to concatenate.  It must not
1620       *                          be {@code null}.
1621       *
1622       * @return  A string containing a concatenation of all of the strings in the
1623       *          provided list.
1624       */
1625      public static String concatenateStrings(final String beforeList,
1626                                              final String beforeElement,
1627                                              final String betweenElements,
1628                                              final String afterElement,
1629                                              final String afterList,
1630                                              final String... a)
1631      {
1632        return concatenateStrings(beforeList, beforeElement, betweenElements,
1633             afterElement, afterList, Arrays.asList(a));
1634      }
1635    
1636    
1637    
1638      /**
1639       * Retrieves a single string which is a concatenation of all of the provided
1640       * strings.
1641       *
1642       * @param  beforeList       A string that should be placed at the beginning of
1643       *                          the list.  It may be {@code null} or empty if
1644       *                          nothing should be placed at the beginning of the
1645       *                          list.
1646       * @param  beforeElement    A string that should be placed before each element
1647       *                          in the list.  It may be {@code null} or empty if
1648       *                          nothing should be placed before each element.
1649       * @param  betweenElements  The separator that should be placed between
1650       *                          elements in the list.  It may be {@code null} or
1651       *                          empty if no separator should be placed between
1652       *                          elements.
1653       * @param  afterElement     A string that should be placed after each element
1654       *                          in the list.  It may be {@code null} or empty if
1655       *                          nothing should be placed after each element.
1656       * @param  afterList        A string that should be placed at the end of the
1657       *                          list.  It may be {@code null} or empty if nothing
1658       *                          should be placed at the end of the list.
1659       * @param  l                The list of strings to concatenate.  It must not
1660       *                          be {@code null}.
1661       *
1662       * @return  A string containing a concatenation of all of the strings in the
1663       *          provided list.
1664       */
1665      public static String concatenateStrings(final String beforeList,
1666                                              final String beforeElement,
1667                                              final String betweenElements,
1668                                              final String afterElement,
1669                                              final String afterList,
1670                                              final List<String> l)
1671      {
1672        ensureNotNull(l);
1673    
1674        final StringBuilder buffer = new StringBuilder();
1675    
1676        if (beforeList != null)
1677        {
1678          buffer.append(beforeList);
1679        }
1680    
1681        final Iterator<String> iterator = l.iterator();
1682        while (iterator.hasNext())
1683        {
1684          if (beforeElement != null)
1685          {
1686            buffer.append(beforeElement);
1687          }
1688    
1689          buffer.append(iterator.next());
1690    
1691          if (afterElement != null)
1692          {
1693            buffer.append(afterElement);
1694          }
1695    
1696          if ((betweenElements != null) && iterator.hasNext())
1697          {
1698            buffer.append(betweenElements);
1699          }
1700        }
1701    
1702        if (afterList != null)
1703        {
1704          buffer.append(afterList);
1705        }
1706    
1707        return buffer.toString();
1708      }
1709    
1710    
1711    
1712      /**
1713       * Converts a duration in seconds to a string with a human-readable duration
1714       * which may include days, hours, minutes, and seconds, to the extent that
1715       * they are needed.
1716       *
1717       * @param  s  The number of seconds to be represented.
1718       *
1719       * @return  A string containing a human-readable representation of the
1720       *          provided time.
1721       */
1722      public static String secondsToHumanReadableDuration(final long s)
1723      {
1724        return millisToHumanReadableDuration(s * 1000L);
1725      }
1726    
1727    
1728    
1729      /**
1730       * Converts a duration in seconds to a string with a human-readable duration
1731       * which may include days, hours, minutes, and seconds, to the extent that
1732       * they are needed.
1733       *
1734       * @param  m  The number of milliseconds to be represented.
1735       *
1736       * @return  A string containing a human-readable representation of the
1737       *          provided time.
1738       */
1739      public static String millisToHumanReadableDuration(final long m)
1740      {
1741        final StringBuilder buffer = new StringBuilder();
1742        long numMillis = m;
1743    
1744        final long numDays = numMillis / 86400000L;
1745        if (numDays > 0)
1746        {
1747          numMillis -= (numDays * 86400000L);
1748          if (numDays == 1)
1749          {
1750            buffer.append(INFO_NUM_DAYS_SINGULAR.get(numDays));
1751          }
1752          else
1753          {
1754            buffer.append(INFO_NUM_DAYS_PLURAL.get(numDays));
1755          }
1756        }
1757    
1758        final long numHours = numMillis / 3600000L;
1759        if (numHours > 0)
1760        {
1761          numMillis -= (numHours * 3600000L);
1762          if (buffer.length() > 0)
1763          {
1764            buffer.append(", ");
1765          }
1766    
1767          if (numHours == 1)
1768          {
1769            buffer.append(INFO_NUM_HOURS_SINGULAR.get(numHours));
1770          }
1771          else
1772          {
1773            buffer.append(INFO_NUM_HOURS_PLURAL.get(numHours));
1774          }
1775        }
1776    
1777        final long numMinutes = numMillis / 60000L;
1778        if (numMinutes > 0)
1779        {
1780          numMillis -= (numMinutes * 60000L);
1781          if (buffer.length() > 0)
1782          {
1783            buffer.append(", ");
1784          }
1785    
1786          if (numMinutes == 1)
1787          {
1788            buffer.append(INFO_NUM_MINUTES_SINGULAR.get(numMinutes));
1789          }
1790          else
1791          {
1792            buffer.append(INFO_NUM_MINUTES_PLURAL.get(numMinutes));
1793          }
1794        }
1795    
1796        if (numMillis == 1000)
1797        {
1798          if (buffer.length() > 0)
1799          {
1800            buffer.append(", ");
1801          }
1802    
1803          buffer.append(INFO_NUM_SECONDS_SINGULAR.get(1));
1804        }
1805        else if ((numMillis > 0) || (buffer.length() == 0))
1806        {
1807          if (buffer.length() > 0)
1808          {
1809            buffer.append(", ");
1810          }
1811    
1812          final long numSeconds = numMillis / 1000L;
1813          numMillis -= (numSeconds * 1000L);
1814          if ((numMillis % 1000L) != 0L)
1815          {
1816            final double numSecondsDouble = numSeconds + (numMillis / 1000.0);
1817            final DecimalFormat decimalFormat = new DecimalFormat("0.000");
1818            buffer.append(INFO_NUM_SECONDS_WITH_DECIMAL.get(
1819                 decimalFormat.format(numSecondsDouble)));
1820          }
1821          else
1822          {
1823            buffer.append(INFO_NUM_SECONDS_PLURAL.get(numSeconds));
1824          }
1825        }
1826    
1827        return buffer.toString();
1828      }
1829    
1830    
1831    
1832      /**
1833       * Converts the provided number of nanoseconds to milliseconds.
1834       *
1835       * @param  nanos  The number of nanoseconds to convert to milliseconds.
1836       *
1837       * @return  The number of milliseconds that most closely corresponds to the
1838       *          specified number of nanoseconds.
1839       */
1840      public static long nanosToMillis(final long nanos)
1841      {
1842        return Math.max(0L, Math.round(nanos / 1000000.0d));
1843      }
1844    
1845    
1846    
1847      /**
1848       * Converts the provided number of milliseconds to nanoseconds.
1849       *
1850       * @param  millis  The number of milliseconds to convert to nanoseconds.
1851       *
1852       * @return  The number of nanoseconds that most closely corresponds to the
1853       *          specified number of milliseconds.
1854       */
1855      public static long millisToNanos(final long millis)
1856      {
1857        return Math.max(0L, (millis * 1000000L));
1858      }
1859    
1860    
1861    
1862      /**
1863       * Indicates whether the provided string is a valid numeric OID.  A numeric
1864       * OID must start and end with a digit, must have at least on period, must
1865       * contain only digits and periods, and must not have two consecutive periods.
1866       *
1867       * @param  s  The string to examine.  It must not be {@code null}.
1868       *
1869       * @return  {@code true} if the provided string is a valid numeric OID, or
1870       *          {@code false} if not.
1871       */
1872      public static boolean isNumericOID(final String s)
1873      {
1874        boolean digitRequired = true;
1875        boolean periodFound   = false;
1876        for (final char c : s.toCharArray())
1877        {
1878          switch (c)
1879          {
1880            case '0':
1881            case '1':
1882            case '2':
1883            case '3':
1884            case '4':
1885            case '5':
1886            case '6':
1887            case '7':
1888            case '8':
1889            case '9':
1890              digitRequired = false;
1891              break;
1892    
1893            case '.':
1894              if (digitRequired)
1895              {
1896                return false;
1897              }
1898              else
1899              {
1900                digitRequired = true;
1901              }
1902              periodFound = true;
1903              break;
1904    
1905            default:
1906              return false;
1907          }
1908    
1909        }
1910    
1911        return (periodFound && (! digitRequired));
1912      }
1913    
1914    
1915    
1916      /**
1917       * Capitalizes the provided string.  The first character will be converted to
1918       * uppercase, and the rest of the string will be left unaltered.
1919       *
1920       * @param  s  The string to be capitalized.
1921       *
1922       * @return  A capitalized version of the provided string.
1923       */
1924      public static String capitalize(final String s)
1925      {
1926        return capitalize(s, false);
1927      }
1928    
1929    
1930    
1931      /**
1932       * Capitalizes the provided string.  The first character of the string (or
1933       * optionally the first character of each word in the string)
1934       *
1935       * @param  s         The string to be capitalized.
1936       * @param  allWords  Indicates whether to capitalize all words in the string,
1937       *                   or only the first word.
1938       *
1939       * @return  A capitalized version of the provided string.
1940       */
1941      public static String capitalize(final String s, final boolean allWords)
1942      {
1943        if (s == null)
1944        {
1945          return null;
1946        }
1947    
1948        switch (s.length())
1949        {
1950          case 0:
1951            return s;
1952    
1953          case 1:
1954            return s.toUpperCase();
1955    
1956          default:
1957            boolean capitalize = true;
1958            final char[] chars = s.toCharArray();
1959            final StringBuilder buffer = new StringBuilder(chars.length);
1960            for (final char c : chars)
1961            {
1962              // Whitespace and punctuation will be considered word breaks.
1963              if (Character.isWhitespace(c) ||
1964                  (((c >= '!') && (c <= '.')) ||
1965                   ((c >= ':') && (c <= '@')) ||
1966                   ((c >= '[') && (c <= '`')) ||
1967                   ((c >= '{') && (c <= '~'))))
1968              {
1969                buffer.append(c);
1970                capitalize |= allWords;
1971              }
1972              else if (capitalize)
1973              {
1974                buffer.append(Character.toUpperCase(c));
1975                capitalize = false;
1976              }
1977              else
1978              {
1979                buffer.append(c);
1980              }
1981            }
1982            return buffer.toString();
1983        }
1984      }
1985    
1986    
1987    
1988      /**
1989       * Encodes the provided UUID to a byte array containing its 128-bit
1990       * representation.
1991       *
1992       * @param  uuid  The UUID to be encoded.  It must not be {@code null}.
1993       *
1994       * @return  The byte array containing the 128-bit encoded UUID.
1995       */
1996      public static byte[] encodeUUID(final UUID uuid)
1997      {
1998        final byte[] b = new byte[16];
1999    
2000        final long mostSignificantBits  = uuid.getMostSignificantBits();
2001        b[0]  = (byte) ((mostSignificantBits >> 56) & 0xFF);
2002        b[1]  = (byte) ((mostSignificantBits >> 48) & 0xFF);
2003        b[2]  = (byte) ((mostSignificantBits >> 40) & 0xFF);
2004        b[3]  = (byte) ((mostSignificantBits >> 32) & 0xFF);
2005        b[4]  = (byte) ((mostSignificantBits >> 24) & 0xFF);
2006        b[5]  = (byte) ((mostSignificantBits >> 16) & 0xFF);
2007        b[6]  = (byte) ((mostSignificantBits >> 8) & 0xFF);
2008        b[7]  = (byte) (mostSignificantBits & 0xFF);
2009    
2010        final long leastSignificantBits = uuid.getLeastSignificantBits();
2011        b[8]  = (byte) ((leastSignificantBits >> 56) & 0xFF);
2012        b[9]  = (byte) ((leastSignificantBits >> 48) & 0xFF);
2013        b[10] = (byte) ((leastSignificantBits >> 40) & 0xFF);
2014        b[11] = (byte) ((leastSignificantBits >> 32) & 0xFF);
2015        b[12] = (byte) ((leastSignificantBits >> 24) & 0xFF);
2016        b[13] = (byte) ((leastSignificantBits >> 16) & 0xFF);
2017        b[14] = (byte) ((leastSignificantBits >> 8) & 0xFF);
2018        b[15] = (byte) (leastSignificantBits & 0xFF);
2019    
2020        return b;
2021      }
2022    
2023    
2024    
2025      /**
2026       * Decodes the value of the provided byte array as a Java UUID.
2027       *
2028       * @param  b  The byte array to be decoded as a UUID.  It must not be
2029       *            {@code null}.
2030       *
2031       * @return  The decoded UUID.
2032       *
2033       * @throws  ParseException  If the provided byte array cannot be parsed as a
2034       *                         UUID.
2035       */
2036      public static UUID decodeUUID(final byte[] b)
2037             throws ParseException
2038      {
2039        if (b.length != 16)
2040        {
2041          throw new ParseException(ERR_DECODE_UUID_INVALID_LENGTH.get(toHex(b)), 0);
2042        }
2043    
2044        long mostSignificantBits = 0L;
2045        for (int i=0; i < 8; i++)
2046        {
2047          mostSignificantBits = (mostSignificantBits << 8) | (b[i] & 0xFF);
2048        }
2049    
2050        long leastSignificantBits = 0L;
2051        for (int i=8; i < 16; i++)
2052        {
2053          leastSignificantBits = (leastSignificantBits << 8) | (b[i] & 0xFF);
2054        }
2055    
2056        return new UUID(mostSignificantBits, leastSignificantBits);
2057      }
2058    
2059    
2060    
2061      /**
2062       * Returns {@code true} if and only if the current process is running on
2063       * a Windows-based operating system.
2064       *
2065       * @return  {@code true} if the current process is running on a Windows-based
2066       *          operating system and {@code false} otherwise.
2067       */
2068      public static boolean isWindows()
2069      {
2070        final String osName = toLowerCase(System.getProperty("os.name"));
2071        return ((osName != null) && osName.contains("windows"));
2072      }
2073    
2074    
2075    
2076      /**
2077       * Attempts to parse the contents of the provided string to an argument list
2078       * (e.g., converts something like "--arg1 arg1value --arg2 --arg3 arg3value"
2079       * to a list of "--arg1", "arg1value", "--arg2", "--arg3", "arg3value").
2080       *
2081       * @param  s  The string to be converted to an argument list.
2082       *
2083       * @return  The parsed argument list.
2084       *
2085       * @throws  ParseException  If a problem is encountered while attempting to
2086       *                          parse the given string to an argument list.
2087       */
2088      public static List<String> toArgumentList(final String s)
2089             throws ParseException
2090      {
2091        if ((s == null) || (s.length() == 0))
2092        {
2093          return Collections.emptyList();
2094        }
2095    
2096        int quoteStartPos = -1;
2097        boolean inEscape = false;
2098        final ArrayList<String> argList = new ArrayList<String>();
2099        final StringBuilder currentArg = new StringBuilder();
2100        for (int i=0; i < s.length(); i++)
2101        {
2102          final char c = s.charAt(i);
2103          if (inEscape)
2104          {
2105            currentArg.append(c);
2106            inEscape = false;
2107            continue;
2108          }
2109    
2110          if (c == '\\')
2111          {
2112            inEscape = true;
2113          }
2114          else if (c == '"')
2115          {
2116            if (quoteStartPos >= 0)
2117            {
2118              quoteStartPos = -1;
2119            }
2120            else
2121            {
2122              quoteStartPos = i;
2123            }
2124          }
2125          else if (c == ' ')
2126          {
2127            if (quoteStartPos >= 0)
2128            {
2129              currentArg.append(c);
2130            }
2131            else if (currentArg.length() > 0)
2132            {
2133              argList.add(currentArg.toString());
2134              currentArg.setLength(0);
2135            }
2136          }
2137          else
2138          {
2139            currentArg.append(c);
2140          }
2141        }
2142    
2143        if (s.endsWith("\\") && (! s.endsWith("\\\\")))
2144        {
2145          throw new ParseException(ERR_ARG_STRING_DANGLING_BACKSLASH.get(),
2146               (s.length() - 1));
2147        }
2148    
2149        if (quoteStartPos >= 0)
2150        {
2151          throw new ParseException(ERR_ARG_STRING_UNMATCHED_QUOTE.get(
2152               quoteStartPos), quoteStartPos);
2153        }
2154    
2155        if (currentArg.length() > 0)
2156        {
2157          argList.add(currentArg.toString());
2158        }
2159    
2160        return Collections.unmodifiableList(argList);
2161      }
2162    
2163    
2164    
2165      /**
2166       * Creates a modifiable list with all of the items of the provided array in
2167       * the same order.  This method behaves much like {@code Arrays.asList},
2168       * except that if the provided array is {@code null}, then it will return a
2169       * {@code null} list rather than throwing an exception.
2170       *
2171       * @param  <T>  The type of item contained in the provided array.
2172       *
2173       * @param  array  The array of items to include in the list.
2174       *
2175       * @return  The list that was created, or {@code null} if the provided array
2176       *          was {@code null}.
2177       */
2178      public static <T> List<T> toList(final T[] array)
2179      {
2180        if (array == null)
2181        {
2182          return null;
2183        }
2184    
2185        final ArrayList<T> l = new ArrayList<T>(array.length);
2186        l.addAll(Arrays.asList(array));
2187        return l;
2188      }
2189    
2190    
2191    
2192      /**
2193       * Creates a modifiable list with all of the items of the provided array in
2194       * the same order.  This method behaves much like {@code Arrays.asList},
2195       * except that if the provided array is {@code null}, then it will return an
2196       * empty list rather than throwing an exception.
2197       *
2198       * @param  <T>  The type of item contained in the provided array.
2199       *
2200       * @param  array  The array of items to include in the list.
2201       *
2202       * @return  The list that was created, or an empty list if the provided array
2203       *          was {@code null}.
2204       */
2205      public static <T> List<T> toNonNullList(final T[] array)
2206      {
2207        if (array == null)
2208        {
2209          return new ArrayList<T>(0);
2210        }
2211    
2212        final ArrayList<T> l = new ArrayList<T>(array.length);
2213        l.addAll(Arrays.asList(array));
2214        return l;
2215      }
2216    
2217    
2218    
2219      /**
2220       * Indicates whether both of the provided objects are {@code null} or both
2221       * are logically equal (using the {@code equals} method).
2222       *
2223       * @param  o1  The first object for which to make the determination.
2224       * @param  o2  The second object for which to make the determination.
2225       *
2226       * @return  {@code true} if both objects are {@code null} or both are
2227       *          logically equal, or {@code false} if only one of the objects is
2228       *          {@code null} or they are not logically equal.
2229       */
2230      public static boolean bothNullOrEqual(final Object o1, final Object o2)
2231      {
2232        if (o1 == null)
2233        {
2234          return (o2 == null);
2235        }
2236        else if (o2 == null)
2237        {
2238          return false;
2239        }
2240    
2241        return o1.equals(o2);
2242      }
2243    
2244    
2245    
2246      /**
2247       * Indicates whether both of the provided strings are {@code null} or both
2248       * are logically equal ignoring differences in capitalization (using the
2249       * {@code equalsIgnoreCase} method).
2250       *
2251       * @param  s1  The first string for which to make the determination.
2252       * @param  s2  The second string for which to make the determination.
2253       *
2254       * @return  {@code true} if both strings are {@code null} or both are
2255       *          logically equal ignoring differences in capitalization, or
2256       *          {@code false} if only one of the objects is {@code null} or they
2257       *          are not logically equal ignoring capitalization.
2258       */
2259      public static boolean bothNullOrEqualIgnoreCase(final String s1,
2260                                                      final String s2)
2261      {
2262        if (s1 == null)
2263        {
2264          return (s2 == null);
2265        }
2266        else if (s2 == null)
2267        {
2268          return false;
2269        }
2270    
2271        return s1.equalsIgnoreCase(s2);
2272      }
2273    
2274    
2275    
2276      /**
2277       * Indicates whether the provided string arrays have the same elements,
2278       * ignoring the order in which they appear and differences in capitalization.
2279       * It is assumed that neither array contains {@code null} strings, and that
2280       * no string appears more than once in each array.
2281       *
2282       * @param  a1  The first array for which to make the determination.
2283       * @param  a2  The second array for which to make the determination.
2284       *
2285       * @return  {@code true} if both arrays have the same set of strings, or
2286       *          {@code false} if not.
2287       */
2288      public static boolean stringsEqualIgnoreCaseOrderIndependent(
2289                                 final String[] a1, final String[] a2)
2290      {
2291        if (a1 == null)
2292        {
2293          return (a2 == null);
2294        }
2295        else if (a2 == null)
2296        {
2297          return false;
2298        }
2299    
2300        if (a1.length != a2.length)
2301        {
2302          return false;
2303        }
2304    
2305        if (a1.length == 1)
2306        {
2307          return (a1[0].equalsIgnoreCase(a2[0]));
2308        }
2309    
2310        final HashSet<String> s1 = new HashSet<String>(a1.length);
2311        for (final String s : a1)
2312        {
2313          s1.add(toLowerCase(s));
2314        }
2315    
2316        final HashSet<String> s2 = new HashSet<String>(a2.length);
2317        for (final String s : a2)
2318        {
2319          s2.add(toLowerCase(s));
2320        }
2321    
2322        return s1.equals(s2);
2323      }
2324    
2325    
2326    
2327      /**
2328       * Indicates whether the provided arrays have the same elements, ignoring the
2329       * order in which they appear.  It is assumed that neither array contains
2330       * {@code null} elements, and that no element appears more than once in each
2331       * array.
2332       *
2333       * @param  <T>  The type of element contained in the arrays.
2334       *
2335       * @param  a1  The first array for which to make the determination.
2336       * @param  a2  The second array for which to make the determination.
2337       *
2338       * @return  {@code true} if both arrays have the same set of elements, or
2339       *          {@code false} if not.
2340       */
2341      public static <T> boolean arraysEqualOrderIndependent(final T[] a1,
2342                                                            final T[] a2)
2343      {
2344        if (a1 == null)
2345        {
2346          return (a2 == null);
2347        }
2348        else if (a2 == null)
2349        {
2350          return false;
2351        }
2352    
2353        if (a1.length != a2.length)
2354        {
2355          return false;
2356        }
2357    
2358        if (a1.length == 1)
2359        {
2360          return (a1[0].equals(a2[0]));
2361        }
2362    
2363        final HashSet<T> s1 = new HashSet<T>(Arrays.asList(a1));
2364        final HashSet<T> s2 = new HashSet<T>(Arrays.asList(a2));
2365        return s1.equals(s2);
2366      }
2367    
2368    
2369    
2370      /**
2371       * Determines the number of bytes in a UTF-8 character that starts with the
2372       * given byte.
2373       *
2374       * @param  b  The byte for which to make the determination.
2375       *
2376       * @return  The number of bytes in a UTF-8 character that starts with the
2377       *          given byte, or -1 if it does not appear to be a valid first byte
2378       *          for a UTF-8 character.
2379       */
2380      public static int numBytesInUTF8CharacterWithFirstByte(final byte b)
2381      {
2382        if ((b & 0x7F) == b)
2383        {
2384          return 1;
2385        }
2386        else if ((b & 0xE0) == 0xC0)
2387        {
2388          return 2;
2389        }
2390        else if ((b & 0xF0) == 0xE0)
2391        {
2392          return 3;
2393        }
2394        else if ((b & 0xF8) == 0xF0)
2395        {
2396          return 4;
2397        }
2398        else
2399        {
2400          return -1;
2401        }
2402      }
2403    
2404    
2405    
2406      /**
2407       * Indicates whether the provided attribute name should be considered a
2408       * sensitive attribute for the purposes of {@code toCode} methods.  If an
2409       * attribute is considered sensitive, then its values will be redacted in the
2410       * output of the {@code toCode} methods.
2411       *
2412       * @param  name  The name for which to make the determination.  It may or may
2413       *               not include attribute options.  It must not be {@code null}.
2414       *
2415       * @return  {@code true} if the specified attribute is one that should be
2416       *          considered sensitive for the
2417       */
2418      public static boolean isSensitiveToCodeAttribute(final String name)
2419      {
2420        final String lowerBaseName = Attribute.getBaseName(name).toLowerCase();
2421        return TO_CODE_SENSITIVE_ATTRIBUTE_NAMES.contains(lowerBaseName);
2422      }
2423    
2424    
2425    
2426      /**
2427       * Retrieves a set containing the base names (in all lowercase characters) of
2428       * any attributes that should be considered sensitive for the purposes of the
2429       * {@code toCode} methods.  By default, only the userPassword and
2430       * authPassword attributes and their respective OIDs will be included.
2431       *
2432       * @return  A set containing the base names (in all lowercase characters) of
2433       *          any attributes that should be considered sensitive for the
2434       *          purposes of the {@code toCode} methods.
2435       */
2436      public static Set<String> getSensitiveToCodeAttributeBaseNames()
2437      {
2438        return TO_CODE_SENSITIVE_ATTRIBUTE_NAMES;
2439      }
2440    
2441    
2442    
2443      /**
2444       * Specifies the names of any attributes that should be considered sensitive
2445       * for the purposes of the {@code toCode} methods.
2446       *
2447       * @param  names  The names of any attributes that should be considered
2448       *                sensitive for the purposes of the {@code toCode} methods.
2449       *                It may be {@code null} or empty if no attributes should be
2450       *                considered sensitive.
2451       */
2452      public static void setSensitiveToCodeAttributes(final String... names)
2453      {
2454        setSensitiveToCodeAttributes(toList(names));
2455      }
2456    
2457    
2458    
2459      /**
2460       * Specifies the names of any attributes that should be considered sensitive
2461       * for the purposes of the {@code toCode} methods.
2462       *
2463       * @param  names  The names of any attributes that should be considered
2464       *                sensitive for the purposes of the {@code toCode} methods.
2465       *                It may be {@code null} or empty if no attributes should be
2466       *                considered sensitive.
2467       */
2468      public static void setSensitiveToCodeAttributes(
2469                              final Collection<String> names)
2470      {
2471        if ((names == null) || names.isEmpty())
2472        {
2473          TO_CODE_SENSITIVE_ATTRIBUTE_NAMES = Collections.emptySet();
2474        }
2475        else
2476        {
2477          final LinkedHashSet<String> nameSet =
2478               new LinkedHashSet<String>(names.size());
2479          for (final String s : names)
2480          {
2481            nameSet.add(Attribute.getBaseName(s).toLowerCase());
2482          }
2483    
2484          TO_CODE_SENSITIVE_ATTRIBUTE_NAMES = Collections.unmodifiableSet(nameSet);
2485        }
2486      }
2487    
2488    
2489    
2490      /**
2491       * Creates a new {@code IOException} with a cause.  The constructor needed to
2492       * do this wasn't available until Java SE 6, so reflection is used to invoke
2493       * this constructor in versions of Java that provide it.  In Java SE 5, the
2494       * provided message will be augmented with information about the cause.
2495       *
2496       * @param  message  The message to use for the exception.  This may be
2497       *                  {@code null} if the message should be generated from the
2498       *                  provided cause.
2499       * @param  cause    The underlying cause for the exception.  It may be
2500       *                  {@code null} if the exception should have only a message.
2501       *
2502       * @return  The {@code IOException} object that was created.
2503       */
2504      public static IOException createIOExceptionWithCause(final String message,
2505                                                           final Throwable cause)
2506      {
2507        if (cause == null)
2508        {
2509          return new IOException(message);
2510        }
2511    
2512        try
2513        {
2514          if (message == null)
2515          {
2516            final Constructor<IOException> constructor =
2517                 IOException.class.getConstructor(Throwable.class);
2518            return constructor.newInstance(cause);
2519          }
2520          else
2521          {
2522            final Constructor<IOException> constructor =
2523                 IOException.class.getConstructor(String.class, Throwable.class);
2524            return constructor.newInstance(message, cause);
2525          }
2526        }
2527        catch (final Exception e)
2528        {
2529          debugException(e);
2530          if (message == null)
2531          {
2532            return new IOException(getExceptionMessage(cause));
2533          }
2534          else
2535          {
2536            return new IOException(message + " (caused by " +
2537                 getExceptionMessage(cause) + ')');
2538          }
2539        }
2540      }
2541    }