diff options
Diffstat (limited to 'java/common/src/main/java/org/apache/qpid/framing/FieldTable.java')
-rw-r--r-- | java/common/src/main/java/org/apache/qpid/framing/FieldTable.java | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java b/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java new file mode 100644 index 0000000000..30f41205dd --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java @@ -0,0 +1,319 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.framing; + +import org.apache.log4j.Logger; +import org.apache.mina.common.ByteBuffer; + +import java.util.*; + +/** + * 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 static final Logger _logger = Logger.getLogger(FieldTable.class); + 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(); + final boolean debug = _logger.isDebugEnabled(); + 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; + default: + String msg = "Field '" + key + "' - 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()); + + if (debug) + { + _logger.debug("FieldTable::FieldTable(buffer," + length + "): Read type '" + type + "', key '" + key + "', value '" + value + "' (now read " + sizeRead + " of " + length + " encoded bytes)..."); + } + + // we deliberately want to call put in the parent class since we do + // not need to do the size calculations + super.put(key, value); + } + + if (debug) + { + _logger.debug("FieldTable::FieldTable(buffer," + length + "): Done."); + } + } + + public void writeToBuffer(ByteBuffer buffer) + { + final boolean debug = _logger.isDebugEnabled(); + + if (debug) + { + _logger.debug("FieldTable::writeToBuffer: Writing encoded size of " + _encodedSize + "..."); + } + + // 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 (debug) + { + _logger.debug("FieldTable::writeToBuffer: Writing key '" + key + "' of type " + value.getClass() + ", value '" + value + "'..."); + } + + 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("Key '" + key + "': Unsupported type in field table, type: " + ((value == null) ? "null-object" : value.getClass())); + } + } + + if (debug) + { + _logger.debug("FieldTable::writeToBuffer: Done."); + } + } + + public byte[] getDataAsBytes() + { + final ByteBuffer buffer = ByteBuffer.allocate((int) _encodedSize); // XXX: Is cast a problem? + 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 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) + { + final boolean debug = _logger.isDebugEnabled(); + + 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()); + } + + Object existing; + + if ((existing = super.remove(key)) != null) + { + if (debug) + { + _logger.debug("Found duplicate of key '" + key + "', previous value '" + existing + "' (" + existing.getClass() + "), to be replaced by '" + value + "', (" + value.getClass() + ") - stack trace of source of duplicate follows...", new Throwable().fillInStackTrace()); + } + + // If we are in effect deleting the value (see comment on null values being deleted + // below) then we also need to remove the name from the encoding length. + if (value == null) + { + _encodedSize -= EncodingUtils.encodedShortStringLength((String) key); + } + + // FIXME: Should be able to short-cut this process if the old and new values are + // the same object and/or type and size... + _encodedSize -= getEncodingSize(existing); + } + else + { + if (value != null) + { + _encodedSize += EncodingUtils.encodedShortStringLength((String) key); + } + } + + // For now: Setting a null value is the equivalent of deleting it. + // This is ambiguous in the JMS spec and needs thrashing out and potentially + // testing against other implementations. + if (value != null) + { + _encodedSize += getEncodingSize(value); + } + + 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); + + // This check is, for now, unnecessary (we don't store null values). + if (value != null) + { + _encodedSize -= getEncodingSize(value); + } + + return value; + } + else + { + return null; + } + } + + /** + * @return unsigned integer + */ + public long getEncodedSize() + { + return _encodedSize; + } + + /** + * @return integer + */ + private static int getEncodingSize(Object value) + { + int encodingSize; + + // the extra byte if for the type indicator that is written out + if (value instanceof String) + { + encodingSize = 1 + EncodingUtils.encodedLongStringLength((String) value); + } + else if (value instanceof char[]) + { + encodingSize = 1 + EncodingUtils.encodedLongStringLength((char[]) value); + } + else if (value instanceof Integer) + { + encodingSize = 1 + 4; + } + else if (value instanceof Long) + { + encodingSize = 1 + 4; + } + else + { + throw new IllegalArgumentException("Unsupported type in field table: " + value.getClass()); + } + + return encodingSize; + } + + public Enumeration keys() + { + return new FieldTableKeyEnumeration(this); + } +} |