com.unboundid.util.StaticUtils Maven / Gradle / Ivy
                 Go to download
                
        
                    Show more of this group  Show more artifacts with this name
Show all versions of unboundid-ldapsdk-commercial-edition Show documentation
                Show all versions of unboundid-ldapsdk-commercial-edition Show documentation
      The UnboundID LDAP SDK for Java is a fast, comprehensive, and easy-to-use
      Java API for communicating with LDAP directory servers and performing
      related tasks like reading and writing LDIF, encoding and decoding data
      using base64 and ASN.1 BER, and performing secure communication.  This
      package contains the Commercial Edition of the LDAP SDK, which includes
      all of the general-purpose functionality contained in the Standard
      Edition, plus additional functionality specific to UnboundID server
      products.
    
                
            /*
 * Copyright 2007-2016 UnboundID Corp.
 * All Rights Reserved.
 */
/*
 * Copyright (C) 2008-2016 UnboundID Corp.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see .
 */
package com.unboundid.util;
import java.lang.reflect.Constructor;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.UUID;
import com.unboundid.ldap.sdk.Attribute;
import com.unboundid.ldap.sdk.Control;
import com.unboundid.ldap.sdk.Version;
import static com.unboundid.util.Debug.*;
import static com.unboundid.util.UtilityMessages.*;
import static com.unboundid.util.Validator.*;
/**
 * This class provides a number of static utility functions.
 */
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class StaticUtils
{
  /**
   * A pre-allocated byte array containing zero bytes.
   */
  public static final byte[] NO_BYTES = new byte[0];
  /**
   * A pre-allocated empty control array.
   */
  public static final Control[] NO_CONTROLS = new Control[0];
  /**
   * A pre-allocated empty string array.
   */
  public static final String[] NO_STRINGS = new String[0];
  /**
   * The end-of-line marker for this platform.
   */
  public static final String EOL = System.getProperty("line.separator");
  /**
   * A byte array containing the end-of-line marker for this platform.
   */
  public static final byte[] EOL_BYTES = getBytes(EOL);
  /**
   * The width of the terminal window, in columns.
   */
  public static final int TERMINAL_WIDTH_COLUMNS;
  static
  {
    // Try to dynamically determine the size of the terminal window using the
    // COLUMNS environment variable.
    int terminalWidth = 80;
    final String columnsEnvVar = System.getenv("COLUMNS");
    if (columnsEnvVar != null)
    {
      try
      {
        terminalWidth = Integer.parseInt(columnsEnvVar);
      }
      catch (final Exception e)
      {
        Debug.debugException(e);
      }
    }
    TERMINAL_WIDTH_COLUMNS = terminalWidth;
  }
  /**
   * The thread-local date formatter used to encode generalized time values.
   */
  private static final ThreadLocal DATE_FORMATTERS =
       new ThreadLocal();
  /**
   * A set containing the names of attributes that will be considered sensitive
   * by the {@code toCode} methods of various request and data structure types.
   */
  private static volatile Set TO_CODE_SENSITIVE_ATTRIBUTE_NAMES;
  static
  {
    final LinkedHashSet nameSet = new LinkedHashSet(4);
    // Add userPassword by name and OID.
    nameSet.add("userpassword");
    nameSet.add("2.5.4.35");
    // add authPassword by name and OID.
    nameSet.add("authpassword");
    nameSet.add("1.3.6.1.4.1.4203.1.3.4");
    TO_CODE_SENSITIVE_ATTRIBUTE_NAMES = Collections.unmodifiableSet(nameSet);
  }
  /**
   * Prevent this class from being instantiated.
   */
  private StaticUtils()
  {
    // No implementation is required.
  }
  /**
   * Retrieves a UTF-8 byte representation of the provided string.
   *
   * @param  s  The string for which to retrieve the UTF-8 byte representation.
   *
   * @return  The UTF-8 byte representation for the provided string.
   */
  public static byte[] getBytes(final String s)
  {
    final int length;
    if ((s == null) || ((length = s.length()) == 0))
    {
      return NO_BYTES;
    }
    final byte[] b = new byte[length];
    for (int i=0; i < length; i++)
    {
      final char c = s.charAt(i);
      if (c <= 0x7F)
      {
        b[i] = (byte) (c & 0x7F);
      }
      else
      {
        try
        {
          return s.getBytes("UTF-8");
        }
        catch (Exception e)
        {
          // This should never happen.
          debugException(e);
          return s.getBytes();
        }
      }
    }
    return b;
  }
  /**
   * Indicates whether the contents of the provided byte array represent an
   * ASCII string, which is also known in LDAP terminology as an IA5 string.
   * An ASCII string is one that contains only bytes in which the most
   * significant bit is zero.
   *
   * @param  b  The byte array for which to make the determination.  It must
   *            not be {@code null}.
   *
   * @return  {@code true} if the contents of the provided array represent an
   *          ASCII string, or {@code false} if not.
   */
  public static boolean isASCIIString(final byte[] b)
  {
    for (final byte by : b)
    {
      if ((by & 0x80) == 0x80)
      {
        return false;
      }
    }
    return true;
  }
  /**
   * Indicates whether the provided character is a printable ASCII character, as
   * per RFC 4517 section 3.2.  The only printable characters are:
   * 
   *   - All uppercase and lowercase ASCII alphabetic letters
 
   *   - All ASCII numeric digits
 
   *   - The following additional ASCII characters:  single quote, left
   *       parenthesis, right parenthesis, plus, comma, hyphen, period, equals,
   *       forward slash, colon, question mark, space.
 
   * 
   *
   * @param  c  The character for which to make the determination.
   *
   * @return  {@code true} if the provided character is a printable ASCII
   *          character, or {@code false} if not.
   */
  public static boolean isPrintable(final char c)
  {
    if (((c >= 'a') && (c <= 'z')) ||
        ((c >= 'A') && (c <= 'Z')) ||
        ((c >= '0') && (c <= '9')))
    {
      return true;
    }
    switch (c)
    {
      case '\'':
      case '(':
      case ')':
      case '+':
      case ',':
      case '-':
      case '.':
      case '=':
      case '/':
      case ':':
      case '?':
      case ' ':
        return true;
      default:
        return false;
    }
  }
  /**
   * Indicates whether the contents of the provided byte array represent a
   * printable LDAP string, as per RFC 4517 section 3.2.  The only characters
   * allowed in a printable string are:
   * 
   *   - All uppercase and lowercase ASCII alphabetic letters
 
   *   - All ASCII numeric digits
 
   *   - The following additional ASCII characters:  single quote, left
   *       parenthesis, right parenthesis, plus, comma, hyphen, period, equals,
   *       forward slash, colon, question mark, space.
 
   * 
   * If the provided array contains anything other than the above characters
   * (i.e., if the byte array contains any non-ASCII characters, or any ASCII
   * control characters, or if it contains excluded ASCII characters like
   * the exclamation point, double quote, octothorpe, dollar sign, etc.), then
   * it will not be considered printable.
   *
   * @param  b  The byte array for which to make the determination.  It must
   *            not be {@code null}.
   *
   * @return  {@code true} if the contents of the provided byte array represent
   *          a printable LDAP string, or {@code false} if not.
   */
  public static boolean isPrintableString(final byte[] b)
  {
    for (final byte by : b)
    {
      if ((by & 0x80) == 0x80)
      {
        return false;
      }
      if (((by >= 'a') && (by <= 'z')) ||
          ((by >= 'A') && (by <= 'Z')) ||
          ((by >= '0') && (by <= '9')))
      {
        continue;
      }
      switch (by)
      {
        case '\'':
        case '(':
        case ')':
        case '+':
        case ',':
        case '-':
        case '.':
        case '=':
        case '/':
        case ':':
        case '?':
        case ' ':
          continue;
        default:
          return false;
      }
    }
    return true;
  }
  /**
   * Retrieves a string generated from the provided byte array using the UTF-8
   * encoding.
   *
   * @param  b  The byte array for which to return the associated string.
   *
   * @return  The string generated from the provided byte array using the UTF-8
   *          encoding.
   */
  public static String toUTF8String(final byte[] b)
  {
    try
    {
      return new String(b, "UTF-8");
    }
    catch (Exception e)
    {
      // This should never happen.
      debugException(e);
      return new String(b);
    }
  }
  /**
   * Retrieves a string generated from the specified portion of the provided
   * byte array using the UTF-8 encoding.
   *
   * @param  b       The byte array for which to return the associated string.
   * @param  offset  The offset in the array at which the value begins.
   * @param  length  The number of bytes in the value to convert to a string.
   *
   * @return  The string generated from the specified portion of the provided
   *          byte array using the UTF-8 encoding.
   */
  public static String toUTF8String(final byte[] b, final int offset,
                                    final int length)
  {
    try
    {
      return new String(b, offset, length, "UTF-8");
    }
    catch (Exception e)
    {
      // This should never happen.
      debugException(e);
      return new String(b, offset, length);
    }
  }
  /**
   * Retrieves a version of the provided string with the first character
   * converted to lowercase but all other characters retaining their original
   * capitalization.
   *
   * @param  s  The string to be processed.
   *
   * @return  A version of the provided string with the first character
   *          converted to lowercase but all other characters retaining their
   *          original capitalization.
   */
  public static String toInitialLowerCase(final String s)
  {
    if ((s == null) || (s.length() == 0))
    {
      return s;
    }
    else if (s.length() == 1)
    {
      return toLowerCase(s);
    }
    else
    {
      final char c = s.charAt(0);
      if (((c >= 'A') && (c <= 'Z')) || (c < ' ') || (c > '~'))
      {
        final StringBuilder b = new StringBuilder(s);
        b.setCharAt(0, Character.toLowerCase(c));
        return b.toString();
      }
      else
      {
        return s;
      }
    }
  }
  /**
   * Retrieves an all-lowercase version of the provided string.
   *
   * @param  s  The string for which to retrieve the lowercase version.
   *
   * @return  An all-lowercase version of the provided string.
   */
  public static String toLowerCase(final String s)
  {
    if (s == null)
    {
      return null;
    }
    final int length = s.length();
    final char[] charArray = s.toCharArray();
    for (int i=0; i < length; i++)
    {
      switch (charArray[i])
      {
        case 'A':
          charArray[i] = 'a';
          break;
        case 'B':
          charArray[i] = 'b';
          break;
        case 'C':
          charArray[i] = 'c';
          break;
        case 'D':
          charArray[i] = 'd';
          break;
        case 'E':
          charArray[i] = 'e';
          break;
        case 'F':
          charArray[i] = 'f';
          break;
        case 'G':
          charArray[i] = 'g';
          break;
        case 'H':
          charArray[i] = 'h';
          break;
        case 'I':
          charArray[i] = 'i';
          break;
        case 'J':
          charArray[i] = 'j';
          break;
        case 'K':
          charArray[i] = 'k';
          break;
        case 'L':
          charArray[i] = 'l';
          break;
        case 'M':
          charArray[i] = 'm';
          break;
        case 'N':
          charArray[i] = 'n';
          break;
        case 'O':
          charArray[i] = 'o';
          break;
        case 'P':
          charArray[i] = 'p';
          break;
        case 'Q':
          charArray[i] = 'q';
          break;
        case 'R':
          charArray[i] = 'r';
          break;
        case 'S':
          charArray[i] = 's';
          break;
        case 'T':
          charArray[i] = 't';
          break;
        case 'U':
          charArray[i] = 'u';
          break;
        case 'V':
          charArray[i] = 'v';
          break;
        case 'W':
          charArray[i] = 'w';
          break;
        case 'X':
          charArray[i] = 'x';
          break;
        case 'Y':
          charArray[i] = 'y';
          break;
        case 'Z':
          charArray[i] = 'z';
          break;
        default:
          if (charArray[i] > 0x7F)
          {
            return s.toLowerCase();
          }
          break;
      }
    }
    return new String(charArray);
  }
  /**
   * Indicates whether the provided character is a valid hexadecimal digit.
   *
   * @param  c  The character for which to make the determination.
   *
   * @return  {@code true} if the provided character does represent a valid
   *          hexadecimal digit, or {@code false} if not.
   */
  public static boolean isHex(final char c)
  {
    switch (c)
    {
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
      case 'a':
      case 'A':
      case 'b':
      case 'B':
      case 'c':
      case 'C':
      case 'd':
      case 'D':
      case 'e':
      case 'E':
      case 'f':
      case 'F':
        return true;
      default:
        return false;
    }
  }
  /**
   * Retrieves a hexadecimal representation of the provided byte.
   *
   * @param  b  The byte to encode as hexadecimal.
   *
   * @return  A string containing the hexadecimal representation of the provided
   *          byte.
   */
  public static String toHex(final byte b)
  {
    final StringBuilder buffer = new StringBuilder(2);
    toHex(b, buffer);
    return buffer.toString();
  }
  /**
   * Appends a hexadecimal representation of the provided byte to the given
   * buffer.
   *
   * @param  b       The byte to encode as hexadecimal.
   * @param  buffer  The buffer to which the hexadecimal representation is to be
   *                 appended.
   */
  public static void toHex(final byte b, final StringBuilder buffer)
  {
    switch (b & 0xF0)
    {
      case 0x00:
        buffer.append('0');
        break;
      case 0x10:
        buffer.append('1');
        break;
      case 0x20:
        buffer.append('2');
        break;
      case 0x30:
        buffer.append('3');
        break;
      case 0x40:
        buffer.append('4');
        break;
      case 0x50:
        buffer.append('5');
        break;
      case 0x60:
        buffer.append('6');
        break;
      case 0x70:
        buffer.append('7');
        break;
      case 0x80:
        buffer.append('8');
        break;
      case 0x90:
        buffer.append('9');
        break;
      case 0xA0:
        buffer.append('a');
        break;
      case 0xB0:
        buffer.append('b');
        break;
      case 0xC0:
        buffer.append('c');
        break;
      case 0xD0:
        buffer.append('d');
        break;
      case 0xE0:
        buffer.append('e');
        break;
      case 0xF0:
        buffer.append('f');
        break;
    }
    switch (b & 0x0F)
    {
      case 0x00:
        buffer.append('0');
        break;
      case 0x01:
        buffer.append('1');
        break;
      case 0x02:
        buffer.append('2');
        break;
      case 0x03:
        buffer.append('3');
        break;
      case 0x04:
        buffer.append('4');
        break;
      case 0x05:
        buffer.append('5');
        break;
      case 0x06:
        buffer.append('6');
        break;
      case 0x07:
        buffer.append('7');
        break;
      case 0x08:
        buffer.append('8');
        break;
      case 0x09:
        buffer.append('9');
        break;
      case 0x0A:
        buffer.append('a');
        break;
      case 0x0B:
        buffer.append('b');
        break;
      case 0x0C:
        buffer.append('c');
        break;
      case 0x0D:
        buffer.append('d');
        break;
      case 0x0E:
        buffer.append('e');
        break;
      case 0x0F:
        buffer.append('f');
        break;
    }
  }
  /**
   * Retrieves a hexadecimal representation of the contents of the provided byte
   * array.  No delimiter character will be inserted between the hexadecimal
   * digits for each byte.
   *
   * @param  b  The byte array to be represented as a hexadecimal string.  It
   *            must not be {@code null}.
   *
   * @return  A string containing a hexadecimal representation of the contents
   *          of the provided byte array.
   */
  public static String toHex(final byte[] b)
  {
    ensureNotNull(b);
    final StringBuilder buffer = new StringBuilder(2 * b.length);
    toHex(b, buffer);
    return buffer.toString();
  }
  /**
   * Retrieves a hexadecimal representation of the contents of the provided byte
   * array.  No delimiter character will be inserted between the hexadecimal
   * digits for each byte.
   *
   * @param  b       The byte array to be represented as a hexadecimal string.
   *                 It must not be {@code null}.
   * @param  buffer  A buffer to which the hexadecimal representation of the
   *                 contents of the provided byte array should be appended.
   */
  public static void toHex(final byte[] b, final StringBuilder buffer)
  {
    toHex(b, null, buffer);
  }
  /**
   * Retrieves a hexadecimal representation of the contents of the provided byte
   * array.  No delimiter character will be inserted between the hexadecimal
   * digits for each byte.
   *
   * @param  b          The byte array to be represented as a hexadecimal
   *                    string.  It must not be {@code null}.
   * @param  delimiter  A delimiter to be inserted between bytes.  It may be
   *                    {@code null} if no delimiter should be used.
   * @param  buffer     A buffer to which the hexadecimal representation of the
   *                    contents of the provided byte array should be appended.
   */
  public static void toHex(final byte[] b, final String delimiter,
                           final StringBuilder buffer)
  {
    boolean first = true;
    for (final byte bt : b)
    {
      if (first)
      {
        first = false;
      }
      else if (delimiter != null)
      {
        buffer.append(delimiter);
      }
      toHex(bt, buffer);
    }
  }
  /**
   * Retrieves a hex-encoded representation of the contents of the provided
   * array, along with an ASCII representation of its contents next to it.  The
   * output will be split across multiple lines, with up to sixteen bytes per
   * line.  For each of those sixteen bytes, the two-digit hex representation
   * will be appended followed by a space.  Then, the ASCII representation of
   * those sixteen bytes will follow that, with a space used in place of any
   * byte that does not have an ASCII representation.
   *
   * @param  array   The array whose contents should be processed.
   * @param  indent  The number of spaces to insert on each line prior to the
   *                 first hex byte.
   *
   * @return  A hex-encoded representation of the contents of the provided
   *          array, along with an ASCII representation of its contents next to
   *          it.
   */
  public static String toHexPlusASCII(final byte[] array, final int indent)
  {
    final StringBuilder buffer = new StringBuilder();
    toHexPlusASCII(array, indent, buffer);
    return buffer.toString();
  }
  /**
   * Appends a hex-encoded representation of the contents of the provided array
   * to the given buffer, along with an ASCII representation of its contents
   * next to it.  The output will be split across multiple lines, with up to
   * sixteen bytes per line.  For each of those sixteen bytes, the two-digit hex
   * representation will be appended followed by a space.  Then, the ASCII
   * representation of those sixteen bytes will follow that, with a space used
   * in place of any byte that does not have an ASCII representation.
   *
   * @param  array   The array whose contents should be processed.
   * @param  indent  The number of spaces to insert on each line prior to the
   *                 first hex byte.
   * @param  buffer  The buffer to which the encoded data should be appended.
   */
  public static void toHexPlusASCII(final byte[] array, final int indent,
                                    final StringBuilder buffer)
  {
    if ((array == null) || (array.length == 0))
    {
      return;
    }
    for (int i=0; i < indent; i++)
    {
      buffer.append(' ');
    }
    int pos = 0;
    int startPos = 0;
    while (pos < array.length)
    {
      toHex(array[pos++], buffer);
      buffer.append(' ');
      if ((pos % 16) == 0)
      {
        buffer.append("  ");
        for (int i=startPos; i < pos; i++)
        {
          if ((array[i] < ' ') || (array[i] > '~'))
          {
            buffer.append(' ');
          }
          else
          {
            buffer.append((char) array[i]);
          }
        }
        buffer.append(EOL);
        startPos = pos;
        if (pos < array.length)
        {
          for (int i=0; i < indent; i++)
          {
            buffer.append(' ');
          }
        }
      }
    }
    // If the last line isn't complete yet, then finish it off.
    if ((array.length % 16) != 0)
    {
      final int missingBytes = (16 - (array.length % 16));
      if (missingBytes > 0)
      {
        for (int i=0; i < missingBytes; i++)
        {
          buffer.append("   ");
        }
        buffer.append("  ");
        for (int i=startPos; i < array.length; i++)
        {
          if ((array[i] < ' ') || (array[i] > '~'))
          {
            buffer.append(' ');
          }
          else
          {
            buffer.append((char) array[i]);
          }
        }
        buffer.append(EOL);
      }
    }
  }
  /**
   * Appends a hex-encoded representation of the provided character to the given
   * buffer.  Each byte of the hex-encoded representation will be prefixed with
   * a backslash.
   *
   * @param  c       The character to be encoded.
   * @param  buffer  The buffer to which the hex-encoded representation should
   *                 be appended.
   */
  public static void hexEncode(final char c, final StringBuilder buffer)
  {
    final byte[] charBytes;
    if (c <= 0x7F)
    {
      charBytes = new byte[] { (byte) (c & 0x7F) };
    }
    else
    {
      charBytes = getBytes(String.valueOf(c));
    }
    for (final byte b : charBytes)
    {
      buffer.append('\\');
      toHex(b, buffer);
    }
  }
  /**
   * Appends the Java code that may be used to create the provided byte
   * array to the given buffer.
   *
   * @param  array   The byte array containing the data to represent.  It must
   *                 not be {@code null}.
   * @param  buffer  The buffer to which the code should be appended.
   */
  public static void byteArrayToCode(final byte[] array,
                                     final StringBuilder buffer)
  {
    buffer.append("new byte[] {");
    for (int i=0; i < array.length; i++)
    {
      if (i > 0)
      {
        buffer.append(',');
      }
      buffer.append(" (byte) 0x");
      toHex(array[i], buffer);
    }
    buffer.append(" }");
  }
  /**
   * Retrieves a single-line string representation of the stack trace for the
   * provided {@code Throwable}.  It will include the unqualified name of the
   * {@code Throwable} class, a list of source files and line numbers (if
   * available) for the stack trace, and will also include the stack trace for
   * the cause (if present).
   *
   * @param  t  The {@code Throwable} for which to retrieve the stack trace.
   *
   * @return  A single-line string representation of the stack trace for the
   *          provided {@code Throwable}.
   */
  public static String getStackTrace(final Throwable t)
  {
    final StringBuilder buffer = new StringBuilder();
    getStackTrace(t, buffer);
    return buffer.toString();
  }
  /**
   * Appends a single-line string representation of the stack trace for the
   * provided {@code Throwable} to the given buffer.  It will include the
   * unqualified name of the {@code Throwable} class, a list of source files and
   * line numbers (if available) for the stack trace, and will also include the
   * stack trace for the cause (if present).
   *
   * @param  t       The {@code Throwable} for which to retrieve the stack
   *                 trace.
   * @param  buffer  The buffer to which the information should be appended.
   */
  public static void getStackTrace(final Throwable t,
                                   final StringBuilder buffer)
  {
    buffer.append(getUnqualifiedClassName(t.getClass()));
    buffer.append('(');
    final String message = t.getMessage();
    if (message != null)
    {
      buffer.append("message='");
      buffer.append(message);
      buffer.append("', ");
    }
    buffer.append("trace='");
    getStackTrace(t.getStackTrace(), buffer);
    buffer.append('\'');
    final Throwable cause = t.getCause();
    if (cause != null)
    {
      buffer.append(", cause=");
      getStackTrace(cause, buffer);
    }
    buffer.append(", revision=");
    buffer.append(Version.REVISION_NUMBER);
    buffer.append(')');
  }
  /**
   * Returns a single-line string representation of the stack trace.  It will
   * include a list of source files and line numbers (if available) for the
   * stack trace.
   *
   * @param  elements  The stack trace.
   *
   * @return  A single-line string representation of the stack trace.
   */
  public static String getStackTrace(final StackTraceElement[] elements)
  {
    final StringBuilder buffer = new StringBuilder();
    getStackTrace(elements, buffer);
    return buffer.toString();
  }
  /**
   * Appends a single-line string representation of the stack trace to the given
   * buffer.  It will include a list of source files and line numbers
   * (if available) for the stack trace.
   *
   * @param  elements  The stack trace.
   * @param  buffer  The buffer to which the information should be appended.
   */
  public static void getStackTrace(final StackTraceElement[] elements,
                                   final StringBuilder buffer)
  {
    for (int i=0; i < elements.length; i++)
    {
      if (i > 0)
      {
        buffer.append(" / ");
      }
      buffer.append(elements[i].getMethodName());
      buffer.append('(');
      buffer.append(elements[i].getFileName());
      final int lineNumber = elements[i].getLineNumber();
      if (lineNumber > 0)
      {
        buffer.append(':');
        buffer.append(lineNumber);
      }
      buffer.append(')');
    }
  }
  /**
   * Retrieves a string representation of the provided {@code Throwable} object
   * suitable for use in a message.  For runtime exceptions and errors, then a
   * full stack trace for the exception will be provided.  For exception types
   * defined in the LDAP SDK, then its {@code getExceptionMessage} method will
   * be used to get the string representation.  For all other types of
   * exceptions, then the standard string representation will be used.
   * 
   * For all types of exceptions, the message will also include the cause if one
   * exists.
   *
   * @param  t  The {@code Throwable} for which to generate the exception
   *            message.
   *
   * @return  A string representation of the provided {@code Throwable} object
   *          suitable for use in a message.
   */
  public static String getExceptionMessage(final Throwable t)
  {
    if (t == null)
    {
      return ERR_NO_EXCEPTION.get();
    }
    final StringBuilder buffer = new StringBuilder();
    if (t instanceof LDAPSDKException)
    {
      buffer.append(((LDAPSDKException) t).getExceptionMessage());
    }
    else if (t instanceof LDAPSDKRuntimeException)
    {
      buffer.append(((LDAPSDKRuntimeException) t).getExceptionMessage());
    }
    if ((t instanceof RuntimeException) || (t instanceof Error))
    {
      return getStackTrace(t);
    }
    else
    {
      buffer.append(String.valueOf(t));
    }
    final Throwable cause = t.getCause();
    if (cause != null)
    {
      buffer.append(" caused by ");
      buffer.append(getExceptionMessage(cause));
    }
    return buffer.toString();
  }
  /**
   * Retrieves the unqualified name (i.e., the name without package information)
   * for the provided class.
   *
   * @param  c  The class for which to retrieve the unqualified name.
   *
   * @return  The unqualified name for the provided class.
   */
  public static String getUnqualifiedClassName(final Class> c)
  {
    final String className     = c.getName();
    final int    lastPeriodPos = className.lastIndexOf('.');
    if (lastPeriodPos > 0)
    {
      return className.substring(lastPeriodPos+1);
    }
    else
    {
      return className;
    }
  }
  /**
   * Encodes the provided timestamp in generalized time format.
   *
   * @param  timestamp  The timestamp to be encoded in generalized time format.
   *                    It should use the same format as the
   *                    {@code System.currentTimeMillis()} method (i.e., the
   *                    number of milliseconds since 12:00am UTC on January 1,
   *                    1970).
   *
   * @return  The generalized time representation of the provided date.
   */
  public static String encodeGeneralizedTime(final long timestamp)
  {
    return encodeGeneralizedTime(new Date(timestamp));
  }
  /**
   * Encodes the provided date in generalized time format.
   *
   * @param  d  The date to be encoded in generalized time format.
   *
   * @return  The generalized time representation of the provided date.
   */
  public static String encodeGeneralizedTime(final Date d)
  {
    SimpleDateFormat dateFormat = DATE_FORMATTERS.get();
    if (dateFormat == null)
    {
      dateFormat = new SimpleDateFormat("yyyyMMddHHmmss.SSS'Z'");
      dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
      DATE_FORMATTERS.set(dateFormat);
    }
    return dateFormat.format(d);
  }
  /**
   * Decodes the provided string as a timestamp in generalized time format.
   *
   * @param  t  The timestamp to be decoded.  It must not be {@code null}.
   *
   * @return  The {@code Date} object decoded from the provided timestamp.
   *
   * @throws  ParseException  If the provided string could not be decoded as a
   *                          timestamp in generalized time format.
   */
  public static Date decodeGeneralizedTime(final String t)
         throws ParseException
  {
    ensureNotNull(t);
    // Extract the time zone information from the end of the value.
    int tzPos;
    final TimeZone tz;
    if (t.endsWith("Z"))
    {
      tz = TimeZone.getTimeZone("UTC");
      tzPos = t.length() - 1;
    }
    else
    {
      tzPos = t.lastIndexOf('-');
      if (tzPos < 0)
      {
        tzPos = t.lastIndexOf('+');
        if (tzPos < 0)
        {
          throw new ParseException(ERR_GENTIME_DECODE_CANNOT_PARSE_TZ.get(t),
                                   0);
        }
      }
      tz = TimeZone.getTimeZone("GMT" + t.substring(tzPos));
      if (tz.getRawOffset() == 0)
      {
        // This is the default time zone that will be returned if the value
        // cannot be parsed.  If it's valid, then it will end in "+0000" or
        // "-0000".  Otherwise, it's invalid and GMT was just a fallback.
        if (! (t.endsWith("+0000") || t.endsWith("-0000")))
        {
          throw new ParseException(ERR_GENTIME_DECODE_CANNOT_PARSE_TZ.get(t),
                                   tzPos);
        }
      }
    }
    // See if the timestamp has a sub-second portion.  Note that if there is a
    // sub-second portion, then we may need to massage the value so that there
    // are exactly three sub-second characters so that it can be interpreted as
    // milliseconds.
    final String subSecFormatStr;
    final String trimmedTimestamp;
    int periodPos = t.lastIndexOf('.', tzPos);
    if (periodPos > 0)
    {
      final int subSecondLength = tzPos - periodPos - 1;
      switch (subSecondLength)
      {
        case 0:
          subSecFormatStr  = "";
          trimmedTimestamp = t.substring(0, periodPos);
          break;
        case 1:
          subSecFormatStr  = ".SSS";
          trimmedTimestamp = t.substring(0, (periodPos+2)) + "00";
          break;
        case 2:
          subSecFormatStr  = ".SSS";
          trimmedTimestamp = t.substring(0, (periodPos+3)) + '0';
          break;
        default:
          subSecFormatStr  = ".SSS";
          trimmedTimestamp = t.substring(0, periodPos+4);
          break;
      }
    }
    else
    {
      subSecFormatStr  = "";
      periodPos        = tzPos;
      trimmedTimestamp = t.substring(0, tzPos);
    }
    // Look at where the period is (or would be if it existed) to see how many
    // characters are in the integer portion.  This will give us what we need
    // for the rest of the format string.
    final String formatStr;
    switch (periodPos)
    {
      case 10:
        formatStr = "yyyyMMddHH" + subSecFormatStr;
        break;
      case 12:
        formatStr = "yyyyMMddHHmm" + subSecFormatStr;
        break;
      case 14:
        formatStr = "yyyyMMddHHmmss" + subSecFormatStr;
        break;
      default:
        throw new ParseException(ERR_GENTIME_CANNOT_PARSE_INVALID_LENGTH.get(t),
                                 periodPos);
    }
    // We should finally be able to create an appropriate date format object
    // to parse the trimmed version of the timestamp.
    final SimpleDateFormat dateFormat = new SimpleDateFormat(formatStr);
    dateFormat.setTimeZone(tz);
    dateFormat.setLenient(false);
    return dateFormat.parse(trimmedTimestamp);
  }
  /**
   * Trims only leading spaces from the provided string, leaving any trailing
   * spaces intact.
   *
   * @param  s  The string to be processed.  It must not be {@code null}.
   *
   * @return  The original string if no trimming was required, or a new string
   *          without leading spaces if the provided string had one or more.  It
   *          may be an empty string if the provided string was an empty string
   *          or contained only spaces.
   */
  public static String trimLeading(final String s)
  {
    ensureNotNull(s);
    int nonSpacePos = 0;
    final int length = s.length();
    while ((nonSpacePos < length) && (s.charAt(nonSpacePos) == ' '))
    {
      nonSpacePos++;
    }
    if (nonSpacePos == 0)
    {
      // There were no leading spaces.
      return s;
    }
    else if (nonSpacePos >= length)
    {
      // There were no non-space characters.
      return "";
    }
    else
    {
      // There were leading spaces, so return the string without them.
      return s.substring(nonSpacePos, length);
    }
  }
  /**
   * Trims only trailing spaces from the provided string, leaving any leading
   * spaces intact.
   *
   * @param  s  The string to be processed.  It must not be {@code null}.
   *
   * @return  The original string if no trimming was required, or a new string
   *          without trailing spaces if the provided string had one or more.
   *          It may be an empty string if the provided string was an empty
   *          string or contained only spaces.
   */
  public static String trimTrailing(final String s)
  {
    ensureNotNull(s);
    final int lastPos = s.length() - 1;
    int nonSpacePos = lastPos;
    while ((nonSpacePos >= 0) && (s.charAt(nonSpacePos) == ' '))
    {
      nonSpacePos--;
    }
    if (nonSpacePos < 0)
    {
      // There were no non-space characters.
      return "";
    }
    else if (nonSpacePos == lastPos)
    {
      // There were no trailing spaces.
      return s;
    }
    else
    {
      // There were trailing spaces, so return the string without them.
      return s.substring(0, (nonSpacePos+1));
    }
  }
  /**
   * Wraps the contents of the specified line using the given width.  It will
   * attempt to wrap at spaces to preserve words, but if that is not possible
   * (because a single "word" is longer than the maximum width), then it will
   * wrap in the middle of the word at the specified maximum width.
   *
   * @param  line      The line to be wrapped.  It must not be {@code null}.
   * @param  maxWidth  The maximum width for lines in the resulting list.  A
   *                   value less than or equal to zero will cause no wrapping
   *                   to be performed.
   *
   * @return  A list of the wrapped lines.  It may be empty if the provided line
   *          contained only spaces.
   */
  public static List wrapLine(final String line, final int maxWidth)
  {
    return wrapLine(line, maxWidth, maxWidth);
  }
  /**
   * Wraps the contents of the specified line using the given width.  It will
   * attempt to wrap at spaces to preserve words, but if that is not possible
   * (because a single "word" is longer than the maximum width), then it will
   * wrap in the middle of the word at the specified maximum width.
   *
   * @param  line                    The line to be wrapped.  It must not be
   *                                 {@code null}.
   * @param  maxFirstLineWidth       The maximum length for the first line in
   *                                 the resulting list.  A value less than or
   *                                 equal to zero will cause no wrapping to be
   *                                 performed.
   * @param  maxSubsequentLineWidth  The maximum length for all lines except the
   *                                 first line.  This must be greater than zero
   *                                 unless {@code maxFirstLineWidth} is less
   *                                 than or equal to zero.
   *
   * @return  A list of the wrapped lines.  It may be empty if the provided line
   *          contained only spaces.
   */
  public static List wrapLine(final String line,
                                      final int maxFirstLineWidth,
                                      final int maxSubsequentLineWidth)
  {
    if (maxFirstLineWidth > 0)
    {
      Validator.ensureTrue(maxSubsequentLineWidth > 0);
    }
    // See if the provided string already contains line breaks.  If so, then
    // treat it as multiple lines rather than a single line.
    final int breakPos = line.indexOf('\n');
    if (breakPos >= 0)
    {
      final ArrayList lineList = new ArrayList(10);
      final StringTokenizer tokenizer = new StringTokenizer(line, "\r\n");
      while (tokenizer.hasMoreTokens())
      {
        lineList.addAll(wrapLine(tokenizer.nextToken(), maxFirstLineWidth,
             maxSubsequentLineWidth));
      }
      return lineList;
    }
    final int length = line.length();
    if ((maxFirstLineWidth <= 0) || (length < maxFirstLineWidth))
    {
      return Arrays.asList(line);
    }
    int wrapPos = maxFirstLineWidth;
    int lastWrapPos = 0;
    final ArrayList lineList = new ArrayList(5);
    while (true)
    {
      final int spacePos = line.lastIndexOf(' ', wrapPos);
      if (spacePos > lastWrapPos)
      {
        // We found a space in an acceptable location, so use it after trimming
        // any trailing spaces.
        final String s = trimTrailing(line.substring(lastWrapPos, spacePos));
        // Don't bother adding the line if it contained only spaces.
        if (s.length() > 0)
        {
          lineList.add(s);
        }
        wrapPos = spacePos;
      }
      else
      {
        // We didn't find any spaces, so we'll have to insert a hard break at
        // the specified wrap column.
        lineList.add(line.substring(lastWrapPos, wrapPos));
      }
      // Skip over any spaces before the next non-space character.
      while ((wrapPos < length) && (line.charAt(wrapPos) == ' '))
      {
        wrapPos++;
      }
      lastWrapPos = wrapPos;
      wrapPos += maxSubsequentLineWidth;
      if (wrapPos >= length)
      {
        // The last fragment can fit on the line, so we can handle that now and
        // break.
        if (lastWrapPos >= length)
        {
          break;
        }
        else
        {
          final String s = line.substring(lastWrapPos);
          if (s.length() > 0)
          {
            lineList.add(s);
          }
          break;
        }
      }
    }
    return lineList;
  }
  /**
   * This method returns a form of the provided argument that is safe to
   * use on the command line for the local platform. This method is provided as
   * a convenience wrapper around {@link ExampleCommandLineArgument}.  Calling
   * this method is equivalent to:
   *
   * 
   *  return ExampleCommandLineArgument.getCleanArgument(s).getLocalForm();
   * 
   *
   * For getting direct access to command line arguments that are safe to
   * use on other platforms, call
   * {@link ExampleCommandLineArgument#getCleanArgument}.
   *
   * @param  s  The string to be processed.  It must not be {@code null}.
   *
   * @return  A cleaned version of the provided string in a form that will allow
   *          it to be displayed as the value of a command-line argument on.
   */
  public static String cleanExampleCommandLineArgument(final String s)
  {
    return ExampleCommandLineArgument.getCleanArgument(s).getLocalForm();
  }
  /**
   * Retrieves a single string which is a concatenation of all of the provided
   * strings.
   *
   * @param  a  The array of strings to concatenate.  It must not be
   *            {@code null}.
   *
   * @return  A string containing a concatenation of all of the strings in the
   *          provided array.
   */
  public static String concatenateStrings(final String... a)
  {
    return concatenateStrings(null, null, "  ", null, null, a);
  }
  /**
   * Retrieves a single string which is a concatenation of all of the provided
   * strings.
   *
   * @param  l  The list of strings to concatenate.  It must not be
   *            {@code null}.
   *
   * @return  A string containing a concatenation of all of the strings in the
   *          provided list.
   */
  public static String concatenateStrings(final List l)
  {
    return concatenateStrings(null, null, "  ", null, null, l);
  }
  /**
   * Retrieves a single string which is a concatenation of all of the provided
   * strings.
   *
   * @param  beforeList       A string that should be placed at the beginning of
   *                          the list.  It may be {@code null} or empty if
   *                          nothing should be placed at the beginning of the
   *                          list.
   * @param  beforeElement    A string that should be placed before each element
   *                          in the list.  It may be {@code null} or empty if
   *                          nothing should be placed before each element.
   * @param  betweenElements  The separator that should be placed between
   *                          elements in the list.  It may be {@code null} or
   *                          empty if no separator should be placed between
   *                          elements.
   * @param  afterElement     A string that should be placed after each element
   *                          in the list.  It may be {@code null} or empty if
   *                          nothing should be placed after each element.
   * @param  afterList        A string that should be placed at the end of the
   *                          list.  It may be {@code null} or empty if nothing
   *                          should be placed at the end of the list.
   * @param  a                The array of strings to concatenate.  It must not
   *                          be {@code null}.
   *
   * @return  A string containing a concatenation of all of the strings in the
   *          provided list.
   */
  public static String concatenateStrings(final String beforeList,
                                          final String beforeElement,
                                          final String betweenElements,
                                          final String afterElement,
                                          final String afterList,
                                          final String... a)
  {
    return concatenateStrings(beforeList, beforeElement, betweenElements,
         afterElement, afterList, Arrays.asList(a));
  }
  /**
   * Retrieves a single string which is a concatenation of all of the provided
   * strings.
   *
   * @param  beforeList       A string that should be placed at the beginning of
   *                          the list.  It may be {@code null} or empty if
   *                          nothing should be placed at the beginning of the
   *                          list.
   * @param  beforeElement    A string that should be placed before each element
   *                          in the list.  It may be {@code null} or empty if
   *                          nothing should be placed before each element.
   * @param  betweenElements  The separator that should be placed between
   *                          elements in the list.  It may be {@code null} or
   *                          empty if no separator should be placed between
   *                          elements.
   * @param  afterElement     A string that should be placed after each element
   *                          in the list.  It may be {@code null} or empty if
   *                          nothing should be placed after each element.
   * @param  afterList        A string that should be placed at the end of the
   *                          list.  It may be {@code null} or empty if nothing
   *                          should be placed at the end of the list.
   * @param  l                The list of strings to concatenate.  It must not
   *                          be {@code null}.
   *
   * @return  A string containing a concatenation of all of the strings in the
   *          provided list.
   */
  public static String concatenateStrings(final String beforeList,
                                          final String beforeElement,
                                          final String betweenElements,
                                          final String afterElement,
                                          final String afterList,
                                          final List l)
  {
    ensureNotNull(l);
    final StringBuilder buffer = new StringBuilder();
    if (beforeList != null)
    {
      buffer.append(beforeList);
    }
    final Iterator iterator = l.iterator();
    while (iterator.hasNext())
    {
      if (beforeElement != null)
      {
        buffer.append(beforeElement);
      }
      buffer.append(iterator.next());
      if (afterElement != null)
      {
        buffer.append(afterElement);
      }
      if ((betweenElements != null) && iterator.hasNext())
      {
        buffer.append(betweenElements);
      }
    }
    if (afterList != null)
    {
      buffer.append(afterList);
    }
    return buffer.toString();
  }
  /**
   * Converts a duration in seconds to a string with a human-readable duration
   * which may include days, hours, minutes, and seconds, to the extent that
   * they are needed.
   *
   * @param  s  The number of seconds to be represented.
   *
   * @return  A string containing a human-readable representation of the
   *          provided time.
   */
  public static String secondsToHumanReadableDuration(final long s)
  {
    return millisToHumanReadableDuration(s * 1000L);
  }
  /**
   * Converts a duration in seconds to a string with a human-readable duration
   * which may include days, hours, minutes, and seconds, to the extent that
   * they are needed.
   *
   * @param  m  The number of milliseconds to be represented.
   *
   * @return  A string containing a human-readable representation of the
   *          provided time.
   */
  public static String millisToHumanReadableDuration(final long m)
  {
    final StringBuilder buffer = new StringBuilder();
    long numMillis = m;
    final long numDays = numMillis / 86400000L;
    if (numDays > 0)
    {
      numMillis -= (numDays * 86400000L);
      if (numDays == 1)
      {
        buffer.append(INFO_NUM_DAYS_SINGULAR.get(numDays));
      }
      else
      {
        buffer.append(INFO_NUM_DAYS_PLURAL.get(numDays));
      }
    }
    final long numHours = numMillis / 3600000L;
    if (numHours > 0)
    {
      numMillis -= (numHours * 3600000L);
      if (buffer.length() > 0)
      {
        buffer.append(", ");
      }
      if (numHours == 1)
      {
        buffer.append(INFO_NUM_HOURS_SINGULAR.get(numHours));
      }
      else
      {
        buffer.append(INFO_NUM_HOURS_PLURAL.get(numHours));
      }
    }
    final long numMinutes = numMillis / 60000L;
    if (numMinutes > 0)
    {
      numMillis -= (numMinutes * 60000L);
      if (buffer.length() > 0)
      {
        buffer.append(", ");
      }
      if (numMinutes == 1)
      {
        buffer.append(INFO_NUM_MINUTES_SINGULAR.get(numMinutes));
      }
      else
      {
        buffer.append(INFO_NUM_MINUTES_PLURAL.get(numMinutes));
      }
    }
    if (numMillis == 1000)
    {
      if (buffer.length() > 0)
      {
        buffer.append(", ");
      }
      buffer.append(INFO_NUM_SECONDS_SINGULAR.get(1));
    }
    else if ((numMillis > 0) || (buffer.length() == 0))
    {
      if (buffer.length() > 0)
      {
        buffer.append(", ");
      }
      final long numSeconds = numMillis / 1000L;
      numMillis -= (numSeconds * 1000L);
      if ((numMillis % 1000L) != 0L)
      {
        final double numSecondsDouble = numSeconds + (numMillis / 1000.0);
        final DecimalFormat decimalFormat = new DecimalFormat("0.000");
        buffer.append(INFO_NUM_SECONDS_WITH_DECIMAL.get(
             decimalFormat.format(numSecondsDouble)));
      }
      else
      {
        buffer.append(INFO_NUM_SECONDS_PLURAL.get(numSeconds));
      }
    }
    return buffer.toString();
  }
  /**
   * Converts the provided number of nanoseconds to milliseconds.
   *
   * @param  nanos  The number of nanoseconds to convert to milliseconds.
   *
   * @return  The number of milliseconds that most closely corresponds to the
   *          specified number of nanoseconds.
   */
  public static long nanosToMillis(final long nanos)
  {
    return Math.max(0L, Math.round(nanos / 1000000.0d));
  }
  /**
   * Converts the provided number of milliseconds to nanoseconds.
   *
   * @param  millis  The number of milliseconds to convert to nanoseconds.
   *
   * @return  The number of nanoseconds that most closely corresponds to the
   *          specified number of milliseconds.
   */
  public static long millisToNanos(final long millis)
  {
    return Math.max(0L, (millis * 1000000L));
  }
  /**
   * Indicates whether the provided string is a valid numeric OID.  A numeric
   * OID must start and end with a digit, must have at least on period, must
   * contain only digits and periods, and must not have two consecutive periods.
   *
   * @param  s  The string to examine.  It must not be {@code null}.
   *
   * @return  {@code true} if the provided string is a valid numeric OID, or
   *          {@code false} if not.
   */
  public static boolean isNumericOID(final String s)
  {
    boolean digitRequired = true;
    boolean periodFound   = false;
    for (final char c : s.toCharArray())
    {
      switch (c)
      {
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
          digitRequired = false;
          break;
        case '.':
          if (digitRequired)
          {
            return false;
          }
          else
          {
            digitRequired = true;
          }
          periodFound = true;
          break;
        default:
          return false;
      }
    }
    return (periodFound && (! digitRequired));
  }
  /**
   * Capitalizes the provided string.  The first character will be converted to
   * uppercase, and the rest of the string will be left unaltered.
   *
   * @param  s  The string to be capitalized.
   *
   * @return  A capitalized version of the provided string.
   */
  public static String capitalize(final String s)
  {
    return capitalize(s, false);
  }
  /**
   * Capitalizes the provided string.  The first character of the string (or
   * optionally the first character of each word in the string)
   *
   * @param  s         The string to be capitalized.
   * @param  allWords  Indicates whether to capitalize all words in the string,
   *                   or only the first word.
   *
   * @return  A capitalized version of the provided string.
   */
  public static String capitalize(final String s, final boolean allWords)
  {
    if (s == null)
    {
      return null;
    }
    switch (s.length())
    {
      case 0:
        return s;
      case 1:
        return s.toUpperCase();
      default:
        boolean capitalize = true;
        final char[] chars = s.toCharArray();
        final StringBuilder buffer = new StringBuilder(chars.length);
        for (final char c : chars)
        {
          // Whitespace and punctuation will be considered word breaks.
          if (Character.isWhitespace(c) ||
              (((c >= '!') && (c <= '.')) ||
               ((c >= ':') && (c <= '@')) ||
               ((c >= '[') && (c <= '`')) ||
               ((c >= '{') && (c <= '~'))))
          {
            buffer.append(c);
            capitalize |= allWords;
          }
          else if (capitalize)
          {
            buffer.append(Character.toUpperCase(c));
            capitalize = false;
          }
          else
          {
            buffer.append(c);
          }
        }
        return buffer.toString();
    }
  }
  /**
   * Encodes the provided UUID to a byte array containing its 128-bit
   * representation.
   *
   * @param  uuid  The UUID to be encoded.  It must not be {@code null}.
   *
   * @return  The byte array containing the 128-bit encoded UUID.
   */
  public static byte[] encodeUUID(final UUID uuid)
  {
    final byte[] b = new byte[16];
    final long mostSignificantBits  = uuid.getMostSignificantBits();
    b[0]  = (byte) ((mostSignificantBits >> 56) & 0xFF);
    b[1]  = (byte) ((mostSignificantBits >> 48) & 0xFF);
    b[2]  = (byte) ((mostSignificantBits >> 40) & 0xFF);
    b[3]  = (byte) ((mostSignificantBits >> 32) & 0xFF);
    b[4]  = (byte) ((mostSignificantBits >> 24) & 0xFF);
    b[5]  = (byte) ((mostSignificantBits >> 16) & 0xFF);
    b[6]  = (byte) ((mostSignificantBits >> 8) & 0xFF);
    b[7]  = (byte) (mostSignificantBits & 0xFF);
    final long leastSignificantBits = uuid.getLeastSignificantBits();
    b[8]  = (byte) ((leastSignificantBits >> 56) & 0xFF);
    b[9]  = (byte) ((leastSignificantBits >> 48) & 0xFF);
    b[10] = (byte) ((leastSignificantBits >> 40) & 0xFF);
    b[11] = (byte) ((leastSignificantBits >> 32) & 0xFF);
    b[12] = (byte) ((leastSignificantBits >> 24) & 0xFF);
    b[13] = (byte) ((leastSignificantBits >> 16) & 0xFF);
    b[14] = (byte) ((leastSignificantBits >> 8) & 0xFF);
    b[15] = (byte) (leastSignificantBits & 0xFF);
    return b;
  }
  /**
   * Decodes the value of the provided byte array as a Java UUID.
   *
   * @param  b  The byte array to be decoded as a UUID.  It must not be
   *            {@code null}.
   *
   * @return  The decoded UUID.
   *
   * @throws  ParseException  If the provided byte array cannot be parsed as a
   *                         UUID.
   */
  public static UUID decodeUUID(final byte[] b)
         throws ParseException
  {
    if (b.length != 16)
    {
      throw new ParseException(ERR_DECODE_UUID_INVALID_LENGTH.get(toHex(b)), 0);
    }
    long mostSignificantBits = 0L;
    for (int i=0; i < 8; i++)
    {
      mostSignificantBits = (mostSignificantBits << 8) | (b[i] & 0xFF);
    }
    long leastSignificantBits = 0L;
    for (int i=8; i < 16; i++)
    {
      leastSignificantBits = (leastSignificantBits << 8) | (b[i] & 0xFF);
    }
    return new UUID(mostSignificantBits, leastSignificantBits);
  }
  /**
   * Returns {@code true} if and only if the current process is running on
   * a Windows-based operating system.
   *
   * @return  {@code true} if the current process is running on a Windows-based
   *          operating system and {@code false} otherwise.
   */
  public static boolean isWindows()
  {
    final String osName = toLowerCase(System.getProperty("os.name"));
    return ((osName != null) && osName.contains("windows"));
  }
  /**
   * Attempts to parse the contents of the provided string to an argument list
   * (e.g., converts something like "--arg1 arg1value --arg2 --arg3 arg3value"
   * to a list of "--arg1", "arg1value", "--arg2", "--arg3", "arg3value").
   *
   * @param  s  The string to be converted to an argument list.
   *
   * @return  The parsed argument list.
   *
   * @throws  ParseException  If a problem is encountered while attempting to
   *                          parse the given string to an argument list.
   */
  public static List toArgumentList(final String s)
         throws ParseException
  {
    if ((s == null) || (s.length() == 0))
    {
      return Collections.emptyList();
    }
    int quoteStartPos = -1;
    boolean inEscape = false;
    final ArrayList argList = new ArrayList();
    final StringBuilder currentArg = new StringBuilder();
    for (int i=0; i < s.length(); i++)
    {
      final char c = s.charAt(i);
      if (inEscape)
      {
        currentArg.append(c);
        inEscape = false;
        continue;
      }
      if (c == '\\')
      {
        inEscape = true;
      }
      else if (c == '"')
      {
        if (quoteStartPos >= 0)
        {
          quoteStartPos = -1;
        }
        else
        {
          quoteStartPos = i;
        }
      }
      else if (c == ' ')
      {
        if (quoteStartPos >= 0)
        {
          currentArg.append(c);
        }
        else if (currentArg.length() > 0)
        {
          argList.add(currentArg.toString());
          currentArg.setLength(0);
        }
      }
      else
      {
        currentArg.append(c);
      }
    }
    if (s.endsWith("\\") && (! s.endsWith("\\\\")))
    {
      throw new ParseException(ERR_ARG_STRING_DANGLING_BACKSLASH.get(),
           (s.length() - 1));
    }
    if (quoteStartPos >= 0)
    {
      throw new ParseException(ERR_ARG_STRING_UNMATCHED_QUOTE.get(
           quoteStartPos), quoteStartPos);
    }
    if (currentArg.length() > 0)
    {
      argList.add(currentArg.toString());
    }
    return Collections.unmodifiableList(argList);
  }
  /**
   * Creates a modifiable list with all of the items of the provided array in
   * the same order.  This method behaves much like {@code Arrays.asList},
   * except that if the provided array is {@code null}, then it will return a
   * {@code null} list rather than throwing an exception.
   *
   * @param    The type of item contained in the provided array.
   *
   * @param  array  The array of items to include in the list.
   *
   * @return  The list that was created, or {@code null} if the provided array
   *          was {@code null}.
   */
  public static  List toList(final T[] array)
  {
    if (array == null)
    {
      return null;
    }
    final ArrayList l = new ArrayList(array.length);
    l.addAll(Arrays.asList(array));
    return l;
  }
  /**
   * Creates a modifiable list with all of the items of the provided array in
   * the same order.  This method behaves much like {@code Arrays.asList},
   * except that if the provided array is {@code null}, then it will return an
   * empty list rather than throwing an exception.
   *
   * @param    The type of item contained in the provided array.
   *
   * @param  array  The array of items to include in the list.
   *
   * @return  The list that was created, or an empty list if the provided array
   *          was {@code null}.
   */
  public static  List toNonNullList(final T[] array)
  {
    if (array == null)
    {
      return new ArrayList(0);
    }
    final ArrayList l = new ArrayList(array.length);
    l.addAll(Arrays.asList(array));
    return l;
  }
  /**
   * Indicates whether both of the provided objects are {@code null} or both
   * are logically equal (using the {@code equals} method).
   *
   * @param  o1  The first object for which to make the determination.
   * @param  o2  The second object for which to make the determination.
   *
   * @return  {@code true} if both objects are {@code null} or both are
   *          logically equal, or {@code false} if only one of the objects is
   *          {@code null} or they are not logically equal.
   */
  public static boolean bothNullOrEqual(final Object o1, final Object o2)
  {
    if (o1 == null)
    {
      return (o2 == null);
    }
    else if (o2 == null)
    {
      return false;
    }
    return o1.equals(o2);
  }
  /**
   * Indicates whether both of the provided strings are {@code null} or both
   * are logically equal ignoring differences in capitalization (using the
   * {@code equalsIgnoreCase} method).
   *
   * @param  s1  The first string for which to make the determination.
   * @param  s2  The second string for which to make the determination.
   *
   * @return  {@code true} if both strings are {@code null} or both are
   *          logically equal ignoring differences in capitalization, or
   *          {@code false} if only one of the objects is {@code null} or they
   *          are not logically equal ignoring capitalization.
   */
  public static boolean bothNullOrEqualIgnoreCase(final String s1,
                                                  final String s2)
  {
    if (s1 == null)
    {
      return (s2 == null);
    }
    else if (s2 == null)
    {
      return false;
    }
    return s1.equalsIgnoreCase(s2);
  }
  /**
   * Indicates whether the provided string arrays have the same elements,
   * ignoring the order in which they appear and differences in capitalization.
   * It is assumed that neither array contains {@code null} strings, and that
   * no string appears more than once in each array.
   *
   * @param  a1  The first array for which to make the determination.
   * @param  a2  The second array for which to make the determination.
   *
   * @return  {@code true} if both arrays have the same set of strings, or
   *          {@code false} if not.
   */
  public static boolean stringsEqualIgnoreCaseOrderIndependent(
                             final String[] a1, final String[] a2)
  {
    if (a1 == null)
    {
      return (a2 == null);
    }
    else if (a2 == null)
    {
      return false;
    }
    if (a1.length != a2.length)
    {
      return false;
    }
    if (a1.length == 1)
    {
      return (a1[0].equalsIgnoreCase(a2[0]));
    }
    final HashSet s1 = new HashSet(a1.length);
    for (final String s : a1)
    {
      s1.add(toLowerCase(s));
    }
    final HashSet s2 = new HashSet(a2.length);
    for (final String s : a2)
    {
      s2.add(toLowerCase(s));
    }
    return s1.equals(s2);
  }
  /**
   * Indicates whether the provided arrays have the same elements, ignoring the
   * order in which they appear.  It is assumed that neither array contains
   * {@code null} elements, and that no element appears more than once in each
   * array.
   *
   * @param    The type of element contained in the arrays.
   *
   * @param  a1  The first array for which to make the determination.
   * @param  a2  The second array for which to make the determination.
   *
   * @return  {@code true} if both arrays have the same set of elements, or
   *          {@code false} if not.
   */
  public static  boolean arraysEqualOrderIndependent(final T[] a1,
                                                        final T[] a2)
  {
    if (a1 == null)
    {
      return (a2 == null);
    }
    else if (a2 == null)
    {
      return false;
    }
    if (a1.length != a2.length)
    {
      return false;
    }
    if (a1.length == 1)
    {
      return (a1[0].equals(a2[0]));
    }
    final HashSet s1 = new HashSet(Arrays.asList(a1));
    final HashSet s2 = new HashSet(Arrays.asList(a2));
    return s1.equals(s2);
  }
  /**
   * Determines the number of bytes in a UTF-8 character that starts with the
   * given byte.
   *
   * @param  b  The byte for which to make the determination.
   *
   * @return  The number of bytes in a UTF-8 character that starts with the
   *          given byte, or -1 if it does not appear to be a valid first byte
   *          for a UTF-8 character.
   */
  public static int numBytesInUTF8CharacterWithFirstByte(final byte b)
  {
    if ((b & 0x7F) == b)
    {
      return 1;
    }
    else if ((b & 0xE0) == 0xC0)
    {
      return 2;
    }
    else if ((b & 0xF0) == 0xE0)
    {
      return 3;
    }
    else if ((b & 0xF8) == 0xF0)
    {
      return 4;
    }
    else
    {
      return -1;
    }
  }
  /**
   * Indicates whether the provided attribute name should be considered a
   * sensitive attribute for the purposes of {@code toCode} methods.  If an
   * attribute is considered sensitive, then its values will be redacted in the
   * output of the {@code toCode} methods.
   *
   * @param  name  The name for which to make the determination.  It may or may
   *               not include attribute options.  It must not be {@code null}.
   *
   * @return  {@code true} if the specified attribute is one that should be
   *          considered sensitive for the
   */
  public static boolean isSensitiveToCodeAttribute(final String name)
  {
    final String lowerBaseName = Attribute.getBaseName(name).toLowerCase();
    return TO_CODE_SENSITIVE_ATTRIBUTE_NAMES.contains(lowerBaseName);
  }
  /**
   * Retrieves a set containing the base names (in all lowercase characters) of
   * any attributes that should be considered sensitive for the purposes of the
   * {@code toCode} methods.  By default, only the userPassword and
   * authPassword attributes and their respective OIDs will be included.
   *
   * @return  A set containing the base names (in all lowercase characters) of
   *          any attributes that should be considered sensitive for the
   *          purposes of the {@code toCode} methods.
   */
  public static Set getSensitiveToCodeAttributeBaseNames()
  {
    return TO_CODE_SENSITIVE_ATTRIBUTE_NAMES;
  }
  /**
   * Specifies the names of any attributes that should be considered sensitive
   * for the purposes of the {@code toCode} methods.
   *
   * @param  names  The names of any attributes that should be considered
   *                sensitive for the purposes of the {@code toCode} methods.
   *                It may be {@code null} or empty if no attributes should be
   *                considered sensitive.
   */
  public static void setSensitiveToCodeAttributes(final String... names)
  {
    setSensitiveToCodeAttributes(toList(names));
  }
  /**
   * Specifies the names of any attributes that should be considered sensitive
   * for the purposes of the {@code toCode} methods.
   *
   * @param  names  The names of any attributes that should be considered
   *                sensitive for the purposes of the {@code toCode} methods.
   *                It may be {@code null} or empty if no attributes should be
   *                considered sensitive.
   */
  public static void setSensitiveToCodeAttributes(
                          final Collection names)
  {
    if ((names == null) || names.isEmpty())
    {
      TO_CODE_SENSITIVE_ATTRIBUTE_NAMES = Collections.emptySet();
    }
    else
    {
      final LinkedHashSet nameSet =
           new LinkedHashSet(names.size());
      for (final String s : names)
      {
        nameSet.add(Attribute.getBaseName(s).toLowerCase());
      }
      TO_CODE_SENSITIVE_ATTRIBUTE_NAMES = Collections.unmodifiableSet(nameSet);
    }
  }
  /**
   * Creates a new {@code IOException} with a cause.  The constructor needed to
   * do this wasn't available until Java SE 6, so reflection is used to invoke
   * this constructor in versions of Java that provide it.  In Java SE 5, the
   * provided message will be augmented with information about the cause.
   *
   * @param  message  The message to use for the exception.  This may be
   *                  {@code null} if the message should be generated from the
   *                  provided cause.
   * @param  cause    The underlying cause for the exception.  It may be
   *                  {@code null} if the exception should have only a message.
   *
   * @return  The {@code IOException} object that was created.
   */
  public static IOException createIOExceptionWithCause(final String message,
                                                       final Throwable cause)
  {
    if (cause == null)
    {
      return new IOException(message);
    }
    try
    {
      if (message == null)
      {
        final Constructor constructor =
             IOException.class.getConstructor(Throwable.class);
        return constructor.newInstance(cause);
      }
      else
      {
        final Constructor constructor =
             IOException.class.getConstructor(String.class, Throwable.class);
        return constructor.newInstance(message, cause);
      }
    }
    catch (final Exception e)
    {
      debugException(e);
      if (message == null)
      {
        return new IOException(getExceptionMessage(cause));
      }
      else
      {
        return new IOException(message + " (caused by " +
             getExceptionMessage(cause) + ')');
      }
    }
  }
}
                                                 © 2015 - 2025 Weber Informatics LLC | Privacy Policy