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