/* * 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. } }