Modified: incubator/qpid/trunk/qpid/dotnet/Qpid.Common/Framing/FieldTable.cs URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/dotnet/Qpid.Common/Framing/FieldTable.cs?view=diff&rev=507096&r1=507095&r2=507096 ============================================================================== --- incubator/qpid/trunk/qpid/dotnet/Qpid.Common/Framing/FieldTable.cs (original) +++ incubator/qpid/trunk/qpid/dotnet/Qpid.Common/Framing/FieldTable.cs Tue Feb 13 08:56:03 2007 @@ -21,153 +21,274 @@ using System; using System.Collections; using System.Text; +using log4net; using Qpid.Buffer; using Qpid.Collections; using Qpid.Messaging; namespace Qpid.Framing { - /// - /// 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 : IFieldTable + public class FieldTable : IFieldTable, IEnumerable { - IDictionary _hash = new LinkedHashtable(); - - private uint _encodedSize = 0; + private static readonly ILog _log = LogManager.GetLogger(typeof(FieldTable)); + + IDictionary _properties; + private ByteBuffer _encodedForm; + private object _syncLock; + private uint _encodedSize; public FieldTable() { + _syncLock = new object(); } - /** - * 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, uint length) - { - _encodedSize = length; - int sizeRead = 0; - while (sizeRead < _encodedSize) - { - int sizeRemaining = buffer.remaining(); - string key = EncodingUtils.ReadShortString(buffer); - // TODO: use proper charset decoder - char type = (char)buffer.get(); - object value; - switch (type) - { - case 'S': - value = EncodingUtils.ReadLongString(buffer); - break; - case 'I': - value = buffer.GetUnsignedInt(); - break; - default: - throw new AMQFrameDecodingException("Unsupported field table type: '" + type + "' charcode" + (int)type); - } - sizeRead += (sizeRemaining - buffer.remaining()); - - _hash.Add(key, value); - } + /// <summary> + /// Construct a new field table. + /// </summary> + /// <param name="buffer">the buffer from which to read data. The length byte must be read already</param> + /// <param name="length">the length of the field table. Must be > 0.</param> + /// <exception cref="AMQFrameDecodingException">if there is an error decoding the table</exception> + public FieldTable(ByteBuffer buffer, uint length) : this() + { + _encodedForm = buffer.slice(); + _encodedForm.limit((int)length); + _encodedSize = length; + buffer.skip((int)length); + } + + /// <summary> + /// The set of all property names + /// </summary> + public ICollection Keys + { + get + { + InitMapIfNecessary(); + return _properties.Keys; + } } + /// <summary> + /// Calculated size of this field table once encoded + /// </summary> public uint EncodedSize { - get - { - return _encodedSize; - } + get { return _encodedSize; } } + /// <summary> + /// Number of properties in the field table + /// </summary> public int Count { - get { return _hash.Count; } + get + { + InitMapIfNecessary(); + return _properties.Count; + } } + /// <summary> + /// Gets or sets the specified property. + /// </summary> + /// <param name="key">Property name</param> + /// <returns>The specified property value</returns> public object this[string key] - { - get - { - CheckKey(key); - return _hash[key]; - } + { + get { return GetObject(key); } + set { SetObject(key, value); } + } - set - { - CheckKey(key); - CheckValue(value); + #region Typed Setters and Getters + // + // Typed Setters and Getters + // + public bool GetBoolean(string key) + { + return (bool)this[key]; + } + public void SetBoolean(string key, bool value) + { + CheckPropertyName(key); + SetProperty(key, AMQType.BOOLEAN.AsTypedValue(value)); + } + public byte GetByte(string key) + { + return (byte)this[key]; + } + public void SetByte(string key, byte value) + { + CheckPropertyName(key); + SetProperty(key, AMQType.BYTE.AsTypedValue(value)); + } + public sbyte GetSByte(string key) + { + return (sbyte)this[key]; + } + public void SetSByte(string key, sbyte value) + { + CheckPropertyName(key); + SetProperty(key, AMQType.SBYTE.AsTypedValue(value)); + } + public short GetInt16(string key) + { + return (short)this[key]; + } + public void SetInt16(string key, short value) + { + CheckPropertyName(key); + SetProperty(key, AMQType.INT16.AsTypedValue(value)); + } + public int GetInt32(string key) + { + return (int)this[key]; + } + public void SetInt32(string key, int value) + { + CheckPropertyName(key); + SetProperty(key, AMQType.INT32.AsTypedValue(value)); + } + public long GetInt64(string key) + { + return (long)this[key]; + } + public void SetInt64(string key, long value) + { + CheckPropertyName(key); + SetProperty(key, AMQType.INT64.AsTypedValue(value)); + } + public char GetChar(string key) + { + return (char)this[key]; + } + public void SetChar(string key, char value) + { + CheckPropertyName(key); + SetProperty(key, AMQType.ASCII_CHARACTER.AsTypedValue(value)); + } + public float GetFloat(string key) + { + return (float)this[key]; + } + public void SetFloat(string key, float value) + { + CheckPropertyName(key); + SetProperty(key, AMQType.FLOAT.AsTypedValue(value)); + } + public double GetDouble(string key) + { + return (double)this[key]; + } + public void SetDouble(string key, double value) + { + CheckPropertyName(key); + SetProperty(key, AMQType.DOUBLE.AsTypedValue(value)); + } + public decimal GetDecimal(string key) + { + return (decimal)this[key]; + } + public void SetDecimal(string key, decimal value) + { + CheckPropertyName(key); + SetProperty(key, AMQType.DECIMAL.AsTypedValue(value)); + } + public string GetString(string key) + { + return (string)this[key]; + } + public void SetString(string key, string value) + { + CheckPropertyName(key); + if ( value == null ) + SetProperty(key, AMQType.VOID.AsTypedValue(null)); + else + SetProperty(key, AMQType.LONG_STRING.AsTypedValue(value)); + } + public byte[] GetBytes(string key) + { + return (byte[])this[key]; + } + public void SetBytes(string key, byte[] value) + { + CheckPropertyName(key); + SetProperty(key, AMQType.BINARY.AsTypedValue(value)); + } + public ushort GetUInt16(string key) + { + return (ushort)this[key]; + } + public void SetUInt16(string key, ushort value) + { + CheckPropertyName(key); + SetProperty(key, AMQType.UINT16.AsTypedValue(value)); + } + public uint GetUInt32(string key) + { + return (uint)this[key]; + } + public void SetUInt32(string key, uint value) + { + CheckPropertyName(key); + SetProperty(key, AMQType.UINT32.AsTypedValue(value)); + } + public ulong GetUInt64(string key) + { + return (ulong)this[key]; + } + public void SetUInt64(string key, ulong value) + { + CheckPropertyName(key); + SetProperty(key, AMQType.UINT64.AsTypedValue(value)); + } + #endregion // Typed Setters and Getters - object oldValue = _hash[key]; - if (oldValue != null) - { - AdjustEncodingSizeWhenRemoving(key, oldValue); - } + #region Public Methods + // + // Public Methods + // - _hash[key] = value; - AdjustEncodingSizeWhenAdding(key, value); - } - } + /// <summary> + /// Removes the property with the specified name + /// </summary> + /// <param name="key">The name of the property to remove</param> + /// <returns>The previous value of the property or null</returns> + public AMQTypedValue RemoveKey(string key) + { + InitMapIfNecessary(); + _encodedForm = null; + AMQTypedValue value = (AMQTypedValue)_properties[key]; + if ( value != null ) + { + _properties.Remove(key); + _encodedSize -= EncodingUtils.EncodedShortStringLength(key); + _encodedSize--; + _encodedSize -= value.EncodingLength; - public void WriteToBuffer(ByteBuffer buffer) - { - // Write out the total length, which we have kept up to date as data is added. - buffer.put(_encodedSize); - WritePayload(buffer); + } + return value; } + - private void WritePayload(ByteBuffer buffer) + /// <summary> + /// Remove the property with the specified name + /// </summary> + /// <param name="key">The name of the property to remove</param> + public void Remove(string key) { - foreach (DictionaryEntry lde in _hash) - { - string key = (string) lde.Key; - EncodingUtils.WriteShortStringBytes(buffer, key); - object value = lde.Value; - if (value is byte[]) - { - buffer.put((byte) 'S'); - EncodingUtils.WriteLongstr(buffer, (byte[]) value); - } - else if (value is string) - { - // TODO: look at using proper charset encoder - buffer.put((byte) 'S'); - EncodingUtils.WriteLongStringBytes(buffer, (string) value); - } - else if (value is uint) - { - // TODO: look at using proper charset encoder - buffer.put((byte) 'I'); - buffer.put((uint) value); - } - else - { - // Should never get here. - throw new InvalidOperationException("Unsupported type in FieldTable: " + value.GetType()); - } - } + RemoveKey(key); } - public byte[] GetDataAsBytes() + /// <summary> + /// Remove all properties from the table + /// </summary> + public void Clear() { - ByteBuffer buffer = ByteBuffer.allocate((int)_encodedSize); - WritePayload(buffer); - byte[] result = new byte[_encodedSize]; - buffer.flip(); - buffer.get(result); - //buffer.Release(); - return result; + InitMapIfNecessary(); + _encodedForm = null; + _properties.Clear(); + _encodedSize = 0; } /// <summary> @@ -177,124 +298,338 @@ /// <param name="ft">the source field table</param> public void AddAll(IFieldTable ft) { - foreach (DictionaryEntry dictionaryEntry in ft) - { - this[(string)dictionaryEntry.Key] = dictionaryEntry.Value; - } + foreach ( DictionaryEntry dictionaryEntry in ft ) + { + this[(string)dictionaryEntry.Key] = dictionaryEntry.Value; + } } - private void CheckKey(object key) + /// <summary> + /// Get a enumerator over the internal property set. + /// Notice the enumerator will DictionaryEntry objects with + /// a string as the Key and an <see cref="AMQTypedValue"/> instance as the value + /// </summary> + /// <returns>The enumerator object</returns> + public IEnumerator GetEnumerator() { - if (key == null) - { - throw new ArgumentException("All keys must be Strings - was passed: null"); - } - else if (!(key is string)) - { - throw new ArgumentException("All keys must be Strings - was passed: " + key.GetType()); - } + return _properties.GetEnumerator(); } - private void CheckValue(object value) + /// <summary> + /// Indicates if a property with the given name exists + /// </summary> + /// <param name="s">Property name to check</param> + /// <returns>True if the property exists</returns> + public bool Contains(string s) { - if (!(value is string || value is uint || value is int || value is long)) - { - throw new ArgumentException("All values must be type string or int or long or uint, was passed: " + - value.GetType()); - } + return _properties.Contains(s); } - - void AdjustEncodingSizeWhenAdding(object key, object value) + + /// <summary> + /// Returns a dictionary mapping Property Names to the corresponding + /// <see cref="AMQTypedValue"/> value + /// </summary> + /// <returns>The internal dictionary</returns> + public IDictionary AsDictionary() { - _encodedSize += EncodingUtils.EncodedShortStringLength((string) key); - // the extra byte if for the type indicator what is written out - if (value is string) - { - _encodedSize += 1 + EncodingUtils.EncodedLongStringLength((string) value); - } - else if (value is int || value is uint || value is long) - { - _encodedSize += 1 + 4; - } - else - { - // Should never get here since was already checked - throw new Exception("Unsupported value type: " + value.GetType()); - } + return _properties; } - private void AdjustEncodingSizeWhenRemoving(object key, object value) + /// <summary> + /// Returns a string representation of this field table + /// </summary> + /// <returns>A string</returns> + public override string ToString() { - _encodedSize -= EncodingUtils.EncodedShortStringLength((string) key); - if (value != null) - { - if (value is string) - { - _encodedSize -= 1 + EncodingUtils.EncodedLongStringLength((string) value); - } - else if (value is int || value is uint || value is long) - { - _encodedSize -= 5; - } - else - { - // Should never get here - throw new Exception("Illegal value type: " + value.GetType()); - } - } - } + StringBuilder sb = new StringBuilder("FieldTable {"); - public IEnumerator GetEnumerator() - { - return _hash.GetEnumerator(); - } + bool first = true; + InitMapIfNecessary(); + foreach ( DictionaryEntry entry in _properties ) + { + if ( !first ) + { + sb.Append(", "); + } + first = false; + sb.Append(entry.Key).Append(" => ").Append(entry.Value); + } - public bool Contains(string s) + sb.Append("}"); + return sb.ToString(); + } + + /// <summary> + /// Serializes this instance to the specified <see cref="ByteBuffer"/>. + /// </summary> + /// <param name="buffer">The buffer to write to</param> + public void WriteToBuffer(ByteBuffer buffer) { - return _hash.Contains(s); + if ( _log.IsDebugEnabled ) + { + _log.Debug("FieldTable::writeToBuffer: Writing encoded length of " + EncodedSize + "..."); + } + + EncodingUtils.WriteUnsignedInteger(buffer, EncodedSize); + WritePayload(buffer); } - public void Clear() + /// <summary> + /// Returns a byte array with the serialized representation + /// of this field table + /// </summary> + /// <returns>An array of bytes</returns> + public byte[] GetDataAsBytes() { - _hash.Clear(); - _encodedSize = 0; + ByteBuffer buffer = ByteBuffer.allocate((int)_encodedSize); + WritePayload(buffer); + byte[] result = new byte[_encodedSize]; + buffer.flip(); + buffer.get(result); + //buffer.Release(); + return result; + } + + #endregion // Public Methods + + #region Private Methods + // + // Private Methods + // + + private static void CheckPropertyName(string propertyName) + { + if ( propertyName == null || propertyName.Length == 0 ) + throw new ArgumentNullException("propertyName"); + CheckIdentifierFormat(propertyName); + } + + private static void CheckIdentifierFormat(string propertyName) + { + // AMQP Spec: 4.2.5.5 Field Tables + // Guidelines for implementers: + // * Field names MUST start with a letter, '$' or '#' and may continue with + // letters, '$' or '#', digits, or underlines, to a maximum length of 128 + // characters. + // * The server SHOULD validate field names and upon receiving an invalid + // field name, it SHOULD signal a connection exception with reply code + // 503 (syntax error). Conformance test: amq_wlp_table_01. + // * A peer MUST handle duplicate fields by using only the first instance. + + + // AMQP length limit + if ( propertyName.Length > 128 ) + { + throw new ArgumentException("AMQP limits property names to 128 characters"); + } + + // AMQ start character + if ( !(Char.IsLetter(propertyName[0]) + || propertyName[0] == '$' + || propertyName[0] == '#' + || propertyName[0] == '_' ) )// Not official AMQP added for JMS. + { + throw new ArgumentException("Identifier '" + propertyName + "' does not start with a valid AMQP start character"); + } } - public void Remove(string key) + private object GetObject(string key) { - object value = _hash[key]; - if (value != null) - { - AdjustEncodingSizeWhenRemoving(key, value); - } - _hash.Remove(key); + AMQTypedValue value = GetProperty(key); + return value != null ? value.Value : null; } - - public IDictionary AsDictionary() + + private void SetObject(string key, object value) { - return _hash; + if ( value is bool ) + { + SetBoolean(key, (bool)value); + } else if ( value is byte ) + { + SetByte(key, (byte)value); + } else if ( value is sbyte ) + { + SetSByte(key, (sbyte)value); + } else if ( value is short ) + { + SetInt16(key, (short)value); + } else if ( value is ushort ) + { + SetUInt16(key, (ushort)value); + } else if ( value is int ) + { + SetInt32(key, (int) value); + } else if ( value is uint ) + { + SetUInt32(key, (uint)value); + } else if ( value is long ) + { + SetInt64(key, (long) value); + } else if ( value is ulong ) + { + SetUInt64(key, (ulong)value); + } else if ( value is char ) + { + SetChar(key, (char) value); + } else if ( value is float ) + { + SetFloat(key, (float) value); + } else if ( value is double ) + { + SetDouble(key, (double) value); + } else if ( value is decimal ) + { + SetDecimal(key, (decimal) value); + } else if ( value is string ) + { + SetString(key, (string) value); + } else if ( value is byte[] ) + { + SetBytes(key, (byte[])value); + } else + { + throw new ArgumentException("Data type not supported yet"); + } } - - public override string ToString() + + private AMQTypedValue GetProperty(string name) { - StringBuilder sb = new StringBuilder("FieldTable{"); - - bool first = true; - foreach (DictionaryEntry entry in _hash) - { - if (first) - { - first = !first; - } - else - { - sb.Append(", "); - } - sb.Append(entry.Key).Append(" => ").Append(entry.Value); - } - - sb.Append("}"); - return sb.ToString(); + lock ( _syncLock ) + { + if ( _properties == null ) + { + if ( _encodedForm == null ) + { + return null; + } else + { + PopulateFromBuffer(); + } + } + return (AMQTypedValue) _properties[name]; + } + } + + private void PopulateFromBuffer() + { + try + { + SetFromBuffer(_encodedForm, _encodedSize); + } catch ( AMQFrameDecodingException e ) + { + _log.Error("Error decoding FieldTable in deferred decoding mode ", e); + throw; + } + } + + private void SetFromBuffer(ByteBuffer buffer, uint length) + { + bool trace = _log.IsDebugEnabled; + if ( length > 0 ) + { + int expectedRemaining = buffer.remaining() - (int)length; + _properties = new LinkedHashtable(); + + do + { + string key = EncodingUtils.ReadShortString(buffer); + AMQTypedValue value = AMQTypedValue.ReadFromBuffer(buffer); + if ( trace ) + { + _log.Debug(string.Format("FieldTable::PropFieldTable(buffer,{0}): Read type '{1}', key '{2}', value '{3}'", length, value.Type, key, value.Value)); + } + _properties.Add(key, value); + + } while ( buffer.remaining() > expectedRemaining ); + _encodedSize = length; + } + if ( trace ) + { + _log.Debug("FieldTable::FieldTable(buffer," + length + "): Done."); + } + } + + private void InitMapIfNecessary() + { + lock ( _syncLock ) + { + if ( _properties == null ) + { + if ( _encodedForm == null ) + { + _properties = new LinkedHashtable(); + } else + { + PopulateFromBuffer(); + } + } + } + } + + private AMQTypedValue SetProperty(string key, AMQTypedValue value) + { + InitMapIfNecessary(); + _encodedForm = null; + if ( value == null ) + { + RemoveKey(key); + } + AMQTypedValue oldVal = (AMQTypedValue)_properties[key]; + _properties.Add(key, value); + if ( oldVal != null ) + { + _encodedSize -= oldVal.EncodingLength; + } else + { + _encodedSize += EncodingUtils.EncodedShortStringLength(key) + (uint)1; + } + if ( value != null ) + { + _encodedSize += value.EncodingLength; + } + + return oldVal; + } + + public void WritePayload(ByteBuffer buffer) + { + if ( _encodedForm != null ) + { + buffer.put(_encodedForm); + } else if ( _properties != null ) + { + foreach ( DictionaryEntry de in _properties ) + { + string key = (string)de.Key; + AMQTypedValue value = (AMQTypedValue)de.Value; + try + { + if ( _log.IsDebugEnabled ) + { + _log.Debug("Writing Property:" + key + + " Type:" + value.Type + + " Value:" + value.Value); + _log.Debug("Buffer Position:" + buffer.position() + + " Remaining:" + buffer.remaining()); + } + //Write the actual parameter name + EncodingUtils.WriteShortStringBytes(buffer, key); + value.WriteToBuffer(buffer); + } catch ( Exception ex ) + { + if ( _log.IsDebugEnabled ) + { + _log.Debug("Exception thrown:" + ex); + _log.Debug("Writing Property:" + key + + " Type:" + value.Type + + " Value:" + value.Value); + _log.Debug("Buffer Position:" + buffer.position() + + " Remaining:" + buffer.remaining()); + } + } + } + } } + #endregion // Private Methods } }
Modified: incubator/qpid/trunk/qpid/dotnet/Qpid.Common/Qpid.Common.csproj URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/dotnet/Qpid.Common/Qpid.Common.csproj?view=diff&rev=507096&r1=507095&r2=507096 ============================================================================== --- incubator/qpid/trunk/qpid/dotnet/Qpid.Common/Qpid.Common.csproj (original) +++ incubator/qpid/trunk/qpid/dotnet/Qpid.Common/Qpid.Common.csproj Tue Feb 13 08:56:03 2007 @@ -53,6 +53,7 @@ <Compile Include="Framing\AMQMethodBody.cs" /> <Compile Include="Framing\AMQMethodBodyFactory.cs" /> <Compile Include="Framing\AMQProtocolHeaderException.cs" /> + <Compile Include="Framing\AMQType.cs" /> <Compile Include="Framing\BasicContentHeaderProperties.cs" /> <Compile Include="Framing\CompositeAMQDataBlock.cs" /> <Compile Include="Framing\ContentBody.cs" /> @@ -60,6 +61,8 @@ <Compile Include="Framing\ContentHeaderBody.cs" /> <Compile Include="Framing\ContentHeaderBodyFactory.cs" /> <Compile Include="Framing\ContentHeaderPropertiesFactory.cs" /> + <Compile Include="Framing\AMQTypedValue.cs" /> + <Compile Include="Framing\AMQTypeMap.cs" /> <Compile Include="Framing\EncodingUtils.cs" /> <Compile Include="Framing\FieldTable.cs" /> <Compile Include="Framing\HeartbeatBody.cs" /> Modified: incubator/qpid/trunk/qpid/dotnet/Qpid.Common/Qpid.Common.mdp URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/dotnet/Qpid.Common/Qpid.Common.mdp?view=diff&rev=507096&r1=507095&r2=507096 ============================================================================== --- incubator/qpid/trunk/qpid/dotnet/Qpid.Common/Qpid.Common.mdp (original) +++ incubator/qpid/trunk/qpid/dotnet/Qpid.Common/Qpid.Common.mdp Tue Feb 13 08:56:03 2007 @@ -27,6 +27,9 @@ <File name="./Framing/AMQMethodBody.cs" subtype="Code" buildaction="Compile" /> <File name="./Framing/AMQMethodBodyFactory.cs" subtype="Code" buildaction="Compile" /> <File name="./Framing/AMQProtocolHeaderException.cs" subtype="Code" buildaction="Compile" /> + <File name="./Framing/AMQType.cs" subtype="Code" buildaction="Compile" /> + <File name="./Framing/AMQTypedValue.cs" subtype="Code" buildaction="Compile" /> + <File name="./Framing/AMQTypeMap.cs" subtype="Code" buildaction="Compile" /> <File name="./Framing/BasicContentHeaderProperties.cs" subtype="Code" buildaction="Compile" /> <File name="./Framing/CompositeAMQDataBlock.cs" subtype="Code" buildaction="Compile" /> <File name="./Framing/ContentBody.cs" subtype="Code" buildaction="Compile" /> @@ -151,4 +154,4 @@ <ProjectReference type="Project" localcopy="True" refto="Qpid.Messaging" /> <ProjectReference type="Gac" localcopy="True" refto="System.Xml, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> </References> -</Project> \ No newline at end of file +</Project>
