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