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