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