/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2010 Sun Microsystems, Inc. * Portions Copyright 2011-2015 ForgeRock AS */ package org.opends.server.util; import static org.opends.messages.UtilityMessages.*; import static org.opends.server.util.ServerConstants.*; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; import javax.naming.InitialContext; import javax.naming.NamingException; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.LocalizableMessageBuilder; import org.forgerock.i18n.LocalizableMessageDescriptor; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.opendj.ldap.ByteSequence; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.util.Reject; import org.opends.messages.ToolMessages; import org.opends.server.api.ClientConnection; import org.opends.server.core.DirectoryServer; import org.opends.server.core.ServerContext; import org.opends.server.types.*; import com.forgerock.opendj.cli.Argument; import com.forgerock.opendj.cli.ArgumentException; /** * This class defines a number of static utility methods that may be used * throughout the server. Note that because of the frequency with which these * methods are expected to be used, very little debug logging will be performed * to prevent the log from filling up with unimportant calls and to reduce the * impact that debugging may have on performance. */ @org.opends.server.types.PublicAPI( stability=org.opends.server.types.StabilityLevel.UNCOMMITTED, mayInstantiate=false, mayExtend=false, mayInvoke=true) public final class StaticUtils { private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); /** The number of bytes of a Java int. A Java int is 32 bits, i.e. 4 bytes. */ public static final int INT_SIZE = 4; /** The number of bytes of a Java long. A Java int is 64 bits, i.e. 8 bytes. */ public static final int LONG_SIZE = 8; /** * Number of bytes in a Kibibyte. *
* Example usage: *
* int _10KB = 10 * KB; **/ public static final int KB = 1024; /** * Number of bytes in a Mebibyte. *
* Example usage: *
* int _10MB = 10 * MB; **/ public static final int MB = KB * KB; private static final String[][] BYTE_HEX_STRINGS = new String[2][256]; private static final String[] BYTE_BIN_STRINGS = new String[256]; private static final int UPPER_CASE = 0; private static final int LOWER_CASE = 1; /** Private constructor to prevent instantiation. */ private StaticUtils() { // No implementation required. } static { String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"}; for (int i = 0; i < 256; i++) { BYTE_HEX_STRINGS[UPPER_CASE][i] = hexDigits[i >>> 4] + hexDigits[i & 0xF]; BYTE_HEX_STRINGS[LOWER_CASE][i] = BYTE_HEX_STRINGS[UPPER_CASE][i].toLowerCase(); StringBuilder sb = new StringBuilder(); for (int j = 7; j >= 0; j--) { sb.append((i & (1 << j)) == 0 ? "0" : "1"); } BYTE_BIN_STRINGS[i] = sb.toString(); } } /** * Construct a byte array containing the UTF-8 encoding of the * provided string. This is significantly faster * than calling {@link String#getBytes(String)} for ASCII strings. * * @param s * The string to convert to a UTF-8 byte array. * @return Returns a byte array containing the UTF-8 encoding of the * provided string. */ public static byte[] getBytes(String s) { if (s == null) { return null; } try { char c; int length = s.length(); byte[] returnArray = new byte[length]; for (int i=0; i < length; i++) { c = s.charAt(i); returnArray[i] = (byte) (c & 0x0000007F); if (c != returnArray[i]) { return s.getBytes("UTF-8"); } } return returnArray; } catch (Exception e) { logger.traceException(e); try { return s.getBytes("UTF-8"); } catch (Exception e2) { logger.traceException(e2); return s.getBytes(); } } } /** * Returns the provided byte array decoded as a UTF-8 string without throwing * an UnsupportedEncodingException. This method is equivalent to: * *
* try
* {
* return new String(bytes, "UTF-8");
* }
* catch (UnsupportedEncodingException e)
* {
* // Should never happen: UTF-8 is always supported.
* throw new RuntimeException(e);
* }
*
*
* @param bytes
* The byte array to be decoded as a UTF-8 string.
* @return The decoded string.
*/
public static String decodeUTF8(final byte[] bytes)
{
Reject.ifNull(bytes);
if (bytes.length == 0)
{
return "".intern();
}
final StringBuilder builder = new StringBuilder(bytes.length);
final int sz = bytes.length;
for (int i = 0; i < sz; i++)
{
final byte b = bytes[i];
if ((b & 0x7f) != b)
{
try
{
builder.append(new String(bytes, i, (sz - i), "UTF-8"));
}
catch (UnsupportedEncodingException e)
{
// Should never happen: UTF-8 is always supported.
throw new RuntimeException(e);
}
break;
}
builder.append((char) b);
}
return builder.toString();
}
/**
* Construct a byte array containing the UTF-8 encoding of the
* provided char array.
*
* @param chars
* The character array to convert to a UTF-8 byte array.
* @return Returns a byte array containing the UTF-8 encoding of the
* provided char array.
*/
public static byte[] getBytes(char[] chars)
{
return getBytes(new String(chars));
}
/**
* Retrieves a string representation of the provided byte in hexadecimal.
*
* @param b The byte for which to retrieve the hexadecimal string
* representation.
*
* @return The string representation of the provided byte in hexadecimal.
*/
public static String byteToHex(byte b)
{
return BYTE_HEX_STRINGS[UPPER_CASE][b & 0xFF];
}
/**
* Retrieves a string representation of the provided byte in hexadecimal.
*
* @param b The byte for which to retrieve the hexadecimal string
* representation.
*
* @return The string representation of the provided byte in hexadecimal
* using lowercase characters.
*/
public static String byteToLowerHex(byte b)
{
return BYTE_HEX_STRINGS[LOWER_CASE][b & 0xFF];
}
/**
* Retrieves the printable ASCII representation of the provided byte.
*
* @param b The byte for which to retrieve the printable ASCII
* representation.
*
* @return The printable ASCII representation of the provided byte, or a
* space if the provided byte does not have printable ASCII
* representation.
*/
public static char byteToASCII(byte b)
{
if (32 <= b && b <= 126)
{
return (char) b;
}
return ' ';
}
/**
* Retrieves a string representation of the contents of the provided byte
* array using hexadecimal characters with no space between each byte.
*
* @param b The byte array containing the data.
*
* @return A string representation of the contents of the provided byte
* array using hexadecimal characters.
*/
public static String bytesToHexNoSpace(byte[] b)
{
if (b == null || b.length == 0)
{
return "";
}
int arrayLength = b.length;
StringBuilder buffer = new StringBuilder(arrayLength * 2);
for (int i=0; i < arrayLength; i++)
{
buffer.append(byteToHex(b[i]));
}
return buffer.toString();
}
/**
* Retrieves a string representation of the contents of the provided byte
* array using hexadecimal characters and a space between each byte.
*
* @param b The byte array containing the data.
* @return A string representation of the contents of the provided byte
* array using hexadecimal characters.
*/
public static String bytesToHex(byte[] b)
{
if (b == null || b.length == 0)
{
return "";
}
int arrayLength = b.length;
StringBuilder buffer = new StringBuilder((arrayLength - 1) * 3 + 2);
buffer.append(byteToHex(b[0]));
for (int i=1; i < arrayLength; i++)
{
buffer.append(" ");
buffer.append(byteToHex(b[i]));
}
return buffer.toString();
}
/**
* Retrieves a string representation of the contents of the provided byte
* sequence using hexadecimal characters and a space between each byte.
*
* @param b The byte sequence containing the data.
* @return A string representation of the contents of the provided byte
* sequence using hexadecimal characters.
*/
public static String bytesToHex(ByteSequence b)
{
if (b == null || b.length() == 0)
{
return "";
}
int arrayLength = b.length();
StringBuilder buffer = new StringBuilder((arrayLength - 1) * 3 + 2);
buffer.append(byteToHex(b.byteAt(0)));
for (int i=1; i < arrayLength; i++)
{
buffer.append(" ");
buffer.append(byteToHex(b.byteAt(i)));
}
return buffer.toString();
}
/**
* Retrieves a string representation of the contents of the provided byte
* array using hexadecimal characters and a colon between each byte.
*
* @param b The byte array containing the data.
*
* @return A string representation of the contents of the provided byte
* array using hexadecimal characters.
*/
public static String bytesToColonDelimitedHex(byte[] b)
{
if (b == null || b.length == 0)
{
return "";
}
int arrayLength = b.length;
StringBuilder buffer = new StringBuilder((arrayLength - 1) * 3 + 2);
buffer.append(byteToHex(b[0]));
for (int i=1; i < arrayLength; i++)
{
buffer.append(":");
buffer.append(byteToHex(b[i]));
}
return buffer.toString();
}
/**
* Retrieves a string representation of the contents of the provided byte
* buffer using hexadecimal characters and a space between each byte.
*
* @param b The byte buffer containing the data.
*
* @return A string representation of the contents of the provided byte
* buffer using hexadecimal characters.
*/
public static String bytesToHex(ByteBuffer b)
{
if (b == null)
{
return "";
}
int position = b.position();
int limit = b.limit();
int length = limit - position;
if (length == 0)
{
return "";
}
StringBuilder buffer = new StringBuilder((length - 1) * 3 + 2);
buffer.append(byteToHex(b.get()));
for (int i=1; i < length; i++)
{
buffer.append(" ");
buffer.append(byteToHex(b.get()));
}
b.position(position);
b.limit(limit);
return buffer.toString();
}
/**
* Appends a string representation of the provided byte array to the given
* buffer using the specified indent. The data will be formatted with sixteen
* hex bytes in a row followed by the ASCII representation, then wrapping to a
* new line as necessary.
*
* @param buffer The buffer to which the information is to be appended.
* @param b The byte array containing the data to write.
* @param indent The number of spaces to indent the output.
*/
public static void byteArrayToHexPlusAscii(StringBuilder buffer, byte[] b,
int indent)
{
StringBuilder indentBuf = new StringBuilder(indent);
for (int i=0 ; i < indent; i++)
{
indentBuf.append(' ');
}
int length = b.length;
int pos = 0;
while (length - pos >= 16)
{
StringBuilder asciiBuf = new StringBuilder(17);
buffer.append(indentBuf);
buffer.append(byteToHex(b[pos]));
asciiBuf.append(byteToASCII(b[pos]));
pos++;
for (int i=1; i < 16; i++, pos++)
{
buffer.append(' ');
buffer.append(byteToHex(b[pos]));
asciiBuf.append(byteToASCII(b[pos]));
if (i == 7)
{
buffer.append(" ");
asciiBuf.append(' ');
}
}
buffer.append(" ");
buffer.append(asciiBuf);
buffer.append(EOL);
}
int remaining = length - pos;
if (remaining > 0)
{
StringBuilder asciiBuf = new StringBuilder(remaining+1);
buffer.append(indentBuf);
buffer.append(byteToHex(b[pos]));
asciiBuf.append(byteToASCII(b[pos]));
pos++;
for (int i=1; i < 16; i++)
{
buffer.append(' ');
if (i < remaining)
{
buffer.append(byteToHex(b[pos]));
asciiBuf.append(byteToASCII(b[pos]));
pos++;
}
else
{
buffer.append(" ");
}
if (i == 7)
{
buffer.append(" ");
if (i < remaining)
{
asciiBuf.append(' ');
}
}
}
buffer.append(" ");
buffer.append(asciiBuf);
buffer.append(EOL);
}
}
/**
* Appends a string representation of the remaining unread data in the
* provided byte buffer to the given buffer using the specified indent.
* The data will be formatted with sixteen hex bytes in a row followed by
* the ASCII representation, then wrapping to a new line as necessary.
* The state of the byte buffer is not changed.
*
* @param buffer The buffer to which the information is to be appended.
* @param b The byte buffer containing the data to write.
* The data from the position to the limit is written.
* @param indent The number of spaces to indent the output.
*/
public static void byteArrayToHexPlusAscii(StringBuilder buffer, ByteBuffer b,
int indent)
{
StringBuilder indentBuf = new StringBuilder(indent);
for (int i=0 ; i < indent; i++)
{
indentBuf.append(' ');
}
int position = b.position();
int limit = b.limit();
int length = limit - position;
int pos = 0;
while (length - pos >= 16)
{
StringBuilder asciiBuf = new StringBuilder(17);
byte currentByte = b.get();
buffer.append(indentBuf);
buffer.append(byteToHex(currentByte));
asciiBuf.append(byteToASCII(currentByte));
pos++;
for (int i=1; i < 16; i++, pos++)
{
currentByte = b.get();
buffer.append(' ');
buffer.append(byteToHex(currentByte));
asciiBuf.append(byteToASCII(currentByte));
if (i == 7)
{
buffer.append(" ");
asciiBuf.append(' ');
}
}
buffer.append(" ");
buffer.append(asciiBuf);
buffer.append(EOL);
}
int remaining = length - pos;
if (remaining > 0)
{
StringBuilder asciiBuf = new StringBuilder(remaining+1);
byte currentByte = b.get();
buffer.append(indentBuf);
buffer.append(byteToHex(currentByte));
asciiBuf.append(byteToASCII(currentByte));
for (int i=1; i < 16; i++)
{
buffer.append(' ');
if (i < remaining)
{
currentByte = b.get();
buffer.append(byteToHex(currentByte));
asciiBuf.append(byteToASCII(currentByte));
}
else
{
buffer.append(" ");
}
if (i == 7)
{
buffer.append(" ");
if (i < remaining)
{
asciiBuf.append(' ');
}
}
}
buffer.append(" ");
buffer.append(asciiBuf);
buffer.append(EOL);
}
b.position(position);
b.limit(limit);
}
/**
* Retrieves a binary representation of the provided byte. It will always be
* a sequence of eight zeros and/or ones.
*
* @param b The byte for which to retrieve the binary representation.
*
* @return The binary representation for the provided byte.
*/
public static String byteToBinary(byte b)
{
return BYTE_BIN_STRINGS[b & 0xFF];
}
/**
* Compare two byte arrays for order. Returns a negative integer,
* zero, or a positive integer as the first argument is less than,
* equal to, or greater than the second.
*
* @param a
* The first byte array to be compared.
* @param a2
* The second byte array to be compared.
* @return Returns a negative integer, zero, or a positive integer
* if the first byte array is less than, equal to, or greater
* than the second.
*/
public static int compare(byte[] a, byte[] a2) {
if (a == a2) {
return 0;
}
if (a == null) {
return -1;
}
if (a2 == null) {
return 1;
}
int minLength = Math.min(a.length, a2.length);
for (int i = 0; i < minLength; i++) {
int firstByte = 0xFF & a[i];
int secondByte = 0xFF & a2[i];
if (firstByte != secondByte) {
if (firstByte < secondByte) {
return -1;
} else if (firstByte > secondByte) {
return 1;
}
}
}
return a.length - a2.length;
}
/**
* Indicates whether the two array lists are equal. They will be
* considered equal if they have the same number of elements, and
* the corresponding elements between them are equal (in the same
* order).
*
* @param list1
* The first list for which to make the determination.
* @param list2
* The second list for which to make the determination.
* @return true if the two array lists are equal, or
* false if they are not.
*/
public static boolean listsAreEqual(List> list1, List> list2)
{
if (list1 == null)
{
return list2 == null;
}
else if (list2 == null)
{
return false;
}
int numElements = list1.size();
if (numElements != list2.size())
{
return false;
}
// If either of the lists doesn't support random access, then fall back
// on their equals methods and go ahead and create some garbage with the
// iterators.
if (!(list1 instanceof RandomAccess) ||
!(list2 instanceof RandomAccess))
{
return list1.equals(list2);
}
// Otherwise we can just retrieve the elements efficiently via their index.
for (int i=0; i < numElements; i++)
{
Object o1 = list1.get(i);
Object o2 = list2.get(i);
if (o1 == null)
{
if (o2 != null)
{
return false;
}
}
else if (! o1.equals(o2))
{
return false;
}
}
return true;
}
/**
* Retrieves the best human-readable message for the provided exception. For
* exceptions defined in the OpenDJ project, it will attempt to use the
* message (combining it with the message ID if available). For some
* exceptions that use encapsulation (e.g., InvocationTargetException), it
* will be unwrapped and the cause will be treated. For all others, the
*
*
* @param t The {@code Throwable} object for which to retrieve the message.
*
* @return The human-readable message generated for the provided exception.
*/
public static LocalizableMessage getExceptionMessage(Throwable t)
{
if (t instanceof IdentifiedException)
{
IdentifiedException ie = (IdentifiedException) t;
StringBuilder message = new StringBuilder();
message.append(ie.getMessage());
message.append(" (id=");
LocalizableMessage ieMsg = ie.getMessageObject();
if (ieMsg != null) {
message.append(ieMsg.resourceName()).append("-").append(ieMsg.ordinal());
} else {
message.append("-1");
}
message.append(")");
return LocalizableMessage.raw(message.toString());
}
else if (t instanceof NullPointerException)
{
LocalizableMessageBuilder message = new LocalizableMessageBuilder();
message.append("NullPointerException(");
StackTraceElement[] stackElements = t.getStackTrace();
if (stackElements.length > 0)
{
message.append(stackElements[0].getFileName());
message.append(":");
message.append(stackElements[0].getLineNumber());
}
message.append(")");
return message.toMessage();
}
else if (t instanceof InvocationTargetException && t.getCause() != null)
{
return getExceptionMessage(t.getCause());
}
else
{
StringBuilder message = new StringBuilder();
String className = t.getClass().getName();
int periodPos = className.lastIndexOf('.');
if (periodPos > 0)
{
message.append(className.substring(periodPos+1));
}
else
{
message.append(className);
}
message.append("(");
if (t.getMessage() == null)
{
StackTraceElement[] stackElements = t.getStackTrace();
if (stackElements.length > 0)
{
message.append(stackElements[0].getFileName());
message.append(":");
message.append(stackElements[0].getLineNumber());
// FIXME Temporary to debug issue 2256.
if (t instanceof IllegalStateException)
{
for (int i = 1; i < stackElements.length; i++)
{
message.append(' ');
message.append(stackElements[i].getFileName());
message.append(":");
message.append(stackElements[i].getLineNumber());
}
}
}
}
else
{
message.append(t.getMessage());
}
message.append(")");
return LocalizableMessage.raw(message.toString());
}
}
/**
* Retrieves a stack trace from the provided exception as a single-line
* string.
*
* @param t The exception for which to retrieve the stack trace.
*
* @return A stack trace from the provided exception as a single-line string.
*/
public static String stackTraceToSingleLineString(Throwable t)
{
return com.forgerock.opendj.util.StaticUtils.stackTraceToSingleLineString(
t, DynamicConstants.DEBUG_BUILD);
}
/**
* Appends a single-line string representation of the provided exception to
* the given buffer.
*
* @param buffer The buffer to which the information is to be appended.
* @param t The exception for which to retrieve the stack trace.
*/
public static void stackTraceToSingleLineString(StringBuilder buffer,
Throwable t)
{
com.forgerock.opendj.util.StaticUtils.stackTraceToSingleLineString(
buffer, t, DynamicConstants.DEBUG_BUILD);
}
/**
* Retrieves a string representation of the stack trace for the provided
* exception.
*
* @param t The exception for which to retrieve the stack trace.
*
* @return A string representation of the stack trace for the provided
* exception.
*/
public static String stackTraceToString(Throwable t)
{
StringBuilder buffer = new StringBuilder();
stackTraceToString(buffer, t);
return buffer.toString();
}
/**
* Check if the stack trace of provided exception contains a given cause.
*
* @param throwable
* exception that may contain the cause
* @param searchedCause
* class of the cause to look for. Any subclass will match.
* @return true if and only if the given cause is found as a cause of any
* level in the provided exception.
*/
public static boolean stackTraceContainsCause(
Throwable throwable, Class extends Throwable> searchedCause)
{
Throwable t = throwable;
while ((t = t.getCause()) != null)
{
if (searchedCause.isAssignableFrom(t.getClass()))
{
return true;
}
}
return false;
}
/**
* Appends a string representation of the stack trace for the provided
* exception to the given buffer.
*
* @param buffer The buffer to which the information is to be appended.
* @param t The exception for which to retrieve the stack trace.
*/
public static void stackTraceToString(StringBuilder buffer, Throwable t)
{
if (t == null)
{
return;
}
buffer.append(t);
for (StackTraceElement e : t.getStackTrace())
{
buffer.append(EOL);
buffer.append(" ");
buffer.append(e.getClassName());
buffer.append(".");
buffer.append(e.getMethodName());
buffer.append("(");
buffer.append(e.getFileName());
buffer.append(":");
buffer.append(e.getLineNumber());
buffer.append(")");
}
while (t.getCause() != null)
{
t = t.getCause();
buffer.append(EOL);
buffer.append("Caused by ");
buffer.append(t);
for (StackTraceElement e : t.getStackTrace())
{
buffer.append(EOL);
buffer.append(" ");
buffer.append(e.getClassName());
buffer.append(".");
buffer.append(e.getMethodName());
buffer.append("(");
buffer.append(e.getFileName());
buffer.append(":");
buffer.append(e.getLineNumber());
buffer.append(")");
}
}
buffer.append(EOL);
}
/**
* Retrieves a backtrace for the current thread consisting only of filenames
* and line numbers that may be useful in debugging the origin of problems
* that should not have happened. Note that this may be an expensive
* operation to perform, so it should only be used for error conditions or
* debugging.
*
* @return A backtrace for the current thread.
*/
public static String getBacktrace()
{
StringBuilder buffer = new StringBuilder();
StackTraceElement[] elements = Thread.currentThread().getStackTrace();
if (elements.length > 1)
{
buffer.append(elements[1].getFileName());
buffer.append(":");
buffer.append(elements[1].getLineNumber());
for (int i=2; i < elements.length; i++)
{
buffer.append(" ");
buffer.append(elements[i].getFileName());
buffer.append(":");
buffer.append(elements[i].getLineNumber());
}
}
return buffer.toString();
}
/**
* Retrieves a backtrace for the provided exception consisting of only
* filenames and line numbers that may be useful in debugging the origin of
* problems. This is less expensive than the call to
* getBacktrace without any arguments if an exception has already
* been thrown.
*
* @param t The exception for which to obtain the backtrace.
*
* @return A backtrace from the provided exception.
*/
public static String getBacktrace(Throwable t)
{
StringBuilder buffer = new StringBuilder();
StackTraceElement[] elements = t.getStackTrace();
if (elements.length > 0)
{
buffer.append(elements[0].getFileName());
buffer.append(":");
buffer.append(elements[0].getLineNumber());
for (int i=1; i < elements.length; i++)
{
buffer.append(" ");
buffer.append(elements[i].getFileName());
buffer.append(":");
buffer.append(elements[i].getLineNumber());
}
}
return buffer.toString();
}
/**
* Indicates whether the provided character is a numeric digit.
*
* @param c The character for which to make the determination.
*
* @return true if the provided character represents a numeric
* digit, or false if not.
*/
public static boolean isDigit(char c)
{
switch (c)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return true;
default:
return false;
}
}
/**
* Indicates whether the provided character is an ASCII alphabetic character.
*
* @param c The character for which to make the determination.
*
* @return true if the provided value is an uppercase or
* lowercase ASCII alphabetic character, or false if it
* is not.
*/
public static boolean isAlpha(char c)
{
switch (c)
{
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'H':
case 'I':
case 'J':
case 'K':
case 'L':
case 'M':
case 'N':
case 'O':
case 'P':
case 'Q':
case 'R':
case 'S':
case 'T':
case 'U':
case 'V':
case 'W':
case 'X':
case 'Y':
case 'Z':
return true;
case '[':
case '\\':
case ']':
case '^':
case '_':
case '`':
// Making sure all possible cases are present in one contiguous range
// can result in a performance improvement.
return false;
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
case 'g':
case 'h':
case 'i':
case 'j':
case 'k':
case 'l':
case 'm':
case 'n':
case 'o':
case 'p':
case 'q':
case 'r':
case 's':
case 't':
case 'u':
case 'v':
case 'w':
case 'x':
case 'y':
case 'z':
return true;
default:
return false;
}
}
/**
* Indicates whether the provided character is a hexadecimal digit.
*
* @param c The character for which to make the determination.
*
* @return true if the provided character represents a
* hexadecimal digit, or false if not.
*/
public static boolean isHexDigit(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 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
return true;
default:
return false;
}
}
/**
* Indicates whether the provided byte represents a hexadecimal digit.
*
* @param b The byte for which to make the determination.
*
* @return true if the provided byte represents a hexadecimal
* digit, or false if not.
*/
public static boolean isHexDigit(byte b)
{
switch (b)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
return true;
default:
return false;
}
}
/**
* Converts the provided hexadecimal string to a byte array.
*
* @param hexString The hexadecimal string to convert to a byte array.
*
* @return The byte array containing the binary representation of the
* provided hex string.
*
* @throws ParseException If the provided string contains invalid
* hexadecimal digits or does not contain an even
* number of digits.
*/
public static byte[] hexStringToByteArray(String hexString)
throws ParseException
{
int length;
if (hexString == null || ((length = hexString.length()) == 0))
{
return new byte[0];
}
if ((length % 2) == 1)
{
LocalizableMessage message = ERR_HEX_DECODE_INVALID_LENGTH.get(hexString);
throw new ParseException(message.toString(), 0);
}
int pos = 0;
int arrayLength = length / 2;
byte[] returnArray = new byte[arrayLength];
for (int i=0; i < arrayLength; i++)
{
switch (hexString.charAt(pos++))
{
case '0':
returnArray[i] = 0x00;
break;
case '1':
returnArray[i] = 0x10;
break;
case '2':
returnArray[i] = 0x20;
break;
case '3':
returnArray[i] = 0x30;
break;
case '4':
returnArray[i] = 0x40;
break;
case '5':
returnArray[i] = 0x50;
break;
case '6':
returnArray[i] = 0x60;
break;
case '7':
returnArray[i] = 0x70;
break;
case '8':
returnArray[i] = (byte) 0x80;
break;
case '9':
returnArray[i] = (byte) 0x90;
break;
case 'A':
case 'a':
returnArray[i] = (byte) 0xA0;
break;
case 'B':
case 'b':
returnArray[i] = (byte) 0xB0;
break;
case 'C':
case 'c':
returnArray[i] = (byte) 0xC0;
break;
case 'D':
case 'd':
returnArray[i] = (byte) 0xD0;
break;
case 'E':
case 'e':
returnArray[i] = (byte) 0xE0;
break;
case 'F':
case 'f':
returnArray[i] = (byte) 0xF0;
break;
default:
LocalizableMessage message = ERR_HEX_DECODE_INVALID_CHARACTER.get(
hexString, hexString.charAt(pos-1));
throw new ParseException(message.toString(), 0);
}
switch (hexString.charAt(pos++))
{
case '0':
// No action required.
break;
case '1':
returnArray[i] |= 0x01;
break;
case '2':
returnArray[i] |= 0x02;
break;
case '3':
returnArray[i] |= 0x03;
break;
case '4':
returnArray[i] |= 0x04;
break;
case '5':
returnArray[i] |= 0x05;
break;
case '6':
returnArray[i] |= 0x06;
break;
case '7':
returnArray[i] |= 0x07;
break;
case '8':
returnArray[i] |= 0x08;
break;
case '9':
returnArray[i] |= 0x09;
break;
case 'A':
case 'a':
returnArray[i] |= 0x0A;
break;
case 'B':
case 'b':
returnArray[i] |= 0x0B;
break;
case 'C':
case 'c':
returnArray[i] |= 0x0C;
break;
case 'D':
case 'd':
returnArray[i] |= 0x0D;
break;
case 'E':
case 'e':
returnArray[i] |= 0x0E;
break;
case 'F':
case 'f':
returnArray[i] |= 0x0F;
break;
default:
LocalizableMessage message = ERR_HEX_DECODE_INVALID_CHARACTER.get(
hexString, hexString.charAt(pos-1));
throw new ParseException(message.toString(), 0);
}
}
return returnArray;
}
/**
* Indicates whether the provided value needs to be base64-encoded if it is
* represented in LDIF form.
*
* @param valueBytes The binary representation of the attribute value for
* which to make the determination.
*
* @return true if the value needs to be base64-encoded if it is
* represented in LDIF form, or false if not.
*/
public static boolean needsBase64Encoding(ByteSequence valueBytes)
{
int length;
if (valueBytes == null || ((length = valueBytes.length()) == 0))
{
return false;
}
// If the value starts with a space, colon, or less than, then it needs to
// be base64-encoded.
switch (valueBytes.byteAt(0))
{
case 0x20: // Space
case 0x3A: // Colon
case 0x3C: // Less-than
return true;
}
// If the value ends with a space, then it needs to be base64-encoded.
if (length > 1 && valueBytes.byteAt(length-1) == 0x20)
{
return true;
}
// If the value contains a null, newline, or return character, then it needs
// to be base64-encoded.
byte b;
for (int i = 0; i < valueBytes.length(); i++)
{
b = valueBytes.byteAt(i);
if (b < 0 || 127 < b)
{
return true;
}
switch (b)
{
case 0x00: // Null
case 0x0A: // New line
case 0x0D: // Carriage return
return true;
}
}
// If we've made it here, then there's no reason to base64-encode.
return false;
}
/**
* Indicates whether the provided value needs to be base64-encoded if it is
* represented in LDIF form.
*
* @param valueString The string representation of the attribute value for
* which to make the determination.
*
* @return true if the value needs to be base64-encoded if it is
* represented in LDIF form, or false if not.
*/
public static boolean needsBase64Encoding(String valueString)
{
int length;
if (valueString == null || ((length = valueString.length()) == 0))
{
return false;
}
// If the value starts with a space, colon, or less than, then it needs to
// be base64-encoded.
switch (valueString.charAt(0))
{
case ' ':
case ':':
case '<':
return true;
}
// If the value ends with a space, then it needs to be base64-encoded.
if (length > 1 && valueString.charAt(length-1) == ' ')
{
return true;
}
// If the value contains a null, newline, or return character, then it needs
// to be base64-encoded.
for (int i=0; i < length; i++)
{
char c = valueString.charAt(i);
if (c <= 0 || c == 0x0A || c == 0x0D || c > 127)
{
return true;
}
}
// If we've made it here, then there's no reason to base64-encode.
return false;
}
/**
* Indicates whether the use of the exec method will be allowed on this
* system. It will be allowed by default, but that capability will be removed
* if the org.opends.server.DisableExec system property is set and has any
* value other than "false", "off", "no", or "0".
*
* @return true if the use of the exec method should be allowed,
* or false if it should not be allowed.
*/
public static boolean mayUseExec()
{
return !DirectoryServer.getEnvironmentConfig().disableExec();
}
/**
* Executes the specified command on the system and captures its output. This
* will not return until the specified process has completed.
*
* @param command The command to execute.
* @param args The set of arguments to provide to the command.
* @param workingDirectory The working directory to use for the command, or
* null if the default directory
* should be used.
* @param environment The set of environment variables that should be
* set when executing the command, or
* null if none are needed.
* @param output The output generated by the command while it was
* running. This will include both standard
* output and standard error. It may be
* null if the output does not need to
* be captured.
*
* @return The exit code for the command.
*
* @throws IOException If an I/O problem occurs while trying to execute the
* command.
*
* @throws SecurityException If the security policy will not allow the
* command to be executed.
*
* @throws InterruptedException If the current thread is interrupted by
* another thread while it is waiting, then
* the wait is ended and an InterruptedException
* is thrown.
*/
public static int exec(String command, String[] args, File workingDirectory,
Maptrue if the provided string contains a valid name or
* OID for a schema element, or false if it does not.
*/
public static boolean isValidSchemaElement(String element, int startPos,
int endPos,
LocalizableMessageBuilder invalidReason)
{
if (element == null || startPos >= endPos)
{
invalidReason.append(ERR_SCHEMANAME_EMPTY_VALUE.get());
return false;
}
char c = element.charAt(startPos);
if (isAlpha(c))
{
// This can only be a name and not an OID. The only remaining characters
// must be letters, digits, dashes, and possibly the underscore.
for (int i=startPos+1; i < endPos; i++)
{
c = element.charAt(i);
if (!isAlpha(c)
&& !isDigit(c)
&& c != '-'
&& (c != '_' || !DirectoryServer.allowAttributeNameExceptions()))
{
// This is an illegal character for an attribute name.
invalidReason.append(ERR_SCHEMANAME_ILLEGAL_CHAR.get(element, c, i));
return false;
}
}
}
else if (isDigit(c))
{
// This should indicate an OID, but it may also be a name if name
// exceptions are enabled. Since we don't know for sure, we'll just
// hold off until we know for sure.
boolean isKnown = !DirectoryServer.allowAttributeNameExceptions();
boolean isNumeric = true;
boolean lastWasDot = false;
for (int i=startPos+1; i < endPos; i++)
{
c = element.charAt(i);
if (c == '.')
{
if (isKnown)
{
if (isNumeric)
{
// This is probably legal unless the last character was also a
// period.
if (lastWasDot)
{
invalidReason.append(ERR_SCHEMANAME_CONSECUTIVE_PERIODS.get(
element, i));
return false;
}
else
{
lastWasDot = true;
}
}
else
{
// This is an illegal character.
invalidReason.append(ERR_SCHEMANAME_ILLEGAL_CHAR.get(
element, c, i));
return false;
}
}
else
{
// Now we know that this must be a numeric OID and not an attribute
// name with exceptions allowed.
lastWasDot = true;
isKnown = true;
isNumeric = true;
}
}
else
{
lastWasDot = false;
if (isAlpha(c) || c == '-' || c == '_')
{
if (isKnown)
{
if (isNumeric)
{
// This is an illegal character for a numeric OID.
invalidReason.append(ERR_SCHEMANAME_ILLEGAL_CHAR.get(
element, c, i));
return false;
}
}
else
{
// Now we know that this must be an attribute name with exceptions
// allowed and not a numeric OID.
isKnown = true;
isNumeric = false;
}
}
else if (! isDigit(c))
{
// This is an illegal character.
invalidReason.append(ERR_SCHEMANAME_ILLEGAL_CHAR.get(
element, c, i));
return false;
}
}
}
}
else
{
// This is an illegal character.
invalidReason.append(ERR_SCHEMANAME_ILLEGAL_CHAR.get(
element, c, startPos));
return false;
}
// If we've gotten here, then the value is fine.
return true;
}
/**
* Indicates whether the provided TCP address is already in use.
*
* @param address IP address of the TCP address for which to make
* the determination.
* @param port TCP port number of the TCP address for which to
* make the determination.
* @param allowReuse Whether or not TCP address reuse is allowed when
* making the determination.
*
* @return true if the provided TCP address is already in
* use, or false otherwise.
*/
public static boolean isAddressInUse(
InetAddress address, int port,
boolean allowReuse)
{
// Return pessimistic.
boolean isInUse = true;
Socket clientSocket = null;
ServerSocket serverSocket = null;
try {
// HACK:
// With dual stacks we can have a situation when INADDR_ANY/PORT
// is bound in TCP4 space but available in TCP6 space and since
// JavaServerSocket implemantation will always use TCP46 on dual
// stacks the bind below will always succeed in such cases thus
// shadowing anything that is already bound to INADDR_ANY/PORT.
// While technically correct, with IPv4 and IPv6 being separate
// address spaces, it presents a problem to end users because a
// common case scenario is to have a single service serving both
// address spaces ie listening to the same port in both spaces
// on wildcard addresses 0 and ::. ServerSocket implemantation
// does not provide any means of working with each address space
// separately such as doing TCP4 or TCP6 only binds thus we have
// to do a dummy connect to INADDR_ANY/PORT to check if it is
// bound to something already. This is only needed for wildcard
// addresses as specific IPv4 or IPv6 addresses will always be
// handled in their respective address space.
if (address.isAnyLocalAddress()) {
clientSocket = new Socket();
try {
// This might fail on some stacks but this is the best we
// can do. No need for explicit timeout since it is local
// address and we have to know for sure unless it fails.
clientSocket.connect(new InetSocketAddress(address, port));
} catch (IOException e) {
// Expected, ignore.
}
if (clientSocket.isConnected()) {
return true;
}
}
serverSocket = new ServerSocket();
serverSocket.setReuseAddress(allowReuse);
serverSocket.bind(new InetSocketAddress(address, port));
isInUse = false;
} catch (IOException e) {
isInUse = true;
} finally {
try {
if (serverSocket != null) {
serverSocket.close();
}
} catch (Exception e) {}
try {
if (clientSocket != null) {
clientSocket.close();
}
} catch (Exception e) {}
}
return isInUse;
}
/**
* Retrieves a lowercase representation of the given string. This
* implementation presumes that the provided string will contain only ASCII
* characters and is optimized for that case. However, if a non-ASCII
* character is encountered it will fall back on a more expensive algorithm
* that will work properly for non-ASCII characters.
*
* @param s The string for which to obtain the lowercase representation.
*
* @return The lowercase representation of the given string.
*/
public static String toLowerCase(String s)
{
if (s == null)
{
return null;
}
StringBuilder buffer = new StringBuilder(s.length());
toLowerCase(s, buffer);
return buffer.toString();
}
/**
* Appends a lowercase representation of the given string to the provided
* buffer. This implementation presumes that the provided string will contain
* only ASCII characters and is optimized for that case. However, if a
* non-ASCII character is encountered it will fall back on a more expensive
* algorithm that will work properly for non-ASCII characters.
*
* @param s The string for which to obtain the lowercase
* representation.
* @param buffer The buffer to which the lowercase form of the string should
* be appended.
*/
public static void toLowerCase(String s, StringBuilder buffer)
{
if (s == null)
{
return;
}
int length = s.length();
for (int i=0; i < length; i++)
{
char c = s.charAt(i);
if ((c & 0x7F) != c)
{
buffer.append(s.substring(i).toLowerCase());
return;
}
switch (c)
{
case 'A':
buffer.append('a');
break;
case 'B':
buffer.append('b');
break;
case 'C':
buffer.append('c');
break;
case 'D':
buffer.append('d');
break;
case 'E':
buffer.append('e');
break;
case 'F':
buffer.append('f');
break;
case 'G':
buffer.append('g');
break;
case 'H':
buffer.append('h');
break;
case 'I':
buffer.append('i');
break;
case 'J':
buffer.append('j');
break;
case 'K':
buffer.append('k');
break;
case 'L':
buffer.append('l');
break;
case 'M':
buffer.append('m');
break;
case 'N':
buffer.append('n');
break;
case 'O':
buffer.append('o');
break;
case 'P':
buffer.append('p');
break;
case 'Q':
buffer.append('q');
break;
case 'R':
buffer.append('r');
break;
case 'S':
buffer.append('s');
break;
case 'T':
buffer.append('t');
break;
case 'U':
buffer.append('u');
break;
case 'V':
buffer.append('v');
break;
case 'W':
buffer.append('w');
break;
case 'X':
buffer.append('x');
break;
case 'Y':
buffer.append('y');
break;
case 'Z':
buffer.append('z');
break;
default:
buffer.append(c);
}
}
}
/**
* Appends a lowercase string representation of the contents of the given byte
* array to the provided buffer, optionally trimming leading and trailing
* spaces. This implementation presumes that the provided string will contain
* only ASCII characters and is optimized for that case. However, if a
* non-ASCII character is encountered it will fall back on a more expensive
* algorithm that will work properly for non-ASCII characters.
*
* @param b The byte array for which to obtain the lowercase string
* representation.
* @param buffer The buffer to which the lowercase form of the string should
* be appended.
* @param trim Indicates whether leading and trailing spaces should be
* omitted from the string representation.
*/
public static void toLowerCase(ByteSequence b, StringBuilder buffer,
boolean trim)
{
if (b == null)
{
return;
}
int origBufferLen = buffer.length();
int length = b.length();
for (int i=0; i < length; i++)
{
if ((b.byteAt(i) & 0x7F) != b.byteAt(i))
{
buffer.replace(origBufferLen, buffer.length(),
b.toString().toLowerCase());
break;
}
int bufferLength = buffer.length();
switch (b.byteAt(i))
{
case ' ':
// If we don't care about trimming, then we can always append the
// space. Otherwise, only do so if there are other characters in the value.
if (trim && bufferLength == 0)
{
break;
}
buffer.append(' ');
break;
case 'A':
buffer.append('a');
break;
case 'B':
buffer.append('b');
break;
case 'C':
buffer.append('c');
break;
case 'D':
buffer.append('d');
break;
case 'E':
buffer.append('e');
break;
case 'F':
buffer.append('f');
break;
case 'G':
buffer.append('g');
break;
case 'H':
buffer.append('h');
break;
case 'I':
buffer.append('i');
break;
case 'J':
buffer.append('j');
break;
case 'K':
buffer.append('k');
break;
case 'L':
buffer.append('l');
break;
case 'M':
buffer.append('m');
break;
case 'N':
buffer.append('n');
break;
case 'O':
buffer.append('o');
break;
case 'P':
buffer.append('p');
break;
case 'Q':
buffer.append('q');
break;
case 'R':
buffer.append('r');
break;
case 'S':
buffer.append('s');
break;
case 'T':
buffer.append('t');
break;
case 'U':
buffer.append('u');
break;
case 'V':
buffer.append('v');
break;
case 'W':
buffer.append('w');
break;
case 'X':
buffer.append('x');
break;
case 'Y':
buffer.append('y');
break;
case 'Z':
buffer.append('z');
break;
default:
buffer.append((char) b.byteAt(i));
}
}
if (trim)
{
// Strip off any trailing spaces.
for (int i=buffer.length()-1; i > 0; i--)
{
if (buffer.charAt(i) == ' ')
{
buffer.delete(i, i+1);
}
else
{
break;
}
}
}
}
/**
* Retrieves an uppercase representation of the given string. This
* implementation presumes that the provided string will contain only ASCII
* characters and is optimized for that case. However, if a non-ASCII
* character is encountered it will fall back on a more expensive algorithm
* that will work properly for non-ASCII characters.
*
* @param s The string for which to obtain the uppercase representation.
*
* @return The uppercase representation of the given string.
*/
public static String toUpperCase(String s)
{
if (s == null)
{
return null;
}
StringBuilder buffer = new StringBuilder(s.length());
toUpperCase(s, buffer);
return buffer.toString();
}
/**
* Appends an uppercase representation of the given string to the provided
* buffer. This implementation presumes that the provided string will contain
* only ASCII characters and is optimized for that case. However, if a
* non-ASCII character is encountered it will fall back on a more expensive
* algorithm that will work properly for non-ASCII characters.
*
* @param s The string for which to obtain the uppercase
* representation.
* @param buffer The buffer to which the uppercase form of the string should
* be appended.
*/
public static void toUpperCase(String s, StringBuilder buffer)
{
if (s == null)
{
return;
}
int length = s.length();
for (int i=0; i < length; i++)
{
char c = s.charAt(i);
if ((c & 0x7F) != c)
{
buffer.append(s.substring(i).toUpperCase());
return;
}
switch (c)
{
case 'a':
buffer.append('A');
break;
case 'b':
buffer.append('B');
break;
case 'c':
buffer.append('C');
break;
case 'd':
buffer.append('D');
break;
case 'e':
buffer.append('E');
break;
case 'f':
buffer.append('F');
break;
case 'g':
buffer.append('G');
break;
case 'h':
buffer.append('H');
break;
case 'i':
buffer.append('I');
break;
case 'j':
buffer.append('J');
break;
case 'k':
buffer.append('K');
break;
case 'l':
buffer.append('L');
break;
case 'm':
buffer.append('M');
break;
case 'n':
buffer.append('N');
break;
case 'o':
buffer.append('O');
break;
case 'p':
buffer.append('P');
break;
case 'q':
buffer.append('Q');
break;
case 'r':
buffer.append('R');
break;
case 's':
buffer.append('S');
break;
case 't':
buffer.append('T');
break;
case 'u':
buffer.append('U');
break;
case 'v':
buffer.append('V');
break;
case 'w':
buffer.append('W');
break;
case 'x':
buffer.append('X');
break;
case 'y':
buffer.append('Y');
break;
case 'z':
buffer.append('Z');
break;
default:
buffer.append(c);
}
}
}
/**
* Appends an uppercase string representation of the contents of the given
* byte array to the provided buffer, optionally trimming leading and trailing
* spaces. This implementation presumes that the provided string will contain
* only ASCII characters and is optimized for that case. However, if a
* non-ASCII character is encountered it will fall back on a more expensive
* algorithm that will work properly for non-ASCII characters.
*
* @param b The byte array for which to obtain the uppercase string
* representation.
* @param buffer The buffer to which the uppercase form of the string should
* be appended.
* @param trim Indicates whether leading and trailing spaces should be
* omitted from the string representation.
*/
public static void toUpperCase(byte[] b, StringBuilder buffer, boolean trim)
{
if (b == null)
{
return;
}
int length = b.length;
for (int i=0; i < length; i++)
{
if ((b[i] & 0x7F) != b[i])
{
try
{
buffer.append(new String(b, i, (length-i), "UTF-8").toUpperCase());
}
catch (Exception e)
{
logger.traceException(e);
buffer.append(new String(b, i, (length - i)).toUpperCase());
}
break;
}
int bufferLength = buffer.length();
switch (b[i])
{
case ' ':
// If we don't care about trimming, then we can always append the
// space. Otherwise, only do so if there are other characters in the value.
if (trim && bufferLength == 0)
{
break;
}
buffer.append(' ');
break;
case 'a':
buffer.append('A');
break;
case 'b':
buffer.append('B');
break;
case 'c':
buffer.append('C');
break;
case 'd':
buffer.append('D');
break;
case 'e':
buffer.append('E');
break;
case 'f':
buffer.append('F');
break;
case 'g':
buffer.append('G');
break;
case 'h':
buffer.append('H');
break;
case 'i':
buffer.append('I');
break;
case 'j':
buffer.append('J');
break;
case 'k':
buffer.append('K');
break;
case 'l':
buffer.append('L');
break;
case 'm':
buffer.append('M');
break;
case 'n':
buffer.append('N');
break;
case 'o':
buffer.append('O');
break;
case 'p':
buffer.append('P');
break;
case 'q':
buffer.append('Q');
break;
case 'r':
buffer.append('R');
break;
case 's':
buffer.append('S');
break;
case 't':
buffer.append('T');
break;
case 'u':
buffer.append('U');
break;
case 'v':
buffer.append('V');
break;
case 'w':
buffer.append('W');
break;
case 'x':
buffer.append('X');
break;
case 'y':
buffer.append('Y');
break;
case 'z':
buffer.append('Z');
break;
default:
buffer.append((char) b[i]);
}
}
if (trim)
{
// Strip off any trailing spaces.
for (int i=buffer.length()-1; i > 0; i--)
{
if (buffer.charAt(i) == ' ')
{
buffer.delete(i, i+1);
}
else
{
break;
}
}
}
}
/**
* Append a string to a string builder, escaping any double quotes
* according to the StringValue production in RFC 3641.
* * In RFC 3641 the StringValue production looks like this: * *
* StringValue = dquote *SafeUTF8Character dquote * dquote = %x22 ; " (double quote) * SafeUTF8Character = %x00-21 / %x23-7F / ; ASCII minus dquote * dquote dquote / ; escaped double quote * %xC0-DF %x80-BF / ; 2 byte UTF-8 character * %xE0-EF 2(%x80-BF) / ; 3 byte UTF-8 character * %xF0-F7 3(%x80-BF) ; 4 byte UTF-8 character ** *
* That is, strings are surrounded by double-quotes and any internal
* double-quotes are doubled up.
*
* @param builder
* The string builder.
* @param string
* The string to escape and append.
* @return Returns the string builder.
*/
public static StringBuilder toRFC3641StringValue(StringBuilder builder,
String string)
{
// Initial double-quote.
builder.append('"');
for (char c : string.toCharArray())
{
if (c == '"')
{
// Internal double-quotes are escaped using a double-quote.
builder.append('"');
}
builder.append(c);
}
// Trailing double-quote.
builder.append('"');
return builder;
}
/**
* Retrieves a string array containing the contents of the provided
* list of strings.
*
* @param stringList
* The string list to convert to an array.
* @return A string array containing the contents of the provided list
* of strings.
*/
public static String[] listToArray(List
* For example, consider a method with this signature:
*
*
* Classical use with for or while loop:
*
* fileToRename will be
* moved.
* @throws IOException If a problem occurs while attempting to rename the
* file. On the Windows platform, this typically
* indicates that the file is in use by this or another
* application.
*/
public static void renameFile(File fileToRename, File target)
throws IOException {
if (fileToRename != null && target != null)
{
synchronized(target)
{
if (target.exists() && !target.delete())
{
LocalizableMessage message =
ERR_RENAMEFILE_CANNOT_DELETE_TARGET.get(target.getPath());
throw new IOException(message.toString());
}
}
if (!fileToRename.renameTo(target))
{
LocalizableMessage message = ERR_RENAMEFILE_CANNOT_RENAME.get(
fileToRename.getPath(), target.getPath());
throw new IOException(message.toString());
}
}
}
/**
* Indicates whether the provided path refers to a relative path rather than
* an absolute path.
*
* @param path The path string for which to make the determination.
*
* @return true if the provided path is relative, or
* false if it is absolute.
*/
public static boolean isRelativePath(String path)
{
File f = new File(path);
return !f.isAbsolute();
}
/**
* Retrieves a File object corresponding to the specified path.
* If the given path is an absolute path, then it will be used. If the path
* is relative, then it will be interpreted as if it were relative to the
* Directory Server root.
*
* @param path The path string to be retrieved as a File
*
* @return A File object that corresponds to the specified path.
*/
public static File getFileForPath(String path)
{
File f = new File (path);
if (f.isAbsolute())
{
return f;
}
else
{
return new File(DirectoryServer.getInstanceRoot() + File.separator +
path);
}
}
/**
* Retrieves a File object corresponding to the specified path.
* If the given path is an absolute path, then it will be used. If the path
* is relative, then it will be interpreted as if it were relative to the
* Directory Server root.
*
* @param path
* The path string to be retrieved as a File.
* @param serverContext
* The server context.
*
* @return A File object that corresponds to the specified path.
*/
public static File getFileForPath(String path, ServerContext serverContext)
{
File f = new File (path);
if (f.isAbsolute())
{
return f;
}
else
{
return new File(serverContext.getInstanceRoot() + File.separator +
path);
}
}
/**
* Creates a new, blank entry with the given DN. It will contain only the
* attribute(s) contained in the RDN. The choice of objectclasses will be
* based on the RDN attribute. If there is a single RDN attribute, then the
* following mapping will be used:
*
*
*
*
* Any other single RDN attribute types, or any case in which there are
* multiple RDN attributes, will use the untypedObject objectclass. If the
* RDN includes one or more attributes that are not allowed in the
* untypedObject objectclass, then the extensibleObject class will also be
* added. Note that this method cannot be used to generate an entry
* with an empty or null DN.
*
* @param dn The DN to use for the entry.
*
* @return The entry created with the provided DN.
*/
public static Entry createEntry(DN dn)
{
// If the provided DN was null or empty, then return null because we don't
// support it.
if (dn == null || dn.isRootDN())
{
return null;
}
// Get the information about the RDN attributes.
RDN rdn = dn.rdn();
int numAVAs = rdn.getNumValues();
// If there is only one RDN attribute, then see which objectclass we should use.
ObjectClass structuralClass = DirectoryServer.getObjectClass(getObjectClassName(rdn, numAVAs));
// Get the top and untypedObject classes to include in the entry.
LinkedHashMapargs is
* present and containing an error message identifying the
* arguments in violation
*/
public static void checkOnlyOneArgPresent(Argument... args)
throws ArgumentException
{
if (args != null) {
for (Argument arg : args) {
for (Argument otherArg : args) {
if (arg != otherArg && arg.isPresent() && otherArg.isPresent()) {
throw new ArgumentException(
ToolMessages.ERR_INCOMPATIBLE_ARGUMENTS.get(
arg.getName(), otherArg.getName()));
}
}
}
}
}
/**
* Converts a string representing a time in "yyyyMMddHHmmss.SSS'Z'" or
* "yyyyMMddHHmmss" to a Date.
*
* @param timeStr string formatted appropriately
* @return Date object; null if timeStr is null
* @throws ParseException if there was a problem converting the string to
* a Date.
*/
public static Date parseDateTimeString(String timeStr) throws ParseException
{
Date dateTime = null;
if (timeStr != null)
{
if (timeStr.endsWith("Z"))
{
try
{
SimpleDateFormat dateFormat =
new SimpleDateFormat(DATE_FORMAT_GENERALIZED_TIME);
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
dateFormat.setLenient(true);
dateTime = dateFormat.parse(timeStr);
}
catch (ParseException pe)
{
// Best effort: try with GMT time.
SimpleDateFormat dateFormat =
new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
dateFormat.setLenient(true);
dateTime = dateFormat.parse(timeStr);
}
}
else
{
SimpleDateFormat dateFormat =
new SimpleDateFormat(DATE_FORMAT_COMPACT_LOCAL_TIME);
dateFormat.setLenient(true);
dateTime = dateFormat.parse(timeStr);
}
}
return dateTime;
}
/**
* Formats a Date to String representation in "yyyyMMddHHmmss'Z'".
*
* @param date to format; null if date is null
* @return string representation of the date
*/
public static String formatDateTimeString(Date date)
{
String timeStr = null;
if (date != null)
{
SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
timeStr = dateFormat.format(date);
}
return timeStr;
}
/**
* Indicates whether or not a string represents a syntactically correct
* email address.
*
* @param addr to validate
* @return boolean where true indicates that the string is a
* syntactically correct email address
*/
public static boolean isEmailAddress(String addr) {
// This just does basic syntax checking. Perhaps we
// might want to be stricter about this.
return addr != null && addr.contains("@") && addr.contains(".");
}
/**
* Writes the contents of the provided buffer to the client,
* terminating the connection if the write is unsuccessful for too
* long (e.g., if the client is unresponsive or there is a network
* problem). If possible, it will attempt to use the selector returned
* by the {@code ClientConnection.getWriteSelector} method, but it is
* capable of working even if that method returns {@code null}.
*
* Note that the original position and limit values will not be
* preserved, so if that is important to the caller, then it should
* record them before calling this method and restore them after it
* returns.
*
* @param clientConnection
* The client connection to which the data is to be written.
* @param buffer
* The data to be written to the client.
* @return true if all the data in the provided buffer was
* written to the client and the connection may remain
* established, or false if a problem occurred
* and the client connection is no longer valid. Note that if
* this method does return false, then it must
* have already disconnected the client.
* @throws IOException
* If a problem occurs while attempting to write data to the
* client. The caller will be responsible for catching this
* and terminating the client connection.
*/
public static boolean writeWithTimeout(ClientConnection clientConnection,
ByteBuffer buffer) throws IOException
{
SocketChannel socketChannel = clientConnection.getSocketChannel();
long startTime = System.currentTimeMillis();
long waitTime = clientConnection.getMaxBlockedWriteTimeLimit();
if (waitTime <= 0)
{
// We won't support an infinite time limit, so fall back to using
// five minutes, which is a very long timeout given that we're
// blocking a worker thread.
waitTime = 300000L;
}
long stopTime = startTime + waitTime;
Selector selector = clientConnection.getWriteSelector();
if (selector == null)
{
// The client connection does not provide a selector, so we'll
// fall back
// to a more inefficient way that will work without a selector.
while (buffer.hasRemaining()
&& System.currentTimeMillis() < stopTime)
{
if (socketChannel.write(buffer) < 0)
{
// The client connection has been closed.
return false;
}
}
if (buffer.hasRemaining())
{
// If we've gotten here, then the write timed out.
return false;
}
return true;
}
// Register with the selector for handling write operations.
SelectionKey key =
socketChannel.register(selector, SelectionKey.OP_WRITE);
try
{
selector.select(waitTime);
while (buffer.hasRemaining())
{
long currentTime = System.currentTimeMillis();
if (currentTime >= stopTime)
{
// We've been blocked for too long.
return false;
}
else
{
waitTime = stopTime - currentTime;
}
Iteratornull.
*/
public static void close(Closeable... closeables)
{
if (closeables == null)
{
return;
}
close(Arrays.asList(closeables));
}
/**
* Closes the provided {@link Closeable}'s ignoring any errors which occurred.
*
* @param closeables
* The closeables to be closed, which may be null.
*/
public static void close(Collection extends Closeable> closeables)
{
if (closeables == null)
{
return;
}
for (Closeable closeable : closeables)
{
if (closeable != null)
{
try
{
closeable.close();
}
catch (IOException ignored)
{
logger.traceException(ignored);
}
}
}
}
/**
* Closes the provided {@link InitialContext}s ignoring any errors which occurred.
*
* @param ctxs
* The contexts to be closed, which may be null.
*/
public static void close(InitialContext... ctxs)
{
if (ctxs == null)
{
return;
}
for (InitialContext ctx : ctxs)
{
if (ctx != null)
{
try
{
ctx.close();
}
catch (NamingException ignored)
{
// ignore
}
}
}
}
/**
* Calls {@link Thread#sleep(long)}, surrounding it with the mandatory
* try / catch(InterruptedException) block.
*
* @param millis
* the length of time to sleep in milliseconds
*/
public static void sleep(long millis)
{
try
{
Thread.sleep(millis);
}
catch (InterruptedException wokenUp)
{
// ignore
}
}
/**
* Test if the provided message corresponds to the provided descriptor.
*
* @param msg
* The i18n message.
* @param desc
* The message descriptor.
* @return {@code true} if message corresponds to descriptor
*/
public static boolean hasDescriptor(LocalizableMessage msg,
LocalizableMessageDescriptor.Arg0 desc)
{
return msg.ordinal() == desc.ordinal()
&& msg.resourceName().equals(desc.resourceName());
}
/**
* Test if the provided message corresponds to the provided descriptor.
*
* @param msg
* The i18n message.
* @param desc
* The message descriptor.
* @return {@code true} if message corresponds to descriptor
*/
public static boolean hasDescriptor(LocalizableMessage msg,
LocalizableMessageDescriptor.Arg1 desc)
{
return msg.ordinal() == desc.ordinal()
&& msg.resourceName().equals(desc.resourceName());
}
/**
* Test if the provided message corresponds to the provided descriptor.
*
* @param msg
* The i18n message.
* @param desc
* The message descriptor.
* @return {@code true} if message corresponds to descriptor
*/
public static boolean hasDescriptor(LocalizableMessage msg,
LocalizableMessageDescriptor.Arg2 desc)
{
return msg.ordinal() == desc.ordinal()
&& msg.resourceName().equals(desc.resourceName());
}
/**
* Test if the provided message corresponds to the provided descriptor.
*
* @param msg
* The i18n message.
* @param desc
* The message descriptor.
* @return {@code true} if message corresponds to descriptor
*/
public static boolean hasDescriptor(LocalizableMessage msg,
LocalizableMessageDescriptor.Arg3 desc)
{
return msg.ordinal() == desc.ordinal()
&& msg.resourceName().equals(desc.resourceName());
}
/**
* Test if the provided message corresponds to the provided descriptor.
*
* @param msg
* The i18n message.
* @param desc
* The message descriptor.
* @return {@code true} if message corresponds to descriptor
*/
public static boolean hasDescriptor(LocalizableMessage msg,
LocalizableMessageDescriptor.Arg7 desc)
{
return msg.ordinal() == desc.ordinal()
&& msg.resourceName().equals(desc.resourceName());
}
/**
* Returns an {@link Iterable} returning the passed in {@link Iterator}. THis
* allows using methods returning Iterators with foreach statements.
* public Iterator<String> myIteratorMethod();
*
* for (Iterator<String> it = myIteratorMethod(); it.hasNext();)
* {
* String s = it.next();
* // use it
* }
*
* Iterator<String> it = myIteratorMethod();
* while(it.hasNext();)
* {
* String s = it.next();
* // use it
* }
*
*
* Improved use with foreach:
*
*
* for (String s : StaticUtils.toIterable(myIteratorMethod()))
* {
* }
*
*
*