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