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.