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