001/*
002 * Copyright 2007-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 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 */
021package com.unboundid.util;
022
023
024
025import java.lang.reflect.Constructor;
026import java.io.BufferedReader;
027import java.io.IOException;
028import java.io.StringReader;
029import java.text.DecimalFormat;
030import java.text.ParseException;
031import java.text.SimpleDateFormat;
032import java.util.ArrayList;
033import java.util.Arrays;
034import java.util.Collection;
035import java.util.Collections;
036import java.util.Date;
037import java.util.HashSet;
038import java.util.Iterator;
039import java.util.LinkedHashSet;
040import java.util.List;
041import java.util.Set;
042import java.util.StringTokenizer;
043import java.util.TimeZone;
044import java.util.UUID;
045
046import com.unboundid.ldap.sdk.Attribute;
047import com.unboundid.ldap.sdk.Control;
048import com.unboundid.ldap.sdk.Version;
049
050import static com.unboundid.util.Debug.*;
051import static com.unboundid.util.UtilityMessages.*;
052import 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)
060public 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}