001/*
002 * Copyright 2007-2017 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 Ping Identity Corporation
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.io.BufferedReader;
026import java.io.IOException;
027import java.io.StringReader;
028import java.text.DecimalFormat;
029import java.text.ParseException;
030import java.text.SimpleDateFormat;
031import java.util.ArrayList;
032import java.util.Arrays;
033import java.util.Collection;
034import java.util.Collections;
035import java.util.Date;
036import java.util.HashSet;
037import java.util.Iterator;
038import java.util.LinkedHashSet;
039import java.util.List;
040import java.util.Set;
041import java.util.StringTokenizer;
042import java.util.TimeZone;
043import java.util.UUID;
044
045import com.unboundid.ldap.sdk.Attribute;
046import com.unboundid.ldap.sdk.Control;
047import com.unboundid.ldap.sdk.Version;
048
049import static com.unboundid.util.Debug.*;
050import static com.unboundid.util.UtilityMessages.*;
051import static com.unboundid.util.Validator.*;
052
053
054
055/**
056 * This class provides a number of static utility functions.
057 */
058@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
059public 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 (final 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 (final 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 (final 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(", ldapSDKVersion=");
1027    buffer.append(Version.NUMERIC_VERSION_STRING);
1028    buffer.append(", revision=");
1029    buffer.append(Version.REVISION_ID);
1030    buffer.append(')');
1031  }
1032
1033
1034
1035  /**
1036   * Returns a single-line string representation of the stack trace.  It will
1037   * include a list of source files and line numbers (if available) for the
1038   * stack trace.
1039   *
1040   * @param  elements  The stack trace.
1041   *
1042   * @return  A single-line string representation of the stack trace.
1043   */
1044  public static String getStackTrace(final StackTraceElement[] elements)
1045  {
1046    final StringBuilder buffer = new StringBuilder();
1047    getStackTrace(elements, buffer);
1048    return buffer.toString();
1049  }
1050
1051
1052
1053  /**
1054   * Appends a single-line string representation of the stack trace to the given
1055   * buffer.  It will include a list of source files and line numbers
1056   * (if available) for the stack trace.
1057   *
1058   * @param  elements  The stack trace.
1059   * @param  buffer  The buffer to which the information should be appended.
1060   */
1061  public static void getStackTrace(final StackTraceElement[] elements,
1062                                   final StringBuilder buffer)
1063  {
1064    for (int i=0; i < elements.length; i++)
1065    {
1066      if (i > 0)
1067      {
1068        buffer.append(" / ");
1069      }
1070
1071      buffer.append(elements[i].getMethodName());
1072      buffer.append('(');
1073      buffer.append(elements[i].getFileName());
1074
1075      final int lineNumber = elements[i].getLineNumber();
1076      if (lineNumber > 0)
1077      {
1078        buffer.append(':');
1079        buffer.append(lineNumber);
1080      }
1081      else if (elements[i].isNativeMethod())
1082      {
1083        buffer.append(":native");
1084      }
1085      else
1086      {
1087        buffer.append(":unknown");
1088      }
1089      buffer.append(')');
1090    }
1091  }
1092
1093
1094
1095  /**
1096   * Retrieves a string representation of the provided {@code Throwable} object
1097   * suitable for use in a message.  For runtime exceptions and errors, then a
1098   * full stack trace for the exception will be provided.  For exception types
1099   * defined in the LDAP SDK, then its {@code getExceptionMessage} method will
1100   * be used to get the string representation.  For all other types of
1101   * exceptions, then the standard string representation will be used.
1102   * <BR><BR>
1103   * For all types of exceptions, the message will also include the cause if one
1104   * exists.
1105   *
1106   * @param  t  The {@code Throwable} for which to generate the exception
1107   *            message.
1108   *
1109   * @return  A string representation of the provided {@code Throwable} object
1110   *          suitable for use in a message.
1111   */
1112  public static String getExceptionMessage(final Throwable t)
1113  {
1114    if (t == null)
1115    {
1116      return ERR_NO_EXCEPTION.get();
1117    }
1118
1119    final StringBuilder buffer = new StringBuilder();
1120    if (t instanceof LDAPSDKException)
1121    {
1122      buffer.append(((LDAPSDKException) t).getExceptionMessage());
1123    }
1124    else if (t instanceof LDAPSDKRuntimeException)
1125    {
1126      buffer.append(((LDAPSDKRuntimeException) t).getExceptionMessage());
1127    }
1128    else
1129    {
1130      return getStackTrace(t);
1131    }
1132
1133    final Throwable cause = t.getCause();
1134    if (cause != null)
1135    {
1136      buffer.append(" caused by ");
1137      buffer.append(getExceptionMessage(cause));
1138    }
1139
1140    return buffer.toString();
1141  }
1142
1143
1144
1145  /**
1146   * Retrieves the unqualified name (i.e., the name without package information)
1147   * for the provided class.
1148   *
1149   * @param  c  The class for which to retrieve the unqualified name.
1150   *
1151   * @return  The unqualified name for the provided class.
1152   */
1153  public static String getUnqualifiedClassName(final Class<?> c)
1154  {
1155    final String className     = c.getName();
1156    final int    lastPeriodPos = className.lastIndexOf('.');
1157
1158    if (lastPeriodPos > 0)
1159    {
1160      return className.substring(lastPeriodPos+1);
1161    }
1162    else
1163    {
1164      return className;
1165    }
1166  }
1167
1168
1169
1170  /**
1171   * Encodes the provided timestamp in generalized time format.
1172   *
1173   * @param  timestamp  The timestamp to be encoded in generalized time format.
1174   *                    It should use the same format as the
1175   *                    {@code System.currentTimeMillis()} method (i.e., the
1176   *                    number of milliseconds since 12:00am UTC on January 1,
1177   *                    1970).
1178   *
1179   * @return  The generalized time representation of the provided date.
1180   */
1181  public static String encodeGeneralizedTime(final long timestamp)
1182  {
1183    return encodeGeneralizedTime(new Date(timestamp));
1184  }
1185
1186
1187
1188  /**
1189   * Encodes the provided date in generalized time format.
1190   *
1191   * @param  d  The date to be encoded in generalized time format.
1192   *
1193   * @return  The generalized time representation of the provided date.
1194   */
1195  public static String encodeGeneralizedTime(final Date d)
1196  {
1197    SimpleDateFormat dateFormat = DATE_FORMATTERS.get();
1198    if (dateFormat == null)
1199    {
1200      dateFormat = new SimpleDateFormat("yyyyMMddHHmmss.SSS'Z'");
1201      dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
1202      DATE_FORMATTERS.set(dateFormat);
1203    }
1204
1205    return dateFormat.format(d);
1206  }
1207
1208
1209
1210  /**
1211   * Decodes the provided string as a timestamp in generalized time format.
1212   *
1213   * @param  t  The timestamp to be decoded.  It must not be {@code null}.
1214   *
1215   * @return  The {@code Date} object decoded from the provided timestamp.
1216   *
1217   * @throws  ParseException  If the provided string could not be decoded as a
1218   *                          timestamp in generalized time format.
1219   */
1220  public static Date decodeGeneralizedTime(final String t)
1221         throws ParseException
1222  {
1223    ensureNotNull(t);
1224
1225    // Extract the time zone information from the end of the value.
1226    int tzPos;
1227    final TimeZone tz;
1228    if (t.endsWith("Z"))
1229    {
1230      tz = TimeZone.getTimeZone("UTC");
1231      tzPos = t.length() - 1;
1232    }
1233    else
1234    {
1235      tzPos = t.lastIndexOf('-');
1236      if (tzPos < 0)
1237      {
1238        tzPos = t.lastIndexOf('+');
1239        if (tzPos < 0)
1240        {
1241          throw new ParseException(ERR_GENTIME_DECODE_CANNOT_PARSE_TZ.get(t),
1242                                   0);
1243        }
1244      }
1245
1246      tz = TimeZone.getTimeZone("GMT" + t.substring(tzPos));
1247      if (tz.getRawOffset() == 0)
1248      {
1249        // This is the default time zone that will be returned if the value
1250        // cannot be parsed.  If it's valid, then it will end in "+0000" or
1251        // "-0000".  Otherwise, it's invalid and GMT was just a fallback.
1252        if (! (t.endsWith("+0000") || t.endsWith("-0000")))
1253        {
1254          throw new ParseException(ERR_GENTIME_DECODE_CANNOT_PARSE_TZ.get(t),
1255                                   tzPos);
1256        }
1257      }
1258    }
1259
1260
1261    // See if the timestamp has a sub-second portion.  Note that if there is a
1262    // sub-second portion, then we may need to massage the value so that there
1263    // are exactly three sub-second characters so that it can be interpreted as
1264    // milliseconds.
1265    final String subSecFormatStr;
1266    final String trimmedTimestamp;
1267    int periodPos = t.lastIndexOf('.', tzPos);
1268    if (periodPos > 0)
1269    {
1270      final int subSecondLength = tzPos - periodPos - 1;
1271      switch (subSecondLength)
1272      {
1273        case 0:
1274          subSecFormatStr  = "";
1275          trimmedTimestamp = t.substring(0, periodPos);
1276          break;
1277        case 1:
1278          subSecFormatStr  = ".SSS";
1279          trimmedTimestamp = t.substring(0, (periodPos+2)) + "00";
1280          break;
1281        case 2:
1282          subSecFormatStr  = ".SSS";
1283          trimmedTimestamp = t.substring(0, (periodPos+3)) + '0';
1284          break;
1285        default:
1286          subSecFormatStr  = ".SSS";
1287          trimmedTimestamp = t.substring(0, periodPos+4);
1288          break;
1289      }
1290    }
1291    else
1292    {
1293      subSecFormatStr  = "";
1294      periodPos        = tzPos;
1295      trimmedTimestamp = t.substring(0, tzPos);
1296    }
1297
1298
1299    // Look at where the period is (or would be if it existed) to see how many
1300    // characters are in the integer portion.  This will give us what we need
1301    // for the rest of the format string.
1302    final String formatStr;
1303    switch (periodPos)
1304    {
1305      case 10:
1306        formatStr = "yyyyMMddHH" + subSecFormatStr;
1307        break;
1308      case 12:
1309        formatStr = "yyyyMMddHHmm" + subSecFormatStr;
1310        break;
1311      case 14:
1312        formatStr = "yyyyMMddHHmmss" + subSecFormatStr;
1313        break;
1314      default:
1315        throw new ParseException(ERR_GENTIME_CANNOT_PARSE_INVALID_LENGTH.get(t),
1316                                 periodPos);
1317    }
1318
1319
1320    // We should finally be able to create an appropriate date format object
1321    // to parse the trimmed version of the timestamp.
1322    final SimpleDateFormat dateFormat = new SimpleDateFormat(formatStr);
1323    dateFormat.setTimeZone(tz);
1324    dateFormat.setLenient(false);
1325    return dateFormat.parse(trimmedTimestamp);
1326  }
1327
1328
1329
1330  /**
1331   * Trims only leading spaces from the provided string, leaving any trailing
1332   * spaces intact.
1333   *
1334   * @param  s  The string to be processed.  It must not be {@code null}.
1335   *
1336   * @return  The original string if no trimming was required, or a new string
1337   *          without leading spaces if the provided string had one or more.  It
1338   *          may be an empty string if the provided string was an empty string
1339   *          or contained only spaces.
1340   */
1341  public static String trimLeading(final String s)
1342  {
1343    ensureNotNull(s);
1344
1345    int nonSpacePos = 0;
1346    final int length = s.length();
1347    while ((nonSpacePos < length) && (s.charAt(nonSpacePos) == ' '))
1348    {
1349      nonSpacePos++;
1350    }
1351
1352    if (nonSpacePos == 0)
1353    {
1354      // There were no leading spaces.
1355      return s;
1356    }
1357    else if (nonSpacePos >= length)
1358    {
1359      // There were no non-space characters.
1360      return "";
1361    }
1362    else
1363    {
1364      // There were leading spaces, so return the string without them.
1365      return s.substring(nonSpacePos, length);
1366    }
1367  }
1368
1369
1370
1371  /**
1372   * Trims only trailing spaces from the provided string, leaving any leading
1373   * spaces intact.
1374   *
1375   * @param  s  The string to be processed.  It must not be {@code null}.
1376   *
1377   * @return  The original string if no trimming was required, or a new string
1378   *          without trailing spaces if the provided string had one or more.
1379   *          It may be an empty string if the provided string was an empty
1380   *          string or contained only spaces.
1381   */
1382  public static String trimTrailing(final String s)
1383  {
1384    ensureNotNull(s);
1385
1386    final int lastPos = s.length() - 1;
1387    int nonSpacePos = lastPos;
1388    while ((nonSpacePos >= 0) && (s.charAt(nonSpacePos) == ' '))
1389    {
1390      nonSpacePos--;
1391    }
1392
1393    if (nonSpacePos < 0)
1394    {
1395      // There were no non-space characters.
1396      return "";
1397    }
1398    else if (nonSpacePos == lastPos)
1399    {
1400      // There were no trailing spaces.
1401      return s;
1402    }
1403    else
1404    {
1405      // There were trailing spaces, so return the string without them.
1406      return s.substring(0, (nonSpacePos+1));
1407    }
1408  }
1409
1410
1411
1412  /**
1413   * Wraps the contents of the specified line using the given width.  It will
1414   * attempt to wrap at spaces to preserve words, but if that is not possible
1415   * (because a single "word" is longer than the maximum width), then it will
1416   * wrap in the middle of the word at the specified maximum width.
1417   *
1418   * @param  line      The line to be wrapped.  It must not be {@code null}.
1419   * @param  maxWidth  The maximum width for lines in the resulting list.  A
1420   *                   value less than or equal to zero will cause no wrapping
1421   *                   to be performed.
1422   *
1423   * @return  A list of the wrapped lines.  It may be empty if the provided line
1424   *          contained only spaces.
1425   */
1426  public static List<String> wrapLine(final String line, final int maxWidth)
1427  {
1428    return wrapLine(line, maxWidth, maxWidth);
1429  }
1430
1431
1432
1433  /**
1434   * Wraps the contents of the specified line using the given width.  It will
1435   * attempt to wrap at spaces to preserve words, but if that is not possible
1436   * (because a single "word" is longer than the maximum width), then it will
1437   * wrap in the middle of the word at the specified maximum width.
1438   *
1439   * @param  line                    The line to be wrapped.  It must not be
1440   *                                 {@code null}.
1441   * @param  maxFirstLineWidth       The maximum length for the first line in
1442   *                                 the resulting list.  A value less than or
1443   *                                 equal to zero will cause no wrapping to be
1444   *                                 performed.
1445   * @param  maxSubsequentLineWidth  The maximum length for all lines except the
1446   *                                 first line.  This must be greater than zero
1447   *                                 unless {@code maxFirstLineWidth} is less
1448   *                                 than or equal to zero.
1449   *
1450   * @return  A list of the wrapped lines.  It may be empty if the provided line
1451   *          contained only spaces.
1452   */
1453  public static List<String> wrapLine(final String line,
1454                                      final int maxFirstLineWidth,
1455                                      final int maxSubsequentLineWidth)
1456  {
1457    if (maxFirstLineWidth > 0)
1458    {
1459      Validator.ensureTrue(maxSubsequentLineWidth > 0);
1460    }
1461
1462    // See if the provided string already contains line breaks.  If so, then
1463    // treat it as multiple lines rather than a single line.
1464    final int breakPos = line.indexOf('\n');
1465    if (breakPos >= 0)
1466    {
1467      final ArrayList<String> lineList = new ArrayList<String>(10);
1468      final StringTokenizer tokenizer = new StringTokenizer(line, "\r\n");
1469      while (tokenizer.hasMoreTokens())
1470      {
1471        lineList.addAll(wrapLine(tokenizer.nextToken(), maxFirstLineWidth,
1472             maxSubsequentLineWidth));
1473      }
1474
1475      return lineList;
1476    }
1477
1478    final int length = line.length();
1479    if ((maxFirstLineWidth <= 0) || (length < maxFirstLineWidth))
1480    {
1481      return Arrays.asList(line);
1482    }
1483
1484
1485    int wrapPos = maxFirstLineWidth;
1486    int lastWrapPos = 0;
1487    final ArrayList<String> lineList = new ArrayList<String>(5);
1488    while (true)
1489    {
1490      final int spacePos = line.lastIndexOf(' ', wrapPos);
1491      if (spacePos > lastWrapPos)
1492      {
1493        // We found a space in an acceptable location, so use it after trimming
1494        // any trailing spaces.
1495        final String s = trimTrailing(line.substring(lastWrapPos, spacePos));
1496
1497        // Don't bother adding the line if it contained only spaces.
1498        if (s.length() > 0)
1499        {
1500          lineList.add(s);
1501        }
1502
1503        wrapPos = spacePos;
1504      }
1505      else
1506      {
1507        // We didn't find any spaces, so we'll have to insert a hard break at
1508        // the specified wrap column.
1509        lineList.add(line.substring(lastWrapPos, wrapPos));
1510      }
1511
1512      // Skip over any spaces before the next non-space character.
1513      while ((wrapPos < length) && (line.charAt(wrapPos) == ' '))
1514      {
1515        wrapPos++;
1516      }
1517
1518      lastWrapPos = wrapPos;
1519      wrapPos += maxSubsequentLineWidth;
1520      if (wrapPos >= length)
1521      {
1522        // The last fragment can fit on the line, so we can handle that now and
1523        // break.
1524        if (lastWrapPos >= length)
1525        {
1526          break;
1527        }
1528        else
1529        {
1530          final String s = line.substring(lastWrapPos);
1531          if (s.length() > 0)
1532          {
1533            lineList.add(s);
1534          }
1535          break;
1536        }
1537      }
1538    }
1539
1540    return lineList;
1541  }
1542
1543
1544
1545  /**
1546   * This method returns a form of the provided argument that is safe to
1547   * use on the command line for the local platform. This method is provided as
1548   * a convenience wrapper around {@link ExampleCommandLineArgument}.  Calling
1549   * this method is equivalent to:
1550   *
1551   * <PRE>
1552   *  return ExampleCommandLineArgument.getCleanArgument(s).getLocalForm();
1553   * </PRE>
1554   *
1555   * For getting direct access to command line arguments that are safe to
1556   * use on other platforms, call
1557   * {@link ExampleCommandLineArgument#getCleanArgument}.
1558   *
1559   * @param  s  The string to be processed.  It must not be {@code null}.
1560   *
1561   * @return  A cleaned version of the provided string in a form that will allow
1562   *          it to be displayed as the value of a command-line argument on.
1563   */
1564  public static String cleanExampleCommandLineArgument(final String s)
1565  {
1566    return ExampleCommandLineArgument.getCleanArgument(s).getLocalForm();
1567  }
1568
1569
1570
1571  /**
1572   * Retrieves a single string which is a concatenation of all of the provided
1573   * strings.
1574   *
1575   * @param  a  The array of strings to concatenate.  It must not be
1576   *            {@code null}.
1577   *
1578   * @return  A string containing a concatenation of all of the strings in the
1579   *          provided array.
1580   */
1581  public static String concatenateStrings(final String... a)
1582  {
1583    return concatenateStrings(null, null, "  ", null, null, a);
1584  }
1585
1586
1587
1588  /**
1589   * Retrieves a single string which is a concatenation of all of the provided
1590   * strings.
1591   *
1592   * @param  l  The list of strings to concatenate.  It must not be
1593   *            {@code null}.
1594   *
1595   * @return  A string containing a concatenation of all of the strings in the
1596   *          provided list.
1597   */
1598  public static String concatenateStrings(final List<String> l)
1599  {
1600    return concatenateStrings(null, null, "  ", null, null, l);
1601  }
1602
1603
1604
1605  /**
1606   * Retrieves a single string which is a concatenation of all of the provided
1607   * strings.
1608   *
1609   * @param  beforeList       A string that should be placed at the beginning of
1610   *                          the list.  It may be {@code null} or empty if
1611   *                          nothing should be placed at the beginning of the
1612   *                          list.
1613   * @param  beforeElement    A string that should be placed before each element
1614   *                          in the list.  It may be {@code null} or empty if
1615   *                          nothing should be placed before each element.
1616   * @param  betweenElements  The separator that should be placed between
1617   *                          elements in the list.  It may be {@code null} or
1618   *                          empty if no separator should be placed between
1619   *                          elements.
1620   * @param  afterElement     A string that should be placed after each element
1621   *                          in the list.  It may be {@code null} or empty if
1622   *                          nothing should be placed after each element.
1623   * @param  afterList        A string that should be placed at the end of the
1624   *                          list.  It may be {@code null} or empty if nothing
1625   *                          should be placed at the end of the list.
1626   * @param  a                The array of strings to concatenate.  It must not
1627   *                          be {@code null}.
1628   *
1629   * @return  A string containing a concatenation of all of the strings in the
1630   *          provided list.
1631   */
1632  public static String concatenateStrings(final String beforeList,
1633                                          final String beforeElement,
1634                                          final String betweenElements,
1635                                          final String afterElement,
1636                                          final String afterList,
1637                                          final String... a)
1638  {
1639    return concatenateStrings(beforeList, beforeElement, betweenElements,
1640         afterElement, afterList, Arrays.asList(a));
1641  }
1642
1643
1644
1645  /**
1646   * Retrieves a single string which is a concatenation of all of the provided
1647   * strings.
1648   *
1649   * @param  beforeList       A string that should be placed at the beginning of
1650   *                          the list.  It may be {@code null} or empty if
1651   *                          nothing should be placed at the beginning of the
1652   *                          list.
1653   * @param  beforeElement    A string that should be placed before each element
1654   *                          in the list.  It may be {@code null} or empty if
1655   *                          nothing should be placed before each element.
1656   * @param  betweenElements  The separator that should be placed between
1657   *                          elements in the list.  It may be {@code null} or
1658   *                          empty if no separator should be placed between
1659   *                          elements.
1660   * @param  afterElement     A string that should be placed after each element
1661   *                          in the list.  It may be {@code null} or empty if
1662   *                          nothing should be placed after each element.
1663   * @param  afterList        A string that should be placed at the end of the
1664   *                          list.  It may be {@code null} or empty if nothing
1665   *                          should be placed at the end of the list.
1666   * @param  l                The list of strings to concatenate.  It must not
1667   *                          be {@code null}.
1668   *
1669   * @return  A string containing a concatenation of all of the strings in the
1670   *          provided list.
1671   */
1672  public static String concatenateStrings(final String beforeList,
1673                                          final String beforeElement,
1674                                          final String betweenElements,
1675                                          final String afterElement,
1676                                          final String afterList,
1677                                          final List<String> l)
1678  {
1679    ensureNotNull(l);
1680
1681    final StringBuilder buffer = new StringBuilder();
1682
1683    if (beforeList != null)
1684    {
1685      buffer.append(beforeList);
1686    }
1687
1688    final Iterator<String> iterator = l.iterator();
1689    while (iterator.hasNext())
1690    {
1691      if (beforeElement != null)
1692      {
1693        buffer.append(beforeElement);
1694      }
1695
1696      buffer.append(iterator.next());
1697
1698      if (afterElement != null)
1699      {
1700        buffer.append(afterElement);
1701      }
1702
1703      if ((betweenElements != null) && iterator.hasNext())
1704      {
1705        buffer.append(betweenElements);
1706      }
1707    }
1708
1709    if (afterList != null)
1710    {
1711      buffer.append(afterList);
1712    }
1713
1714    return buffer.toString();
1715  }
1716
1717
1718
1719  /**
1720   * Converts a duration in seconds to a string with a human-readable duration
1721   * which may include days, hours, minutes, and seconds, to the extent that
1722   * they are needed.
1723   *
1724   * @param  s  The number of seconds to be represented.
1725   *
1726   * @return  A string containing a human-readable representation of the
1727   *          provided time.
1728   */
1729  public static String secondsToHumanReadableDuration(final long s)
1730  {
1731    return millisToHumanReadableDuration(s * 1000L);
1732  }
1733
1734
1735
1736  /**
1737   * Converts a duration in seconds to a string with a human-readable duration
1738   * which may include days, hours, minutes, and seconds, to the extent that
1739   * they are needed.
1740   *
1741   * @param  m  The number of milliseconds to be represented.
1742   *
1743   * @return  A string containing a human-readable representation of the
1744   *          provided time.
1745   */
1746  public static String millisToHumanReadableDuration(final long m)
1747  {
1748    final StringBuilder buffer = new StringBuilder();
1749    long numMillis = m;
1750
1751    final long numDays = numMillis / 86400000L;
1752    if (numDays > 0)
1753    {
1754      numMillis -= (numDays * 86400000L);
1755      if (numDays == 1)
1756      {
1757        buffer.append(INFO_NUM_DAYS_SINGULAR.get(numDays));
1758      }
1759      else
1760      {
1761        buffer.append(INFO_NUM_DAYS_PLURAL.get(numDays));
1762      }
1763    }
1764
1765    final long numHours = numMillis / 3600000L;
1766    if (numHours > 0)
1767    {
1768      numMillis -= (numHours * 3600000L);
1769      if (buffer.length() > 0)
1770      {
1771        buffer.append(", ");
1772      }
1773
1774      if (numHours == 1)
1775      {
1776        buffer.append(INFO_NUM_HOURS_SINGULAR.get(numHours));
1777      }
1778      else
1779      {
1780        buffer.append(INFO_NUM_HOURS_PLURAL.get(numHours));
1781      }
1782    }
1783
1784    final long numMinutes = numMillis / 60000L;
1785    if (numMinutes > 0)
1786    {
1787      numMillis -= (numMinutes * 60000L);
1788      if (buffer.length() > 0)
1789      {
1790        buffer.append(", ");
1791      }
1792
1793      if (numMinutes == 1)
1794      {
1795        buffer.append(INFO_NUM_MINUTES_SINGULAR.get(numMinutes));
1796      }
1797      else
1798      {
1799        buffer.append(INFO_NUM_MINUTES_PLURAL.get(numMinutes));
1800      }
1801    }
1802
1803    if (numMillis == 1000)
1804    {
1805      if (buffer.length() > 0)
1806      {
1807        buffer.append(", ");
1808      }
1809
1810      buffer.append(INFO_NUM_SECONDS_SINGULAR.get(1));
1811    }
1812    else if ((numMillis > 0) || (buffer.length() == 0))
1813    {
1814      if (buffer.length() > 0)
1815      {
1816        buffer.append(", ");
1817      }
1818
1819      final long numSeconds = numMillis / 1000L;
1820      numMillis -= (numSeconds * 1000L);
1821      if ((numMillis % 1000L) != 0L)
1822      {
1823        final double numSecondsDouble = numSeconds + (numMillis / 1000.0);
1824        final DecimalFormat decimalFormat = new DecimalFormat("0.000");
1825        buffer.append(INFO_NUM_SECONDS_WITH_DECIMAL.get(
1826             decimalFormat.format(numSecondsDouble)));
1827      }
1828      else
1829      {
1830        buffer.append(INFO_NUM_SECONDS_PLURAL.get(numSeconds));
1831      }
1832    }
1833
1834    return buffer.toString();
1835  }
1836
1837
1838
1839  /**
1840   * Converts the provided number of nanoseconds to milliseconds.
1841   *
1842   * @param  nanos  The number of nanoseconds to convert to milliseconds.
1843   *
1844   * @return  The number of milliseconds that most closely corresponds to the
1845   *          specified number of nanoseconds.
1846   */
1847  public static long nanosToMillis(final long nanos)
1848  {
1849    return Math.max(0L, Math.round(nanos / 1000000.0d));
1850  }
1851
1852
1853
1854  /**
1855   * Converts the provided number of milliseconds to nanoseconds.
1856   *
1857   * @param  millis  The number of milliseconds to convert to nanoseconds.
1858   *
1859   * @return  The number of nanoseconds that most closely corresponds to the
1860   *          specified number of milliseconds.
1861   */
1862  public static long millisToNanos(final long millis)
1863  {
1864    return Math.max(0L, (millis * 1000000L));
1865  }
1866
1867
1868
1869  /**
1870   * Indicates whether the provided string is a valid numeric OID.  A numeric
1871   * OID must start and end with a digit, must have at least on period, must
1872   * contain only digits and periods, and must not have two consecutive periods.
1873   *
1874   * @param  s  The string to examine.  It must not be {@code null}.
1875   *
1876   * @return  {@code true} if the provided string is a valid numeric OID, or
1877   *          {@code false} if not.
1878   */
1879  public static boolean isNumericOID(final String s)
1880  {
1881    boolean digitRequired = true;
1882    boolean periodFound   = false;
1883    for (final char c : s.toCharArray())
1884    {
1885      switch (c)
1886      {
1887        case '0':
1888        case '1':
1889        case '2':
1890        case '3':
1891        case '4':
1892        case '5':
1893        case '6':
1894        case '7':
1895        case '8':
1896        case '9':
1897          digitRequired = false;
1898          break;
1899
1900        case '.':
1901          if (digitRequired)
1902          {
1903            return false;
1904          }
1905          else
1906          {
1907            digitRequired = true;
1908          }
1909          periodFound = true;
1910          break;
1911
1912        default:
1913          return false;
1914      }
1915
1916    }
1917
1918    return (periodFound && (! digitRequired));
1919  }
1920
1921
1922
1923  /**
1924   * Capitalizes the provided string.  The first character will be converted to
1925   * uppercase, and the rest of the string will be left unaltered.
1926   *
1927   * @param  s  The string to be capitalized.
1928   *
1929   * @return  A capitalized version of the provided string.
1930   */
1931  public static String capitalize(final String s)
1932  {
1933    return capitalize(s, false);
1934  }
1935
1936
1937
1938  /**
1939   * Capitalizes the provided string.  The first character of the string (or
1940   * optionally the first character of each word in the string)
1941   *
1942   * @param  s         The string to be capitalized.
1943   * @param  allWords  Indicates whether to capitalize all words in the string,
1944   *                   or only the first word.
1945   *
1946   * @return  A capitalized version of the provided string.
1947   */
1948  public static String capitalize(final String s, final boolean allWords)
1949  {
1950    if (s == null)
1951    {
1952      return null;
1953    }
1954
1955    switch (s.length())
1956    {
1957      case 0:
1958        return s;
1959
1960      case 1:
1961        return s.toUpperCase();
1962
1963      default:
1964        boolean capitalize = true;
1965        final char[] chars = s.toCharArray();
1966        final StringBuilder buffer = new StringBuilder(chars.length);
1967        for (final char c : chars)
1968        {
1969          // Whitespace and punctuation will be considered word breaks.
1970          if (Character.isWhitespace(c) ||
1971              (((c >= '!') && (c <= '.')) ||
1972               ((c >= ':') && (c <= '@')) ||
1973               ((c >= '[') && (c <= '`')) ||
1974               ((c >= '{') && (c <= '~'))))
1975          {
1976            buffer.append(c);
1977            capitalize |= allWords;
1978          }
1979          else if (capitalize)
1980          {
1981            buffer.append(Character.toUpperCase(c));
1982            capitalize = false;
1983          }
1984          else
1985          {
1986            buffer.append(c);
1987          }
1988        }
1989        return buffer.toString();
1990    }
1991  }
1992
1993
1994
1995  /**
1996   * Encodes the provided UUID to a byte array containing its 128-bit
1997   * representation.
1998   *
1999   * @param  uuid  The UUID to be encoded.  It must not be {@code null}.
2000   *
2001   * @return  The byte array containing the 128-bit encoded UUID.
2002   */
2003  public static byte[] encodeUUID(final UUID uuid)
2004  {
2005    final byte[] b = new byte[16];
2006
2007    final long mostSignificantBits  = uuid.getMostSignificantBits();
2008    b[0]  = (byte) ((mostSignificantBits >> 56) & 0xFF);
2009    b[1]  = (byte) ((mostSignificantBits >> 48) & 0xFF);
2010    b[2]  = (byte) ((mostSignificantBits >> 40) & 0xFF);
2011    b[3]  = (byte) ((mostSignificantBits >> 32) & 0xFF);
2012    b[4]  = (byte) ((mostSignificantBits >> 24) & 0xFF);
2013    b[5]  = (byte) ((mostSignificantBits >> 16) & 0xFF);
2014    b[6]  = (byte) ((mostSignificantBits >> 8) & 0xFF);
2015    b[7]  = (byte) (mostSignificantBits & 0xFF);
2016
2017    final long leastSignificantBits = uuid.getLeastSignificantBits();
2018    b[8]  = (byte) ((leastSignificantBits >> 56) & 0xFF);
2019    b[9]  = (byte) ((leastSignificantBits >> 48) & 0xFF);
2020    b[10] = (byte) ((leastSignificantBits >> 40) & 0xFF);
2021    b[11] = (byte) ((leastSignificantBits >> 32) & 0xFF);
2022    b[12] = (byte) ((leastSignificantBits >> 24) & 0xFF);
2023    b[13] = (byte) ((leastSignificantBits >> 16) & 0xFF);
2024    b[14] = (byte) ((leastSignificantBits >> 8) & 0xFF);
2025    b[15] = (byte) (leastSignificantBits & 0xFF);
2026
2027    return b;
2028  }
2029
2030
2031
2032  /**
2033   * Decodes the value of the provided byte array as a Java UUID.
2034   *
2035   * @param  b  The byte array to be decoded as a UUID.  It must not be
2036   *            {@code null}.
2037   *
2038   * @return  The decoded UUID.
2039   *
2040   * @throws  ParseException  If the provided byte array cannot be parsed as a
2041   *                         UUID.
2042   */
2043  public static UUID decodeUUID(final byte[] b)
2044         throws ParseException
2045  {
2046    if (b.length != 16)
2047    {
2048      throw new ParseException(ERR_DECODE_UUID_INVALID_LENGTH.get(toHex(b)), 0);
2049    }
2050
2051    long mostSignificantBits = 0L;
2052    for (int i=0; i < 8; i++)
2053    {
2054      mostSignificantBits = (mostSignificantBits << 8) | (b[i] & 0xFF);
2055    }
2056
2057    long leastSignificantBits = 0L;
2058    for (int i=8; i < 16; i++)
2059    {
2060      leastSignificantBits = (leastSignificantBits << 8) | (b[i] & 0xFF);
2061    }
2062
2063    return new UUID(mostSignificantBits, leastSignificantBits);
2064  }
2065
2066
2067
2068  /**
2069   * Returns {@code true} if and only if the current process is running on
2070   * a Windows-based operating system.
2071   *
2072   * @return  {@code true} if the current process is running on a Windows-based
2073   *          operating system and {@code false} otherwise.
2074   */
2075  public static boolean isWindows()
2076  {
2077    final String osName = toLowerCase(System.getProperty("os.name"));
2078    return ((osName != null) && osName.contains("windows"));
2079  }
2080
2081
2082
2083  /**
2084   * Attempts to parse the contents of the provided string to an argument list
2085   * (e.g., converts something like "--arg1 arg1value --arg2 --arg3 arg3value"
2086   * to a list of "--arg1", "arg1value", "--arg2", "--arg3", "arg3value").
2087   *
2088   * @param  s  The string to be converted to an argument list.
2089   *
2090   * @return  The parsed argument list.
2091   *
2092   * @throws  ParseException  If a problem is encountered while attempting to
2093   *                          parse the given string to an argument list.
2094   */
2095  public static List<String> toArgumentList(final String s)
2096         throws ParseException
2097  {
2098    if ((s == null) || (s.length() == 0))
2099    {
2100      return Collections.emptyList();
2101    }
2102
2103    int quoteStartPos = -1;
2104    boolean inEscape = false;
2105    final ArrayList<String> argList = new ArrayList<String>();
2106    final StringBuilder currentArg = new StringBuilder();
2107    for (int i=0; i < s.length(); i++)
2108    {
2109      final char c = s.charAt(i);
2110      if (inEscape)
2111      {
2112        currentArg.append(c);
2113        inEscape = false;
2114        continue;
2115      }
2116
2117      if (c == '\\')
2118      {
2119        inEscape = true;
2120      }
2121      else if (c == '"')
2122      {
2123        if (quoteStartPos >= 0)
2124        {
2125          quoteStartPos = -1;
2126        }
2127        else
2128        {
2129          quoteStartPos = i;
2130        }
2131      }
2132      else if (c == ' ')
2133      {
2134        if (quoteStartPos >= 0)
2135        {
2136          currentArg.append(c);
2137        }
2138        else if (currentArg.length() > 0)
2139        {
2140          argList.add(currentArg.toString());
2141          currentArg.setLength(0);
2142        }
2143      }
2144      else
2145      {
2146        currentArg.append(c);
2147      }
2148    }
2149
2150    if (s.endsWith("\\") && (! s.endsWith("\\\\")))
2151    {
2152      throw new ParseException(ERR_ARG_STRING_DANGLING_BACKSLASH.get(),
2153           (s.length() - 1));
2154    }
2155
2156    if (quoteStartPos >= 0)
2157    {
2158      throw new ParseException(ERR_ARG_STRING_UNMATCHED_QUOTE.get(
2159           quoteStartPos), quoteStartPos);
2160    }
2161
2162    if (currentArg.length() > 0)
2163    {
2164      argList.add(currentArg.toString());
2165    }
2166
2167    return Collections.unmodifiableList(argList);
2168  }
2169
2170
2171
2172  /**
2173   * Creates a modifiable list with all of the items of the provided array in
2174   * the same order.  This method behaves much like {@code Arrays.asList},
2175   * except that if the provided array is {@code null}, then it will return a
2176   * {@code null} list rather than throwing an exception.
2177   *
2178   * @param  <T>  The type of item contained in the provided array.
2179   *
2180   * @param  array  The array of items to include in the list.
2181   *
2182   * @return  The list that was created, or {@code null} if the provided array
2183   *          was {@code null}.
2184   */
2185  public static <T> List<T> toList(final T[] array)
2186  {
2187    if (array == null)
2188    {
2189      return null;
2190    }
2191
2192    final ArrayList<T> l = new ArrayList<T>(array.length);
2193    l.addAll(Arrays.asList(array));
2194    return l;
2195  }
2196
2197
2198
2199  /**
2200   * Creates a modifiable list with all of the items of the provided array in
2201   * the same order.  This method behaves much like {@code Arrays.asList},
2202   * except that if the provided array is {@code null}, then it will return an
2203   * empty list rather than throwing an exception.
2204   *
2205   * @param  <T>  The type of item contained in the provided array.
2206   *
2207   * @param  array  The array of items to include in the list.
2208   *
2209   * @return  The list that was created, or an empty list if the provided array
2210   *          was {@code null}.
2211   */
2212  public static <T> List<T> toNonNullList(final T[] array)
2213  {
2214    if (array == null)
2215    {
2216      return new ArrayList<T>(0);
2217    }
2218
2219    final ArrayList<T> l = new ArrayList<T>(array.length);
2220    l.addAll(Arrays.asList(array));
2221    return l;
2222  }
2223
2224
2225
2226  /**
2227   * Indicates whether both of the provided objects are {@code null} or both
2228   * are logically equal (using the {@code equals} method).
2229   *
2230   * @param  o1  The first object for which to make the determination.
2231   * @param  o2  The second object for which to make the determination.
2232   *
2233   * @return  {@code true} if both objects are {@code null} or both are
2234   *          logically equal, or {@code false} if only one of the objects is
2235   *          {@code null} or they are not logically equal.
2236   */
2237  public static boolean bothNullOrEqual(final Object o1, final Object o2)
2238  {
2239    if (o1 == null)
2240    {
2241      return (o2 == null);
2242    }
2243    else if (o2 == null)
2244    {
2245      return false;
2246    }
2247
2248    return o1.equals(o2);
2249  }
2250
2251
2252
2253  /**
2254   * Indicates whether both of the provided strings are {@code null} or both
2255   * are logically equal ignoring differences in capitalization (using the
2256   * {@code equalsIgnoreCase} method).
2257   *
2258   * @param  s1  The first string for which to make the determination.
2259   * @param  s2  The second string for which to make the determination.
2260   *
2261   * @return  {@code true} if both strings are {@code null} or both are
2262   *          logically equal ignoring differences in capitalization, or
2263   *          {@code false} if only one of the objects is {@code null} or they
2264   *          are not logically equal ignoring capitalization.
2265   */
2266  public static boolean bothNullOrEqualIgnoreCase(final String s1,
2267                                                  final String s2)
2268  {
2269    if (s1 == null)
2270    {
2271      return (s2 == null);
2272    }
2273    else if (s2 == null)
2274    {
2275      return false;
2276    }
2277
2278    return s1.equalsIgnoreCase(s2);
2279  }
2280
2281
2282
2283  /**
2284   * Indicates whether the provided string arrays have the same elements,
2285   * ignoring the order in which they appear and differences in capitalization.
2286   * It is assumed that neither array contains {@code null} strings, and that
2287   * no string appears more than once in each array.
2288   *
2289   * @param  a1  The first array for which to make the determination.
2290   * @param  a2  The second array for which to make the determination.
2291   *
2292   * @return  {@code true} if both arrays have the same set of strings, or
2293   *          {@code false} if not.
2294   */
2295  public static boolean stringsEqualIgnoreCaseOrderIndependent(
2296                             final String[] a1, final String[] a2)
2297  {
2298    if (a1 == null)
2299    {
2300      return (a2 == null);
2301    }
2302    else if (a2 == null)
2303    {
2304      return false;
2305    }
2306
2307    if (a1.length != a2.length)
2308    {
2309      return false;
2310    }
2311
2312    if (a1.length == 1)
2313    {
2314      return (a1[0].equalsIgnoreCase(a2[0]));
2315    }
2316
2317    final HashSet<String> s1 = new HashSet<String>(a1.length);
2318    for (final String s : a1)
2319    {
2320      s1.add(toLowerCase(s));
2321    }
2322
2323    final HashSet<String> s2 = new HashSet<String>(a2.length);
2324    for (final String s : a2)
2325    {
2326      s2.add(toLowerCase(s));
2327    }
2328
2329    return s1.equals(s2);
2330  }
2331
2332
2333
2334  /**
2335   * Indicates whether the provided arrays have the same elements, ignoring the
2336   * order in which they appear.  It is assumed that neither array contains
2337   * {@code null} elements, and that no element appears more than once in each
2338   * array.
2339   *
2340   * @param  <T>  The type of element contained in the arrays.
2341   *
2342   * @param  a1  The first array for which to make the determination.
2343   * @param  a2  The second array for which to make the determination.
2344   *
2345   * @return  {@code true} if both arrays have the same set of elements, or
2346   *          {@code false} if not.
2347   */
2348  public static <T> boolean arraysEqualOrderIndependent(final T[] a1,
2349                                                        final T[] a2)
2350  {
2351    if (a1 == null)
2352    {
2353      return (a2 == null);
2354    }
2355    else if (a2 == null)
2356    {
2357      return false;
2358    }
2359
2360    if (a1.length != a2.length)
2361    {
2362      return false;
2363    }
2364
2365    if (a1.length == 1)
2366    {
2367      return (a1[0].equals(a2[0]));
2368    }
2369
2370    final HashSet<T> s1 = new HashSet<T>(Arrays.asList(a1));
2371    final HashSet<T> s2 = new HashSet<T>(Arrays.asList(a2));
2372    return s1.equals(s2);
2373  }
2374
2375
2376
2377  /**
2378   * Determines the number of bytes in a UTF-8 character that starts with the
2379   * given byte.
2380   *
2381   * @param  b  The byte for which to make the determination.
2382   *
2383   * @return  The number of bytes in a UTF-8 character that starts with the
2384   *          given byte, or -1 if it does not appear to be a valid first byte
2385   *          for a UTF-8 character.
2386   */
2387  public static int numBytesInUTF8CharacterWithFirstByte(final byte b)
2388  {
2389    if ((b & 0x7F) == b)
2390    {
2391      return 1;
2392    }
2393    else if ((b & 0xE0) == 0xC0)
2394    {
2395      return 2;
2396    }
2397    else if ((b & 0xF0) == 0xE0)
2398    {
2399      return 3;
2400    }
2401    else if ((b & 0xF8) == 0xF0)
2402    {
2403      return 4;
2404    }
2405    else
2406    {
2407      return -1;
2408    }
2409  }
2410
2411
2412
2413  /**
2414   * Indicates whether the provided attribute name should be considered a
2415   * sensitive attribute for the purposes of {@code toCode} methods.  If an
2416   * attribute is considered sensitive, then its values will be redacted in the
2417   * output of the {@code toCode} methods.
2418   *
2419   * @param  name  The name for which to make the determination.  It may or may
2420   *               not include attribute options.  It must not be {@code null}.
2421   *
2422   * @return  {@code true} if the specified attribute is one that should be
2423   *          considered sensitive for the
2424   */
2425  public static boolean isSensitiveToCodeAttribute(final String name)
2426  {
2427    final String lowerBaseName = Attribute.getBaseName(name).toLowerCase();
2428    return TO_CODE_SENSITIVE_ATTRIBUTE_NAMES.contains(lowerBaseName);
2429  }
2430
2431
2432
2433  /**
2434   * Retrieves a set containing the base names (in all lowercase characters) of
2435   * any attributes that should be considered sensitive for the purposes of the
2436   * {@code toCode} methods.  By default, only the userPassword and
2437   * authPassword attributes and their respective OIDs will be included.
2438   *
2439   * @return  A set containing the base names (in all lowercase characters) of
2440   *          any attributes that should be considered sensitive for the
2441   *          purposes of the {@code toCode} methods.
2442   */
2443  public static Set<String> getSensitiveToCodeAttributeBaseNames()
2444  {
2445    return TO_CODE_SENSITIVE_ATTRIBUTE_NAMES;
2446  }
2447
2448
2449
2450  /**
2451   * Specifies the names of any attributes that should be considered sensitive
2452   * for the purposes of the {@code toCode} methods.
2453   *
2454   * @param  names  The names of any attributes that should be considered
2455   *                sensitive for the purposes of the {@code toCode} methods.
2456   *                It may be {@code null} or empty if no attributes should be
2457   *                considered sensitive.
2458   */
2459  public static void setSensitiveToCodeAttributes(final String... names)
2460  {
2461    setSensitiveToCodeAttributes(toList(names));
2462  }
2463
2464
2465
2466  /**
2467   * Specifies the names of any attributes that should be considered sensitive
2468   * for the purposes of the {@code toCode} methods.
2469   *
2470   * @param  names  The names of any attributes that should be considered
2471   *                sensitive for the purposes of the {@code toCode} methods.
2472   *                It may be {@code null} or empty if no attributes should be
2473   *                considered sensitive.
2474   */
2475  public static void setSensitiveToCodeAttributes(
2476                          final Collection<String> names)
2477  {
2478    if ((names == null) || names.isEmpty())
2479    {
2480      TO_CODE_SENSITIVE_ATTRIBUTE_NAMES = Collections.emptySet();
2481    }
2482    else
2483    {
2484      final LinkedHashSet<String> nameSet =
2485           new LinkedHashSet<String>(names.size());
2486      for (final String s : names)
2487      {
2488        nameSet.add(Attribute.getBaseName(s).toLowerCase());
2489      }
2490
2491      TO_CODE_SENSITIVE_ATTRIBUTE_NAMES = Collections.unmodifiableSet(nameSet);
2492    }
2493  }
2494
2495
2496
2497  /**
2498   * Creates a new {@code IOException} with a cause.  The constructor needed to
2499   * do this wasn't available until Java SE 6, so reflection is used to invoke
2500   * this constructor in versions of Java that provide it.  In Java SE 5, the
2501   * provided message will be augmented with information about the cause.
2502   *
2503   * @param  message  The message to use for the exception.  This may be
2504   *                  {@code null} if the message should be generated from the
2505   *                  provided cause.
2506   * @param  cause    The underlying cause for the exception.  It may be
2507   *                  {@code null} if the exception should have only a message.
2508   *
2509   * @return  The {@code IOException} object that was created.
2510   */
2511  public static IOException createIOExceptionWithCause(final String message,
2512                                                       final Throwable cause)
2513  {
2514    if (cause == null)
2515    {
2516      return new IOException(message);
2517    }
2518    else if (message == null)
2519    {
2520      return new IOException(cause);
2521    }
2522    else
2523    {
2524      return new IOException(message, cause);
2525    }
2526  }
2527
2528
2529
2530  /**
2531   * Converts the provided string (which may include line breaks) into a list
2532   * containing the lines without the line breaks.
2533   *
2534   * @param  s  The string to convert into a list of its representative lines.
2535   *
2536   * @return  A list containing the lines that comprise the given string.
2537   */
2538  public static List<String> stringToLines(final String s)
2539  {
2540    final ArrayList<String> l = new ArrayList<String>(10);
2541
2542    if (s == null)
2543    {
2544      return l;
2545    }
2546
2547    final BufferedReader reader = new BufferedReader(new StringReader(s));
2548
2549    try
2550    {
2551      while (true)
2552      {
2553        try
2554        {
2555          final String line = reader.readLine();
2556          if (line == null)
2557          {
2558            return l;
2559          }
2560          else
2561          {
2562            l.add(line);
2563          }
2564        }
2565        catch (final Exception e)
2566        {
2567          debugException(e);
2568
2569          // This should never happen.  If it does, just return a list
2570          // containing a single item that is the original string.
2571          l.clear();
2572          l.add(s);
2573          return l;
2574        }
2575      }
2576    }
2577    finally
2578    {
2579      try
2580      {
2581        // This is technically not necessary in this case, but it's good form.
2582        reader.close();
2583      }
2584      catch (final Exception e)
2585      {
2586        debugException(e);
2587        // This should never happen, and there's nothing we need to do even if
2588        // it does.
2589      }
2590    }
2591  }
2592}