package org.openamq.framing; import org.apache.mina.common.ByteBuffer; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; /** * From the protocol document: * field-table = short-integer *field-value-pair * field-value-pair = field-name field-value * field-name = short-string * field-value = 'S' long-string / 'I' long-integer / 'D' decimal-value / 'T' long-integer * decimal-value = decimals long-integer * decimals = OCTET */ public class FieldTable extends LinkedHashMap { private long _encodedSize = 0; public FieldTable() { super(); } /** * Construct a new field table. * @param buffer the buffer from which to read data. The length byte must be read already * @param length the length of the field table. Must be > 0. * @throws AMQFrameDecodingException if there is an error decoding the table */ public FieldTable(ByteBuffer buffer, long length) throws AMQFrameDecodingException { super(); assert length > 0; _encodedSize = length; int sizeRead = 0; while (sizeRead < _encodedSize) { int sizeRemaining = buffer.remaining(); final String key = EncodingUtils.readShortString(buffer); // TODO: use proper charset decoder byte iType = buffer.get(); final char type = (char)iType; Object value; switch (type) { case 'S': value = EncodingUtils.readLongString(buffer); break; case 'I': value = new Long(buffer.getUnsignedInt()); break; case 'V': value = null; break; default: String msg = "Unsupported field table type: " + type; //some extra debug information... msg += " (" + iType + "), length=" + length + ", sizeRead=" + sizeRead + ", sizeRemaining=" + sizeRemaining; throw new AMQFrameDecodingException(msg); } sizeRead += (sizeRemaining - buffer.remaining()); // we deliberately want to call put in the parent class since we do // not need to do the size calculations super.put(key, value); } } public void writeToBuffer(ByteBuffer buffer) { // write out the total length, which we have kept up to date as data is added EncodingUtils.writeUnsignedInteger(buffer, _encodedSize); final Iterator it = this.entrySet().iterator(); while (it.hasNext()) { Map.Entry me = (Map.Entry) it.next(); String key = (String) me.getKey(); EncodingUtils.writeShortStringBytes(buffer, key); Object value = me.getValue(); if (value == null ) { buffer.put((byte)'V'); } else if (value instanceof byte[]) { buffer.put((byte)'S'); EncodingUtils.writeLongstr(buffer, (byte[]) value); } else if (value instanceof String) { // TODO: look at using proper charset encoder buffer.put((byte)'S'); EncodingUtils.writeLongStringBytes(buffer, (String) value); } else if (value instanceof Long) { // TODO: look at using proper charset encoder buffer.put((byte)'I'); EncodingUtils.writeUnsignedInteger(buffer, ((Long)value).longValue()); } else { // Should never get here throw new IllegalArgumentException("Unsupported type in field table: " + value.getClass()); } } } public byte[] getDataAsBytes() { final ByteBuffer buffer = ByteBuffer.allocate((int)_encodedSize); final Iterator it = this.entrySet().iterator(); while (it.hasNext()) { Map.Entry me = (Map.Entry) it.next(); String key = (String) me.getKey(); EncodingUtils.writeShortStringBytes(buffer, key); Object value = me.getValue(); if (value == null ) { buffer.put((byte)'V'); } else if (value instanceof byte[]) { buffer.put((byte)'S'); EncodingUtils.writeLongstr(buffer, (byte[]) value); } else if (value instanceof String) { // TODO: look at using proper charset encoder buffer.put((byte)'S'); EncodingUtils.writeLongStringBytes(buffer, (String) value); } else if (value instanceof char[]) { // TODO: look at using proper charset encoder buffer.put((byte)'S'); EncodingUtils.writeLongStringBytes(buffer, (char[]) value); } else if (value instanceof Long || value instanceof Integer) { // TODO: look at using proper charset encoder buffer.put((byte)'I'); EncodingUtils.writeUnsignedInteger(buffer, ((Long)value).longValue()); } else { // Should never get here assert false; } } final byte[] result = new byte[(int)_encodedSize]; buffer.flip(); buffer.get(result); buffer.release(); return result; } public Object put(Object key, Object value) { if (key == null) { throw new IllegalArgumentException("All keys must be Strings - was passed: null"); } else if (!(key instanceof String)) { throw new IllegalArgumentException("All keys must be Strings - was passed: " + key.getClass()); } _encodedSize += EncodingUtils.encodedShortStringLength((String) key); // the extra byte if for the type indicator what is written out if (value == null) { _encodedSize += 1; } else if (value instanceof String) { _encodedSize += 1 + EncodingUtils.encodedLongStringLength((String) value); } else if (value instanceof char[]) { _encodedSize += 1 + EncodingUtils.encodedLongStringLength((char[]) value); } else if (value instanceof Integer) { _encodedSize += 1 + 4; } else if (value instanceof Long) { _encodedSize += 1 + 4; } else { throw new IllegalArgumentException("Unsupported type in field table: " + value.getClass()); } return super.put(key, value); } public Object remove(Object key) { if (super.containsKey(key)) { final Object value = super.remove(key); _encodedSize -= EncodingUtils.encodedShortStringLength((String) key); if (value != null) { if (value == null) { _encodedSize -= 1; } else if (value instanceof String) { _encodedSize -= 1 + EncodingUtils.encodedLongStringLength((String) value); } else if (value instanceof char[]) { _encodedSize -= 1 + EncodingUtils.encodedLongStringLength((char[]) value); } else if (value instanceof Integer) { _encodedSize -= 5; } else if (value instanceof Long) { _encodedSize -= 5; } else { throw new IllegalArgumentException("Internal error: unsupported type in field table: " + value.getClass()); } } return value; } else { return null; } } /** * * @return unsigned integer */ public long getEncodedSize() { return _encodedSize; } }