diff options
Diffstat (limited to 'java/common/src')
7 files changed, 820 insertions, 492 deletions
diff --git a/java/common/src/main/java/org/apache/qpid/framing/BasicContentHeaderProperties.java b/java/common/src/main/java/org/apache/qpid/framing/BasicContentHeaderProperties.java index 6a4c385033..a908c76286 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/BasicContentHeaderProperties.java +++ b/java/common/src/main/java/org/apache/qpid/framing/BasicContentHeaderProperties.java @@ -435,7 +435,7 @@ public class BasicContentHeaderProperties implements ContentHeaderProperties if (_headers == null) { - setHeaders(new FieldTable()); + setHeaders(FieldTableFactory.newFieldTable()); } return _headers; diff --git a/java/common/src/main/java/org/apache/qpid/framing/EncodingUtils.java b/java/common/src/main/java/org/apache/qpid/framing/EncodingUtils.java index 3a683b8e90..97fb434e1c 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/EncodingUtils.java +++ b/java/common/src/main/java/org/apache/qpid/framing/EncodingUtils.java @@ -286,7 +286,7 @@ public class EncodingUtils } else { - return new FieldTable(buffer, length); + return FieldTableFactory.newFieldTable(buffer, length); } } @@ -330,9 +330,9 @@ public class EncodingUtils // 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]; + 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]; @@ -350,7 +350,7 @@ public class EncodingUtils } else { - byte[] result = new byte[(int)length]; + byte[] result = new byte[(int) length]; buffer.get(result); return result; } @@ -388,7 +388,7 @@ public class EncodingUtils // TODO: Doesn't support embedded quotes properly. String[] expressions = selector.split(" +"); - FieldTable result = new FieldTable(); + FieldTable result = FieldTableFactory.newFieldTable(); for (int i = 0; i < expressions.length; i++) { @@ -481,7 +481,7 @@ public class EncodingUtils public static char[] convertToHexCharArray(byte[] from) { int length = from.length; - char[] result_buff = new char[length * 2 + 2]; + char[] result_buff = new char[length * 2 + 2]; result_buff[0] = '0'; result_buff[1] = 'x'; 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 index 796e1843af..44d0268561 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java +++ b/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java @@ -1,317 +1,96 @@ /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. + * 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 +import java.util.Map; +import java.util.Enumeration; + +public interface FieldTable extends Map { - 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; - } + void writeToBuffer(ByteBuffer buffer); + + void setFromBuffer(ByteBuffer buffer, long length) throws AMQFrameDecodingException; + + byte[] getDataAsBytes(); + + public long getEncodedSize(); + + Object put(Object key, Object value); + + Object remove(Object key); + + + public Enumeration getPropertyNames(); + + public boolean propertyExists(String propertyName); + + //Getters + + public Boolean getBoolean(String string); + + public Byte getByte(String string); + + public Short getShort(String string); + + public Integer getInteger(String string); + + public Long getLong(String string); + + public Float getFloat(String string); + + public Double getDouble(String string); + + public String getString(String string); + + public Character getCharacter(String string); + + public byte[] getBytes(String string); + + public Object getObject(String string); + + // Setters + public Object setBoolean(String string, boolean b); + + public Object setByte(String string, byte b); + + public Object setShort(String string, short i); + + public Object setInteger(String string, int i); + + public Object setLong(String string, long l); + + public Object setFloat(String string, float v); + + public Object setDouble(String string, double v); + + public Object setString(String string, String string1); + + public Object setChar(String string, char c); + + public Object setBytes(String string, byte[] bytes); + + public Object setBytes(String string, byte[] bytes, int start, int length); + + public Object setObject(String string, Object object); + } diff --git a/java/common/src/main/java/org/apache/qpid/framing/FieldTableFactory.java b/java/common/src/main/java/org/apache/qpid/framing/FieldTableFactory.java new file mode 100644 index 0000000000..1ec57da35b --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/framing/FieldTableFactory.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.mina.common.ByteBuffer; + +public class FieldTableFactory +{ + public static FieldTable newFieldTable() + { + return new PropertyFieldTable(); + } + + public static FieldTable newFieldTable(ByteBuffer byteBuffer, long length) throws AMQFrameDecodingException + { + return new PropertyFieldTable(byteBuffer, length); + } + + public static PropertyFieldTable newFieldTable(String text) + { + return new PropertyFieldTable(text); + } +} diff --git a/java/common/src/main/java/org/apache/qpid/framing/FieldTableKeyEnumeration.java b/java/common/src/main/java/org/apache/qpid/framing/FieldTableKeyEnumeration.java deleted file mode 100644 index e3ba9080c7..0000000000 --- a/java/common/src/main/java/org/apache/qpid/framing/FieldTableKeyEnumeration.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 java.util.Enumeration; -import java.util.Iterator; - - -public class FieldTableKeyEnumeration implements Enumeration -{ - protected FieldTable _table; - protected Iterator _iterator; - - public FieldTableKeyEnumeration(FieldTable ft) - { - _table = ft; - _iterator = ft.keySet().iterator(); - } - - public boolean hasMoreElements() - { - return _iterator.hasNext(); - } - - public Object nextElement() - { - return _iterator.next(); - } -} diff --git a/java/common/src/main/java/org/apache/qpid/framing/PropertyFieldTable.java b/java/common/src/main/java/org/apache/qpid/framing/PropertyFieldTable.java index 96360e4aaa..36558011ac 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/PropertyFieldTable.java +++ b/java/common/src/main/java/org/apache/qpid/framing/PropertyFieldTable.java @@ -21,20 +21,28 @@ package org.apache.qpid.framing; import org.apache.log4j.Logger; +import org.apache.mina.common.ByteBuffer; +import java.util.Collection; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; import java.util.StringTokenizer; import java.util.Vector; //extends FieldTable -public class PropertyFieldTable +public class PropertyFieldTable implements FieldTable, Map { - private static final Logger _logger = Logger.getLogger(PropertyFieldTable.class); + + public static final char AMQP_DECIMAL_PROPERTY_PREFIX = 'D'; + public static final char AMQP_UNSIGNEDINT_PROPERTY_PREFIX = 'I'; + public static final char AMQP_TIMESTAMP_PROPERTY_PREFIX = 'T'; + public static final char AMQP_STRING_PROPERTY_PREFIX = 'S'; + public static final char BOOLEAN_PROPERTY_PREFIX = 'B'; public static final char BYTE_PROPERTY_PREFIX = 'b'; public static final char SHORT_PROPERTY_PREFIX = 's'; @@ -42,10 +50,12 @@ public class PropertyFieldTable public static final char LONG_PROPERTY_PREFIX = 'l'; public static final char FLOAT_PROPERTY_PREFIX = 'f'; public static final char DOUBLE_PROPERTY_PREFIX = 'd'; - public static final char STRING_PROPERTY_PREFIX = 'S'; + public static final char STRING_PROPERTY_PREFIX = AMQP_STRING_PROPERTY_PREFIX; public static final char CHAR_PROPERTY_PREFIX = 'c'; public static final char BYTES_PROPERTY_PREFIX = 'y'; + //Our custom prefix for encoding across the wire + private static final char XML_PROPERTY_PREFIX = 'X'; private static final String BOOLEAN = "boolean"; private static final String BYTE = "byte"; @@ -66,7 +76,7 @@ public class PropertyFieldTable private LinkedHashMap<String, Object> _properties; private LinkedHashMap<String, String> _propertyNamesTypeMap; - + private long _encodedSize = 0; public PropertyFieldTable() { @@ -84,73 +94,167 @@ public class PropertyFieldTable } catch (Exception e) { - System.out.println(textFormat); - e.printStackTrace(); + _logger.error("Unable to decode PropertyFieldTable format:" + textFormat, e); } + } + /** + * 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 PropertyFieldTable(ByteBuffer buffer, long length) throws AMQFrameDecodingException + { + this(); + setFromBuffer(buffer, length); } // ************ Getters + private Object get(String propertyName, char prefix) + { + String type = _propertyNamesTypeMap.get(propertyName); + + if (type == null) + { + return null; + } + + if (type.equals("" + prefix)) + { + return _properties.get(propertyName); + } + else + { + return null; + } + } + public Boolean getBoolean(String string) { - return (Boolean) _properties.get(BOOLEAN_PROPERTY_PREFIX + string); + Object o = get(string, BOOLEAN_PROPERTY_PREFIX); + if (o != null) + { + return (Boolean) o; + } + else + { + return null; + } } public Byte getByte(String string) { - return (Byte) _properties.get(BYTE_PROPERTY_PREFIX + string); + Object o = get(string, BYTE_PROPERTY_PREFIX); + if (o != null) + { + return (Byte) o; + } + else + { + return null; + } } public Short getShort(String string) { - return (Short) _properties.get(SHORT_PROPERTY_PREFIX + string); + Object o = get(string, SHORT_PROPERTY_PREFIX); + if (o != null) + { + return (Short) o; + } + else + { + return null; + } } public Integer getInteger(String string) { - return (Integer) _properties.get(INT_PROPERTY_PREFIX + string); + Object o = get(string, INT_PROPERTY_PREFIX); + if (o != null) + { + return (Integer) o; + } + else + { + return null; + } } public Long getLong(String string) { - return (Long) _properties.get(LONG_PROPERTY_PREFIX + string); + Object o = get(string, LONG_PROPERTY_PREFIX); + if (o != null) + { + return (Long) o; + } + else + { + return null; + } } public Float getFloat(String string) { - return (Float) _properties.get(FLOAT_PROPERTY_PREFIX + string); + Object o = get(string, FLOAT_PROPERTY_PREFIX); + if (o != null) + { + return (Float) o; + } + else + { + return null; + } } public Double getDouble(String string) { - return (Double) _properties.get(DOUBLE_PROPERTY_PREFIX + string); + Object o = get(string, DOUBLE_PROPERTY_PREFIX); + if (o != null) + { + return (Double) o; + } + else + { + return null; + } } public String getString(String string) { - return (String) _properties.get(STRING_PROPERTY_PREFIX + string); + Object o = get(string, STRING_PROPERTY_PREFIX); + if (o != null) + { + return (String) o; + } + else + { + return null; + } } public Character getCharacter(String string) { - return (Character) _properties.get(CHAR_PROPERTY_PREFIX + string); + Object o = get(string, CHAR_PROPERTY_PREFIX); + if (o != null) + { + return (Character) o; + } + else + { + return null; + } } public byte[] getBytes(String string) { - return (byte[]) _properties.get(BYTES_PROPERTY_PREFIX + string); - } - - public Object getObject(String string) - { - String typestring = _propertyNamesTypeMap.get(string); - - if (typestring != null && !typestring.equals("")) + Object o = get(string, BYTES_PROPERTY_PREFIX); + if (o != null) { - char type = typestring.charAt(0); - - return _properties.get(type + string); + return (byte[]) o; } else { @@ -158,91 +262,66 @@ public class PropertyFieldTable } } - // ************ Setters - - - public void setBoolean(String string, boolean b) + public Object getObject(String string) { - checkPropertyName(string, BOOLEAN_PROPERTY_PREFIX); + return _properties.get(string); + } + // ************ Setters - _propertyNamesTypeMap.put(string, "" + BOOLEAN_PROPERTY_PREFIX); - _properties.put(BOOLEAN_PROPERTY_PREFIX + string, b);// ? new Long(1) : new Long(0)); + public Object setBoolean(String string, boolean b) + { + return put(BOOLEAN_PROPERTY_PREFIX + string, b); } - public void setByte(String string, byte b) + public Object setByte(String string, byte b) { - checkPropertyName(string, BYTE_PROPERTY_PREFIX); - - - _properties.put(BYTE_PROPERTY_PREFIX + string, b); + return put(BYTE_PROPERTY_PREFIX + string, b); } - public void setShort(String string, short i) + public Object setShort(String string, short i) { - checkPropertyName(string, SHORT_PROPERTY_PREFIX); - - - _properties.put(SHORT_PROPERTY_PREFIX + string, i); + return put(SHORT_PROPERTY_PREFIX + string, i); } - public void setInteger(String string, int i) + public Object setInteger(String string, int i) { - checkPropertyName(string, INT_PROPERTY_PREFIX); - - - _properties.put(INT_PROPERTY_PREFIX + string, i); + return put(INT_PROPERTY_PREFIX + string, i); } - public void setLong(String string, long l) + public Object setLong(String string, long l) { - checkPropertyName(string, LONG_PROPERTY_PREFIX); - - - _properties.put(LONG_PROPERTY_PREFIX + string, l); + return put(LONG_PROPERTY_PREFIX + string, l); } - public void setFloat(String string, float v) + public Object setFloat(String string, float v) { - checkPropertyName(string, FLOAT_PROPERTY_PREFIX); - - - _properties.put(FLOAT_PROPERTY_PREFIX + string, v); + return put(FLOAT_PROPERTY_PREFIX + string, v); } - public void setDouble(String string, double v) + public Object setDouble(String string, double v) { - checkPropertyName(string, DOUBLE_PROPERTY_PREFIX); - - - _properties.put(DOUBLE_PROPERTY_PREFIX + string, v); + return put(DOUBLE_PROPERTY_PREFIX + string, v); } - public void setString(String string, String string1) + public Object setString(String string, String string1) { - checkPropertyName(string, STRING_PROPERTY_PREFIX); - - - _properties.put(STRING_PROPERTY_PREFIX + string, string1); + return put(STRING_PROPERTY_PREFIX + string, string1); } - public void setChar(String string, char c) + public Object setChar(String string, char c) { - checkPropertyName(string, CHAR_PROPERTY_PREFIX); - - _properties.put(CHAR_PROPERTY_PREFIX + string, c); + return put(CHAR_PROPERTY_PREFIX + string, c); } - public void setBytes(String string, byte[] bytes) + public Object setBytes(String string, byte[] bytes) { - setBytes(string, bytes, 0, bytes.length); + return setBytes(string, bytes, 0, bytes.length); } - public void setBytes(String string, byte[] bytes, int start, int length) + public Object setBytes(String string, byte[] bytes, int start, int length) { - checkPropertyName(string, BYTES_PROPERTY_PREFIX); - - _properties.put(BYTES_PROPERTY_PREFIX + string, sizeByteArray(bytes, start, length)); + return put(BYTES_PROPERTY_PREFIX + string, sizeByteArray(bytes, start, length)); } private byte[] sizeByteArray(byte[] bytes, int start, int length) @@ -259,65 +338,65 @@ public class PropertyFieldTable } - public void setObject(String string, Object object) + public Object setObject(String string, Object object) { if (object instanceof Boolean) { - setBoolean(string, (Boolean) object); + return setBoolean(string, (Boolean) object); } else { if (object instanceof Byte) { - setByte(string, (Byte) object); + return setByte(string, (Byte) object); } else { if (object instanceof Short) { - setShort(string, (Short) object); + return setShort(string, (Short) object); } else { if (object instanceof Integer) { - setInteger(string, (Integer) object); + return setInteger(string, (Integer) object); } else { if (object instanceof Long) { - setLong(string, (Long) object); + return setLong(string, (Long) object); } else { if (object instanceof Float) { - setFloat(string, (Float) object); + return setFloat(string, (Float) object); } else { if (object instanceof Double) { - setDouble(string, (Double) object); + return setDouble(string, (Double) object); } else { if (object instanceof String) { - setString(string, (String) object); + return setString(string, (String) object); } else { if (object instanceof Character) { - setChar(string, (Character) object); + return setChar(string, (Character) object); } else { if (object instanceof byte[]) { - setBytes(string, (byte[]) object); + return setBytes(string, (byte[]) object); } } } @@ -328,8 +407,7 @@ public class PropertyFieldTable } } } - - + return null; } // ***** Methods @@ -344,12 +422,16 @@ public class PropertyFieldTable { String key = (String) keys.next(); - names.add(key.substring(1)); + names.add(key); } return names.elements(); } + public boolean propertyExists(String propertyName) + { + return _propertyNamesTypeMap.containsKey(propertyName); + } public boolean itemExists(String string) { @@ -367,7 +449,6 @@ public class PropertyFieldTable return false; } - public String toString() { return valueOf(this); @@ -390,35 +471,55 @@ public class PropertyFieldTable else { buf.append('\n'); - buf.append(propertyXML(propertyName, true)); - if (propertyName.charAt(0) == BYTES_PROPERTY_PREFIX) - { - //remove '>' - buf.deleteCharAt(buf.length() - 1); + buf.append(valueAsXML(table._propertyNamesTypeMap.get(propertyName) + propertyName, entry.getValue())); + } + } + buf.append("\n"); + buf.append(PROPERTY_FIELD_TABLE_CLOSE_XML); - byte[] bytes = (byte[]) entry.getValue(); - buf.append(" length='").append(bytes.length).append("'>"); + return buf.toString(); + } - buf.append(byteArrayToXML(propertyName.substring(1), bytes)); - } - else - { + private static String valueAsXML(String name, Object value) + { + char propertyPrefix = name.charAt(0); + String propertyName = name.substring(1); - buf.append(String.valueOf(entry.getValue())); - } - buf.append(propertyXML(propertyName, false)); - } + StringBuffer buf = new StringBuffer(); + // Start Tag + buf.append(propertyXML(name, true)); + + // Value + if (propertyPrefix == BYTES_PROPERTY_PREFIX) + { + //remove '>' + buf.deleteCharAt(buf.length() - 1); + + byte[] bytes = (byte[]) value; + buf.append(" length='").append(bytes.length).append("'>"); + + buf.append(byteArrayToXML(propertyName, bytes)); + } + else + { + buf.append(String.valueOf(value)); } - buf.append("\n"); - buf.append(PROPERTY_FIELD_TABLE_CLOSE_XML); + + //End Tag + buf.append(propertyXML(name, false)); return buf.toString(); } - private void checkPropertyName(String propertyName, char propertyPrefix) + private Object checkPropertyName(String name) { + String propertyName = name.substring(1); + char propertyPrefix = name.charAt(0); + + Object previous = null; + if (propertyName == null) { throw new IllegalArgumentException("Property name must not be null"); @@ -432,15 +533,29 @@ public class PropertyFieldTable if (currentValue != null) { - _properties.remove(currentValue + propertyName); + previous = _properties.remove(currentValue + propertyName); + + // 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 (previous == null) + { + _encodedSize -= EncodingUtils.encodedShortStringLength(propertyName); + } + + // 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(currentValue + propertyName, previous); } _propertyNamesTypeMap.put(propertyName, "" + propertyPrefix); + + return previous; } - private static String propertyXML(String propertyName, boolean start) + private static String propertyXML(String name, boolean start) { - char typeIdentifier = propertyName.charAt(0); + char propertyPrefix = name.charAt(0); + String propertyName = name.substring(1); StringBuffer buf = new StringBuffer(); @@ -453,8 +568,7 @@ public class PropertyFieldTable buf.append("</"); } - - switch (typeIdentifier) + switch (propertyPrefix) { case BOOLEAN_PROPERTY_PREFIX: buf.append(BOOLEAN); @@ -487,14 +601,13 @@ public class PropertyFieldTable buf.append(CHAR); break; default: - buf.append(UNKNOWN + " (identifier ").append(typeIdentifier).append(")"); + buf.append(UNKNOWN + " (identifier ").append(propertyPrefix).append(")"); break; } - if (start) { - buf.append(" name='").append(propertyName.substring(1)).append("'"); + buf.append(" name='").append(propertyName).append("'"); } buf.append(">"); @@ -519,8 +632,6 @@ public class PropertyFieldTable private void processBytesXMLLine(String xmlline) { - String type = xmlline.substring(1, xmlline.indexOf(" ")); - String propertyName = xmlline.substring(xmlline.indexOf('\'') + 1, xmlline.indexOf('\'', xmlline.indexOf('\'') + 1)); String value = xmlline.substring(xmlline.indexOf(">") + 1, @@ -545,7 +656,6 @@ public class PropertyFieldTable { String token = tokenizer.nextToken(); - if (token.equals(PROPERTY_FIELD_TABLE_CLOSE_XML) || token.equals(BYTES_CLOSE_XML)) { @@ -555,7 +665,6 @@ public class PropertyFieldTable if (token.equals(BYTES_CLOSE_XML)) { processing_bytes = false; - } if (processing) @@ -578,11 +687,9 @@ public class PropertyFieldTable { processing = true; } - } } - private void processXMLLine(String xmlline) { // <<type> name='<property>'><value></<type>> @@ -611,11 +718,39 @@ public class PropertyFieldTable } if (type.equals(BYTES)) { - Integer length = Integer.parseInt(xmlline.substring( - xmlline.lastIndexOf("=") + 2 - , xmlline.lastIndexOf("'"))); + int headerEnd = xmlline.indexOf('>'); + String bytesHeader = xmlline.substring(0, headerEnd); + + //Extract length value + Integer length = Integer.parseInt(bytesHeader.substring( + bytesHeader.lastIndexOf("=") + 2 + , bytesHeader.lastIndexOf("'"))); + + byte[] bytes = new byte[length]; setBytes(propertyName, bytes); + + //Check if the line contains all the byte values + // This is needed as the XMLLine sent across the wire is the bytes value + + int byteStart = xmlline.indexOf('<', headerEnd); + + if (byteStart > 0) + { + while (!xmlline.startsWith(BYTES_CLOSE_XML, byteStart)) + { + //This should be the next byte line + int bytePrefixEnd = xmlline.indexOf('>', byteStart) + 1; + int byteEnd = xmlline.indexOf('>', bytePrefixEnd) + 1; + + String byteline = xmlline.substring(byteStart, byteEnd); + + processBytesXMLLine(byteline); + + byteStart = xmlline.indexOf('<', byteEnd); + } + } + } if (type.equals(SHORT)) { @@ -651,6 +786,311 @@ public class PropertyFieldTable } } + // ************************* Byte Buffer Processing -} + public void writeToBuffer(ByteBuffer buffer) + { + final boolean debug = _logger.isDebugEnabled(); + + if (debug) + { + _logger.debug("FieldTable::writeToBuffer: Writing encoded size of " + _encodedSize + "..."); + } + + EncodingUtils.writeUnsignedInteger(buffer, _encodedSize); + + putDataInBuffer(buffer); + } + + public byte[] getDataAsBytes() + { + final ByteBuffer buffer = ByteBuffer.allocate((int) _encodedSize); // FIXME XXX: Is cast a problem? + + putDataInBuffer(buffer); + + final byte[] result = new byte[(int) _encodedSize]; + buffer.flip(); + buffer.get(result); + buffer.release(); + return result; + } + + public int size() + { + return _properties.size(); + } + + public boolean isEmpty() + { + return _properties.isEmpty(); + } + + public boolean containsKey(Object key) + { + return _properties.containsKey(key); + } + + public boolean containsValue(Object value) + { + return _properties.containsValue(value); + } + + public Object get(Object key) + { + return _properties.get(key); + } + + + public Object put(Object key, Object value) + { + return setObject(key.toString(), value); + } + + protected Object put(String key, Object value) + { + Object previous = checkPropertyName(key); + + + String propertyName = key.substring(1); + char propertyPrefix = _propertyNamesTypeMap.get(propertyName).charAt(0); + + if (value != null) + { + //Add the size of the propertyName + _encodedSize += EncodingUtils.encodedShortStringLength(propertyName); + + // 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. + + //Add the size of the content + _encodedSize += getEncodingSize(key, value); + } + + _properties.put((String) propertyName, value); + + return previous; + } + + public Object remove(Object key) + { + if (key instanceof String) + { + throw new IllegalArgumentException("Property key be a string"); + } + + char propertyPrefix = ((String) key).charAt(0); + + if (_properties.containsKey(key)) + { + final Object value = _properties.remove(key); + // plus one for the type + _encodedSize -= EncodingUtils.encodedShortStringLength(((String) key)); + + // This check is, for now, unnecessary (we don't store null values). + if (value != null) + { + _encodedSize -= getEncodingSize(propertyPrefix + (String) key, value); + } + + return value; + } + else + { + return null; + } + } + + public void putAll(Map t) + { + Iterator it = t.keySet().iterator(); + + while (it.hasNext()) + { + Object key = it.next(); + put(key, t.get(key)); + } + } + + public void clear() + { + _properties.clear(); + _propertyNamesTypeMap.clear(); + } + + public Set keySet() + { + return _properties.keySet(); + } + + public Collection values() + { + return _properties.values(); + } + + public Set entrySet() + { + return _properties.entrySet(); + } + + public long getEncodedSize() + { + return _encodedSize; + } + + + private void putDataInBuffer(ByteBuffer buffer) + { + final Iterator it = _properties.entrySet().iterator(); + + //If there are values then write out the encoded Size... could check _encodedSize != 0 + // write out the total length, which we have kept up to date as data is added + + + while (it.hasNext()) + { + Map.Entry me = (Map.Entry) it.next(); + String propertyName = (String) me.getKey(); + + //The type value + char propertyPrefix = _propertyNamesTypeMap.get(propertyName).charAt(0); + //The actual param name skipping type + + EncodingUtils.writeShortStringBytes(buffer, propertyName); + Object value = me.getValue(); + + switch (propertyPrefix) + { + + case STRING_PROPERTY_PREFIX: + // TODO: look at using proper charset encoder + buffer.put((byte) STRING_PROPERTY_PREFIX); + EncodingUtils.writeLongStringBytes(buffer, (String) value); + break; + + case AMQP_UNSIGNEDINT_PROPERTY_PREFIX: + case LONG_PROPERTY_PREFIX: + case INT_PROPERTY_PREFIX: + case BOOLEAN_PROPERTY_PREFIX: + case BYTE_PROPERTY_PREFIX: + case SHORT_PROPERTY_PREFIX: + case FLOAT_PROPERTY_PREFIX: + case DOUBLE_PROPERTY_PREFIX: + case CHAR_PROPERTY_PREFIX: + case BYTES_PROPERTY_PREFIX: + case XML_PROPERTY_PREFIX: + // Encode as XML + buffer.put((byte) XML_PROPERTY_PREFIX); + EncodingUtils.writeLongStringBytes(buffer, valueAsXML(propertyPrefix + propertyName, value)); + break; + default: + { + + // Should never get here + throw new IllegalArgumentException("Key '" + propertyName + "': Unsupported type in field table, type: " + ((value == null) ? "null-object" : value.getClass())); + } + } + } + } + + + public void setFromBuffer(ByteBuffer buffer, long length) throws AMQFrameDecodingException + { + final boolean debug = _logger.isDebugEnabled(); + + int sizeRead = 0; + while (sizeRead < length) + { + 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 = null; + + switch (type) + { + case STRING_PROPERTY_PREFIX: + value = EncodingUtils.readLongString(buffer); + break; + case LONG_PROPERTY_PREFIX: + case INT_PROPERTY_PREFIX: + case BOOLEAN_PROPERTY_PREFIX: + case BYTE_PROPERTY_PREFIX: + case SHORT_PROPERTY_PREFIX: + case FLOAT_PROPERTY_PREFIX: + case DOUBLE_PROPERTY_PREFIX: + case CHAR_PROPERTY_PREFIX: + case BYTES_PROPERTY_PREFIX: + case XML_PROPERTY_PREFIX: + processXMLLine(EncodingUtils.readLongString(buffer)); + 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::PropFieldTable(buffer," + length + "): Read type '" + type + "', key '" + key + "', value '" + value + "' (now read " + sizeRead + " of " + length + " encoded bytes)..."); + } + + if (type != XML_PROPERTY_PREFIX) + { + setObject(key, value); + } + } + + if (debug) + { + _logger.debug("FieldTable::FieldTable(buffer," + length + "): Done."); + } + } + + + /** + * @param name the property name with type prefix + * @param value the property value + * @return integer + */ + private static int getEncodingSize(String name, Object value) + { + int encodingSize; + + char propertyPrefix = name.charAt(0); + + switch (propertyPrefix) + { + // the extra byte if for the type indicator that is written out + case STRING_PROPERTY_PREFIX: + encodingSize = 1 + EncodingUtils.encodedLongStringLength((String) value); + break; + case LONG_PROPERTY_PREFIX: + case INT_PROPERTY_PREFIX: + case BOOLEAN_PROPERTY_PREFIX: + case BYTE_PROPERTY_PREFIX: + case SHORT_PROPERTY_PREFIX: + case FLOAT_PROPERTY_PREFIX: + case DOUBLE_PROPERTY_PREFIX: + case CHAR_PROPERTY_PREFIX: + case BYTES_PROPERTY_PREFIX: + case XML_PROPERTY_PREFIX: + encodingSize = 1 + EncodingUtils.encodedLongStringLength(valueAsXML(name, value)); + break; + default: + //encodingSize = 1 + EncodingUtils.encodedLongStringLength(String.valueOf(value)); + // We are using XML String encoding + throw new IllegalArgumentException("Unsupported type in field table: " + value.getClass()); + } + +// the extra byte for the type indicator is calculated in the name + return encodingSize; + } + + +} diff --git a/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java b/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java index 5070b6ecb1..0b6820b8a9 100644 --- a/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java +++ b/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java @@ -25,6 +25,10 @@ import junit.framework.TestCase; import java.util.Enumeration; +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.ByteBufferProxy; +import org.apache.mina.common.support.BaseByteBuffer; + public class PropertyFieldTableTest extends TestCase { @@ -206,7 +210,7 @@ public class PropertyFieldTableTest extends TestCase PropertyFieldTable table2 = new PropertyFieldTable(table1XML); - Assert.assertEquals(table1XML, table2.toString()); + Assert.assertEquals(table1XML, table2.toString()); } public void testKeyEnumeration() @@ -273,6 +277,117 @@ public class PropertyFieldTableTest extends TestCase Assert.assertEquals(Short.MAX_VALUE, table.getObject("object-short")); } + + public void testwriteBuffer() + { + byte[] bytes = {99, 98, 97, 96, 95}; + + PropertyFieldTable table = new PropertyFieldTable(); + table.setBoolean("bool", true); + table.setByte("byte", Byte.MAX_VALUE); + + table.setBytes("bytes", bytes); + table.setChar("char", 'c'); + table.setDouble("double", Double.MAX_VALUE); + table.setFloat("float", Float.MAX_VALUE); + table.setInteger("int", Integer.MAX_VALUE); + table.setLong("long", Long.MAX_VALUE); + table.setShort("short", Short.MAX_VALUE); + + + final ByteBuffer buffer = ByteBuffer.allocate((int) table.getEncodedSize()); // FIXME XXX: Is cast a problem? + + table.writeToBuffer(buffer); + + buffer.flip(); + + long length = buffer.getUnsignedInt(); + + try + { + PropertyFieldTable table2 = new PropertyFieldTable(buffer, length); + + Assert.assertEquals((Boolean) true, table2.getBoolean("bool")); + Assert.assertEquals((Byte) Byte.MAX_VALUE, table2.getByte("byte")); + assertBytesEqual(bytes, table2.getBytes("bytes")); + Assert.assertEquals((Character) 'c', table2.getCharacter("char")); + Assert.assertEquals(Double.MAX_VALUE, table2.getDouble("double")); + Assert.assertEquals(Float.MAX_VALUE, table2.getFloat("float")); + Assert.assertEquals((Integer) Integer.MAX_VALUE, table2.getInteger("int")); + Assert.assertEquals((Long) Long.MAX_VALUE, table2.getLong("long")); + Assert.assertEquals((Short) Short.MAX_VALUE, table2.getShort("short")); + } + catch (AMQFrameDecodingException e) + { + e.printStackTrace(); + fail("PFT should be instantiated from bytes." + e.getCause()); + } + } + + public void testEncodingSize() + { + FieldTable result = FieldTableFactory.newFieldTable(); + int size = 0; + result.put("one", 1L); + // size is 1(size) + bytes for short string + size = 1 + 3; // 1 + key length + // or size is 1(the type) + number of bytes (4bytes worth) + bytes + size += 1 + 4; // 1 + 4 + value length + size += "<long name='one'>1</long>".length(); // this is the xml encoding for a long. + assertEquals(size, result.getEncodedSize()); + + result.put("two", 2L); + size += 1 + 3; // 1 + key length + size += 1 + 4; // 1 + 4 + value length + size += "<long name='two'>2</long>".length(); // this is the xml encoding for a long. + assertEquals(size, result.getEncodedSize()); + + result.put("three", 3L); + size += 1 + 5; // 1 + key length + size += 1 + 4; // 1 + 4 + value length + size += "<long name='three'>3</long>".length(); // this is the xml encoding for a long. + assertEquals(size, result.getEncodedSize()); + + result.put("four", 4L); + size += 1 + 4; // 1 + key length + size += 1 + 4; // 1 + 4 + value length + size += "<long name='four'>4</long>".length(); // this is the xml encoding for a long. + assertEquals(size, result.getEncodedSize()); + + result.put("five", 5L); + size += 1 + 4; // 1 + key length + size += 1 + 4; // 1 + 4 + value length + size += "<long name='five'>5</long>".length(); // this is the xml encoding for a long. + assertEquals(size, result.getEncodedSize()); + + //fixme should perhaps be expanded to incorporate all types. + + final ByteBuffer buffer = ByteBuffer.allocate((int) result.getEncodedSize()); // FIXME XXX: Is cast a problem? + + result.writeToBuffer(buffer); + + buffer.flip(); + + long length = buffer.getUnsignedInt(); + + try + { + PropertyFieldTable table2 = new PropertyFieldTable(buffer, length); + + Assert.assertEquals((Long) 1L, table2.getLong("one")); + Assert.assertEquals((Long) 2L, table2.getLong("two")); + Assert.assertEquals((Long) 3L, table2.getLong("three")); + Assert.assertEquals((Long) 4L, table2.getLong("four")); + Assert.assertEquals((Long) 5L, table2.getLong("five")); + } + catch (AMQFrameDecodingException e) + { + e.printStackTrace(); + fail("PFT should be instantiated from bytes." + e.getCause()); + } + + } + private void assertBytesEqual(byte[] expected, byte[] actual) { Assert.assertEquals(expected.length, actual.length); |