diff --git a/cmake/libprotoc.cmake b/cmake/libprotoc.cmake index e9e88af51..8bf0a1f36 100644 --- a/cmake/libprotoc.cmake +++ b/cmake/libprotoc.cmake @@ -19,6 +19,7 @@ set(libprotoc_files ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_field_base.cc ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_generator.cc ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_helpers.cc + ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_map_field.cc ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_message.cc ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_message_field.cc ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_primitive_field.cc diff --git a/csharp/src/ProtocolBuffers.Test/GeneratedMessageTest.cs b/csharp/src/ProtocolBuffers.Test/GeneratedMessageTest.cs index 261654281..81e35940b 100644 --- a/csharp/src/ProtocolBuffers.Test/GeneratedMessageTest.cs +++ b/csharp/src/ProtocolBuffers.Test/GeneratedMessageTest.cs @@ -1,4 +1,6 @@ using System; +using System.Configuration; +using System.IO; using Google.Protobuf.TestProtos; using NUnit.Framework; @@ -146,6 +148,65 @@ namespace Google.Protobuf Assert.AreEqual(message, parsed); } + [Test] + public void RoundTrip_Maps() + { + var message = new TestAllTypes + { + MapBoolToEnum = { + { false, TestAllTypes.Types.NestedEnum.BAR}, + { true, TestAllTypes.Types.NestedEnum.BAZ} + }, + MapInt32ToBytes = { + { 5, ByteString.CopyFrom(6, 7, 8) }, + { 25, ByteString.CopyFrom(1, 2, 3, 4, 5) }, + { 10, ByteString.Empty } + }, + MapStringToNestedMessage = { + { "", new TestAllTypes.Types.NestedMessage { Bb = 10 } }, + { "null value", null }, + } + }; + + byte[] bytes = message.ToByteArray(); + TestAllTypes parsed = TestAllTypes.Parser.ParseFrom(bytes); + Assert.AreEqual(message, parsed); + } + + [Test] + public void MapWithEmptyEntry() + { + var message = new TestAllTypes + { + MapInt32ToBytes = { { 0, ByteString.Empty } } + }; + + byte[] bytes = message.ToByteArray(); + Assert.AreEqual(3, bytes.Length); // Tag for field entry (2 bytes), length of entry (0; 1 byte) + + var parsed = TestAllTypes.Parser.ParseFrom(bytes); + Assert.AreEqual(1, parsed.MapInt32ToBytes.Count); + Assert.AreEqual(ByteString.Empty, parsed.MapInt32ToBytes[0]); + } + + [Test] + public void MapWithOnlyValue() + { + // Hand-craft the stream to contain a single entry with just a value. + var memoryStream = new MemoryStream(); + var output = CodedOutputStream.CreateInstance(memoryStream); + output.WriteTag(TestAllTypes.MapStringToNestedMessageFieldNumber, WireFormat.WireType.LengthDelimited); + var nestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 20 }; + // Size of the entry (tag, size written by WriteMessage, data written by WriteMessage) + output.WriteRawVarint32((uint)(nestedMessage.CalculateSize() + 3)); + output.WriteTag(2, WireFormat.WireType.LengthDelimited); + output.WriteMessage(nestedMessage); + output.Flush(); + + var parsed = TestAllTypes.Parser.ParseFrom(memoryStream.ToArray()); + Assert.AreEqual(nestedMessage, parsed.MapStringToNestedMessage[""]); + } + [Test] public void CloneSingleNonMessageValues() { diff --git a/csharp/src/ProtocolBuffers/CodedOutputStream.cs b/csharp/src/ProtocolBuffers/CodedOutputStream.cs index e56ce789c..def874c0b 100644 --- a/csharp/src/ProtocolBuffers/CodedOutputStream.cs +++ b/csharp/src/ProtocolBuffers/CodedOutputStream.cs @@ -475,6 +475,14 @@ namespace Google.Protobuf WriteRawVarint32(WireFormat.MakeTag(fieldNumber, type)); } + /// + /// Writes an already-encoded tag. + /// + public void WriteTag(uint tag) + { + WriteRawVarint32(tag); + } + /// /// Writes the given single-byte tag directly to the stream. /// diff --git a/csharp/src/ProtocolBuffers/Collections/MapField.cs b/csharp/src/ProtocolBuffers/Collections/MapField.cs new file mode 100644 index 000000000..2f5a741d3 --- /dev/null +++ b/csharp/src/ProtocolBuffers/Collections/MapField.cs @@ -0,0 +1,400 @@ +#region Copyright notice and license +// Protocol Buffers - Google's data interchange format +// Copyright 2015 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Google.Protobuf.Collections +{ + /// + /// Representation of a map field in a Protocol Buffer message. + /// + /// + /// This implementation preserves insertion order for simplicity of testing + /// code using maps fields. Overwriting an existing entry does not change the + /// position of that entry within the map. Equality is not order-sensitive. + /// For string keys, the equality comparison is provided by . + /// + /// Key type in the map. Must be a type supported by Protocol Buffer map keys. + /// Value type in the map. Must be a type supported by Protocol Buffers. + public sealed class MapField : IDeepCloneable>, IFreezable, IDictionary, IEquatable> + { + // TODO: Don't create the map/list until we have an entry. (Assume many maps will be empty.) + private bool frozen; + private readonly Dictionary>> map = + new Dictionary>>(); + private readonly LinkedList> list = new LinkedList>(); + + public MapField Clone() + { + var clone = new MapField(); + // Keys are never cloneable. Values might be. + if (typeof(IDeepCloneable).IsAssignableFrom(typeof(TValue))) + { + foreach (var pair in list) + { + clone.Add(pair.Key, pair.Value == null ? pair.Value : ((IDeepCloneable) pair.Value).Clone()); + } + } + else + { + // Nothing is cloneable, so we don't need to worry. + clone.Add(this); + } + return clone; + } + + public void Add(TKey key, TValue value) + { + ThrowHelper.ThrowIfNull(key, "key"); + this.CheckMutable(); + if (ContainsKey(key)) + { + throw new ArgumentException("Key already exists in map", "key"); + } + this[key] = value; + } + + public bool ContainsKey(TKey key) + { + return map.ContainsKey(key); + } + + public bool Remove(TKey key) + { + this.CheckMutable(); + LinkedListNode> node; + if (map.TryGetValue(key, out node)) + { + map.Remove(key); + node.List.Remove(node); + return true; + } + else + { + return false; + } + } + + public bool TryGetValue(TKey key, out TValue value) + { + LinkedListNode> node; + if (map.TryGetValue(key, out node)) + { + value = node.Value.Value; + return true; + } + else + { + value = default(TValue); + return false; + } + } + + public TValue this[TKey key] + { + get + { + TValue value; + if (TryGetValue(key, out value)) + { + return value; + } + throw new KeyNotFoundException(); + } + set + { + this.CheckMutable(); + LinkedListNode> node; + var pair = new KeyValuePair(key, value); + if (map.TryGetValue(key, out node)) + { + node.Value = pair; + } + else + { + node = list.AddLast(pair); + map[key] = node; + } + } + } + + // TODO: Make these views? + public ICollection Keys { get { return list.Select(t => t.Key).ToList(); } } + public ICollection Values { get { return list.Select(t => t.Value).ToList(); } } + + public void Add(IDictionary entries) + { + foreach (var pair in entries) + { + Add(pair); + } + } + + public IEnumerator> GetEnumerator() + { + return list.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Add(KeyValuePair item) + { + this.CheckMutable(); + Add(item.Key, item.Value); + } + + public void Clear() + { + this.CheckMutable(); + list.Clear(); + map.Clear(); + } + + public bool Contains(KeyValuePair item) + { + TValue value; + return TryGetValue(item.Key, out value) + && EqualityComparer.Default.Equals(item.Value, value); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + list.CopyTo(array, arrayIndex); + } + + public bool Remove(KeyValuePair item) + { + this.CheckMutable(); + return Remove(item.Key); + } + + public int Count { get { return list.Count; } } + public bool IsReadOnly { get { return frozen; } } + + public void Freeze() + { + if (IsFrozen) + { + return; + } + frozen = true; + // Only values can be frozen, as all the key types are simple. + // Everything can be done in-place, as we're just freezing objects. + if (typeof(IFreezable).IsAssignableFrom(typeof(TValue))) + { + for (var node = list.First; node != null; node = node.Next) + { + var pair = node.Value; + IFreezable freezableValue = pair.Value as IFreezable; + if (freezableValue != null) + { + freezableValue.Freeze(); + } + } + } + } + + public bool IsFrozen { get { return frozen; } } + + public override bool Equals(object other) + { + return Equals(other as MapField); + } + + public override int GetHashCode() + { + var valueComparer = EqualityComparer.Default; + int hash = 0; + foreach (var pair in list) + { + hash ^= pair.Key.GetHashCode() * 31 + valueComparer.GetHashCode(pair.Value); + } + return hash; + } + + public bool Equals(MapField other) + { + if (other == null) + { + return false; + } + if (other == this) + { + return true; + } + if (other.Count != this.Count) + { + return false; + } + var valueComparer = EqualityComparer.Default; + foreach (var pair in this) + { + TValue value; + if (!other.TryGetValue(pair.Key, out value)) + { + return false; + } + if (!valueComparer.Equals(value, pair.Value)) + { + return false; + } + } + return true; + } + + public void AddEntriesFrom(CodedInputStream input, Codec codec) + { + // TODO: Peek at the next tag and see if it's the same. If it is, we can reuse the entry object... + var adapter = new Codec.MessageAdapter(codec); + adapter.Reset(); + input.ReadMessage(adapter); + this[adapter.Key] = adapter.Value; + } + + public void WriteTo(CodedOutputStream output, Codec codec) + { + var message = new Codec.MessageAdapter(codec); + foreach (var entry in list) + { + message.Key = entry.Key; + message.Value = entry.Value; + output.WriteTag(codec.MapTag); + output.WriteMessage(message); + } + } + + public int CalculateSize(Codec codec) + { + var message = new Codec.MessageAdapter(codec); + int size = 0; + foreach (var entry in list) + { + message.Key = entry.Key; + message.Value = entry.Value; + size += CodedOutputStream.ComputeRawVarint32Size(codec.MapTag); + size += CodedOutputStream.ComputeMessageSize(message); + } + return size; + } + + /// + /// A codec for a specific map field. This contains all the information required to encoded and + /// decode the nested messages. + /// + public sealed class Codec + { + private readonly FieldCodec keyCodec; + private readonly FieldCodec valueCodec; + private readonly uint mapTag; + + public Codec(FieldCodec keyCodec, FieldCodec valueCodec, uint mapTag) + { + this.keyCodec = keyCodec; + this.valueCodec = valueCodec; + this.mapTag = mapTag; + } + + /// + /// The tag used in the enclosing message to indicate map entries. + /// + internal uint MapTag { get { return mapTag; } } + + /// + /// A mutable message class, used for parsing and serializing. This + /// delegates the work to a codec, but implements the interface + /// for interop with and . + /// This is nested inside Codec as it's tightly coupled to the associated codec, + /// and it's simpler if it has direct access to all its fields. + /// + internal class MessageAdapter : IMessage + { + private readonly Codec codec; + internal TKey Key { get; set; } + internal TValue Value { get; set; } + internal int Size { get; set; } + + internal MessageAdapter(Codec codec) + { + this.codec = codec; + } + + internal void Reset() + { + Key = codec.keyCodec.DefaultValue; + Value = codec.valueCodec.DefaultValue; + } + + public void MergeFrom(CodedInputStream input) + { + uint tag; + while (input.ReadTag(out tag)) + { + if (tag == 0) + { + throw InvalidProtocolBufferException.InvalidTag(); + } + if (tag == codec.keyCodec.Tag) + { + Key = codec.keyCodec.Read(input); + } + else if (tag == codec.valueCodec.Tag) + { + Value = codec.valueCodec.Read(input); + } + else if (WireFormat.IsEndGroupTag(tag)) + { + // TODO(jonskeet): Do we need this? (Given that we don't support groups...) + return; + } + } + } + + public void WriteTo(CodedOutputStream output) + { + codec.keyCodec.Write(output, Key); + codec.valueCodec.Write(output, Value); + } + + public int CalculateSize() + { + return codec.keyCodec.CalculateSize(Key) + codec.valueCodec.CalculateSize(Value); + } + } + } + } +} diff --git a/csharp/src/ProtocolBuffers/FieldCodec.cs b/csharp/src/ProtocolBuffers/FieldCodec.cs new file mode 100644 index 000000000..931b54d33 --- /dev/null +++ b/csharp/src/ProtocolBuffers/FieldCodec.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; + +namespace Google.Protobuf +{ + /// + /// Factory methods for . + /// + public static class FieldCodec + { + public static FieldCodec ForString(uint tag) + { + return new FieldCodec(input => input.ReadString(), (output, value) => output.WriteString(value), CodedOutputStream.ComputeStringSize, tag); + } + + public static FieldCodec ForBytes(uint tag) + { + return new FieldCodec(input => input.ReadBytes(), (output, value) => output.WriteBytes(value), CodedOutputStream.ComputeBytesSize, tag); + } + + public static FieldCodec ForBool(uint tag) + { + return new FieldCodec(input => input.ReadBool(), (output, value) => output.WriteBool(value), CodedOutputStream.ComputeBoolSize, tag); + } + + public static FieldCodec ForInt32(uint tag) + { + return new FieldCodec(input => input.ReadInt32(), (output, value) => output.WriteInt32(value), CodedOutputStream.ComputeInt32Size, tag); + } + + public static FieldCodec ForSInt32(uint tag) + { + return new FieldCodec(input => input.ReadSInt32(), (output, value) => output.WriteSInt32(value), CodedOutputStream.ComputeSInt32Size, tag); + } + + public static FieldCodec ForFixedInt32(uint tag) + { + return new FieldCodec(input => input.ReadFixed32(), (output, value) => output.WriteFixed32(value), CodedOutputStream.ComputeFixed32Size, tag); + } + + public static FieldCodec ForUInt32(uint tag) + { + return new FieldCodec(input => input.ReadUInt32(), (output, value) => output.WriteUInt32(value), CodedOutputStream.ComputeUInt32Size, tag); + } + + public static FieldCodec ForInt64(uint tag) + { + return new FieldCodec(input => input.ReadInt64(), (output, value) => output.WriteInt64(value), CodedOutputStream.ComputeInt64Size, tag); + } + + public static FieldCodec ForSInt64(uint tag) + { + return new FieldCodec(input => input.ReadSInt64(), (output, value) => output.WriteSInt64(value), CodedOutputStream.ComputeSInt64Size, tag); + } + + public static FieldCodec ForFixedInt64(uint tag) + { + return new FieldCodec(input => input.ReadFixed64(), (output, value) => output.WriteFixed64(value), CodedOutputStream.ComputeFixed64Size, tag); + } + + public static FieldCodec ForUInt64(uint tag) + { + return new FieldCodec(input => input.ReadUInt64(), (output, value) => output.WriteUInt64(value), CodedOutputStream.ComputeUInt64Size, tag); + } + + public static FieldCodec ForFloat(uint tag) + { + return new FieldCodec(input => input.ReadFloat(), (output, value) => output.WriteFloat(value), CodedOutputStream.ComputeFloatSize, tag); + } + + public static FieldCodec ForDouble(uint tag) + { + return new FieldCodec(input => input.ReadFloat(), (output, value) => output.WriteDouble(value), CodedOutputStream.ComputeDoubleSize, tag); + } + + // Enums are tricky. We can probably use expression trees to build these delegates automatically, + // but it's easy to generate the code fdor it. + public static FieldCodec ForEnum(uint tag, Func toInt32, Func fromInt32) + { + return new FieldCodec(input => fromInt32( + input.ReadEnum()), + (output, value) => output.WriteEnum(toInt32(value)), + value => CodedOutputStream.ComputeEnumSize(toInt32(value)), tag); + } + + public static FieldCodec ForMessage(uint tag, MessageParser parser) where T : IMessage + { + return new FieldCodec(input => { T message = parser.CreateTemplate(); input.ReadMessage(message); return message; }, + (output, value) => output.WriteMessage(value), message => CodedOutputStream.ComputeMessageSize(message), tag); + } + } + + /// + /// An encode/decode pair for a single field. This effectively encapsulates + /// all the information needed to read or write the field value from/to a coded + /// stream. + /// + /// + /// This never writes default values to the stream, and is not currently designed + /// to play well with packed arrays. + /// + public sealed class FieldCodec + { + private static readonly Func IsDefault; + private static readonly T Default; + + static FieldCodec() + { + if (typeof(T) == typeof(string)) + { + Default = (T)(object)""; + IsDefault = CreateDefaultValueCheck(x => x.Length == 0); + } + else if (typeof(T) == typeof(ByteString)) + { + Default = (T)(object)ByteString.Empty; + IsDefault = CreateDefaultValueCheck(x => x.Length == 0); + } + else if (!typeof(T).IsValueType) + { + // Default default + IsDefault = CreateDefaultValueCheck(x => x == null); + } + else + { + // Default default + IsDefault = CreateDefaultValueCheck(x => EqualityComparer.Default.Equals(x, default(T))); + } + } + + private static Func CreateDefaultValueCheck(Func check) + { + return (Func)(object)check; + } + + private readonly Func reader; + private readonly Action writer; + private readonly Func sizeComputer; + private readonly uint tag; + private readonly int tagSize; + + internal FieldCodec( + Func reader, + Action writer, + Func sizeComputer, + uint tag) + { + this.reader = reader; + this.writer = writer; + this.sizeComputer = sizeComputer; + this.tag = tag; + tagSize = CodedOutputStream.ComputeRawVarint32Size(tag); + } + + public uint Tag { get { return tag; } } + + public T DefaultValue { get { return Default; } } + + public void Write(CodedOutputStream output, T value) + { + if (!IsDefault(value)) + { + output.WriteTag(tag); + writer(output, value); + } + } + + public T Read(CodedInputStream input) + { + return reader(input); + } + + public int CalculateSize(T value) + { + return IsDefault(value) ? 0 : sizeComputer(value) + CodedOutputStream.ComputeRawVarint32Size(tag); + } + } +} diff --git a/csharp/src/ProtocolBuffers/ProtocolBuffers.csproj b/csharp/src/ProtocolBuffers/ProtocolBuffers.csproj index d1551148a..5edeff703 100644 --- a/csharp/src/ProtocolBuffers/ProtocolBuffers.csproj +++ b/csharp/src/ProtocolBuffers/ProtocolBuffers.csproj @@ -60,6 +60,7 @@ + @@ -84,6 +85,7 @@ + diff --git a/src/Makefile.am b/src/Makefile.am index 866af48a0..cea3089e4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -425,6 +425,8 @@ libprotoc_la_SOURCES = \ google/protobuf/compiler/csharp/csharp_generator.cc \ google/protobuf/compiler/csharp/csharp_helpers.cc \ google/protobuf/compiler/csharp/csharp_helpers.h \ + google/protobuf/compiler/csharp/csharp_map_field.cc \ + google/protobuf/compiler/csharp/csharp_map_field.h \ google/protobuf/compiler/csharp/csharp_message.cc \ google/protobuf/compiler/csharp/csharp_message.h \ google/protobuf/compiler/csharp/csharp_message_field.cc \ diff --git a/src/google/protobuf/compiler/csharp/csharp_enum_field.cc b/src/google/protobuf/compiler/csharp/csharp_enum_field.cc index af34f50cb..d38fb1ed2 100644 --- a/src/google/protobuf/compiler/csharp/csharp_enum_field.cc +++ b/src/google/protobuf/compiler/csharp/csharp_enum_field.cc @@ -74,6 +74,12 @@ void EnumFieldGenerator::GenerateSerializedSizeCode(io::Printer* printer) { "}\n"); } +void EnumFieldGenerator::GenerateCodecCode(io::Printer* printer) { + printer->Print( + variables_, + "pb::FieldCodec.ForEnum($tag$, x => (int) x, x => ($type_name$) x)"); +} + EnumOneofFieldGenerator::EnumOneofFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal) : PrimitiveOneofFieldGenerator(descriptor, fieldOrdinal) { diff --git a/src/google/protobuf/compiler/csharp/csharp_enum_field.h b/src/google/protobuf/compiler/csharp/csharp_enum_field.h index e627b7ccd..083641578 100644 --- a/src/google/protobuf/compiler/csharp/csharp_enum_field.h +++ b/src/google/protobuf/compiler/csharp/csharp_enum_field.h @@ -46,6 +46,7 @@ class EnumFieldGenerator : public PrimitiveFieldGenerator { EnumFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal); ~EnumFieldGenerator(); + virtual void GenerateCodecCode(io::Printer* printer); virtual void GenerateParsingCode(io::Printer* printer); virtual void GenerateSerializationCode(io::Printer* printer); virtual void GenerateSerializedSizeCode(io::Printer* printer); diff --git a/src/google/protobuf/compiler/csharp/csharp_field_base.cc b/src/google/protobuf/compiler/csharp/csharp_field_base.cc index c716e1bf7..bfb39a64f 100644 --- a/src/google/protobuf/compiler/csharp/csharp_field_base.cc +++ b/src/google/protobuf/compiler/csharp/csharp_field_base.cc @@ -65,6 +65,7 @@ void FieldGeneratorBase::SetCommonFieldVariables( tag_bytes += ", " + SimpleItoa(tag_array[i]); } + (*variables)["tag"] = SimpleItoa(tag); (*variables)["tag_size"] = SimpleItoa(tag_size); (*variables)["tag_bytes"] = tag_bytes; @@ -112,6 +113,11 @@ void FieldGeneratorBase::GenerateFreezingCode(io::Printer* printer) { // special handling for freezing, so default to not generating any code. } +void FieldGeneratorBase::GenerateCodecCode(io::Printer* printer) { + // No-op: expect this to be overridden by appropriate types. + // Could fail if we get called here though... +} + void FieldGeneratorBase::AddDeprecatedFlag(io::Printer* printer) { if (descriptor_->options().deprecated()) { @@ -151,12 +157,16 @@ std::string FieldGeneratorBase::name() { } std::string FieldGeneratorBase::type_name() { - switch (descriptor_->type()) { + return type_name(descriptor_); +} + +std::string FieldGeneratorBase::type_name(const FieldDescriptor* descriptor) { + switch (descriptor->type()) { case FieldDescriptor::TYPE_ENUM: - return GetClassName(descriptor_->enum_type()); + return GetClassName(descriptor->enum_type()); case FieldDescriptor::TYPE_MESSAGE: case FieldDescriptor::TYPE_GROUP: - return GetClassName(descriptor_->message_type()); + return GetClassName(descriptor->message_type()); case FieldDescriptor::TYPE_DOUBLE: return "double"; case FieldDescriptor::TYPE_FLOAT: diff --git a/src/google/protobuf/compiler/csharp/csharp_field_base.h b/src/google/protobuf/compiler/csharp/csharp_field_base.h index abf9254b6..349d835ba 100644 --- a/src/google/protobuf/compiler/csharp/csharp_field_base.h +++ b/src/google/protobuf/compiler/csharp/csharp_field_base.h @@ -49,6 +49,7 @@ class FieldGeneratorBase : public SourceGeneratorBase { virtual void GenerateCloningCode(io::Printer* printer) = 0; virtual void GenerateFreezingCode(io::Printer* printer); + virtual void GenerateCodecCode(io::Printer* printer); virtual void GenerateMembers(io::Printer* printer) = 0; virtual void GenerateMergingCode(io::Printer* printer) = 0; virtual void GenerateParsingCode(io::Printer* printer) = 0; @@ -76,6 +77,7 @@ class FieldGeneratorBase : public SourceGeneratorBase { std::string property_name(); std::string name(); std::string type_name(); + std::string type_name(const FieldDescriptor* descriptor); bool has_default_value(); bool is_nullable_type(); std::string default_value(); diff --git a/src/google/protobuf/compiler/csharp/csharp_helpers.cc b/src/google/protobuf/compiler/csharp/csharp_helpers.cc index 39a53268f..927fea975 100644 --- a/src/google/protobuf/compiler/csharp/csharp_helpers.cc +++ b/src/google/protobuf/compiler/csharp/csharp_helpers.cc @@ -46,6 +46,7 @@ #include #include +#include #include #include #include @@ -355,7 +356,11 @@ FieldGeneratorBase* CreateFieldGenerator(const FieldDescriptor* descriptor, case FieldDescriptor::TYPE_GROUP: case FieldDescriptor::TYPE_MESSAGE: if (descriptor->is_repeated()) { - return new RepeatedMessageFieldGenerator(descriptor, fieldOrdinal); + if (descriptor->is_map()) { + return new MapFieldGenerator(descriptor, fieldOrdinal); + } else { + return new RepeatedMessageFieldGenerator(descriptor, fieldOrdinal); + } } else { if (descriptor->containing_oneof()) { return new MessageOneofFieldGenerator(descriptor, fieldOrdinal); diff --git a/src/google/protobuf/compiler/csharp/csharp_map_field.cc b/src/google/protobuf/compiler/csharp/csharp_map_field.cc new file mode 100644 index 000000000..b8f1592c2 --- /dev/null +++ b/src/google/protobuf/compiler/csharp/csharp_map_field.cc @@ -0,0 +1,141 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2015 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace google { +namespace protobuf { +namespace compiler { +namespace csharp { + +MapFieldGenerator::MapFieldGenerator(const FieldDescriptor* descriptor, + int fieldOrdinal) + : FieldGeneratorBase(descriptor, fieldOrdinal) { +} + +MapFieldGenerator::~MapFieldGenerator() { +} + +void MapFieldGenerator::GenerateMembers(io::Printer* printer) { + const FieldDescriptor* key_descriptor = + descriptor_->message_type()->FindFieldByName("key"); + const FieldDescriptor* value_descriptor = + descriptor_->message_type()->FindFieldByName("value"); + variables_["key_type_name"] = type_name(key_descriptor); + variables_["value_type_name"] = type_name(value_descriptor); + scoped_ptr key_generator(CreateFieldGenerator(key_descriptor, 1)); + scoped_ptr value_generator(CreateFieldGenerator(value_descriptor, 2)); + + printer->Print( + variables_, + "private static readonly pbc::MapField<$key_type_name$, $value_type_name$>.Codec _map_$name$_codec\n" + " = new pbc::MapField<$key_type_name$, $value_type_name$>.Codec("); + key_generator->GenerateCodecCode(printer); + printer->Print(", "); + value_generator->GenerateCodecCode(printer); + printer->Print( + variables_, + ", $tag$);\n" + "private readonly pbc::MapField<$key_type_name$, $value_type_name$> $name$_ = new pbc::MapField<$key_type_name$, $value_type_name$>();\n"); + AddDeprecatedFlag(printer); + printer->Print( + variables_, + "public pbc::MapField<$key_type_name$, $value_type_name$> $property_name$ {\n" + " get { return $name$_; }\n" + "}\n"); +} + +void MapFieldGenerator::GenerateMergingCode(io::Printer* printer) { + printer->Print( + variables_, + "$name$_.Add(other.$name$_);\n"); +} + +void MapFieldGenerator::GenerateParsingCode(io::Printer* printer) { + printer->Print( + variables_, + "$name$_.AddEntriesFrom(input, _map_$name$_codec);\n"); +} + +void MapFieldGenerator::GenerateSerializationCode(io::Printer* printer) { + printer->Print( + variables_, + "$name$_.WriteTo(output, _map_$name$_codec);\n"); +} + +void MapFieldGenerator::GenerateSerializedSizeCode(io::Printer* printer) { + printer->Print( + variables_, + "size += $name$_.CalculateSize(_map_$name$_codec);\n"); +} + +void MapFieldGenerator::WriteHash(io::Printer* printer) { + printer->Print( + variables_, + "hash ^= $property_name$.GetHashCode();\n"); +} +void MapFieldGenerator::WriteEquals(io::Printer* printer) { + printer->Print( + variables_, + "if (!$property_name$.Equals(other.$property_name$)) return false;\n"); +} +void MapFieldGenerator::WriteToString(io::Printer* printer) { + /* + variables_["field_name"] = GetFieldName(descriptor_); + printer->Print( + variables_, + "PrintField(\"$field_name$\", has$property_name$, $name$_, writer);\n");*/ +} + +void MapFieldGenerator::GenerateCloningCode(io::Printer* printer) { + printer->Print(variables_, + "$name$_ = other.$name$_.Clone();\n"); +} + +void MapFieldGenerator::GenerateFreezingCode(io::Printer* printer) { + printer->Print(variables_, + "$name$_.Freeze();\n"); +} + +} // namespace csharp +} // namespace compiler +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/compiler/csharp/csharp_map_field.h b/src/google/protobuf/compiler/csharp/csharp_map_field.h new file mode 100644 index 000000000..f33fe1c39 --- /dev/null +++ b/src/google/protobuf/compiler/csharp/csharp_map_field.h @@ -0,0 +1,71 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_COMPILER_CSHARP_MAP_FIELD_H__ +#define GOOGLE_PROTOBUF_COMPILER_CSHARP_MAP_FIELD_H__ + +#include + +#include +#include + +namespace google { +namespace protobuf { +namespace compiler { +namespace csharp { + +class MapFieldGenerator : public FieldGeneratorBase { + public: + MapFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal); + ~MapFieldGenerator(); + + virtual void GenerateCloningCode(io::Printer* printer); + virtual void GenerateFreezingCode(io::Printer* printer); + virtual void GenerateMembers(io::Printer* printer); + virtual void GenerateMergingCode(io::Printer* printer); + virtual void GenerateParsingCode(io::Printer* printer); + virtual void GenerateSerializationCode(io::Printer* printer); + virtual void GenerateSerializedSizeCode(io::Printer* printer); + + virtual void WriteHash(io::Printer* printer); + virtual void WriteEquals(io::Printer* printer); + virtual void WriteToString(io::Printer* printer); + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(MapFieldGenerator); +}; + +} // namespace csharp +} // namespace compiler +} // namespace protobuf +} // namespace google + +#endif // GOOGLE_PROTOBUF_COMPILER_CSHARP_MAP_FIELD_H__ + diff --git a/src/google/protobuf/compiler/csharp/csharp_message.cc b/src/google/protobuf/compiler/csharp/csharp_message.cc index a6c8e32b0..3cb91951a 100644 --- a/src/google/protobuf/compiler/csharp/csharp_message.cc +++ b/src/google/protobuf/compiler/csharp/csharp_message.cc @@ -268,8 +268,6 @@ void MessageGenerator::Generate(io::Printer* printer) { "}\n\n"); } - // TODO(jonskeet): Map properties - // Standard methods GenerateFrameworkMethods(printer); GenerateMessageSerializationMethods(printer); @@ -299,7 +297,6 @@ void MessageGenerator::Generate(io::Printer* printer) { printer->Outdent(); printer->Print("}\n"); printer->Print("\n"); - } void MessageGenerator::GenerateCloningCode(io::Printer* printer) { @@ -451,7 +448,7 @@ void MessageGenerator::GenerateMessageSerializationMethods(io::Printer* printer) } printer->Print("return size;\n"); printer->Outdent(); - printer->Print("}\n"); + printer->Print("}\n\n"); } void MessageGenerator::GenerateMergingMethods(io::Printer* printer) { @@ -469,7 +466,6 @@ void MessageGenerator::GenerateMergingMethods(io::Printer* printer) { "if (other == null) {\n" " return;\n" "}\n"); - // TODO(jonskeet): Maps? // Merge non-oneof fields for (int i = 0; i < descriptor_->field_count(); i++) { if (!descriptor_->field(i)->containing_oneof()) { diff --git a/src/google/protobuf/compiler/csharp/csharp_message_field.cc b/src/google/protobuf/compiler/csharp/csharp_message_field.cc index cbf182d27..d8c82271f 100644 --- a/src/google/protobuf/compiler/csharp/csharp_message_field.cc +++ b/src/google/protobuf/compiler/csharp/csharp_message_field.cc @@ -138,6 +138,12 @@ void MessageFieldGenerator::GenerateFreezingCode(io::Printer* printer) { "if ($has_property_check$) $property_name$.Freeze();\n"); } +void MessageFieldGenerator::GenerateCodecCode(io::Printer* printer) { + printer->Print( + variables_, + "pb::FieldCodec.ForMessage($tag$, $type_name$.Parser)"); +} + MessageOneofFieldGenerator::MessageOneofFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal) : MessageFieldGenerator(descriptor, fieldOrdinal) { diff --git a/src/google/protobuf/compiler/csharp/csharp_message_field.h b/src/google/protobuf/compiler/csharp/csharp_message_field.h index 3e17f92a8..dc6e4dc5a 100644 --- a/src/google/protobuf/compiler/csharp/csharp_message_field.h +++ b/src/google/protobuf/compiler/csharp/csharp_message_field.h @@ -46,6 +46,7 @@ class MessageFieldGenerator : public FieldGeneratorBase { MessageFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal); ~MessageFieldGenerator(); + virtual void GenerateCodecCode(io::Printer* printer); virtual void GenerateCloningCode(io::Printer* printer); virtual void GenerateFreezingCode(io::Printer* printer); virtual void GenerateMembers(io::Printer* printer); diff --git a/src/google/protobuf/compiler/csharp/csharp_primitive_field.cc b/src/google/protobuf/compiler/csharp/csharp_primitive_field.cc index d5542f57e..c40cba13a 100644 --- a/src/google/protobuf/compiler/csharp/csharp_primitive_field.cc +++ b/src/google/protobuf/compiler/csharp/csharp_primitive_field.cc @@ -129,7 +129,7 @@ void PrimitiveFieldGenerator::GenerateSerializedSizeCode(io::Printer* printer) { "size += $tag_size$ + $fixed_size$;\n", "fixed_size", SimpleItoa(fixedSize), "tag_size", variables_["tag_size"]); - } + } printer->Outdent(); printer->Print("}\n"); } @@ -155,6 +155,12 @@ void PrimitiveFieldGenerator::GenerateCloningCode(io::Printer* printer) { "$name$_ = other.$name$_;\n"); } +void PrimitiveFieldGenerator::GenerateCodecCode(io::Printer* printer) { + printer->Print( + variables_, + "pb::FieldCodec.For$capitalized_type_name$($tag$)"); +} + PrimitiveOneofFieldGenerator::PrimitiveOneofFieldGenerator( const FieldDescriptor* descriptor, int fieldOrdinal) : PrimitiveFieldGenerator(descriptor, fieldOrdinal) { diff --git a/src/google/protobuf/compiler/csharp/csharp_primitive_field.h b/src/google/protobuf/compiler/csharp/csharp_primitive_field.h index bac332148..8b87ebc49 100644 --- a/src/google/protobuf/compiler/csharp/csharp_primitive_field.h +++ b/src/google/protobuf/compiler/csharp/csharp_primitive_field.h @@ -46,6 +46,7 @@ class PrimitiveFieldGenerator : public FieldGeneratorBase { PrimitiveFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal); ~PrimitiveFieldGenerator(); + virtual void GenerateCodecCode(io::Printer* printer); virtual void GenerateCloningCode(io::Printer* printer); virtual void GenerateMembers(io::Printer* printer); virtual void GenerateMergingCode(io::Printer* printer); diff --git a/src/google/protobuf/unittest_proto3.proto b/src/google/protobuf/unittest_proto3.proto index f59d21786..41fa56dc3 100644 --- a/src/google/protobuf/unittest_proto3.proto +++ b/src/google/protobuf/unittest_proto3.proto @@ -140,6 +140,11 @@ message TestAllTypes { string oneof_string = 113; bytes oneof_bytes = 114; } + + // Sample maps + map map_string_to_nested_message = 200; + map map_int32_to_bytes = 201; + map map_bool_to_enum = 202; } // This proto includes a recusively nested message.