package org.openamq.framing; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.mina.common.ByteBuffer; import java.nio.charset.Charset; public class EncodingUtils { private static final Logger _logger = LoggerFactory.getLogger(EncodingUtils.class); private static final String STRING_ENCODING = "iso8859-15"; private static final Charset _charset = Charset.forName("iso8859-15"); public static final int SIZEOF_UNSIGNED_SHORT = 2; public static final int SIZEOF_UNSIGNED_INT = 4; public static int encodedShortStringLength(String s) { if (s == null) { return 1; } else { return (short) (1 + s.length()); } } public static int encodedLongStringLength(String s) { if (s == null) { return 4; } else { return 4 + s.length(); } } public static int encodedLongStringLength(char[] s) { if (s == null) { return 4; } else { return 4 + s.length; } } public static int encodedLongstrLength(byte[] bytes) { if (bytes == null) { return 4; } else { return 4 + bytes.length; } } public static int encodedFieldTableLength(FieldTable table) { if (table == null) { // size is encoded as 4 octets return 4; } else { // size of the table plus 4 octets for the size return (int)table.getEncodedSize() + 4; } } public static void writeShortStringBytes(ByteBuffer buffer, String s) { if (s != null) { byte[] encodedString = new byte[s.length()]; char[] cha = s.toCharArray(); for (int i = 0; i < cha.length; i++) { encodedString[i] = (byte) cha[i]; } // TODO: check length fits in an unsigned byte writeUnsignedByte(buffer, (short)encodedString.length); buffer.put(encodedString); } else { // really writing out unsigned byte buffer.put((byte) 0); } } public static void writeLongStringBytes(ByteBuffer buffer, String s) { assert s == null || s.length() <= 0xFFFE; if (s != null) { int len = s.length(); writeUnsignedInteger(buffer, s.length()); byte[] encodedString = new byte[len]; char[] cha = s.toCharArray(); for (int i = 0; i < cha.length; i++) { encodedString[i] = (byte) cha[i]; } buffer.put(encodedString); } else { writeUnsignedInteger(buffer, 0); } } public static void writeLongStringBytes(ByteBuffer buffer, char[] s) { assert s == null || s.length <= 0xFFFE; if (s != null) { int len = s.length; writeUnsignedInteger(buffer, s.length); byte[] encodedString = new byte[len]; for (int i = 0; i < s.length; i++) { encodedString[i] = (byte) s[i]; } buffer.put(encodedString); } else { writeUnsignedInteger(buffer, 0); } } public static void writeLongStringBytes(ByteBuffer buffer, byte[] bytes) { assert bytes == null || bytes.length <= 0xFFFE; if (bytes != null) { writeUnsignedInteger(buffer, bytes.length); buffer.put(bytes); } else { writeUnsignedInteger(buffer, 0); } } public static void writeUnsignedByte(ByteBuffer buffer, short b) { byte bv = (byte) b; buffer.put(bv); } public static void writeUnsignedShort(ByteBuffer buffer, int s) { // TODO: Is this comparison safe? Do I need to cast RHS to long? if (s < Short.MAX_VALUE) { buffer.putShort((short)s); } else { short sv = (short) s; buffer.put((byte) (0xFF & (sv >> 8))); buffer.put((byte)(0xFF & sv)); } } public static void writeUnsignedInteger(ByteBuffer buffer, long l) { // TODO: Is this comparison safe? Do I need to cast RHS to long? if (l < Integer.MAX_VALUE) { buffer.putInt((int)l); } else { int iv = (int) l; // FIXME: This *may* go faster if we build this into a local 4-byte array and then // put the array in a single call. buffer.put((byte) (0xFF & (iv >> 24))); buffer.put((byte) (0xFF & (iv >> 16))); buffer.put((byte) (0xFF & (iv >> 8 ))); buffer.put((byte) (0xFF & iv)); } } public static void writeFieldTableBytes(ByteBuffer buffer, FieldTable table) { if (table != null) { table.writeToBuffer(buffer); } else { EncodingUtils.writeUnsignedInteger(buffer, 0); } } public static void writeBooleans(ByteBuffer buffer, boolean[] values) { byte packedValue = 0; for (int i = 0; i < values.length; i++) { if (values[i]) { packedValue = (byte)(packedValue | (1 << i)); } } buffer.put(packedValue); } /** * This is used for writing longstrs. * @param buffer * @param data */ public static void writeLongstr(ByteBuffer buffer, byte[] data) { if (data != null) { writeUnsignedInteger(buffer, data.length); buffer.put(data); } else { writeUnsignedInteger(buffer, 0); } } public static boolean[] readBooleans(ByteBuffer buffer) { byte packedValue = buffer.get(); boolean[] result = new boolean[8]; for (int i = 0; i < 8; i++) { result[i] = ((packedValue & (1 << i)) != 0); } return result; } public static FieldTable readFieldTable(ByteBuffer buffer) throws AMQFrameDecodingException { long length = buffer.getUnsignedInt(); if (length == 0) { return null; } else { return new FieldTable(buffer, length); } } public static String readShortString(ByteBuffer buffer) { byte length = buffer.get(); if (length == 0) { return null; } else { // this may seem rather odd to declare two array but testing has shown // that constructing a string from a byte array is 5 (five) times slower // than constructing one from a char array. // this approach here is valid since we know that all the chars are // ASCII (0-127) byte[] stringBytes = new byte[length]; buffer.get(stringBytes, 0, length); char[] stringChars = new char[length]; for (int i = 0; i < stringChars.length; i++) { stringChars[i] = (char) stringBytes[i]; } return new String(stringChars); } } public static String readLongString(ByteBuffer buffer) { long length = buffer.getUnsignedInt(); if (length == 0) { return null; } else { // this may seem rather odd to declare two array but testing has shown // that constructing a string from a byte array is 5 (five) times slower // than constructing one from a char array. // this approach here is valid since we know that all the chars are // ASCII (0-127) byte[] stringBytes = new byte[(int)length]; buffer.get(stringBytes, 0, (int)length); char[] stringChars = new char[(int)length]; for (int i = 0; i < stringChars.length; i++) { stringChars[i] = (char) stringBytes[i]; } return new String(stringChars); } } public static byte[] readLongstr(ByteBuffer buffer) throws AMQFrameDecodingException { long length = buffer.getUnsignedInt(); if (length == 0) { return null; } else { byte[] result = new byte[(int)length]; buffer.get(result); return result; } } // Will barf with a NPE on a null input. Not sure whether it should return null or // an empty field-table (which would be slower - perhaps unnecessarily). // // Some sample input and the result output: // // Input: "a=1" "a=2 c=3" "a=3 c=4 d" "a='four' b='five'" "a=bad" // // Parsing <a=1>... // {a=1} // Parsing <a=2 c=3>... // {a=2, c=3} // Parsing <a=3 c=4 d>... // {a=3, c=4, d=null} // Parsing <a='four' b='five'>... // {a=four, b=five} // Parsing <a=bad>... // java.lang.IllegalArgumentException: a: Invalid integer in <bad> from <a=bad>. // public static final FieldTable createFieldTableFromMessageSelector(String selector) { boolean debug = _logger.isDebugEnabled(); // TODO: Doesn't support embedded quotes properly. String[] expressions = selector.split(" +"); FieldTable result = new FieldTable(); for(int i = 0; i < expressions.length; i++) { String expr = expressions[i]; if (debug) _logger.debug("Expression = <" + expr + ">"); int equals = expr.indexOf('='); if (equals < 0) { // Existence check result.put("S" + expr.trim(),null); } else { String key = expr.substring(0,equals).trim(); String value = expr.substring(equals + 1).trim(); if (debug) _logger.debug("Key = <" + key + ">, Value = <" + value + ">"); if (value.charAt(0) == '\'') { if (value.charAt(value.length()- 1) != '\'') { throw new IllegalArgumentException(key + ": Missing quote in <" + value + "> from <" + selector + ">."); } else { value = value.substring(1,value.length() - 1); result.put("S" + key,value); } } else { try { int intValue = Integer.parseInt(value); result.put("i" + key,value); } catch(NumberFormatException e) { throw new IllegalArgumentException(key + ": Invalid integer in <" + value + "> from <" + selector + ">."); } } } } if (debug) _logger.debug("Field-table created from <" + selector + "> is <" + result + ">"); return(result); } static byte[] hexToByteArray(String id) { // Should check param for null, long enough for this check, upper-case and trailing char String s = (id.charAt(1) == 'x') ? id.substring(2) : id; // strip 0x int len = s.length(); int byte_len = len / 2; byte[] b = new byte[byte_len]; for(int i = 0; i < byte_len; i++) { // fixme: refine these repetitive subscript calcs. int ch = i * 2; byte b1 = Byte.parseByte(s.substring(ch,ch + 1),16); byte b2 = Byte.parseByte(s.substring(ch + 1,ch + 2),16); b[i] = (byte)(b1 * 16 + b2); } return(b); } public static char[] convertToHexCharArray(byte[] from) { int length = from.length; char[] result_buff = new char[length * 2 + 2]; result_buff[0] = '0'; result_buff[1] = 'x'; int bite; int dest = 2; for(int i = 0; i < length; i++) { bite = from[i]; if (bite < 0) bite += 256; result_buff[dest++] = hex_chars[bite >> 4]; result_buff[dest++] = hex_chars[bite & 0x0f]; } return(result_buff); } public static String convertToHexString(byte[] from) { return(new String(convertToHexCharArray(from))); } public static String convertToHexString(ByteBuffer bb) { int size = bb.limit(); byte[] from = new byte[size]; for(int i = 0; i < size; i++) { from[i] = bb.get(i); } return(new String(convertToHexCharArray(from))); } public static void main(String[] args) { for(int i = 0; i < args.length; i++) { String selector = args[i]; System.err.println("Parsing <" + selector + ">..."); try { System.err.println(createFieldTableFromMessageSelector(selector)); } catch(IllegalArgumentException e) { System.err.println(e); } } } private static char hex_chars[] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; }