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