Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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:
*
*
*
* 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 extends CharSequence> 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;
}
}
}