summaryrefslogtreecommitdiff
path: root/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java
diff options
context:
space:
mode:
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.java319
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);
+ }
+}