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