diff --git a/src/ProtoBench/Program.cs b/src/ProtoBench/Program.cs index 2239fe08b5..34860f33a7 100644 --- a/src/ProtoBench/Program.cs +++ b/src/ProtoBench/Program.cs @@ -137,6 +137,10 @@ namespace Google.ProtocolBuffers.ProtoBench new JsonFormatWriter(temp).WriteMessage(sampleMessage); string jsonMessageText = temp.ToString(); + IDictionary dictionary = new Dictionary(StringComparer.Ordinal); + new DictionaryWriter(dictionary).WriteMessage(sampleMessage); + + //Serializers if(!FastTest) RunBenchmark("Serialize to byte string", inputData.Length, () => sampleMessage.ToByteString()); RunBenchmark("Serialize to byte array", inputData.Length, () => sampleMessage.ToByteArray()); @@ -145,6 +149,7 @@ namespace Google.ProtocolBuffers.ProtoBench RunBenchmark("Serialize to xml", xmlMessageText.Length, () => new XmlFormatWriter(new StringWriter()).WriteMessage(sampleMessage)); RunBenchmark("Serialize to json", jsonMessageText.Length, () => new JsonFormatWriter(new StringWriter()).WriteMessage(sampleMessage)); + RunBenchmark("Serialize to dictionary", sampleMessage.SerializedSize, () => new DictionaryWriter().WriteMessage(sampleMessage)); //Deserializers if (!FastTest) RunBenchmark("Deserialize from byte string", inputData.Length, @@ -168,6 +173,7 @@ namespace Google.ProtocolBuffers.ProtoBench RunBenchmark("Deserialize from xml", xmlMessageText.Length, () => new XmlFormatReader(xmlMessageText).Merge(defaultMessage.WeakCreateBuilderForType()).WeakBuild()); RunBenchmark("Deserialize from json", jsonMessageText.Length, () => new JsonFormatReader(jsonMessageText).Merge(defaultMessage.WeakCreateBuilderForType()).WeakBuild()); + RunBenchmark("Deserialize from dictionary", sampleMessage.SerializedSize, () => new DictionaryReader(dictionary).Merge(defaultMessage.WeakCreateBuilderForType()).WeakBuild()); Console.WriteLine(); return true; diff --git a/src/ProtocolBuffers.Test/CompatTests/CompatibilityTests.cs b/src/ProtocolBuffers.Test/CompatTests/CompatibilityTests.cs index 49c562907c..0306c41ee0 100644 --- a/src/ProtocolBuffers.Test/CompatTests/CompatibilityTests.cs +++ b/src/ProtocolBuffers.Test/CompatTests/CompatibilityTests.cs @@ -14,6 +14,11 @@ namespace Google.ProtocolBuffers.CompatTests where TMessage : IMessageLite where TBuilder : IBuilderLite; + protected virtual void AssertOutputEquals(object lhs, object rhs) + { + Assert.AreEqual(lhs, rhs); + } + [Test] public virtual void RoundTripMessage1OptimizeSize() { @@ -23,7 +28,7 @@ namespace Google.ProtocolBuffers.CompatTests SizeMessage1 copy = DeerializeMessage(content, SizeMessage1.CreateBuilder(), ExtensionRegistry.Empty).Build(); Assert.AreEqual(msg, copy); - Assert.AreEqual(content, SerializeMessage(copy)); + AssertOutputEquals(content, SerializeMessage(copy)); Assert.AreEqual(TestResources.google_message1, copy.ToByteArray()); } @@ -36,7 +41,7 @@ namespace Google.ProtocolBuffers.CompatTests SizeMessage2 copy = DeerializeMessage(content, SizeMessage2.CreateBuilder(), ExtensionRegistry.Empty).Build(); Assert.AreEqual(msg, copy); - Assert.AreEqual(content, SerializeMessage(copy)); + AssertOutputEquals(content, SerializeMessage(copy)); Assert.AreEqual(TestResources.google_message2, copy.ToByteArray()); } @@ -49,7 +54,7 @@ namespace Google.ProtocolBuffers.CompatTests SpeedMessage1 copy = DeerializeMessage(content, SpeedMessage1.CreateBuilder(), ExtensionRegistry.Empty).Build(); Assert.AreEqual(msg, copy); - Assert.AreEqual(content, SerializeMessage(copy)); + AssertOutputEquals(content, SerializeMessage(copy)); Assert.AreEqual(TestResources.google_message1, copy.ToByteArray()); } @@ -62,7 +67,7 @@ namespace Google.ProtocolBuffers.CompatTests SpeedMessage2 copy = DeerializeMessage(content, SpeedMessage2.CreateBuilder(), ExtensionRegistry.Empty).Build(); Assert.AreEqual(msg, copy); - Assert.AreEqual(content, SerializeMessage(copy)); + AssertOutputEquals(content, SerializeMessage(copy)); Assert.AreEqual(TestResources.google_message2, copy.ToByteArray()); } diff --git a/src/ProtocolBuffers.Test/CompatTests/DictionaryCompatibilityTests.cs b/src/ProtocolBuffers.Test/CompatTests/DictionaryCompatibilityTests.cs new file mode 100644 index 0000000000..8de82ea92d --- /dev/null +++ b/src/ProtocolBuffers.Test/CompatTests/DictionaryCompatibilityTests.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Google.ProtocolBuffers.Serialization; +using NUnit.Framework; + +namespace Google.ProtocolBuffers.CompatTests +{ + [TestFixture] + public class DictionaryCompatibilityTests : CompatibilityTests + { + protected override object SerializeMessage(TMessage message) + { + DictionaryWriter writer = new DictionaryWriter(); + writer.WriteMessage(message); + return writer.ToDictionary(); + } + + protected override TBuilder DeerializeMessage(object message, TBuilder builder, ExtensionRegistry registry) + { + new DictionaryReader((IDictionary)message).Merge(builder); + return builder; + } + + protected override void AssertOutputEquals(object lhs, object rhs) + { + IDictionary left = (IDictionary)lhs; + IDictionary right = (IDictionary)rhs; + + Assert.AreEqual( + String.Join(",", new List(left.Keys).ToArray()), + String.Join(",", new List(right.Keys).ToArray()) + ); + } + } +} \ No newline at end of file diff --git a/src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj b/src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj index 199e410ad9..04a02999c6 100644 --- a/src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj +++ b/src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj @@ -77,6 +77,7 @@ + True diff --git a/src/ProtocolBuffers/ProtocolBuffers.csproj b/src/ProtocolBuffers/ProtocolBuffers.csproj index 19a97a46e1..22f3c139ec 100644 --- a/src/ProtocolBuffers/ProtocolBuffers.csproj +++ b/src/ProtocolBuffers/ProtocolBuffers.csproj @@ -184,6 +184,8 @@ + + diff --git a/src/ProtocolBuffers/Serialization/AbstractWriter.cs b/src/ProtocolBuffers/Serialization/AbstractWriter.cs index d8a0c0a14b..96153162b6 100644 --- a/src/ProtocolBuffers/Serialization/AbstractWriter.cs +++ b/src/ProtocolBuffers/Serialization/AbstractWriter.cs @@ -41,13 +41,6 @@ namespace Google.ProtocolBuffers.Serialization /// public abstract void WriteMessage(IMessageLite message); - /// - /// Writes a message - /// - public abstract void WriteMessage(string field, IMessageLite message); - - - /// /// Writes a Boolean value /// diff --git a/src/ProtocolBuffers/Serialization/DictionaryReader.cs b/src/ProtocolBuffers/Serialization/DictionaryReader.cs new file mode 100644 index 0000000000..f8ef1eda81 --- /dev/null +++ b/src/ProtocolBuffers/Serialization/DictionaryReader.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Google.ProtocolBuffers.Descriptors; + +namespace Google.ProtocolBuffers.Serialization +{ + /// + /// Allows reading messages from a name/value dictionary + /// + public class DictionaryReader : AbstractReader + { + private readonly IEnumerator> _input; + private bool _ready; + + /// + /// Creates a dictionary reader from an enumeration of KeyValuePair data, like an IDictionary + /// + public DictionaryReader(IEnumerable> input) + { + _input = input.GetEnumerator(); + _ready = _input.MoveNext(); + } + + /// + /// Merges the contents of stream into the provided message builder + /// + public override TBuilder Merge(TBuilder builder, ExtensionRegistry registry) + { + builder.WeakMergeFrom(this, registry); + return builder; + } + + /// + /// Peeks at the next field in the input stream and returns what information is available. + /// + /// + /// This may be called multiple times without actually reading the field. Only after the field + /// is either read, or skipped, should PeekNext return a different value. + /// + protected override bool PeekNext(out string field) + { + field = _ready ? _input.Current.Key : null; + return _ready; + } + + /// + /// Causes the reader to skip past this field + /// + protected override void Skip() + { + _ready = _input.MoveNext(); + } + + private bool GetValue(ref T value) + { + if (!_ready) return false; + + object obj = _input.Current.Value; + if (obj is T) + value = (T)obj; + else + { + try + { + if (obj is IConvertible) + value = (T)Convert.ChangeType(obj, typeof(T)); + else + value = (T)obj; + } + catch + { + _ready = _input.MoveNext(); + return false; + } + } + _ready = _input.MoveNext(); + return true; + } + + /// + /// Returns true if it was able to read a Boolean from the input + /// + protected override bool Read(ref bool value) + { + return GetValue(ref value); + } + + /// + /// Returns true if it was able to read a Int32 from the input + /// + protected override bool Read(ref int value) + { + return GetValue(ref value); + } + + /// + /// Returns true if it was able to read a UInt32 from the input + /// + [CLSCompliant(false)] + protected override bool Read(ref uint value) + { + return GetValue(ref value); + } + + /// + /// Returns true if it was able to read a Int64 from the input + /// + protected override bool Read(ref long value) + { + return GetValue(ref value); + } + + /// + /// Returns true if it was able to read a UInt64 from the input + /// + [CLSCompliant(false)] + protected override bool Read(ref ulong value) + { + return GetValue(ref value); + } + + /// + /// Returns true if it was able to read a Single from the input + /// + protected override bool Read(ref float value) + { + return GetValue(ref value); + } + + /// + /// Returns true if it was able to read a Double from the input + /// + protected override bool Read(ref double value) + { + return GetValue(ref value); + } + + /// + /// Returns true if it was able to read a String from the input + /// + protected override bool Read(ref string value) + { + return GetValue(ref value); + } + + /// + /// Returns true if it was able to read a ByteString from the input + /// + protected override bool Read(ref ByteString value) + { + byte[] rawbytes = null; + if (GetValue(ref rawbytes)) + { + value = ByteString.AttachBytes(rawbytes); + return true; + } + return false; + } + + /// + /// returns true if it was able to read a single value into the value reference. The value + /// stored may be of type System.String, System.Int32, or an IEnumLite from the IEnumLiteMap. + /// + protected override bool ReadEnum(ref object value) + { + return GetValue(ref value); + } + + /// + /// Merges the input stream into the provided IBuilderLite + /// + protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry) + { + IDictionary values = null; + if (GetValue(ref values)) + { + new DictionaryReader(values).Merge(builder, registry); + return true; + } + return false; + } + + public override bool ReadArray(FieldType type, string field, ICollection items) + { + object[] array = null; + if (GetValue(ref array)) + { + foreach (T item in array) + items.Add(item); + return true; + } + return false; + } + + public override bool ReadEnumArray(string field, ICollection items) + { + object[] array = null; + if (GetValue(ref array)) + { + foreach (object item in array) + items.Add(item); + return true; + } + return false; + } + + public override bool ReadMessageArray(string field, ICollection items, IMessageLite messageType, ExtensionRegistry registry) + { + object[] array = null; + if (GetValue(ref array)) + { + foreach (IDictionary item in array) + { + IBuilderLite builder = messageType.WeakCreateBuilderForType(); + new DictionaryReader(item).Merge(builder); + items.Add((T)builder.WeakBuild()); + } + return true; + } + return false; + } + + public override bool ReadGroupArray(string field, ICollection items, IMessageLite messageType, ExtensionRegistry registry) + { return ReadMessageArray(field, items, messageType, registry); } + } +} diff --git a/src/ProtocolBuffers/Serialization/DictionaryWriter.cs b/src/ProtocolBuffers/Serialization/DictionaryWriter.cs new file mode 100644 index 0000000000..596a926291 --- /dev/null +++ b/src/ProtocolBuffers/Serialization/DictionaryWriter.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using Google.ProtocolBuffers.Descriptors; + +namespace Google.ProtocolBuffers.Serialization +{ + /// + /// Allows writing messages to a name/value dictionary + /// + public class DictionaryWriter : AbstractWriter + { + private readonly IDictionary _output; + + /// + /// Constructs a writer using a new dictionary + /// + public DictionaryWriter() + : this(new Dictionary()) + { } + + /// + /// Constructs a writer using an existing dictionary + /// + public DictionaryWriter(IDictionary output) + { + ThrowHelper.ThrowIfNull(output, "output"); + _output = output; + } + + /// + /// Accesses the dictionary that is backing this writer + /// + public IDictionary ToDictionary() { return _output; } + + /// + /// Writes the message to the the formatted stream. + /// + public override void WriteMessage(IMessageLite message) + { + message.WriteTo(this); + } + + /// + /// Writes a Boolean value + /// + protected override void Write(string field, bool value) + { + _output[field] = value; + } + + /// + /// Writes a Int32 value + /// + protected override void Write(string field, int value) + { + _output[field] = value; + } + + /// + /// Writes a UInt32 value + /// + [CLSCompliant(false)] + protected override void Write(string field, uint value) + { + _output[field] = value; + } + + /// + /// Writes a Int64 value + /// + protected override void Write(string field, long value) + { + _output[field] = value; + } + + /// + /// Writes a UInt64 value + /// + [CLSCompliant(false)] + protected override void Write(string field, ulong value) + { + _output[field] = value; + } + + /// + /// Writes a Single value + /// + protected override void Write(string field, float value) + { + _output[field] = value; + } + + /// + /// Writes a Double value + /// + protected override void Write(string field, double value) + { + _output[field] = value; + } + + /// + /// Writes a String value + /// + protected override void Write(string field, string value) + { + _output[field] = value; + } + + /// + /// Writes a set of bytes + /// + protected override void Write(string field, ByteString value) + { + _output[field] = value.ToByteArray(); + } + + /// + /// Writes a message or group as a field + /// + protected override void WriteMessageOrGroup(string field, IMessageLite message) + { + DictionaryWriter writer = new DictionaryWriter(); + writer.WriteMessage(message); + + _output[field] = writer.ToDictionary(); + } + + /// + /// Writes a System.Enum by the numeric and textual value + /// + protected override void WriteEnum(string field, int number, string name) + { + _output[field] = number; + } + + /// + /// Writes an array of field values + /// + protected override void WriteArray(FieldType fieldType, string field, System.Collections.IEnumerable items) + { + List objects = new List(); + foreach (object o in items) + { + switch (fieldType) + { + case FieldType.Group: + case FieldType.Message: + { + DictionaryWriter writer = new DictionaryWriter(); + writer.WriteMessage((IMessageLite)o); + objects.Add(writer.ToDictionary()); + } + break; + case FieldType.Bytes: + objects.Add(((ByteString)o).ToByteArray()); + break; + case FieldType.Enum: + if (o is IEnumLite) + objects.Add(((IEnumLite)o).Number); + else + objects.Add((int)o); + break; + default: + objects.Add(o); + break; + } + } + + _output[field] = objects.ToArray(); + } + } +} diff --git a/src/ProtocolBuffers/Serialization/JsonFormatWriter.cs b/src/ProtocolBuffers/Serialization/JsonFormatWriter.cs index 5d5144ba02..0baf6cacda 100644 --- a/src/ProtocolBuffers/Serialization/JsonFormatWriter.cs +++ b/src/ProtocolBuffers/Serialization/JsonFormatWriter.cs @@ -263,16 +263,6 @@ namespace Google.ProtocolBuffers.Serialization Flush(); } - /// - /// Writes a message - /// - [System.ComponentModel.Browsable(false)] - [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] - public override void WriteMessage(string field, IMessageLite message) - { - WriteMessage(message); - } - /// /// Used in streaming arrays of objects to the writer /// diff --git a/src/ProtocolBuffers/Serialization/XmlFormatWriter.cs b/src/ProtocolBuffers/Serialization/XmlFormatWriter.cs index 5ed96651b1..b5dbf5fae7 100644 --- a/src/ProtocolBuffers/Serialization/XmlFormatWriter.cs +++ b/src/ProtocolBuffers/Serialization/XmlFormatWriter.cs @@ -73,7 +73,7 @@ namespace Google.ProtocolBuffers.Serialization /// /// Writes a message as an element with the given name /// - public override void WriteMessage(string elementName, IMessageLite message) + public void WriteMessage(string elementName, IMessageLite message) { if (TestOption(XmlWriterOptions.OutputJsonTypes)) {