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