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