/*
* 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
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* 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
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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 2009 Sun Microsystems, Inc.
*/
package com.sun.opends.sdk.util;
import static com.sun.opends.sdk.messages.Messages.*;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import org.opends.sdk.*;
/**
* Common utility methods.
*/
public final class StaticUtils
{
public static final Logger DEBUG_LOG = Logger
.getLogger("org.opends.sdk");
/**
* The end-of-line character for this platform.
*/
public static final String EOL = System.getProperty("line.separator");
// The name of the time zone for universal coordinated time (UTC).
private static final String TIME_ZONE_UTC = "UTC";
// UTC TimeZone is assumed to never change over JVM lifetime
private static final TimeZone TIME_ZONE_UTC_OBJ = TimeZone
.getTimeZone(TIME_ZONE_UTC);
/**
* 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)
{
switch (b & 0xFF)
{
case 0x00:
return "00";
case 0x01:
return "01";
case 0x02:
return "02";
case 0x03:
return "03";
case 0x04:
return "04";
case 0x05:
return "05";
case 0x06:
return "06";
case 0x07:
return "07";
case 0x08:
return "08";
case 0x09:
return "09";
case 0x0A:
return "0A";
case 0x0B:
return "0B";
case 0x0C:
return "0C";
case 0x0D:
return "0D";
case 0x0E:
return "0E";
case 0x0F:
return "0F";
case 0x10:
return "10";
case 0x11:
return "11";
case 0x12:
return "12";
case 0x13:
return "13";
case 0x14:
return "14";
case 0x15:
return "15";
case 0x16:
return "16";
case 0x17:
return "17";
case 0x18:
return "18";
case 0x19:
return "19";
case 0x1A:
return "1A";
case 0x1B:
return "1B";
case 0x1C:
return "1C";
case 0x1D:
return "1D";
case 0x1E:
return "1E";
case 0x1F:
return "1F";
case 0x20:
return "20";
case 0x21:
return "21";
case 0x22:
return "22";
case 0x23:
return "23";
case 0x24:
return "24";
case 0x25:
return "25";
case 0x26:
return "26";
case 0x27:
return "27";
case 0x28:
return "28";
case 0x29:
return "29";
case 0x2A:
return "2A";
case 0x2B:
return "2B";
case 0x2C:
return "2C";
case 0x2D:
return "2D";
case 0x2E:
return "2E";
case 0x2F:
return "2F";
case 0x30:
return "30";
case 0x31:
return "31";
case 0x32:
return "32";
case 0x33:
return "33";
case 0x34:
return "34";
case 0x35:
return "35";
case 0x36:
return "36";
case 0x37:
return "37";
case 0x38:
return "38";
case 0x39:
return "39";
case 0x3A:
return "3A";
case 0x3B:
return "3B";
case 0x3C:
return "3C";
case 0x3D:
return "3D";
case 0x3E:
return "3E";
case 0x3F:
return "3F";
case 0x40:
return "40";
case 0x41:
return "41";
case 0x42:
return "42";
case 0x43:
return "43";
case 0x44:
return "44";
case 0x45:
return "45";
case 0x46:
return "46";
case 0x47:
return "47";
case 0x48:
return "48";
case 0x49:
return "49";
case 0x4A:
return "4A";
case 0x4B:
return "4B";
case 0x4C:
return "4C";
case 0x4D:
return "4D";
case 0x4E:
return "4E";
case 0x4F:
return "4F";
case 0x50:
return "50";
case 0x51:
return "51";
case 0x52:
return "52";
case 0x53:
return "53";
case 0x54:
return "54";
case 0x55:
return "55";
case 0x56:
return "56";
case 0x57:
return "57";
case 0x58:
return "58";
case 0x59:
return "59";
case 0x5A:
return "5A";
case 0x5B:
return "5B";
case 0x5C:
return "5C";
case 0x5D:
return "5D";
case 0x5E:
return "5E";
case 0x5F:
return "5F";
case 0x60:
return "60";
case 0x61:
return "61";
case 0x62:
return "62";
case 0x63:
return "63";
case 0x64:
return "64";
case 0x65:
return "65";
case 0x66:
return "66";
case 0x67:
return "67";
case 0x68:
return "68";
case 0x69:
return "69";
case 0x6A:
return "6A";
case 0x6B:
return "6B";
case 0x6C:
return "6C";
case 0x6D:
return "6D";
case 0x6E:
return "6E";
case 0x6F:
return "6F";
case 0x70:
return "70";
case 0x71:
return "71";
case 0x72:
return "72";
case 0x73:
return "73";
case 0x74:
return "74";
case 0x75:
return "75";
case 0x76:
return "76";
case 0x77:
return "77";
case 0x78:
return "78";
case 0x79:
return "79";
case 0x7A:
return "7A";
case 0x7B:
return "7B";
case 0x7C:
return "7C";
case 0x7D:
return "7D";
case 0x7E:
return "7E";
case 0x7F:
return "7F";
case 0x80:
return "80";
case 0x81:
return "81";
case 0x82:
return "82";
case 0x83:
return "83";
case 0x84:
return "84";
case 0x85:
return "85";
case 0x86:
return "86";
case 0x87:
return "87";
case 0x88:
return "88";
case 0x89:
return "89";
case 0x8A:
return "8A";
case 0x8B:
return "8B";
case 0x8C:
return "8C";
case 0x8D:
return "8D";
case 0x8E:
return "8E";
case 0x8F:
return "8F";
case 0x90:
return "90";
case 0x91:
return "91";
case 0x92:
return "92";
case 0x93:
return "93";
case 0x94:
return "94";
case 0x95:
return "95";
case 0x96:
return "96";
case 0x97:
return "97";
case 0x98:
return "98";
case 0x99:
return "99";
case 0x9A:
return "9A";
case 0x9B:
return "9B";
case 0x9C:
return "9C";
case 0x9D:
return "9D";
case 0x9E:
return "9E";
case 0x9F:
return "9F";
case 0xA0:
return "A0";
case 0xA1:
return "A1";
case 0xA2:
return "A2";
case 0xA3:
return "A3";
case 0xA4:
return "A4";
case 0xA5:
return "A5";
case 0xA6:
return "A6";
case 0xA7:
return "A7";
case 0xA8:
return "A8";
case 0xA9:
return "A9";
case 0xAA:
return "AA";
case 0xAB:
return "AB";
case 0xAC:
return "AC";
case 0xAD:
return "AD";
case 0xAE:
return "AE";
case 0xAF:
return "AF";
case 0xB0:
return "B0";
case 0xB1:
return "B1";
case 0xB2:
return "B2";
case 0xB3:
return "B3";
case 0xB4:
return "B4";
case 0xB5:
return "B5";
case 0xB6:
return "B6";
case 0xB7:
return "B7";
case 0xB8:
return "B8";
case 0xB9:
return "B9";
case 0xBA:
return "BA";
case 0xBB:
return "BB";
case 0xBC:
return "BC";
case 0xBD:
return "BD";
case 0xBE:
return "BE";
case 0xBF:
return "BF";
case 0xC0:
return "C0";
case 0xC1:
return "C1";
case 0xC2:
return "C2";
case 0xC3:
return "C3";
case 0xC4:
return "C4";
case 0xC5:
return "C5";
case 0xC6:
return "C6";
case 0xC7:
return "C7";
case 0xC8:
return "C8";
case 0xC9:
return "C9";
case 0xCA:
return "CA";
case 0xCB:
return "CB";
case 0xCC:
return "CC";
case 0xCD:
return "CD";
case 0xCE:
return "CE";
case 0xCF:
return "CF";
case 0xD0:
return "D0";
case 0xD1:
return "D1";
case 0xD2:
return "D2";
case 0xD3:
return "D3";
case 0xD4:
return "D4";
case 0xD5:
return "D5";
case 0xD6:
return "D6";
case 0xD7:
return "D7";
case 0xD8:
return "D8";
case 0xD9:
return "D9";
case 0xDA:
return "DA";
case 0xDB:
return "DB";
case 0xDC:
return "DC";
case 0xDD:
return "DD";
case 0xDE:
return "DE";
case 0xDF:
return "DF";
case 0xE0:
return "E0";
case 0xE1:
return "E1";
case 0xE2:
return "E2";
case 0xE3:
return "E3";
case 0xE4:
return "E4";
case 0xE5:
return "E5";
case 0xE6:
return "E6";
case 0xE7:
return "E7";
case 0xE8:
return "E8";
case 0xE9:
return "E9";
case 0xEA:
return "EA";
case 0xEB:
return "EB";
case 0xEC:
return "EC";
case 0xED:
return "ED";
case 0xEE:
return "EE";
case 0xEF:
return "EF";
case 0xF0:
return "F0";
case 0xF1:
return "F1";
case 0xF2:
return "F2";
case 0xF3:
return "F3";
case 0xF4:
return "F4";
case 0xF5:
return "F5";
case 0xF6:
return "F6";
case 0xF7:
return "F7";
case 0xF8:
return "F8";
case 0xF9:
return "F9";
case 0xFA:
return "FA";
case 0xFB:
return "FB";
case 0xFC:
return "FC";
case 0xFD:
return "FD";
case 0xFE:
return "FE";
case 0xFF:
return "FF";
default:
return "??";
}
}
/**
* Attempts to compress the data in the provided source array into the
* given destination array. If the compressed data will fit into the
* destination array, then this method will return the number of bytes
* of compressed data in the array. Otherwise, it will return -1 to
* indicate that the compression was not successful. Note that if -1
* is returned, then the data in the destination array should be
* considered invalid.
*
* @param src
* The array containing the raw data to compress.
* @param srcOff
* The start offset of the source data.
* @param srcLen
* The maximum number of source data bytes to compress.
* @param dst
* The array into which the compressed data should be
* written.
* @param dstOff
* The start offset of the compressed data.
* @param dstLen
* The maximum number of bytes of compressed data.
* @return The number of bytes of compressed data, or -1 if it was not
* possible to actually compress the data.
*/
public static int compress(byte[] src, int srcOff, int srcLen,
byte[] dst, int dstOff, int dstLen)
{
final Deflater deflater = new Deflater();
try
{
deflater.setInput(src, srcOff, srcLen);
deflater.finish();
final int compressedLength = deflater
.deflate(dst, dstOff, dstLen);
if (deflater.finished())
{
return compressedLength;
}
else
{
return -1;
}
}
finally
{
deflater.end();
}
}
/**
* Attempts to compress the data in the provided byte sequence into
* the provided byte string builder. Note that if compression was not
* successful, then the byte string builder will be left unchanged.
*
* @param input
* The source data to be compressed.
* @param output
* The destination buffer to which the compressed data will
* be appended.
* @return true if compression was successful or
* false otherwise.
*/
public static boolean compress(ByteSequence input,
ByteStringBuilder output)
{
byte[] inputBytes = input.toByteArray();
byte[] outputBytes = new byte[inputBytes.length];
final int compressedSize = compress(inputBytes, 0,
inputBytes.length, outputBytes, 0, outputBytes.length);
if (compressedSize != -1)
{
if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINE))
{
StaticUtils.DEBUG_LOG.fine(String.format("Compression %d/%d%n",
compressedSize, inputBytes.length));
}
output.append(outputBytes, 0, compressedSize);
return true;
}
return false;
}
public static ByteString evaluateEscapes(SubstringReader reader,
char[] escapeChars, boolean trim) throws DecodeException
{
return evaluateEscapes(reader, escapeChars, escapeChars, trim);
}
public static ByteString evaluateEscapes(SubstringReader reader,
char[] escapeChars, char[] delimiterChars, boolean trim)
throws DecodeException
{
int length = 0;
int lengthWithoutSpace = 0;
char c;
ByteStringBuilder valueBuffer = null;
if (trim)
{
reader.skipWhitespaces();
}
reader.mark();
while (reader.remaining() > 0)
{
c = reader.read();
if (c == 0x5C) // The backslash character
{
if (valueBuffer == null)
{
valueBuffer = new ByteStringBuilder();
}
valueBuffer.append(reader.read(length));
valueBuffer.append(evaluateEscapedChar(reader, escapeChars));
reader.mark();
length = lengthWithoutSpace = 0;
}
if (delimiterChars != null)
{
for (final char delimiterChar : delimiterChars)
{
if (c == delimiterChar)
{
reader.reset();
if (valueBuffer != null)
{
if (trim)
{
valueBuffer.append(reader.read(lengthWithoutSpace));
}
else
{
valueBuffer.append(reader.read(length));
}
return valueBuffer.toByteString();
}
else
{
if (trim)
{
if (lengthWithoutSpace > 0)
{
return ByteString.valueOf(reader
.read(lengthWithoutSpace));
}
return ByteString.empty();
}
if (length > 0)
{
return ByteString.valueOf(reader.read(length));
}
return ByteString.empty();
}
}
}
}
length++;
if (c != ' ')
{
lengthWithoutSpace = length;
}
else
{
lengthWithoutSpace++;
}
}
reader.reset();
if (valueBuffer != null)
{
if (trim)
{
valueBuffer.append(reader.read(lengthWithoutSpace));
}
else
{
valueBuffer.append(reader.read(length));
}
return valueBuffer.toByteString();
}
else
{
if (trim)
{
if (lengthWithoutSpace > 0)
{
return ByteString.valueOf(reader.read(lengthWithoutSpace));
}
return ByteString.empty();
}
if (length > 0)
{
return ByteString.valueOf(reader.read(length));
}
return ByteString.empty();
}
}
/**
* Returns a string containing provided date formatted using the
* generalized time syntax.
*
* @param date
* The date to be formated.
* @return The string containing provided date formatted using the
* generalized time syntax.
* @throws NullPointerException
* If {@code date} was {@code null}.
*/
public static String formatAsGeneralizedTime(Date date)
{
return formatAsGeneralizedTime(date.getTime());
}
/**
* Returns a string containing provided date formatted using the
* generalized time syntax.
*
* @param date
* The date to be formated.
* @return The string containing provided date formatted using the
* generalized time syntax.
* @throws IllegalArgumentException
* If {@code date} was invalid.
*/
public static String formatAsGeneralizedTime(long date)
{
// Generalized time has the format yyyyMMddHHmmss.SSS'Z'
// Do this in a thread-safe non-synchronized fashion.
// (Simple)DateFormat is neither fast nor thread-safe.
final StringBuilder sb = new StringBuilder(19);
final GregorianCalendar calendar = new GregorianCalendar(
TIME_ZONE_UTC_OBJ);
calendar.setLenient(false);
calendar.setTimeInMillis(date);
// Format the year yyyy.
int n = calendar.get(Calendar.YEAR);
if (n < 0)
{
final IllegalArgumentException e = new IllegalArgumentException(
"Year cannot be < 0:" + n);
StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "format",
e);
throw e;
}
else if (n < 10)
{
sb.append("000");
}
else if (n < 100)
{
sb.append("00");
}
else if (n < 1000)
{
sb.append("0");
}
sb.append(n);
// Format the month MM.
n = calendar.get(Calendar.MONTH) + 1;
if (n < 10)
{
sb.append("0");
}
sb.append(n);
// Format the day dd.
n = calendar.get(Calendar.DAY_OF_MONTH);
if (n < 10)
{
sb.append("0");
}
sb.append(n);
// Format the hour HH.
n = calendar.get(Calendar.HOUR_OF_DAY);
if (n < 10)
{
sb.append("0");
}
sb.append(n);
// Format the minute mm.
n = calendar.get(Calendar.MINUTE);
if (n < 10)
{
sb.append("0");
}
sb.append(n);
// Format the seconds ss.
n = calendar.get(Calendar.SECOND);
if (n < 10)
{
sb.append("0");
}
sb.append(n);
// Format the milli-seconds.
sb.append('.');
n = calendar.get(Calendar.MILLISECOND);
if (n < 10)
{
sb.append("00");
}
else if (n < 100)
{
sb.append("0");
}
sb.append(n);
// Format the timezone (always Z).
sb.append('Z');
return 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;
final int length = s.length();
final 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 (final Exception e)
{
DEBUG_LOG.warning("Unable to encode UTF-8 string " + s);
return s.getBytes();
}
}
/**
* Retrieves the best human-readable message for the provided
* exception. For exceptions defined in the OpenDS 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 LocalizableException)
{
final LocalizableException ie = (LocalizableException) t;
return ie.getMessageObject();
}
else if (t instanceof NullPointerException)
{
final StackTraceElement[] stackElements = t.getStackTrace();
final LocalizableMessageBuilder message = new LocalizableMessageBuilder();
message.append("NullPointerException(");
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
{
final StringBuilder message = new StringBuilder();
final String className = t.getClass().getName();
final int periodPos = className.lastIndexOf('.');
if (periodPos > 0)
{
message.append(className.substring(periodPos + 1));
}
else
{
message.append(className);
}
message.append("(");
if (t.getMessage() == null)
{
final StackTraceElement[] stackElements = t.getStackTrace();
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());
}
}
/**
* 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 java.text.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)
{
final LocalizableMessage message = ERR_HEX_DECODE_INVALID_LENGTH
.get(hexString);
throw new ParseException(message.toString(), 0);
}
final int arrayLength = length / 2;
final byte[] returnArray = new byte[arrayLength];
for (int i = 0; i < arrayLength; i++)
{
returnArray[i] = hexToByte(hexString.charAt(i * 2), hexString
.charAt(i * 2 + 1));
}
return returnArray;
}
/**
* Converts the provided pair of characters to a byte.
*
* @param c1
* The first hexadecimal character.
* @param c2
* The second hexadecimal character.
* @return The byte containing the binary representation of the
* provided hex characters.
* @throws ParseException
* If the provided string contains invalid hexadecimal
* digits or does not contain an even number of digits.
*/
public static byte hexToByte(char c1, char c2) throws ParseException
{
byte b;
switch (c1)
{
case '0':
b = 0x00;
break;
case '1':
b = 0x10;
break;
case '2':
b = 0x20;
break;
case '3':
b = 0x30;
break;
case '4':
b = 0x40;
break;
case '5':
b = 0x50;
break;
case '6':
b = 0x60;
break;
case '7':
b = 0x70;
break;
case '8':
b = (byte) 0x80;
break;
case '9':
b = (byte) 0x90;
break;
case 'A':
case 'a':
b = (byte) 0xA0;
break;
case 'B':
case 'b':
b = (byte) 0xB0;
break;
case 'C':
case 'c':
b = (byte) 0xC0;
break;
case 'D':
case 'd':
b = (byte) 0xD0;
break;
case 'E':
case 'e':
b = (byte) 0xE0;
break;
case 'F':
case 'f':
b = (byte) 0xF0;
break;
default:
final LocalizableMessage message = ERR_HEX_DECODE_INVALID_CHARACTER
.get(new String(new char[] { c1, c2 }), c1);
throw new ParseException(message.toString(), 0);
}
switch (c2)
{
case '0':
// No action required.
break;
case '1':
b |= 0x01;
break;
case '2':
b |= 0x02;
break;
case '3':
b |= 0x03;
break;
case '4':
b |= 0x04;
break;
case '5':
b |= 0x05;
break;
case '6':
b |= 0x06;
break;
case '7':
b |= 0x07;
break;
case '8':
b |= 0x08;
break;
case '9':
b |= 0x09;
break;
case 'A':
case 'a':
b |= 0x0A;
break;
case 'B':
case 'b':
b |= 0x0B;
break;
case 'C':
case 'c':
b |= 0x0C;
break;
case 'D':
case 'd':
b |= 0x0D;
break;
case 'E':
case 'e':
b |= 0x0E;
break;
case 'F':
case 'f':
b |= 0x0F;
break;
default:
final LocalizableMessage message = ERR_HEX_DECODE_INVALID_CHARACTER
.get(new String(new char[] { c1, c2 }), c1);
throw new ParseException(message.toString(), 0);
}
return b;
}
/**
* 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)
{
final ASCIICharProp cp = ASCIICharProp.valueOf(c);
return cp != null ? cp.isLetter() : false;
}
/**
* 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)
{
final ASCIICharProp cp = ASCIICharProp.valueOf(c);
return cp != null ? cp.isDigit() : 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)
{
final ASCIICharProp cp = ASCIICharProp.valueOf(c);
return cp != null ? cp.isHexDigit() : false;
}
/**
* Returns a string representation of the contents of the provided
* byte sequence using hexadecimal characters and a space between each
* byte.
*
* @param bytes
* The byte sequence.
* @return A string representation of the contents of the provided
* byte sequence using hexadecimal characters.
*/
public static String toHex(ByteSequence bytes)
{
return toHex(bytes, new StringBuilder((bytes.length() - 1) * 3 + 2))
.toString();
}
/**
* Appends the string representation of the contents of the provided
* byte sequence to a string builder using hexadecimal characters and
* a space between each byte.
*
* @param bytes
* The byte sequence.
* @param builder
* The string builder to which the hexadecimal representation
* of {@code bytes} should be appended.
* @return The string builder.
*/
public static StringBuilder toHex(ByteSequence bytes,
StringBuilder builder)
{
final int length = bytes.length();
builder.ensureCapacity(builder.length() + (length - 1) * 3 + 2);
builder.append(StaticUtils.byteToHex(bytes.byteAt(0)));
for (int i = 1; i < length; i++)
{
builder.append(" ");
builder.append(StaticUtils.byteToHex(bytes.byteAt(i)));
}
return builder;
}
/**
* Appends a string representation of the data in the provided byte
* sequence to the given string builder 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 bytes
* The byte sequence.
* @param builder
* The string builder to which the information is to be
* appended.
* @param indent
* The number of spaces to indent the output.
* @return The string builder.
*/
public static StringBuilder toHexPlusAscii(ByteSequence bytes,
StringBuilder builder, int indent)
{
final StringBuilder indentBuf = new StringBuilder(indent);
for (int i = 0; i < indent; i++)
{
indentBuf.append(' ');
}
final int length = bytes.length();
int pos = 0;
while (length - pos >= 16)
{
final StringBuilder asciiBuf = new StringBuilder(17);
byte currentByte = bytes.byteAt(pos);
builder.append(indentBuf);
builder.append(StaticUtils.byteToHex(currentByte));
asciiBuf.append(byteToASCII(currentByte));
pos++;
for (int i = 1; i < 16; i++, pos++)
{
currentByte = bytes.byteAt(pos);
builder.append(' ');
builder.append(StaticUtils.byteToHex(currentByte));
asciiBuf.append(byteToASCII(currentByte));
if (i == 7)
{
builder.append(" ");
asciiBuf.append(' ');
}
}
builder.append(" ");
builder.append(asciiBuf);
builder.append(EOL);
}
final int remaining = length - pos;
if (remaining > 0)
{
final StringBuilder asciiBuf = new StringBuilder(remaining + 1);
byte currentByte = bytes.byteAt(pos);
builder.append(indentBuf);
builder.append(StaticUtils.byteToHex(currentByte));
asciiBuf.append(byteToASCII(currentByte));
pos++;
for (int i = 1; i < 16; i++, pos++)
{
builder.append(' ');
if (i < remaining)
{
currentByte = bytes.byteAt(pos);
builder.append(StaticUtils.byteToHex(currentByte));
asciiBuf.append(byteToASCII(currentByte));
}
else
{
builder.append(" ");
}
if (i == 7)
{
builder.append(" ");
if (i < remaining)
{
asciiBuf.append(' ');
}
}
}
builder.append(" ");
builder.append(asciiBuf);
builder.append(EOL);
}
return builder;
}
/**
* Appends a lowercase string representation of the contents of the
* given byte array 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 b
* The byte array for which to obtain the lowercase string
* representation.
* @param builder
* The buffer to which the lowercase form of the string
* should be appended.
* @return The updated {@code StringBuilder}.
*/
public static StringBuilder toLowerCase(ByteSequence b,
StringBuilder builder)
{
Validator.ensureNotNull(b, builder);
// FIXME: What locale should we use for non-ASCII characters? I
// think we should use default to the Unicode StringPrep.
final int origBufferLen = builder.length();
final int length = b.length();
for (int i = 0; i < length; i++)
{
final int c = b.byteAt(i);
if (c < 0)
{
builder.replace(origBufferLen, builder.length(), b.toString()
.toLowerCase(Locale.ENGLISH));
return builder;
}
// At this point 0 <= 'c' <= 128.
final ASCIICharProp cp = ASCIICharProp.valueOf(c);
builder.append(cp.toLowerCase());
}
return builder;
}
/**
* Retrieves a lower-case 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 lower-case
* representation.
* @return The lower-case representation of the given string.
*/
public static String toLowerCase(String s)
{
Validator.ensureNotNull(s);
// FIXME: What locale should we use for non-ASCII characters? I
// think we should use default to the Unicode StringPrep.
// This code is optimized for the case where the input string 's'
// has already been converted to lowercase.
final int length = s.length();
int i = 0;
ASCIICharProp cp = null;
// Scan for non lowercase ASCII.
while (i < length)
{
cp = ASCIICharProp.valueOf(s.charAt(i));
if (cp == null || cp.isUpperCase()) break;
i++;
}
if (i == length)
{
// String was already lowercase ASCII.
return s;
}
// Found non lowercase ASCII.
final StringBuilder builder = new StringBuilder(length);
builder.append(s, 0, i);
if (cp != null)
{
// Upper-case ASCII.
builder.append(cp.toLowerCase());
i++;
while (i < length)
{
cp = ASCIICharProp.valueOf(s.charAt(i));
if (cp == null) break;
builder.append(cp.toLowerCase());
i++;
}
}
if (i < length)
{
builder.append(s.substring(i).toLowerCase(Locale.ENGLISH));
}
return builder.toString();
}
/**
* Appends a lower-case 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 lower-case
* representation.
* @param builder
* The {@code StringBuilder} to which the lower-case form of
* the string should be appended.
* @return The updated {@code StringBuilder}.
*/
public static StringBuilder toLowerCase(String s,
StringBuilder builder)
{
Validator.ensureNotNull(s, builder);
// FIXME: What locale should we use for non-ASCII characters? I
// think we should use default to the Unicode StringPrep.
final int length = s.length();
builder.ensureCapacity(builder.length() + length);
for (int i = 0; i < length; i++)
{
final ASCIICharProp cp = ASCIICharProp.valueOf(s.charAt(i));
if (cp != null)
{
builder.append(cp.toLowerCase());
}
else
{
// Non-ASCII.
builder.append(s.substring(i).toLowerCase(Locale.ENGLISH));
return builder;
}
}
return builder;
}
/**
* Attempts to uncompress the data in the provided source array into
* the given destination array. If the uncompressed data will fit into
* the given destination array, then this method will return the
* number of bytes of uncompressed data written into the destination
* buffer. Otherwise, it will return a negative value to indicate that
* the destination buffer was not large enough. The absolute value of
* that negative return value will indicate the buffer size required
* to fully decompress the data. Note that if a negative value is
* returned, then the data in the destination array should be
* considered invalid.
*
* @param src
* The array containing the raw data to compress.
* @param srcOff
* The start offset of the source data.
* @param srcLen
* The maximum number of source data bytes to compress.
* @param dst
* The array into which the compressed data should be
* written.
* @param dstOff
* The start offset of the compressed data.
* @param dstLen
* The maximum number of bytes of compressed data.
* @return A positive value containing the number of bytes of
* uncompressed data written into the destination buffer, or a
* negative value whose absolute value is the size of the
* destination buffer required to fully decompress the
* provided data.
* @throws java.util.zip.DataFormatException
* If a problem occurs while attempting to uncompress the
* data.
*/
public static int uncompress(byte[] src, int srcOff, int srcLen,
byte[] dst, int dstOff, int dstLen) throws DataFormatException
{
final Inflater inflater = new Inflater();
try
{
inflater.setInput(src, srcOff, srcLen);
final int decompressedLength = inflater.inflate(dst, dstOff,
dstLen);
if (inflater.finished())
{
return decompressedLength;
}
else
{
int totalLength = decompressedLength;
while (!inflater.finished())
{
totalLength += inflater.inflate(dst, dstOff, dstLen);
}
return -totalLength;
}
}
finally
{
inflater.end();
}
}
/**
* Attempts to uncompress the data in the provided byte sequence into
* the provided byte string builder. Note that if uncompression was
* not successful, then the data in the destination buffer should be
* considered invalid.
*
* @param input
* The source data to be uncompressed.
* @param output
* The destination buffer to which the uncompressed data will
* be appended.
* @param uncompressedSize
* The uncompressed size of the data if known or 0 otherwise.
* @return true if decompression was successful or
* false otherwise.
* @throws java.util.zip.DataFormatException
* If a problem occurs while attempting to uncompress the
* data.
*/
public static boolean uncompress(ByteSequence input,
ByteStringBuilder output, int uncompressedSize)
throws DataFormatException
{
byte[] inputBytes = input.toByteArray();
byte[] outputBytes = new byte[uncompressedSize > 0 ? uncompressedSize
: 0];
int decompressResult = uncompress(inputBytes, 0, inputBytes.length,
outputBytes, 0, outputBytes.length);
if (decompressResult < 0)
{
// The destination buffer wasn't big enough. Resize and retry.
outputBytes = new byte[-decompressResult];
decompressResult = uncompress(inputBytes, 0, inputBytes.length,
outputBytes, 0, outputBytes.length);
}
if (decompressResult >= 0)
{
// It was successful.
output.append(outputBytes, 0, decompressResult);
return true;
}
// Still unsuccessful. Give up.
return false;
}
/**
* 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.
*/
private static char byteToASCII(byte b)
{
if (b >= 32 && b <= 126)
{
return (char) b;
}
return ' ';
}
private static char evaluateEscapedChar(SubstringReader reader,
char[] escapeChars) throws DecodeException
{
final char c1 = reader.read();
byte b;
switch (c1)
{
case '0':
b = 0x00;
break;
case '1':
b = 0x10;
break;
case '2':
b = 0x20;
break;
case '3':
b = 0x30;
break;
case '4':
b = 0x40;
break;
case '5':
b = 0x50;
break;
case '6':
b = 0x60;
break;
case '7':
b = 0x70;
break;
case '8':
b = (byte) 0x80;
break;
case '9':
b = (byte) 0x90;
break;
case 'A':
case 'a':
b = (byte) 0xA0;
break;
case 'B':
case 'b':
b = (byte) 0xB0;
break;
case 'C':
case 'c':
b = (byte) 0xC0;
break;
case 'D':
case 'd':
b = (byte) 0xD0;
break;
case 'E':
case 'e':
b = (byte) 0xE0;
break;
case 'F':
case 'f':
b = (byte) 0xF0;
break;
default:
if (c1 == 0x5C)
{
return c1;
}
if (escapeChars != null)
{
for (final char escapeChar : escapeChars)
{
if (c1 == escapeChar)
{
return c1;
}
}
}
final LocalizableMessage message = ERR_INVALID_ESCAPE_CHAR.get(
reader.getString(), c1);
throw DecodeException.error(message);
}
// The two positions must be the hex characters that
// comprise the escaped value.
if (reader.remaining() == 0)
{
final LocalizableMessage message = ERR_HEX_DECODE_INVALID_LENGTH
.get(reader.getString());
throw DecodeException.error(message);
}
final char c2 = reader.read();
switch (c2)
{
case '0':
// No action required.
break;
case '1':
b |= 0x01;
break;
case '2':
b |= 0x02;
break;
case '3':
b |= 0x03;
break;
case '4':
b |= 0x04;
break;
case '5':
b |= 0x05;
break;
case '6':
b |= 0x06;
break;
case '7':
b |= 0x07;
break;
case '8':
b |= 0x08;
break;
case '9':
b |= 0x09;
break;
case 'A':
case 'a':
b |= 0x0A;
break;
case 'B':
case 'b':
b |= 0x0B;
break;
case 'C':
case 'c':
b |= 0x0C;
break;
case 'D':
case 'd':
b |= 0x0D;
break;
case 'E':
case 'e':
b |= 0x0E;
break;
case 'F':
case 'f':
b |= 0x0F;
break;
default:
final LocalizableMessage message = ERR_HEX_DECODE_INVALID_CHARACTER
.get(new String(new char[] { c1, c2 }), c1);
throw DecodeException.error(message);
}
return (char) b;
}
// Prevent instantiation.
private StaticUtils()
{
// No implementation required.
}
}