From 60fd773d300cd0390ba08812590690ab2d28c837 Mon Sep 17 00:00:00 2001 From: csharptest Date: Fri, 9 Sep 2011 12:18:16 -0500 Subject: Completed work and testing for manually reading/writing start/end message --- .../AbstractReader.cs | 10 +- .../AbstractTextReader.cs | 4 - .../AbstractWriter.cs | 12 +- .../DictionaryReader.cs | 13 +- .../DictionaryWriter.cs | 6 +- .../Http/MessageFormatFactory.cs | 33 ++- .../Http/MessageFormatOptions.cs | 2 +- .../Http/ServiceExtensions.cs | 3 +- .../JsonFormatReader.cs | 13 +- .../JsonFormatWriter.cs | 21 +- .../XmlFormatReader.cs | 142 +++++++------ .../XmlFormatWriter.cs | 28 ++- .../ProtocolBuffers.Test.csproj | 1 + src/ProtocolBuffers.Test/TestMimeMessageFormats.cs | 225 +++++++++++++++++++++ src/ProtocolBuffers.Test/TestWriterFormatJson.cs | 34 +++- src/ProtocolBuffers.Test/TestWriterFormatXml.cs | 63 +++++- src/ProtocolBuffers/CodedInputStream.cs | 3 + src/ProtocolBuffers/CodedOutputStream.cs | 3 + src/ProtocolBuffers/ICodedInputStream.cs | 18 ++ src/ProtocolBuffers/ICodedOutputStream.cs | 18 ++ 20 files changed, 486 insertions(+), 166 deletions(-) create mode 100644 src/ProtocolBuffers.Test/TestMimeMessageFormats.cs diff --git a/src/ProtocolBuffers.Serialization/AbstractReader.cs b/src/ProtocolBuffers.Serialization/AbstractReader.cs index 538af38f..f3e6fd6f 100644 --- a/src/ProtocolBuffers.Serialization/AbstractReader.cs +++ b/src/ProtocolBuffers.Serialization/AbstractReader.cs @@ -17,12 +17,6 @@ namespace Google.ProtocolBuffers.Serialization /// Constructs a new reader protected AbstractReader() { MaxDepth = DefaultMaxDepth; } - /// Constructs a new child reader - protected AbstractReader(AbstractReader copyFrom) - { - _depth = copyFrom._depth + 1; - MaxDepth = copyFrom.MaxDepth; - } /// Gets or sets the maximum recursion depth allowed public int MaxDepth { get; set; } @@ -116,12 +110,12 @@ namespace Google.ProtocolBuffers.Serialization /// /// Reads the root-message preamble specific to this formatter /// - public abstract AbstractReader ReadStartMessage(); + public abstract void ReadMessageStart(); /// /// Reads the root-message close specific to this formatter /// - public abstract void ReadEndMessage(); + public abstract void ReadMessageEnd(); /// /// Merges the input stream into the provided IBuilderLite diff --git a/src/ProtocolBuffers.Serialization/AbstractTextReader.cs b/src/ProtocolBuffers.Serialization/AbstractTextReader.cs index 59b9057b..ddfa6436 100644 --- a/src/ProtocolBuffers.Serialization/AbstractTextReader.cs +++ b/src/ProtocolBuffers.Serialization/AbstractTextReader.cs @@ -11,10 +11,6 @@ namespace Google.ProtocolBuffers.Serialization { /// Constructs a new reader protected AbstractTextReader() { } - /// Constructs a new child reader - protected AbstractTextReader(AbstractTextReader copyFrom) - : base(copyFrom) - { } /// /// Reads a typed field as a string diff --git a/src/ProtocolBuffers.Serialization/AbstractWriter.cs b/src/ProtocolBuffers.Serialization/AbstractWriter.cs index 50dfe671..7ae26864 100644 --- a/src/ProtocolBuffers.Serialization/AbstractWriter.cs +++ b/src/ProtocolBuffers.Serialization/AbstractWriter.cs @@ -45,23 +45,23 @@ namespace Google.ProtocolBuffers.Serialization /// /// Used to write any nessary root-message preamble. After this call you can call - /// IMessageLite.MergeTo(...) and complete the message with a call to EndMessage(). + /// IMessageLite.MergeTo(...) and complete the message with a call to WriteMessageEnd(). /// These three calls are identical to just calling WriteMessage(message); /// /// /// AbstractWriter writer; - /// writer.StartMessage(); + /// writer.WriteMessageStart(); /// message.WriteTo(writer); - /// writer.EndMessage(); + /// writer.WriteMessageEnd(); /// // ... or, but not both ... /// writer.WriteMessage(message); /// - public abstract void StartMessage(); + public abstract void WriteMessageStart(); /// - /// Used to complete a root-message previously started with a call to StartMessage() + /// Used to complete a root-message previously started with a call to WriteMessageStart() /// - public abstract void EndMessage(); + public abstract void WriteMessageEnd(); /// /// Writes a Boolean value diff --git a/src/ProtocolBuffers.Serialization/DictionaryReader.cs b/src/ProtocolBuffers.Serialization/DictionaryReader.cs index f606bc9b..bb83ef9c 100644 --- a/src/ProtocolBuffers.Serialization/DictionaryReader.cs +++ b/src/ProtocolBuffers.Serialization/DictionaryReader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using Google.ProtocolBuffers.Descriptors; @@ -25,17 +25,14 @@ namespace Google.ProtocolBuffers.Serialization /// /// No-op /// - public override AbstractReader ReadStartMessage() - { - return this; - } + public override void ReadMessageStart() + { } /// /// No-op /// - public override void ReadEndMessage() - { - } + public override void ReadMessageEnd() + { } /// /// Merges the contents of stream into the provided message builder diff --git a/src/ProtocolBuffers.Serialization/DictionaryWriter.cs b/src/ProtocolBuffers.Serialization/DictionaryWriter.cs index 4c3b0116..6d823301 100644 --- a/src/ProtocolBuffers.Serialization/DictionaryWriter.cs +++ b/src/ProtocolBuffers.Serialization/DictionaryWriter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using Google.ProtocolBuffers.Descriptors; @@ -57,13 +57,13 @@ namespace Google.ProtocolBuffers.Serialization /// /// No-op /// - public override void StartMessage() + public override void WriteMessageStart() { } /// /// No-op /// - public override void EndMessage() + public override void WriteMessageEnd() { } /// diff --git a/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs b/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs index 8a19a8b7..2ee7eb4d 100644 --- a/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs +++ b/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs @@ -3,7 +3,7 @@ using System.IO; using System.Xml; using System.Text; -namespace Google.ProtocolBuffers.Serialization +namespace Google.ProtocolBuffers.Serialization.Http { /// /// Extensions and helpers to abstract the reading/writing of messages by a client-specified content type. @@ -29,14 +29,14 @@ namespace Google.ProtocolBuffers.Serialization else if (inputType == FormatType.Json) { JsonFormatReader reader = JsonFormatReader.CreateInstance(input); - codedInput = reader.ReadStartMessage(); + codedInput = reader; } else if (inputType == FormatType.Xml) { XmlFormatReader reader = XmlFormatReader.CreateInstance(input); reader.RootElementName = options.XmlReaderRootElementName; reader.Options = options.XmlReaderOptions; - codedInput = reader.ReadStartMessage(); + codedInput = reader; } else throw new NotSupportedException(); @@ -56,6 +56,7 @@ namespace Google.ProtocolBuffers.Serialization public static TBuilder MergeFrom(this TBuilder builder, MessageFormatOptions options, string contentType, Stream input) where TBuilder : IBuilderLite { ICodedInputStream codedInput = CreateInputStream(options, contentType, input); + codedInput.ReadMessageStart(); return (TBuilder)builder.WeakMergeFrom(codedInput, options.ExtensionRegistry); } @@ -82,13 +83,12 @@ namespace Google.ProtocolBuffers.Serialization { writer.Formatted(); } - writer.StartMessage(); codedOutput = writer; } else if (outputType == FormatType.Xml) { XmlFormatWriter writer; - if (options.FormattedOutput) + if (!options.FormattedOutput) { writer = XmlFormatWriter.CreateInstance(output); } @@ -99,16 +99,15 @@ namespace Google.ProtocolBuffers.Serialization CheckCharacters = false, NewLineHandling = NewLineHandling.Entitize, OmitXmlDeclaration = true, - Encoding = Encoding.UTF8, + Encoding = new UTF8Encoding(false), Indent = true, - IndentChars = " ", + IndentChars = " ", NewLineChars = Environment.NewLine, }; writer = XmlFormatWriter.CreateInstance(XmlWriter.Create(output, settings)); } writer.RootElementName = options.XmlWriterRootElementName; writer.Options = options.XmlWriterOptions; - writer.StartMessage(); codedOutput = writer; } else @@ -126,19 +125,17 @@ namespace Google.ProtocolBuffers.Serialization /// The stream to write the message to public static void WriteTo(this IMessageLite message, MessageFormatOptions options, string contentType, Stream output) { - using (ICodedOutputStream codedOutput = CreateOutputStream(options, contentType, output)) - { - message.WriteTo(codedOutput); + ICodedOutputStream codedOutput = CreateOutputStream(options, contentType, output); - // This is effectivly done by Dispose(); however, if you need to finalize a message - // without disposing the underlying stream, this is the only way to do it. - if (codedOutput is AbstractWriter) - ((AbstractWriter)codedOutput).EndMessage(); + // Output the appropriate message preamble + codedOutput.WriteMessageStart(); - codedOutput.Flush(); - } - } + // Write the message content to the output + message.WriteTo(codedOutput); + // Write the closing message fragment + codedOutput.WriteMessageEnd(); + } enum FormatType { ProtoBuffer, Json, Xml }; diff --git a/src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs b/src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs index 5b88ac94..02f2ea46 100644 --- a/src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs +++ b/src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs @@ -1,6 +1,6 @@ using System; -namespace Google.ProtocolBuffers.Serialization +namespace Google.ProtocolBuffers.Serialization.Http { /// /// Defines control information for the various formatting used with HTTP services diff --git a/src/ProtocolBuffers.Serialization/Http/ServiceExtensions.cs b/src/ProtocolBuffers.Serialization/Http/ServiceExtensions.cs index 3ca9964a..022cfb6a 100644 --- a/src/ProtocolBuffers.Serialization/Http/ServiceExtensions.cs +++ b/src/ProtocolBuffers.Serialization/Http/ServiceExtensions.cs @@ -3,7 +3,7 @@ using System.Text; using Google.ProtocolBuffers; using System.IO; -namespace Google.ProtocolBuffers.Serialization +namespace Google.ProtocolBuffers.Serialization.Http { /// /// Extensions for the IRpcServerStub @@ -25,6 +25,7 @@ namespace Google.ProtocolBuffers.Serialization string contentType, Stream input, string responseType, Stream output) { ICodedInputStream codedInput = MessageFormatFactory.CreateInputStream(options, contentType, input); + codedInput.ReadMessageStart(); IMessageLite response = stub.CallMethod(methodName, codedInput, options.ExtensionRegistry); response.WriteTo(options, responseType, output); } diff --git a/src/ProtocolBuffers.Serialization/JsonFormatReader.cs b/src/ProtocolBuffers.Serialization/JsonFormatReader.cs index 40336787..240ce625 100644 --- a/src/ProtocolBuffers.Serialization/JsonFormatReader.cs +++ b/src/ProtocolBuffers.Serialization/JsonFormatReader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Xml; @@ -103,19 +103,18 @@ namespace Google.ProtocolBuffers.Serialization /// /// Reads the root-message preamble specific to this formatter /// - public override AbstractReader ReadStartMessage() + public override void ReadMessageStart() { _input.Consume('{'); _stopChar.Push('}'); _state = ReaderState.BeginObject; - return this; } /// /// Reads the root-message close specific to this formatter /// - public override void ReadEndMessage() + public override void ReadMessageEnd() { _input.Consume((char)_stopChar.Pop()); _state = ReaderState.EndValue; @@ -126,9 +125,9 @@ namespace Google.ProtocolBuffers.Serialization /// public override TBuilder Merge(TBuilder builder, ExtensionRegistry registry) { - AbstractReader rdr = ReadStartMessage(); - builder.WeakMergeFrom(rdr, registry); - rdr.ReadEndMessage(); + ReadMessageStart(); + builder.WeakMergeFrom(this, registry); + ReadMessageEnd(); return builder; } diff --git a/src/ProtocolBuffers.Serialization/JsonFormatWriter.cs b/src/ProtocolBuffers.Serialization/JsonFormatWriter.cs index 5f396ae5..f387f39d 100644 --- a/src/ProtocolBuffers.Serialization/JsonFormatWriter.cs +++ b/src/ProtocolBuffers.Serialization/JsonFormatWriter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.IO; @@ -101,7 +101,7 @@ namespace Google.ProtocolBuffers.Serialization private class JsonStreamWriter : JsonFormatWriter { #if SILVERLIGHT2 || COMPACT_FRAMEWORK_35 - static readonly Encoding Encoding = Encoding.UTF8; + static readonly Encoding Encoding = new UTF8Encoding(false); #else private static readonly Encoding Encoding = Encoding.ASCII; #endif @@ -244,9 +244,10 @@ namespace Google.ProtocolBuffers.Serialization /// protected override void Dispose(bool disposing) { - if (disposing && _counter.Count == 1) + if (disposing) { - EndMessage(); + while(_counter.Count > 1) + WriteMessageEnd(); } base.Dispose(disposing); @@ -458,17 +459,17 @@ namespace Google.ProtocolBuffers.Serialization /// public override void WriteMessage(IMessageLite message) { - StartMessage(); + WriteMessageStart(); message.WriteTo(this); - EndMessage(); + WriteMessageEnd(); } /// /// Used to write the root-message preamble, in json this is the left-curly brace '{'. /// After this call you can call IMessageLite.MergeTo(...) and complete the message with - /// a call to EndMessage(). + /// a call to WriteMessageEnd(). /// - public override void StartMessage() + public override void WriteMessageStart() { if (_isArray) { @@ -479,9 +480,9 @@ namespace Google.ProtocolBuffers.Serialization } /// - /// Used to complete a root-message previously started with a call to StartMessage() + /// Used to complete a root-message previously started with a call to WriteMessageStart() /// - public override void EndMessage() + public override void WriteMessageEnd() { _counter.RemoveAt(_counter.Count - 1); WriteLine("}"); diff --git a/src/ProtocolBuffers.Serialization/XmlFormatReader.cs b/src/ProtocolBuffers.Serialization/XmlFormatReader.cs index 0d3bca67..98b69776 100644 --- a/src/ProtocolBuffers.Serialization/XmlFormatReader.cs +++ b/src/ProtocolBuffers.Serialization/XmlFormatReader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Xml; @@ -14,8 +14,23 @@ namespace Google.ProtocolBuffers.Serialization { public const string DefaultRootElementName = XmlFormatWriter.DefaultRootElementName; private readonly XmlReader _input; + private readonly Stack _elements; private string _rootElementName; + private struct ElementStack + { + public readonly string LocalName; + public readonly int Depth; + public readonly bool IsEmpty; + + public ElementStack(string localName, int depth, bool isEmpty) : this() + { + LocalName = localName; + IsEmpty = isEmpty; + Depth = depth; + } + } + private static XmlReaderSettings DefaultSettings { get @@ -72,20 +87,10 @@ namespace Google.ProtocolBuffers.Serialization { _input = input; _rootElementName = DefaultRootElementName; + _elements = new Stack(); Options = XmlReaderOptions.None; } - /// - /// Constructs the XmlFormatReader with the XmlReader and options - /// - protected XmlFormatReader(XmlFormatReader copyFrom, XmlReader input) - : base(copyFrom) - { - _input = input; - _rootElementName = copyFrom._rootElementName; - Options = copyFrom.Options; - } - /// /// Gets or sets the options to use when reading the xml /// @@ -113,20 +118,6 @@ namespace Google.ProtocolBuffers.Serialization } } - private XmlFormatReader CloneWith(XmlReader rdr) - { - XmlFormatReader copy = new XmlFormatReader(this, rdr); - return copy; - } - - private void NextElement() - { - while (!_input.IsStartElement() && _input.Read()) - { - continue; - } - } - private static void Assert(bool cond) { if (!cond) @@ -138,36 +129,49 @@ namespace Google.ProtocolBuffers.Serialization /// /// Reads the root-message preamble specific to this formatter /// - public override AbstractReader ReadStartMessage() + public override void ReadMessageStart() { - return ReadStartMessage(_rootElementName); + ReadMessageStart(_rootElementName); } - public AbstractReader ReadStartMessage(string element) + /// + /// Reads the root-message preamble specific to this formatter + /// + public void ReadMessageStart(string element) { - string field; - Assert(PeekNext(out field) && field == element); - - XmlReader child = _input.ReadSubtree(); - while (!child.IsStartElement() && child.Read()) + while (!_input.IsStartElement() && _input.Read()) { continue; } - child.Read(); - return CloneWith(child); + Assert(_input.IsStartElement() && _input.LocalName == element); + _elements.Push(new ElementStack(element, _input.Depth, _input.IsEmptyElement)); + _input.Read(); } /// /// Reads the root-message close specific to this formatter, MUST be called - /// on the reader obtained from ReadStartMessage(string element). + /// on the reader obtained from ReadMessageStart(string element). /// - public override void ReadEndMessage() + public override void ReadMessageEnd() { - Assert(0 == _input.Depth); - if(_input.NodeType == XmlNodeType.EndElement) + Assert(_elements.Count > 0); + + ElementStack stop = _elements.Peek(); + while (_input.NodeType != XmlNodeType.EndElement && _input.NodeType != XmlNodeType.Element + && _input.Depth > stop.Depth && _input.Read()) { + continue; + } + + if (!stop.IsEmpty) + { + Assert(_input.NodeType == XmlNodeType.EndElement + && _input.LocalName == stop.LocalName + && _input.Depth == stop.Depth); + _input.Read(); } + _elements.Pop(); } /// @@ -192,9 +196,9 @@ namespace Google.ProtocolBuffers.Serialization public TBuilder Merge(string element, TBuilder builder, ExtensionRegistry registry) where TBuilder : IBuilderLite { - string field; - Assert(PeekNext(out field) && field == element); - ReadMessage(builder, registry); + ReadMessageStart(element); + builder.WeakMergeFrom(this, registry); + ReadMessageEnd(); return builder; } @@ -207,7 +211,21 @@ namespace Google.ProtocolBuffers.Serialization /// protected override bool PeekNext(out string field) { - NextElement(); + ElementStack stopNode; + if (_elements.Count == 0) + { + stopNode = new ElementStack(null, _input.Depth - 1, false); + } + else + { + stopNode = _elements.Peek(); + } + + while (!_input.IsStartElement() && _input.Depth > stopNode.Depth && _input.Read()) + { + continue; + } + if (_input.IsStartElement()) { field = _input.LocalName; @@ -270,20 +288,9 @@ namespace Google.ProtocolBuffers.Serialization protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry) { Assert(_input.IsStartElement()); - - if (!_input.IsEmptyElement) - { - int depth = _input.Depth; - XmlReader child = _input.ReadSubtree(); - while (!child.IsStartElement() && child.Read()) - { - continue; - } - child.Read(); - builder.WeakMergeFrom(CloneWith(child), registry); - Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement); - } - _input.Read(); + ReadMessageStart(_input.LocalName); + builder.WeakMergeFrom(this, registry); + ReadMessageEnd(); return true; } @@ -305,27 +312,16 @@ namespace Google.ProtocolBuffers.Serialization { yield return item; } - yield break; } - if (!_input.IsEmptyElement) + else { - int depth = _input.Depth; - XmlReader child = _input.ReadSubtree(); - - while (!child.IsStartElement() && child.Read()) - { - continue; - } - child.Read(); - - foreach (string item in CloneWith(child).NonNestedArrayItems("item")) + ReadMessageStart(field); + foreach (string item in NonNestedArrayItems("item")) { yield return item; } - Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement); + ReadMessageEnd(); } - _input.Read(); - yield break; } } } \ No newline at end of file diff --git a/src/ProtocolBuffers.Serialization/XmlFormatWriter.cs b/src/ProtocolBuffers.Serialization/XmlFormatWriter.cs index a9cfcc1e..79f403df 100644 --- a/src/ProtocolBuffers.Serialization/XmlFormatWriter.cs +++ b/src/ProtocolBuffers.Serialization/XmlFormatWriter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.IO; using System.Text; @@ -14,10 +14,12 @@ namespace Google.ProtocolBuffers.Serialization /// public class XmlFormatWriter : AbstractTextWriter { + private static readonly Encoding DefaultEncoding = new UTF8Encoding(false); public const string DefaultRootElementName = "root"; private const int NestedArrayFlag = 0x0001; private readonly XmlWriter _output; private string _rootElementName; + private int _messageOpenCount; private static XmlWriterSettings DefaultSettings(Encoding encoding) { @@ -43,7 +45,7 @@ namespace Google.ProtocolBuffers.Serialization /// public static XmlFormatWriter CreateInstance(Stream output) { - return new XmlFormatWriter(XmlWriter.Create(output, DefaultSettings(Encoding.UTF8))); + return new XmlFormatWriter(XmlWriter.Create(output, DefaultSettings(DefaultEncoding))); } /// @@ -65,6 +67,7 @@ namespace Google.ProtocolBuffers.Serialization protected XmlFormatWriter(XmlWriter output) { _output = output; + _messageOpenCount = 0; _rootElementName = DefaultRootElementName; } @@ -75,8 +78,8 @@ namespace Google.ProtocolBuffers.Serialization { if (disposing) { - if (_output.WriteState != WriteState.Closed && _output.WriteState != WriteState.Start) - _output.WriteEndDocument(); + while(_messageOpenCount > 0) + WriteMessageEnd(); _output.Close(); } @@ -128,9 +131,9 @@ namespace Google.ProtocolBuffers.Serialization /// /// Used to write the root-message preamble, in xml this is open element for RootElementName, /// by default "<root>". After this call you can call IMessageLite.MergeTo(...) and - /// complete the message with a call to EndMessage(). + /// complete the message with a call to WriteMessageEnd(). /// - public override void StartMessage() + public override void WriteMessageStart() { StartMessage(_rootElementName); } @@ -138,7 +141,7 @@ namespace Google.ProtocolBuffers.Serialization /// /// Used to write the root-message preamble, in xml this is open element for elementName. /// After this call you can call IMessageLite.MergeTo(...) and complete the message with - /// a call to EndMessage(). + /// a call to WriteMessageEnd(). /// public void StartMessage(string elementName) { @@ -151,15 +154,20 @@ namespace Google.ProtocolBuffers.Serialization { _output.WriteStartElement(elementName); } + _messageOpenCount++; } /// - /// Used to complete a root-message previously started with a call to StartMessage() + /// Used to complete a root-message previously started with a call to WriteMessageStart() /// - public override void EndMessage() + public override void WriteMessageEnd() { + if (_messageOpenCount <= 0) + throw new InvalidOperationException(); + _output.WriteEndElement(); _output.Flush(); + _messageOpenCount--; } /// @@ -177,7 +185,7 @@ namespace Google.ProtocolBuffers.Serialization { StartMessage(elementName); message.WriteTo(this); - EndMessage(); + WriteMessageEnd(); } /// diff --git a/src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj b/src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj index 95ab0b9c..549fe88c 100644 --- a/src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj +++ b/src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj @@ -106,6 +106,7 @@ + diff --git a/src/ProtocolBuffers.Test/TestMimeMessageFormats.cs b/src/ProtocolBuffers.Test/TestMimeMessageFormats.cs new file mode 100644 index 00000000..6f4b7e0f --- /dev/null +++ b/src/ProtocolBuffers.Test/TestMimeMessageFormats.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Google.ProtocolBuffers.Serialization; +using Google.ProtocolBuffers.Serialization.Http; +using Google.ProtocolBuffers.TestProtos; +using NUnit.Framework; + +namespace Google.ProtocolBuffers +{ + [TestFixture] + public class TestMimeMessageFormats + { + // There is a whole host of various json mime types in use around the net, this is the set we accept... + readonly IEnumerable JsonTypes = new string[] { "application/json", "application/x-json", "application/x-javascript", "text/javascript", "text/x-javascript", "text/x-json", "text/json" }; + readonly IEnumerable XmlTypes = new string[] { "text/xml", "application/xml" }; + readonly IEnumerable ProtobufTypes = new string[] { "application/binary", "application/x-protobuf", "application/vnd.google.protobuf" }; + + [Test] + public void TestReadJsonMimeTypes() + { + foreach (string type in JsonTypes) + { + Assert.IsTrue( + MessageFormatFactory.CreateInputStream(new MessageFormatOptions(), type, Stream.Null) + is JsonFormatReader); + } + Assert.IsTrue( + MessageFormatFactory.CreateInputStream(new MessageFormatOptions() { DefaultContentType = "application/json" }, null, Stream.Null) + is JsonFormatReader); + } + [Test] + public void TestWriteJsonMimeTypes() + { + foreach (string type in JsonTypes) + { + Assert.IsTrue( + MessageFormatFactory.CreateOutputStream(new MessageFormatOptions(), type, Stream.Null) + is JsonFormatWriter); + } + Assert.IsTrue( + MessageFormatFactory.CreateOutputStream(new MessageFormatOptions() { DefaultContentType = "application/json" }, null, Stream.Null) + is JsonFormatWriter); + } + [Test] + public void TestReadXmlMimeTypes() + { + foreach (string type in XmlTypes) + { + Assert.IsTrue( + MessageFormatFactory.CreateInputStream(new MessageFormatOptions(), type, Stream.Null) + is XmlFormatReader); + } + Assert.IsTrue( + MessageFormatFactory.CreateInputStream(new MessageFormatOptions() { DefaultContentType = "application/xml" }, null, Stream.Null) + is XmlFormatReader); + } + [Test] + public void TestWriteXmlMimeTypes() + { + foreach (string type in XmlTypes) + { + Assert.IsTrue( + MessageFormatFactory.CreateOutputStream(new MessageFormatOptions(), type, Stream.Null) + is XmlFormatWriter); + } + Assert.IsTrue( + MessageFormatFactory.CreateOutputStream(new MessageFormatOptions() { DefaultContentType = "application/xml" }, null, Stream.Null) + is XmlFormatWriter); + } + [Test] + public void TestReadProtoMimeTypes() + { + foreach (string type in ProtobufTypes) + { + Assert.IsTrue( + MessageFormatFactory.CreateInputStream(new MessageFormatOptions(), type, Stream.Null) + is CodedInputStream); + } + Assert.IsTrue( + MessageFormatFactory.CreateInputStream(new MessageFormatOptions() { DefaultContentType = "application/vnd.google.protobuf" }, null, Stream.Null) + is CodedInputStream); + } + [Test] + public void TestWriteProtoMimeTypes() + { + foreach (string type in ProtobufTypes) + { + Assert.IsTrue( + MessageFormatFactory.CreateOutputStream(new MessageFormatOptions(), type, Stream.Null) + is CodedOutputStream); + } + Assert.IsTrue( + MessageFormatFactory.CreateOutputStream(new MessageFormatOptions() { DefaultContentType = "application/vnd.google.protobuf" }, null, Stream.Null) + is CodedOutputStream); + } + [Test] + public void TestMergeFromJsonType() + { + TestXmlMessage msg = new TestXmlMessage.Builder().MergeFrom( + new MessageFormatOptions(), "application/json", new MemoryStream(Encoding.ASCII.GetBytes( + TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build().ToJson() + ))) + .Build(); + Assert.AreEqual("a", msg.Text); + Assert.AreEqual(1, msg.Number); + } + [Test] + public void TestMergeFromXmlType() + { + TestXmlMessage msg = new TestXmlMessage.Builder().MergeFrom( + new MessageFormatOptions(), "application/xml", new MemoryStream(Encoding.ASCII.GetBytes( + TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build().ToXml() + ))) + .Build(); + Assert.AreEqual("a", msg.Text); + Assert.AreEqual(1, msg.Number); + } + [Test] + public void TestMergeFromProtoType() + { + TestXmlMessage msg = new TestXmlMessage.Builder().MergeFrom( + new MessageFormatOptions(), "application/vnd.google.protobuf", new MemoryStream( + TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build().ToByteArray() + )) + .Build(); + Assert.AreEqual("a", msg.Text); + Assert.AreEqual(1, msg.Number); + } + [Test] + public void TestWriteToJsonType() + { + MemoryStream ms = new MemoryStream(); + TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build() + .WriteTo(new MessageFormatOptions(), "application/json", ms); + + Assert.AreEqual(@"{""text"":""a"",""number"":1}", Encoding.UTF8.GetString(ms.ToArray())); + } + [Test] + public void TestWriteToXmlType() + { + MemoryStream ms = new MemoryStream(); + TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build() + .WriteTo(new MessageFormatOptions(), "application/xml", ms); + + Assert.AreEqual("a1", Encoding.UTF8.GetString(ms.ToArray())); + } + [Test] + public void TestWriteToProtoType() + { + MemoryStream ms = new MemoryStream(); + TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build() + .WriteTo(new MessageFormatOptions(), "application/vnd.google.protobuf", ms); + + byte[] bytes = TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build().ToByteArray(); + Assert.AreEqual(bytes, ms.ToArray()); + } + [Test] + public void TestXmlReaderOptions() + { + MemoryStream ms = new MemoryStream(); + XmlFormatWriter.CreateInstance(ms) + .SetOptions(XmlWriterOptions.OutputNestedArrays) + .WriteMessage("my-root-node", TestXmlMessage.CreateBuilder().SetText("a").AddNumbers(1).AddNumbers(2).Build()); + ms.Position = 0; + + MessageFormatOptions options = new MessageFormatOptions() + { + XmlReaderOptions = XmlReaderOptions.ReadNestedArrays, + XmlReaderRootElementName = "my-root-node" + }; + + TestXmlMessage msg = new TestXmlMessage.Builder().MergeFrom( + options, "application/xml", ms) + .Build(); + + Assert.AreEqual("a", msg.Text); + Assert.AreEqual(1, msg.NumbersList[0]); + Assert.AreEqual(2, msg.NumbersList[1]); + + } + [Test] + public void TestXmlWriterOptions() + { + TestXmlMessage message = TestXmlMessage.CreateBuilder().SetText("a").AddNumbers(1).AddNumbers(2).Build(); + MessageFormatOptions options = new MessageFormatOptions() + { + XmlWriterOptions = XmlWriterOptions.OutputNestedArrays, + XmlWriterRootElementName = "root-node" + }; + + MemoryStream ms = new MemoryStream(); + message.WriteTo(options, "application/xml", ms); + ms.Position = 0; + + TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder(); + XmlFormatReader.CreateInstance(ms) + .SetOptions(XmlReaderOptions.ReadNestedArrays) + .Merge("root-node", builder); + + Assert.AreEqual("a", builder.Text); + Assert.AreEqual(1, builder.NumbersList[0]); + Assert.AreEqual(2, builder.NumbersList[1]); + } + [Test] + public void TestJsonFormatted() + { + MemoryStream ms = new MemoryStream(); + TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build() + .WriteTo(new MessageFormatOptions() { FormattedOutput = true }, "application/json", ms); + + Assert.AreEqual("{\r\n \"text\": \"a\",\r\n \"number\": 1\r\n}", Encoding.UTF8.GetString(ms.ToArray())); + } + [Test] + public void TestXmlFormatted() + { + MemoryStream ms = new MemoryStream(); + TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build() + .WriteTo(new MessageFormatOptions() { FormattedOutput = true }, "application/xml", ms); + + Assert.AreEqual("\r\n a\r\n 1\r\n", Encoding.UTF8.GetString(ms.ToArray())); + } + } +} \ No newline at end of file diff --git a/src/ProtocolBuffers.Test/TestWriterFormatJson.cs b/src/ProtocolBuffers.Test/TestWriterFormatJson.cs index 341f0b3c..1958df0d 100644 --- a/src/ProtocolBuffers.Test/TestWriterFormatJson.cs +++ b/src/ProtocolBuffers.Test/TestWriterFormatJson.cs @@ -45,7 +45,7 @@ namespace Google.ProtocolBuffers using (TextWriter output = new StringWriter()) using (AbstractWriter writer = JsonFormatWriter.CreateInstance(output)) { - writer.StartMessage(); //manually begin the message, output is '{' + writer.WriteMessageStart(); //manually begin the message, output is '{' writer.Flush(); Assert.AreEqual("{", output.ToString()); @@ -56,7 +56,7 @@ namespace Google.ProtocolBuffers writer.Flush(); Assert.AreEqual(@"{""valid"":true", output.ToString()); - writer.EndMessage(); //manually write the end message '}' + writer.WriteMessageEnd(); //manually write the end message '}' Assert.AreEqual(@"{""valid"":true}", output.ToString()); } } @@ -65,13 +65,13 @@ namespace Google.ProtocolBuffers public void Example_ReadJsonUsingICodedInputStream() { TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder(); - AbstractReader reader = JsonFormatReader.CreateInstance(@"{""valid"":true}"); + ICodedInputStream reader = JsonFormatReader.CreateInstance(@"{""valid"":true}"); - AbstractReader stream = reader.ReadStartMessage(); //manually read the begin the message '{' + reader.ReadMessageStart(); //manually read the begin the message '{' - builder.MergeFrom(stream); //write the message normally + builder.MergeFrom(reader); //write the message normally - stream.ReadEndMessage(); //manually read the end message '}' + reader.ReadMessageEnd(); //manually read the end message '}' } protected string Content; @@ -401,6 +401,28 @@ namespace Google.ProtocolBuffers Assert.AreEqual(3, ordinal); Assert.AreEqual(3, builder.TextlinesCount); } + [Test] + public void TestReadWriteJsonWithoutRoot() + { + TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder(); + TestXmlMessage message = builder.SetText("abc").SetNumber(123).Build(); + + string Json; + using (StringWriter sw = new StringWriter()) + { + ICodedOutputStream output = JsonFormatWriter.CreateInstance(sw); + + message.WriteTo(output); + output.Flush(); + Json = sw.ToString(); + } + Assert.AreEqual(@"""text"":""abc"",""number"":123", Json); + + ICodedInputStream input = JsonFormatReader.CreateInstance(Json); + TestXmlMessage copy = TestXmlMessage.CreateBuilder().MergeFrom(input).Build(); + + Assert.AreEqual(message, copy); + } [Test,ExpectedException(typeof(RecursionLimitExceededException))] public void TestRecursiveLimit() { diff --git a/src/ProtocolBuffers.Test/TestWriterFormatXml.cs b/src/ProtocolBuffers.Test/TestWriterFormatXml.cs index d56cc525..f401481e 100644 --- a/src/ProtocolBuffers.Test/TestWriterFormatXml.cs +++ b/src/ProtocolBuffers.Test/TestWriterFormatXml.cs @@ -48,12 +48,12 @@ namespace Google.ProtocolBuffers using (TextWriter output = new StringWriter()) using (AbstractWriter writer = XmlFormatWriter.CreateInstance(output)) { - writer.StartMessage(); //manually begin the message, output is '{' + writer.WriteMessageStart(); //manually begin the message, output is '{' ICodedOutputStream stream = writer; - message.WriteTo(stream); //write the message normally + message.WriteTo(stream); //write the message normally - writer.EndMessage(); //manually write the end message '}' + writer.WriteMessageEnd(); //manually write the end message '}' Assert.AreEqual(@"true", output.ToString()); } } @@ -62,13 +62,13 @@ namespace Google.ProtocolBuffers public void Example_ReadXmlUsingICodedInputStream() { TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder(); - AbstractReader reader = XmlFormatReader.CreateInstance(@"true"); + ICodedInputStream reader = XmlFormatReader.CreateInstance(@"true"); - AbstractReader stream = reader.ReadStartMessage(); //manually read the begin the message '{' + reader.ReadMessageStart(); //manually read the begin the message '{' - builder.MergeFrom(stream); //write the message normally + builder.MergeFrom(reader); //read the message normally - stream.ReadEndMessage(); //manually read the end message '}' + reader.ReadMessageEnd(); //manually read the end message '}' } [Test] @@ -387,13 +387,54 @@ namespace Google.ProtocolBuffers public void TestXmlReadEmptyRoot() { TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder(); - AbstractReader reader = XmlFormatReader.CreateInstance(@""); + ICodedInputStream reader = XmlFormatReader.CreateInstance(@""); - AbstractReader stream = reader.ReadStartMessage(); //manually read the begin the message '{' + reader.ReadMessageStart(); //manually read the begin the message '{' - builder.MergeFrom(stream); //write the message normally + builder.MergeFrom(reader); //write the message normally - stream.ReadEndMessage(); //manually read the end message '}' + reader.ReadMessageEnd(); //manually read the end message '}' + } + + [Test] + public void TestXmlReadEmptyChild() + { + TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder(); + ICodedInputStream reader = XmlFormatReader.CreateInstance(@""); + + reader.ReadMessageStart(); //manually read the begin the message '{' + + builder.MergeFrom(reader); //write the message normally + Assert.IsTrue(builder.HasText); + Assert.AreEqual(String.Empty, builder.Text); + } + + [Test] + public void TestXmlReadWriteWithoutRoot() + { + TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder(); + TestXmlMessage message = builder.SetText("abc").SetNumber(123).Build(); + + string xml; + using (StringWriter sw = new StringWriter()) + { + ICodedOutputStream output = XmlFormatWriter.CreateInstance( + XmlWriter.Create(sw, new XmlWriterSettings() { ConformanceLevel = ConformanceLevel.Fragment })); + + message.WriteTo(output); + output.Flush(); + xml = sw.ToString(); + } + Assert.AreEqual("abc123", xml); + + TestXmlMessage copy; + using (XmlReader xr = XmlReader.Create(new StringReader(xml), new XmlReaderSettings() { ConformanceLevel = ConformanceLevel.Fragment })) + { + ICodedInputStream input = XmlFormatReader.CreateInstance(xr); + copy = TestXmlMessage.CreateBuilder().MergeFrom(input).Build(); + } + + Assert.AreEqual(message, copy); } [Test, ExpectedException(typeof(RecursionLimitExceededException))] diff --git a/src/ProtocolBuffers/CodedInputStream.cs b/src/ProtocolBuffers/CodedInputStream.cs index 509b4164..35ebba92 100644 --- a/src/ProtocolBuffers/CodedInputStream.cs +++ b/src/ProtocolBuffers/CodedInputStream.cs @@ -144,6 +144,9 @@ namespace Google.ProtocolBuffers #endregion + void ICodedInputStream.ReadMessageStart() { } + void ICodedInputStream.ReadMessageEnd() { } + #region Validation /// diff --git a/src/ProtocolBuffers/CodedOutputStream.cs b/src/ProtocolBuffers/CodedOutputStream.cs index bb133a77..560719af 100644 --- a/src/ProtocolBuffers/CodedOutputStream.cs +++ b/src/ProtocolBuffers/CodedOutputStream.cs @@ -136,6 +136,9 @@ namespace Google.ProtocolBuffers } } + void ICodedOutputStream.WriteMessageStart() { } + void ICodedOutputStream.WriteMessageEnd() { Flush(); } + #region Writing of unknown fields [Obsolete] diff --git a/src/ProtocolBuffers/ICodedInputStream.cs b/src/ProtocolBuffers/ICodedInputStream.cs index 1d9d26e3..a3c0f30b 100644 --- a/src/ProtocolBuffers/ICodedInputStream.cs +++ b/src/ProtocolBuffers/ICodedInputStream.cs @@ -45,6 +45,24 @@ namespace Google.ProtocolBuffers { public interface ICodedInputStream { + /// + /// Reads any message initialization data expected from the input stream + /// + /// + /// This is primarily used by text formats and unnecessary for protobuffers' own + /// binary format. The API for MessageStart/End was added for consistent handling + /// of output streams regardless of the actual writer implementation. + /// + void ReadMessageStart(); + /// + /// Reads any message finalization data expected from the input stream + /// + /// + /// This is primarily used by text formats and unnecessary for protobuffers' own + /// binary format. The API for MessageStart/End was added for consistent handling + /// of output streams regardless of the actual writer implementation. + /// + void ReadMessageEnd(); /// /// Attempt to read a field tag, returning false if we have reached the end /// of the input data. diff --git a/src/ProtocolBuffers/ICodedOutputStream.cs b/src/ProtocolBuffers/ICodedOutputStream.cs index 2c324792..a686fed1 100644 --- a/src/ProtocolBuffers/ICodedOutputStream.cs +++ b/src/ProtocolBuffers/ICodedOutputStream.cs @@ -51,6 +51,24 @@ namespace Google.ProtocolBuffers /// public interface ICodedOutputStream : IDisposable { + /// + /// Writes any message initialization data needed to the output stream + /// + /// + /// This is primarily used by text formats and unnecessary for protobuffers' own + /// binary format. The API for MessageStart/End was added for consistent handling + /// of output streams regardless of the actual writer implementation. + /// + void WriteMessageStart(); + /// + /// Writes any message finalization data needed to the output stream + /// + /// + /// This is primarily used by text formats and unnecessary for protobuffers' own + /// binary format. The API for MessageStart/End was added for consistent handling + /// of output streams regardless of the actual writer implementation. + /// + void WriteMessageEnd(); /// /// Indicates that all temporary buffers be written to the final output. /// -- cgit v1.2.3