All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.unboundid.util.StaticUtils Maven / Gradle / Ivy

/*
 * Copyright 2007-2019 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright (C) 2008-2019 Ping Identity Corporation
 *
 * 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.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Array;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.nio.charset.StandardCharsets;
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.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.UUID;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.unboundid.ldap.sdk.Attribute;
import com.unboundid.ldap.sdk.Control;
import com.unboundid.ldap.sdk.LDAPConnectionOptions;
import com.unboundid.ldap.sdk.NameResolver;
import com.unboundid.ldap.sdk.Version;

import static com.unboundid.util.UtilityMessages.*;



/**
 * 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 character array.
   */
  public static final char[] NO_CHARS = new char[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 = getSystemProperty("line.separator", "\n");



  /**
   * A byte array containing the end-of-line marker for this platform.
   */
  public static final byte[] EOL_BYTES = getBytes(EOL);



  /**
   * Indicates whether the unit tests are currently running.
   */
  private static final boolean IS_WITHIN_UNIT_TESTS =
       Boolean.getBoolean("com.unboundid.ldap.sdk.RunningUnitTests") ||
       Boolean.getBoolean("com.unboundid.directory.server.RunningUnitTests");



  /**
   * 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 = getEnvironmentVariable("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<>();



  /**
   * The {@code TimeZone} object that represents the UTC (universal coordinated
   * time) time zone.
   */
  private static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("UTC");



  /**
   * 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 =
       setOf("userpassword", "2.5.4.35",
            "authpassword", "1.3.6.1.4.1.4203.1.3.4");



  /**
   * 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
      {
        return s.getBytes(StandardCharsets.UTF_8);
      }
    }

    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; } /** * Indicates whether the contents of the provided array are valid UTF-8. * * @param b The byte array to examine. It must not be {@code null}. * * @return {@code true} if the byte array can be parsed as a valid UTF-8 * string, or {@code false} if not. */ public static boolean isValidUTF8(final byte[] b) { int i = 0; while (i < b.length) { final byte currentByte = b[i++]; // If the most significant bit is not set, then this represents a valid // single-byte character. if ((currentByte & 0b1000_0000) == 0b0000_0000) { continue; } // If the first byte starts with 0b110, then it must be followed by // another byte that starts with 0b10. if ((currentByte & 0b1110_0000) == 0b1100_0000) { if (! hasExpectedSubsequentUTF8Bytes(b, i, 1)) { return false; } i++; continue; } // If the first byte starts with 0b1110, then it must be followed by two // more bytes that start with 0b10. if ((currentByte & 0b1111_0000) == 0b1110_0000) { if (! hasExpectedSubsequentUTF8Bytes(b, i, 2)) { return false; } i += 2; continue; } // If the first byte starts with 0b11110, then it must be followed by // three more bytes that start with 0b10. if ((currentByte & 0b1111_1000) == 0b1111_0000) { if (! hasExpectedSubsequentUTF8Bytes(b, i, 3)) { return false; } i += 3; continue; } // If the first byte starts with 0b111110, then it must be followed by // four more bytes that start with 0b10. if ((currentByte & 0b1111_1100) == 0b1111_1000) { if (! hasExpectedSubsequentUTF8Bytes(b, i, 4)) { return false; } i += 4; continue; } // If the first byte starts with 0b1111110, then it must be followed by // five more bytes that start with 0b10. if ((currentByte & 0b1111_1110) == 0b1111_1100) { if (! hasExpectedSubsequentUTF8Bytes(b, i, 5)) { return false; } i += 5; continue; } // This is not a valid first byte for a UTF-8 character. return false; } // If we've gotten here, then the provided array represents a valid UTF-8 // string. return true; } /** * Ensures that the provided array has the expected number of bytes that start * with 0b10 starting at the specified position in the array. * * @param b The byte array to examine. * @param p The position in the byte array at which to start looking. * @param n The number of bytes to examine. * * @return {@code true} if the provided byte array has the expected number of * bytes that start with 0b10, or {@code false} if not. */ private static boolean hasExpectedSubsequentUTF8Bytes(final byte[] b, final int p, final int n) { if (b.length < (p + n)) { return false; } for (int i=0; i < n; i++) { if ((b[p+i] & 0b1100_0000) != 0b1000_0000) { 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, StandardCharsets.UTF_8); } catch (final Exception e) { // This should never happen. Debug.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, StandardCharsets.UTF_8); } catch (final Exception e) { // This should never happen. Debug.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.isEmpty()) { 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); } /** * Retrieves an all-uppercase version of the provided string. * * @param s The string for which to retrieve the uppercase version. * * @return An all-uppercase version of the provided string. */ public static String toUpperCase(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.toUpperCase(); } 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) { Validator.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); } } } /** * Retrieves the bytes that correspond to the provided hexadecimal string. * * @param hexString The hexadecimal string for which to retrieve the bytes. * It must not be {@code null}, and there must not be any * delimiter between bytes. * * @return The bytes that correspond to the provided hexadecimal string. * * @throws ParseException If the provided string does not represent valid * hexadecimal data, or if the provided string does * not contain an even number of characters. */ public static byte[] fromHex(final String hexString) throws ParseException { if ((hexString.length() % 2) != 0) { throw new ParseException( ERR_FROM_HEX_ODD_NUMBER_OF_CHARACTERS.get(hexString.length()), hexString.length()); } final byte[] decodedBytes = new byte[hexString.length() / 2]; for (int i=0, j=0; i < decodedBytes.length; i++, j+= 2) { switch (hexString.charAt(j)) { case '0': // No action is required. break; case '1': decodedBytes[i] = 0x10; break; case '2': decodedBytes[i] = 0x20; break; case '3': decodedBytes[i] = 0x30; break; case '4': decodedBytes[i] = 0x40; break; case '5': decodedBytes[i] = 0x50; break; case '6': decodedBytes[i] = 0x60; break; case '7': decodedBytes[i] = 0x70; break; case '8': decodedBytes[i] = (byte) 0x80; break; case '9': decodedBytes[i] = (byte) 0x90; break; case 'a': case 'A': decodedBytes[i] = (byte) 0xA0; break; case 'b': case 'B': decodedBytes[i] = (byte) 0xB0; break; case 'c': case 'C': decodedBytes[i] = (byte) 0xC0; break; case 'd': case 'D': decodedBytes[i] = (byte) 0xD0; break; case 'e': case 'E': decodedBytes[i] = (byte) 0xE0; break; case 'f': case 'F': decodedBytes[i] = (byte) 0xF0; break; default: throw new ParseException(ERR_FROM_HEX_NON_HEX_CHARACTER.get(j), j); } switch (hexString.charAt(j+1)) { case '0': // No action is required. break; case '1': decodedBytes[i] |= 0x01; break; case '2': decodedBytes[i] |= 0x02; break; case '3': decodedBytes[i] |= 0x03; break; case '4': decodedBytes[i] |= 0x04; break; case '5': decodedBytes[i] |= 0x05; break; case '6': decodedBytes[i] |= 0x06; break; case '7': decodedBytes[i] |= 0x07; break; case '8': decodedBytes[i] |= 0x08; break; case '9': decodedBytes[i] |= 0x09; break; case 'a': case 'A': decodedBytes[i] |= 0x0A; break; case 'b': case 'B': decodedBytes[i] |= 0x0B; break; case 'c': case 'C': decodedBytes[i] |= 0x0C; break; case 'd': case 'D': decodedBytes[i] |= 0x0D; break; case 'e': case 'E': decodedBytes[i] |= 0x0E; break; case 'f': case 'F': decodedBytes[i] |= 0x0F; break; default: throw new ParseException(ERR_FROM_HEX_NON_HEX_CHARACTER.get(j+1), j+1); } } return decodedBytes; } /** * 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 a hex-encoded representation of the provided code point to the * given buffer. Each byte of the hex-encoded representation will be prefixed * with a backslash. * * @param codePoint The code point to be encoded. * @param buffer The buffer to which the hex-encoded representation * should be appended. */ public static void hexEncode(final int codePoint, final StringBuilder buffer) { final byte[] charBytes = getBytes(new String(new int[] { codePoint }, 0, 1)); 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); } final String ldapSDKVersionString = ", ldapSDKVersion=" + Version.NUMERIC_VERSION_STRING + ", revision=" + Version.REVISION_ID; if (buffer.indexOf(ldapSDKVersionString) < 0) { buffer.append(ldapSDKVersionString); } 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) { getStackTrace(elements, buffer, -1); } /** * 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. * @param maxPreSDKFrames The maximum number of stack trace frames to * include from code invoked before calling into the * LDAP SDK. A value of zero indicates that only * stack trace frames from the LDAP SDK itself (or * things that it calls) will be included. A * negative value indicates that */ public static void getStackTrace(final StackTraceElement[] elements, final StringBuilder buffer, final int maxPreSDKFrames) { boolean sdkElementFound = false; int numPreSDKElementsFound = 0; for (int i=0; i < elements.length; i++) { if (i > 0) { buffer.append(" / "); } if (elements[i].getClassName().startsWith("com.unboundid.")) { sdkElementFound = true; } else if (sdkElementFound) { if ((maxPreSDKFrames >= 0) && (numPreSDKElementsFound >= maxPreSDKFrames)) { buffer.append("..."); return; } numPreSDKElementsFound++; } 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); } else if (elements[i].isNativeMethod()) { buffer.append(":native"); } else { buffer.append(":unknown"); } 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) { final boolean includeCause = Boolean.getBoolean(Debug.PROPERTY_INCLUDE_CAUSE_IN_EXCEPTION_MESSAGES); final boolean includeStackTrace = Boolean.getBoolean( Debug.PROPERTY_INCLUDE_STACK_TRACE_IN_EXCEPTION_MESSAGES); return getExceptionMessage(t, includeCause, includeStackTrace); } /** * 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. * @param includeCause Indicates whether to include information about * the cause (if any) in the exception message. * @param includeStackTrace Indicates whether to include a condensed * representation of the stack trace in 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, final boolean includeCause, final boolean includeStackTrace) { 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()); } else if (t instanceof NullPointerException) { // For NullPointerExceptions, we'll always print at least a portion of // the stack trace that includes all of the LDAP SDK code, and up to // three frames of whatever called into the SDK. buffer.append("NullPointerException("); getStackTrace(t.getStackTrace(), buffer, 3); buffer.append(')'); } else if ((t.getMessage() == null) || t.getMessage().isEmpty() || t.getMessage().equalsIgnoreCase("null")) { getStackTrace(t, buffer); } else { buffer.append(t.getClass().getSimpleName()); buffer.append('('); buffer.append(t.getMessage()); buffer.append(')'); if (includeStackTrace) { buffer.append(" trace="); getStackTrace(t, buffer); } else if (includeCause) { final Throwable cause = t.getCause(); if (cause != null) { buffer.append(" caused by "); buffer.append(getExceptionMessage(cause)); } } } final String ldapSDKVersionString = ", ldapSDKVersion=" + Version.NUMERIC_VERSION_STRING + ", revision=" + Version.REVISION_ID; if (buffer.indexOf(ldapSDKVersionString) < 0) { buffer.append(ldapSDKVersionString); } 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; } } /** * Retrieves a {@code TimeZone} object that represents the UTC (universal * coordinated time) time zone. * * @return A {@code TimeZone} object that represents the UTC time zone. */ public static TimeZone getUTCTimeZone() { return UTC_TIME_ZONE; } /** * 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(UTC_TIME_ZONE); 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 { Validator.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) { Validator.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) { Validator.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 Collections.singletonList(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.isEmpty()) { 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.isEmpty()) { 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) { Validator.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 / 86_400_000L; if (numDays > 0) { numMillis -= (numDays * 86_400_000L); if (numDays == 1) { buffer.append(INFO_NUM_DAYS_SINGULAR.get(numDays)); } else { buffer.append(INFO_NUM_DAYS_PLURAL.get(numDays)); } } final long numHours = numMillis / 3_600_000L; if (numHours > 0) { numMillis -= (numHours * 3_600_000L); 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 / 60_000L; if (numMinutes > 0) { numMillis -= (numMinutes * 60_000L); 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 / 1_000_000.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 * 1_000_000L)); } /** * 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(getSystemProperty("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.isEmpty()) { return Collections.emptyList(); } int quoteStartPos = -1; boolean inEscape = false; final ArrayList argList = new ArrayList<>(20); 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); } /** * Retrieves an array containing the elements of the provided collection. * * @param The type of element included in the provided * collection. * @param collection The collection to convert to an array. * @param type The type of element contained in the collection. * * @return An array containing the elements of the provided list. */ public static T[] toArray(final Collection collection, final Class type) { if (collection == null) { return null; } @SuppressWarnings("unchecked") final T[] array = (T[]) Array.newInstance(type, collection.size()); return collection.toArray(array); } /** * 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<>(computeMapCapacity(a1.length)); for (final String s : a1) { s1.add(toLowerCase(s)); } final HashSet s2 = new HashSet<>(computeMapCapacity(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); } else if (message == null) { return new IOException(cause); } else { return new IOException(message, cause); } } /** * Converts the provided string (which may include line breaks) into a list * containing the lines without the line breaks. * * @param s The string to convert into a list of its representative lines. * * @return A list containing the lines that comprise the given string. */ public static List stringToLines(final String s) { final ArrayList l = new ArrayList<>(10); if (s == null) { return l; } final BufferedReader reader = new BufferedReader(new StringReader(s)); try { while (true) { try { final String line = reader.readLine(); if (line == null) { return l; } else { l.add(line); } } catch (final Exception e) { Debug.debugException(e); // This should never happen. If it does, just return a list // containing a single item that is the original string. l.clear(); l.add(s); return l; } } } finally { try { // This is technically not necessary in this case, but it's good form. reader.close(); } catch (final Exception e) { Debug.debugException(e); // This should never happen, and there's nothing we need to do even if // it does. } } } /** * Creates a string that is a concatenation of all of the provided lines, with * a line break (using the end-of-line sequence appropriate for the underlying * platform) after each line (including the last line). * * @param lines The lines to include in the string. * * @return The string resulting from concatenating the provided lines with * line breaks. */ public static String linesToString(final CharSequence... lines) { if (lines == null) { return ""; } return linesToString(Arrays.asList(lines)); } /** * Creates a string that is a concatenation of all of the provided lines, with * a line break (using the end-of-line sequence appropriate for the underlying * platform) after each line (including the last line). * * @param lines The lines to include in the string. * * @return The string resulting from concatenating the provided lines with * line breaks. */ public static String linesToString(final List lines) { if (lines == null) { return ""; } final StringBuilder buffer = new StringBuilder(); for (final CharSequence line : lines) { buffer.append(line); buffer.append(EOL); } return buffer.toString(); } /** * Constructs a {@code File} object from the provided path. * * @param baseDirectory The base directory to use as the starting point. * It must not be {@code null} and is expected to * represent a directory. * @param pathElements An array of the elements that make up the remainder * of the path to the specified file, in order from * paths closest to the root of the filesystem to * furthest away (that is, the first element should * represent a file or directory immediately below the * base directory, the second is one level below that, * and so on). It may be {@code null} or empty if the * base directory should be used. * * @return The constructed {@code File} object. */ public static File constructPath(final File baseDirectory, final String... pathElements) { Validator.ensureNotNull(baseDirectory); File f = baseDirectory; if (pathElements != null) { for (final String pathElement : pathElements) { f = new File(f, pathElement); } } return f; } /** * Creates a byte array from the provided integer values. All of the integer * values must be between 0x00 and 0xFF (0 and 255), inclusive. Any bits * set outside of that range will be ignored. * * @param bytes The values to include in the byte array. * * @return A byte array with the provided set of values. */ public static byte[] byteArray(final int... bytes) { if ((bytes == null) || (bytes.length == 0)) { return NO_BYTES; } final byte[] byteArray = new byte[bytes.length]; for (int i=0; i < bytes.length; i++) { byteArray[i] = (byte) (bytes[i] & 0xFF); } return byteArray; } /** * Indicates whether the unit tests are currently running in this JVM. * * @return {@code true} if the unit tests are currently running, or * {@code false} if not. */ public static boolean isWithinUnitTest() { return IS_WITHIN_UNIT_TESTS; } /** * Throws an {@code Error} or a {@code RuntimeException} based on the provided * {@code Throwable} object. This method will always throw something, * regardless of the provided {@code Throwable} object. * * @param throwable The {@code Throwable} object to use to create the * exception to throw. * * @throws Error If the provided {@code Throwable} object is an * {@code Error} instance, then that {@code Error} instance * will be re-thrown. * * @throws RuntimeException If the provided {@code Throwable} object is a * {@code RuntimeException} instance, then that * {@code RuntimeException} instance will be * re-thrown. Otherwise, it must be a checked * exception and that checked exception will be * re-thrown as a {@code RuntimeException}. */ public static void throwErrorOrRuntimeException(final Throwable throwable) throws Error, RuntimeException { Validator.ensureNotNull(throwable); if (throwable instanceof Error) { throw (Error) throwable; } else if (throwable instanceof RuntimeException) { throw (RuntimeException) throwable; } else { throw new RuntimeException(throwable); } } /** * Re-throws the provided {@code Throwable} instance only if it is an * {@code Error} or a {@code RuntimeException} instance; otherwise, this * method will return without taking any action. * * @param throwable The {@code Throwable} object to examine and potentially * re-throw. * * @throws Error If the provided {@code Throwable} object is an * {@code Error} instance, then that {@code Error} instance * will be re-thrown. * * @throws RuntimeException If the provided {@code Throwable} object is a * {@code RuntimeException} instance, then that * {@code RuntimeException} instance will be * re-thrown. */ public static void rethrowIfErrorOrRuntimeException(final Throwable throwable) throws Error, RuntimeException { if (throwable instanceof Error) { throw (Error) throwable; } else if (throwable instanceof RuntimeException) { throw (RuntimeException) throwable; } } /** * Re-throws the provided {@code Throwable} instance only if it is an * {@code Error}; otherwise, this method will return without taking any * action. * * @param throwable The {@code Throwable} object to examine and potentially * re-throw. * * @throws Error If the provided {@code Throwable} object is an * {@code Error} instance, then that {@code Error} instance * will be re-thrown. */ public static void rethrowIfError(final Throwable throwable) throws Error { if (throwable instanceof Error) { throw (Error) throwable; } } /** * Computes the capacity that should be used for a map or a set with the * expected number of elements, which can help avoid the need to re-hash or * re-balance the map if too many items are added. This method bases its * computation on the default map load factor of 0.75. * * @param expectedItemCount The expected maximum number of items that will * be placed in the map or set. It must be greater * than or equal to zero. * * @return The capacity that should be used for a map or a set with the * expected number of elements */ public static int computeMapCapacity(final int expectedItemCount) { switch (expectedItemCount) { case 0: return 0; case 1: return 2; case 2: return 3; case 3: return 5; case 4: return 6; case 5: return 7; case 6: return 9; case 7: return 10; case 8: return 11; case 9: return 13; case 10: return 14; case 11: return 15; case 12: return 17; case 13: return 18; case 14: return 19; case 15: return 21; case 16: return 22; case 17: return 23; case 18: return 25; case 19: return 26; case 20: return 27; case 30: return 41; case 40: return 54; case 50: return 67; case 60: return 81; case 70: return 94; case 80: return 107; case 90: return 121; case 100: return 134; case 110: return 147; case 120: return 161; case 130: return 174; case 140: return 187; case 150: return 201; case 160: return 214; case 170: return 227; case 180: return 241; case 190: return 254; case 200: return 267; default: Validator.ensureTrue((expectedItemCount >= 0), "StaticUtils.computeMapOrSetCapacity.expectedItemCount must be " + "greater than or equal to zero."); // NOTE: 536,870,911 is Integer.MAX_VALUE/4. If the value is larger // than that, then we'll fall back to using floating-point arithmetic // if (expectedItemCount > 536_870_911) { final int computedCapacity = ((int) (expectedItemCount / 0.75)) + 1; if (computedCapacity <= expectedItemCount) { // This suggests that the expected number of items is so big that // the computed capacity can't be adequately represented by an // integer. In that case, we'll just return the expected item // count and let the map or set get re-hashed/re-balanced if it // actually gets anywhere near that size. return expectedItemCount; } else { return computedCapacity; } } else { return ((expectedItemCount * 4) / 3) + 1; } } } /** * Creates an unmodifiable set containing the provided items. The iteration * order of the provided items will be preserved. * * @param The type of item to include in the set. * @param items The items to include in the set. It must not be * {@code null}, but may be empty. * * @return An unmodifiable set containing the provided items. */ @SafeVarargs() @SuppressWarnings("varargs") public static Set setOf(final T... items) { return Collections.unmodifiableSet( new LinkedHashSet<>(Arrays.asList(items))); } /** * Creates a {@code HashSet} containing the provided items. * * @param The type of item to include in the set. * @param items The items to include in the set. It must not be * {@code null}, but may be empty. * * @return A {@code HashSet} containing the provided items. */ @SafeVarargs() @SuppressWarnings("varargs") public static HashSet hashSetOf(final T... items) { return new HashSet<>(Arrays.asList(items)); } /** * Creates a {@code LinkedHashSet} containing the provided items. * * @param The type of item to include in the set. * @param items The items to include in the set. It must not be * {@code null}, but may be empty. * * @return A {@code LinkedHashSet} containing the provided items. */ @SafeVarargs() @SuppressWarnings("varargs") public static LinkedHashSet linkedHashSetOf(final T... items) { return new LinkedHashSet<>(Arrays.asList(items)); } /** * Creates a {@code TreeSet} containing the provided items. * * @param The type of item to include in the set. * @param items The items to include in the set. It must not be * {@code null}, but may be empty. * * @return A {@code LinkedHashSet} containing the provided items. */ @SafeVarargs() @SuppressWarnings("varargs") public static TreeSet treeSetOf(final T... items) { return new TreeSet<>(Arrays.asList(items)); } /** * Creates an unmodifiable map containing the provided items. * * @param The type for the map keys. * @param The type for the map values. * @param key The only key to include in the map. * @param value The only value to include in the map. * * @return The unmodifiable map that was created. */ public static Map mapOf(final K key, final V value) { return Collections.singletonMap(key, value); } /** * Creates an unmodifiable map containing the provided items. * * @param The type for the map keys. * @param The type for the map values. * @param key1 The first key to include in the map. * @param value1 The first value to include in the map. * @param key2 The second key to include in the map. * @param value2 The second value to include in the map. * * @return The unmodifiable map that was created. */ public static Map mapOf(final K key1, final V value1, final K key2, final V value2) { final LinkedHashMap map = new LinkedHashMap<>(computeMapCapacity(2)); map.put(key1, value1); map.put(key2, value2); return Collections.unmodifiableMap(map); } /** * Creates an unmodifiable map containing the provided items. * * @param The type for the map keys. * @param The type for the map values. * @param key1 The first key to include in the map. * @param value1 The first value to include in the map. * @param key2 The second key to include in the map. * @param value2 The second value to include in the map. * @param key3 The third key to include in the map. * @param value3 The third value to include in the map. * * @return The unmodifiable map that was created. */ public static Map mapOf(final K key1, final V value1, final K key2, final V value2, final K key3, final V value3) { final LinkedHashMap map = new LinkedHashMap<>(computeMapCapacity(3)); map.put(key1, value1); map.put(key2, value2); map.put(key3, value3); return Collections.unmodifiableMap(map); } /** * Creates an unmodifiable map containing the provided items. * * @param The type for the map keys. * @param The type for the map values. * @param key1 The first key to include in the map. * @param value1 The first value to include in the map. * @param key2 The second key to include in the map. * @param value2 The second value to include in the map. * @param key3 The third key to include in the map. * @param value3 The third value to include in the map. * @param key4 The fourth key to include in the map. * @param value4 The fourth value to include in the map. * * @return The unmodifiable map that was created. */ public static Map mapOf(final K key1, final V value1, final K key2, final V value2, final K key3, final V value3, final K key4, final V value4) { final LinkedHashMap map = new LinkedHashMap<>(computeMapCapacity(4)); map.put(key1, value1); map.put(key2, value2); map.put(key3, value3); map.put(key4, value4); return Collections.unmodifiableMap(map); } /** * Creates an unmodifiable map containing the provided items. * * @param The type for the map keys. * @param The type for the map values. * @param key1 The first key to include in the map. * @param value1 The first value to include in the map. * @param key2 The second key to include in the map. * @param value2 The second value to include in the map. * @param key3 The third key to include in the map. * @param value3 The third value to include in the map. * @param key4 The fourth key to include in the map. * @param value4 The fourth value to include in the map. * @param key5 The fifth key to include in the map. * @param value5 The fifth value to include in the map. * * @return The unmodifiable map that was created. */ public static Map mapOf(final K key1, final V value1, final K key2, final V value2, final K key3, final V value3, final K key4, final V value4, final K key5, final V value5) { final LinkedHashMap map = new LinkedHashMap<>(computeMapCapacity(5)); map.put(key1, value1); map.put(key2, value2); map.put(key3, value3); map.put(key4, value4); map.put(key5, value5); return Collections.unmodifiableMap(map); } /** * Creates an unmodifiable map containing the provided items. * * @param The type for the map keys. * @param The type for the map values. * @param key1 The first key to include in the map. * @param value1 The first value to include in the map. * @param key2 The second key to include in the map. * @param value2 The second value to include in the map. * @param key3 The third key to include in the map. * @param value3 The third value to include in the map. * @param key4 The fourth key to include in the map. * @param value4 The fourth value to include in the map. * @param key5 The fifth key to include in the map. * @param value5 The fifth value to include in the map. * @param key6 The sixth key to include in the map. * @param value6 The sixth value to include in the map. * * @return The unmodifiable map that was created. */ public static Map mapOf(final K key1, final V value1, final K key2, final V value2, final K key3, final V value3, final K key4, final V value4, final K key5, final V value5, final K key6, final V value6) { final LinkedHashMap map = new LinkedHashMap<>(computeMapCapacity(6)); map.put(key1, value1); map.put(key2, value2); map.put(key3, value3); map.put(key4, value4); map.put(key5, value5); map.put(key6, value6); return Collections.unmodifiableMap(map); } /** * Creates an unmodifiable map containing the provided items. * * @param The type for the map keys. * @param The type for the map values. * @param key1 The first key to include in the map. * @param value1 The first value to include in the map. * @param key2 The second key to include in the map. * @param value2 The second value to include in the map. * @param key3 The third key to include in the map. * @param value3 The third value to include in the map. * @param key4 The fourth key to include in the map. * @param value4 The fourth value to include in the map. * @param key5 The fifth key to include in the map. * @param value5 The fifth value to include in the map. * @param key6 The sixth key to include in the map. * @param value6 The sixth value to include in the map. * @param key7 The seventh key to include in the map. * @param value7 The seventh value to include in the map. * * @return The unmodifiable map that was created. */ public static Map mapOf(final K key1, final V value1, final K key2, final V value2, final K key3, final V value3, final K key4, final V value4, final K key5, final V value5, final K key6, final V value6, final K key7, final V value7) { final LinkedHashMap map = new LinkedHashMap<>(computeMapCapacity(7)); map.put(key1, value1); map.put(key2, value2); map.put(key3, value3); map.put(key4, value4); map.put(key5, value5); map.put(key6, value6); map.put(key7, value7); return Collections.unmodifiableMap(map); } /** * Creates an unmodifiable map containing the provided items. * * @param The type for the map keys. * @param The type for the map values. * @param key1 The first key to include in the map. * @param value1 The first value to include in the map. * @param key2 The second key to include in the map. * @param value2 The second value to include in the map. * @param key3 The third key to include in the map. * @param value3 The third value to include in the map. * @param key4 The fourth key to include in the map. * @param value4 The fourth value to include in the map. * @param key5 The fifth key to include in the map. * @param value5 The fifth value to include in the map. * @param key6 The sixth key to include in the map. * @param value6 The sixth value to include in the map. * @param key7 The seventh key to include in the map. * @param value7 The seventh value to include in the map. * @param key8 The eighth key to include in the map. * @param value8 The eighth value to include in the map. * * @return The unmodifiable map that was created. */ public static Map mapOf(final K key1, final V value1, final K key2, final V value2, final K key3, final V value3, final K key4, final V value4, final K key5, final V value5, final K key6, final V value6, final K key7, final V value7, final K key8, final V value8) { final LinkedHashMap map = new LinkedHashMap<>(computeMapCapacity(8)); map.put(key1, value1); map.put(key2, value2); map.put(key3, value3); map.put(key4, value4); map.put(key5, value5); map.put(key6, value6); map.put(key7, value7); map.put(key8, value8); return Collections.unmodifiableMap(map); } /** * Creates an unmodifiable map containing the provided items. * * @param The type for the map keys. * @param The type for the map values. * @param key1 The first key to include in the map. * @param value1 The first value to include in the map. * @param key2 The second key to include in the map. * @param value2 The second value to include in the map. * @param key3 The third key to include in the map. * @param value3 The third value to include in the map. * @param key4 The fourth key to include in the map. * @param value4 The fourth value to include in the map. * @param key5 The fifth key to include in the map. * @param value5 The fifth value to include in the map. * @param key6 The sixth key to include in the map. * @param value6 The sixth value to include in the map. * @param key7 The seventh key to include in the map. * @param value7 The seventh value to include in the map. * @param key8 The eighth key to include in the map. * @param value8 The eighth value to include in the map. * @param key9 The ninth key to include in the map. * @param value9 The ninth value to include in the map. * * @return The unmodifiable map that was created. */ public static Map mapOf(final K key1, final V value1, final K key2, final V value2, final K key3, final V value3, final K key4, final V value4, final K key5, final V value5, final K key6, final V value6, final K key7, final V value7, final K key8, final V value8, final K key9, final V value9) { final LinkedHashMap map = new LinkedHashMap<>(computeMapCapacity(9)); map.put(key1, value1); map.put(key2, value2); map.put(key3, value3); map.put(key4, value4); map.put(key5, value5); map.put(key6, value6); map.put(key7, value7); map.put(key8, value8); map.put(key9, value9); return Collections.unmodifiableMap(map); } /** * Creates an unmodifiable map containing the provided items. * * @param The type for the map keys. * @param The type for the map values. * @param key1 The first key to include in the map. * @param value1 The first value to include in the map. * @param key2 The second key to include in the map. * @param value2 The second value to include in the map. * @param key3 The third key to include in the map. * @param value3 The third value to include in the map. * @param key4 The fourth key to include in the map. * @param value4 The fourth value to include in the map. * @param key5 The fifth key to include in the map. * @param value5 The fifth value to include in the map. * @param key6 The sixth key to include in the map. * @param value6 The sixth value to include in the map. * @param key7 The seventh key to include in the map. * @param value7 The seventh value to include in the map. * @param key8 The eighth key to include in the map. * @param value8 The eighth value to include in the map. * @param key9 The ninth key to include in the map. * @param value9 The ninth value to include in the map. * @param key10 The tenth key to include in the map. * @param value10 The tenth value to include in the map. * * @return The unmodifiable map that was created. */ public static Map mapOf(final K key1, final V value1, final K key2, final V value2, final K key3, final V value3, final K key4, final V value4, final K key5, final V value5, final K key6, final V value6, final K key7, final V value7, final K key8, final V value8, final K key9, final V value9, final K key10, final V value10) { final LinkedHashMap map = new LinkedHashMap<>(computeMapCapacity(10)); map.put(key1, value1); map.put(key2, value2); map.put(key3, value3); map.put(key4, value4); map.put(key5, value5); map.put(key6, value6); map.put(key7, value7); map.put(key8, value8); map.put(key9, value9); map.put(key10, value10); return Collections.unmodifiableMap(map); } /** * Creates an unmodifiable map containing the provided items. The map entries * must have the same data type for keys and values. * * @param The type for the map keys and values. * @param items The items to include in the map. If it is null or empty, * the map will be empty. If it is non-empty, then the number * of elements in the array must be a multiple of two. * Elements in even-numbered indexes will be the keys for the * map entries, while elements in odd-numbered indexes will be * the map values. * * @return The unmodifiable map that was created. */ @SafeVarargs() public static Map mapOf(final T... items) { if ((items == null) || (items.length == 0)) { return Collections.emptyMap(); } Validator.ensureTrue(((items.length % 2) == 0), "StaticUtils.mapOf.items must have an even number of elements"); final int numEntries = items.length / 2; final LinkedHashMap map = new LinkedHashMap<>(computeMapCapacity(numEntries)); for (int i=0; i < items.length; ) { map.put(items[i++], items[i++]); } return Collections.unmodifiableMap(map); } /** * Creates an unmodifiable map containing the provided items. * * @param The type for the map keys. * @param The type for the map values. * @param items The items to include in the map. * * @return The unmodifiable map that was created. */ @SafeVarargs() public static Map mapOfObjectPairs(final ObjectPair... items) { if ((items == null) || (items.length == 0)) { return Collections.emptyMap(); } final LinkedHashMap map = new LinkedHashMap<>( computeMapCapacity(items.length)); for (final ObjectPair item : items) { map.put(item.getFirst(), item.getSecond()); } return Collections.unmodifiableMap(map); } /** * Retrieves the set of currently defined system properties. If possible, * this will simply return the result of a call to * {@code System.getProperties}. However, the LDAP SDK is known to be used in * environments where a security manager prevents setting system properties, * and in that case, calls to {@code System.getProperties} will be rejected * with a {@code SecurityException} because the returned structure is mutable * and could be used to alter system property values. In such cases, a new * empty {@code Properties} object will be created, and may optionally be * populated with the values of a specific set of named properties. * * @param propertyNames An optional set of property names whose values (if * defined) should be included in the * {@code Properties} object that will be returned if a * security manager prevents retrieving the full set of * system properties. This may be {@code null} or * empty if no specific properties should be retrieved. * * @return The value returned by a call to {@code System.getProperties} if * possible, or a newly-created properties map (possibly including * the values of a specified set of system properties) if it is not * possible to get a mutable set of the system properties. */ public static Properties getSystemProperties(final String... propertyNames) { try { final Properties properties = System.getProperties(); final String forceThrowPropertyName = StaticUtils.class.getName() + ".forceGetSystemPropertiesToThrow"; // To ensure that we can get coverage for the code below in which there is // a restrictive security manager in place, look for a system property // that will cause us to throw an exception. final Object forceThrowPropertyValue = properties.getProperty(forceThrowPropertyName); if (forceThrowPropertyValue != null) { throw new SecurityException(forceThrowPropertyName + '=' + forceThrowPropertyValue); } return System.getProperties(); } catch (final SecurityException e) { Debug.debugException(e); } // If we have gotten here, then we can assume that a security manager // prevents us from accessing all system properties. Create a new proper final Properties properties = new Properties(); if (propertyNames != null) { for (final String propertyName : propertyNames) { final Object propertyValue = System.getProperty(propertyName); if (propertyValue != null) { properties.put(propertyName, propertyValue); } } } return properties; } /** * Retrieves the value of the specified system property. * * @param name The name of the system property for which to retrieve the * value. * * @return The value of the requested system property, or {@code null} if * that variable was not set or its value could not be retrieved * (for example, because a security manager prevents it). */ public static String getSystemProperty(final String name) { try { return System.getProperty(name); } catch (final Throwable t) { // It is possible that the call to System.getProperty could fail under // some security managers. In that case, simply swallow the error and // act as if that system property is not set. Debug.debugException(t); return null; } } /** * Retrieves the value of the specified system property. * * @param name The name of the system property for which to retrieve * the value. * @param defaultValue The default value to return if the specified * system property is not set or could not be * retrieved. * * @return The value of the requested system property, or the provided * default value if that system property was not set or its value * could not be retrieved (for example, because a security manager * prevents it). */ public static String getSystemProperty(final String name, final String defaultValue) { try { return System.getProperty(name, defaultValue); } catch (final Throwable t) { // It is possible that the call to System.getProperty could fail under // some security managers. In that case, simply swallow the error and // act as if that system property is not set. Debug.debugException(t); return defaultValue; } } /** * Attempts to set the value of the specified system property. Note that this * may not be permitted by some security managers, in which case the attempt * will have no effect. * * @param name The name of the System property to set. It must not be * {@code null}. * @param value The value to use for the system property. If it is * {@code null}, then the property will be cleared. * * @return The former value of the system property, or {@code null} if it * did not have a value or if it could not be set (for example, * because a security manager prevents it). */ public static String setSystemProperty(final String name, final String value) { try { if (value == null) { return System.clearProperty(name); } else { return System.setProperty(name, value); } } catch (final Throwable t) { // It is possible that the call to System.setProperty or // System.clearProperty could fail under some security managers. In that // case, simply swallow the error and act as if that system property is // not set. Debug.debugException(t); return null; } } /** * Attempts to clear the value of the specified system property. Note that * this may not be permitted by some security managers, in which case the * attempt will have no effect. * * @param name The name of the System property to clear. It must not be * {@code null}. * * @return The former value of the system property, or {@code null} if it * did not have a value or if it could not be set (for example, * because a security manager prevents it). */ public static String clearSystemProperty(final String name) { try { return System.clearProperty(name); } catch (final Throwable t) { // It is possible that the call to System.clearProperty could fail under // some security managers. In that case, simply swallow the error and // act as if that system property is not set. Debug.debugException(t); return null; } } /** * Retrieves a map of all environment variables defined in the JVM's process. * * @return A map of all environment variables defined in the JVM's process, * or an empty map if no environment variables are set or the actual * set could not be retrieved (for example, because a security * manager prevents it). */ public static Map getEnvironmentVariables() { try { return System.getenv(); } catch (final Throwable t) { // It is possible that the call to System.getenv could fail under some // security managers. In that case, simply swallow the error and pretend // that the environment variable is not set. Debug.debugException(t); return Collections.emptyMap(); } } /** * Retrieves the value of the specified environment variable. * * @param name The name of the environment variable for which to retrieve * the value. * * @return The value of the requested environment variable, or {@code null} * if that variable was not set or its value could not be retrieved * (for example, because a security manager prevents it). */ public static String getEnvironmentVariable(final String name) { try { return System.getenv(name); } catch (final Throwable t) { // It is possible that the call to System.getenv could fail under some // security managers. In that case, simply swallow the error and pretend // that the environment variable is not set. Debug.debugException(t); return null; } } /** * Attempts to set the desired log level for the specified logger. Note that * this may not be permitted by some security managers, in which case the * attempt will have no effect. * * @param logger The logger whose level should be updated. * @param logLevel The log level to set for the logger. */ public static void setLoggerLevel(final Logger logger, final Level logLevel) { try { logger.setLevel(logLevel); } catch (final Throwable t) { Debug.debugException(t); } } /** * Attempts to set the desired log level for the specified log handler. Note * that this may not be permitted by some security managers, in which case the * attempt will have no effect. * * @param logHandler The log handler whose level should be updated. * @param logLevel The log level to set for the log handler. */ public static void setLogHandlerLevel(final Handler logHandler, final Level logLevel) { try { logHandler.setLevel(logLevel); } catch (final Throwable t) { Debug.debugException(t); } } /** * Attempts to determine all addresses associated with the local system. * * @param nameResolver The name resolver to use to determine the local * host and loopback addresses. If this is * {@code null}, then the LDAP SDK's default name * resolver will be used. * * @return A set of the local addresses that were identified. */ public static Set getAllLocalAddresses( final NameResolver nameResolver) { final NameResolver resolver; if (nameResolver == null) { resolver = LDAPConnectionOptions.DEFAULT_NAME_RESOLVER; } else { resolver = nameResolver; } final LinkedHashSet localAddresses = new LinkedHashSet<>(computeMapCapacity(10)); try { localAddresses.add(resolver.getLocalHost()); } catch (final Exception e) { Debug.debugException(e); } try { final Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); while (networkInterfaces.hasMoreElements()) { final NetworkInterface networkInterface = networkInterfaces.nextElement(); final Enumeration interfaceAddresses = networkInterface.getInetAddresses(); while (interfaceAddresses.hasMoreElements()) { localAddresses.add(interfaceAddresses.nextElement()); } } } catch (final Exception e) { Debug.debugException(e); } try { localAddresses.add(resolver.getLoopbackAddress()); } catch (final Exception e) { Debug.debugException(e); } return Collections.unmodifiableSet(localAddresses); } /** * Retrieves the canonical host name for the provided address, if it can be * resolved to a name. * * @param nameResolver The name resolver to use to obtain the canonical * host name. If this is {@code null}, then the LDAP * SDK's default name resolver will be used. * @param address The {@code InetAddress} for which to attempt to * obtain the canonical host name. * * @return The canonical host name for the provided address, or {@code null} * if it cannot be obtained (either because the attempt returns * {@code null}, which shouldn't happen, or because it matches the * IP address). */ public static String getCanonicalHostNameIfAvailable( final NameResolver nameResolver, final InetAddress address) { final NameResolver resolver; if (nameResolver == null) { resolver = LDAPConnectionOptions.DEFAULT_NAME_RESOLVER; } else { resolver = nameResolver; } final String hostAddress = address.getHostAddress(); final String trimmedHostAddress = trimInterfaceNameFromHostAddress(hostAddress); final String canonicalHostName = resolver.getCanonicalHostName(address); if ((canonicalHostName == null) || canonicalHostName.equalsIgnoreCase(hostAddress) || canonicalHostName.equalsIgnoreCase(trimmedHostAddress)) { return null; } return canonicalHostName; } /** * Retrieves the canonical host names for the provided set of * {@code InetAddress} objects. If any of the provided addresses cannot be * resolved to a canonical host name (in which case the attempt to get the * canonical host name will return its IP address), it will be excluded from * the returned set. * * @param nameResolver The name resolver to use to obtain the canonical * host names. If this is {@code null}, then the LDAP * SDK's default name resolver will be used. * @param addresses The set of addresses for which to obtain the * canonical host names. * * @return A set of the canonical host names that could be obtained from the * provided addresses. */ public static Set getAvailableCanonicalHostNames( final NameResolver nameResolver, final Collection addresses) { final NameResolver resolver; if (nameResolver == null) { resolver = LDAPConnectionOptions.DEFAULT_NAME_RESOLVER; } else { resolver = nameResolver; } final Set canonicalHostNames = new LinkedHashSet<>(computeMapCapacity(addresses.size())); for (final InetAddress address : addresses) { final String canonicalHostName = getCanonicalHostNameIfAvailable(resolver, address); if (canonicalHostName != null) { canonicalHostNames.add(canonicalHostName); } } return Collections.unmodifiableSet(canonicalHostNames); } /** * Retrieves a version of the provided host address with the interface name * stripped off. Java sometimes follows an IP address with a percent sign and * the interface name. If that interface name is present in the provided * host address, then this method will trim it off, leaving just the IP * address. If the provided host address does not include the interface name, * then the provided address will be returned as-is. * * @param hostAddress The host address to be trimmed. * * @return The provided host address without the interface name. */ public static String trimInterfaceNameFromHostAddress( final String hostAddress) { final int percentPos = hostAddress.indexOf('%'); if (percentPos > 0) { return hostAddress.substring(0, percentPos); } else { return hostAddress; } } /** * Retrieves the value of the specified environment variable. * * @param name The name of the environment variable for which to * retrieve the value. * @param defaultValue The default value to use if the specified environment * variable is not set. It may be {@code null} if no * default should be used. * * @return The value of the requested environment variable, or {@code null} * if that variable was not set or its value could not be retrieved * (for example, because a security manager prevents it) and there * is no default value. */ public static String getEnvironmentVariable(final String name, final String defaultValue) { final String value = getEnvironmentVariable(name); if (value == null) { return defaultValue; } else { return value; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy