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