Commit bfee2dfe authored by Jon Skeet's avatar Jon Skeet

Implement freezing for messages and repeated fields.

Fixes issue #523.
parent 94071b54
...@@ -76,6 +76,9 @@ namespace Google.ProtocolBuffers.Examples.AddressBook { ...@@ -76,6 +76,9 @@ namespace Google.ProtocolBuffers.Examples.AddressBook {
get { return global::Google.ProtocolBuffers.Examples.AddressBook.Addressbook.internal__static_tutorial_Person__FieldAccessorTable; } get { return global::Google.ProtocolBuffers.Examples.AddressBook.Addressbook.internal__static_tutorial_Person__FieldAccessorTable; }
} }
private bool _frozen = false;
public bool IsFrozen { get { return _frozen; } }
public Person() { } public Person() { }
public Person(Person other) { public Person(Person other) {
...@@ -89,29 +92,43 @@ namespace Google.ProtocolBuffers.Examples.AddressBook { ...@@ -89,29 +92,43 @@ namespace Google.ProtocolBuffers.Examples.AddressBook {
return new Person(this); return new Person(this);
} }
public void Freeze() {
if (IsFrozen) {
return;
}
_frozen = true;
phone_.Freeze();
}
public const int NameFieldNumber = 1; public const int NameFieldNumber = 1;
private string name_ = ""; private string name_ = "";
public string Name { public string Name {
get { return name_; } get { return name_; }
set { name_ = value ?? ""; } set {
pb::Freezable.CheckMutable(this);
name_ = value ?? "";
}
} }
public const int IdFieldNumber = 2; public const int IdFieldNumber = 2;
private int id_; private int id_;
public int Id { public int Id {
get { return id_; } get { return id_; }
set { id_ = value; } set {
pb::Freezable.CheckMutable(this);
id_ = value;
}
} }
public const int EmailFieldNumber = 3; public const int EmailFieldNumber = 3;
private string email_ = ""; private string email_ = "";
public string Email { public string Email {
get { return email_; } get { return email_; }
set { email_ = value ?? ""; } set {
pb::Freezable.CheckMutable(this);
email_ = value ?? "";
}
} }
public const int PhoneFieldNumber = 4; public const int PhoneFieldNumber = 4;
private readonly pbc::RepeatedField<global::Google.ProtocolBuffers.Examples.AddressBook.Person.Types.PhoneNumber> phone_ = new pbc::RepeatedField<global::Google.ProtocolBuffers.Examples.AddressBook.Person.Types.PhoneNumber>(); private readonly pbc::RepeatedField<global::Google.ProtocolBuffers.Examples.AddressBook.Person.Types.PhoneNumber> phone_ = new pbc::RepeatedField<global::Google.ProtocolBuffers.Examples.AddressBook.Person.Types.PhoneNumber>();
...@@ -254,6 +271,9 @@ namespace Google.ProtocolBuffers.Examples.AddressBook { ...@@ -254,6 +271,9 @@ namespace Google.ProtocolBuffers.Examples.AddressBook {
get { return global::Google.ProtocolBuffers.Examples.AddressBook.Addressbook.internal__static_tutorial_Person_PhoneNumber__FieldAccessorTable; } get { return global::Google.ProtocolBuffers.Examples.AddressBook.Addressbook.internal__static_tutorial_Person_PhoneNumber__FieldAccessorTable; }
} }
private bool _frozen = false;
public bool IsFrozen { get { return _frozen; } }
public PhoneNumber() { } public PhoneNumber() { }
public PhoneNumber(PhoneNumber other) { public PhoneNumber(PhoneNumber other) {
...@@ -265,21 +285,32 @@ namespace Google.ProtocolBuffers.Examples.AddressBook { ...@@ -265,21 +285,32 @@ namespace Google.ProtocolBuffers.Examples.AddressBook {
return new PhoneNumber(this); return new PhoneNumber(this);
} }
public void Freeze() {
if (IsFrozen) {
return;
}
_frozen = true;
}
public const int NumberFieldNumber = 1; public const int NumberFieldNumber = 1;
private string number_ = ""; private string number_ = "";
public string Number { public string Number {
get { return number_; } get { return number_; }
set { number_ = value ?? ""; } set {
pb::Freezable.CheckMutable(this);
number_ = value ?? "";
}
} }
public const int TypeFieldNumber = 2; public const int TypeFieldNumber = 2;
private global::Google.ProtocolBuffers.Examples.AddressBook.Person.Types.PhoneType type_ = global::Google.ProtocolBuffers.Examples.AddressBook.Person.Types.PhoneType.HOME; private global::Google.ProtocolBuffers.Examples.AddressBook.Person.Types.PhoneType type_ = global::Google.ProtocolBuffers.Examples.AddressBook.Person.Types.PhoneType.HOME;
public global::Google.ProtocolBuffers.Examples.AddressBook.Person.Types.PhoneType Type { public global::Google.ProtocolBuffers.Examples.AddressBook.Person.Types.PhoneType Type {
get { return type_; } get { return type_; }
set { type_ = value; } set {
pb::Freezable.CheckMutable(this);
type_ = value;
}
} }
public override bool Equals(object other) { public override bool Equals(object other) {
return Equals(other as PhoneNumber); return Equals(other as PhoneNumber);
...@@ -382,6 +413,9 @@ namespace Google.ProtocolBuffers.Examples.AddressBook { ...@@ -382,6 +413,9 @@ namespace Google.ProtocolBuffers.Examples.AddressBook {
get { return global::Google.ProtocolBuffers.Examples.AddressBook.Addressbook.internal__static_tutorial_AddressBook__FieldAccessorTable; } get { return global::Google.ProtocolBuffers.Examples.AddressBook.Addressbook.internal__static_tutorial_AddressBook__FieldAccessorTable; }
} }
private bool _frozen = false;
public bool IsFrozen { get { return _frozen; } }
public AddressBook() { } public AddressBook() { }
public AddressBook(AddressBook other) { public AddressBook(AddressBook other) {
...@@ -392,6 +426,14 @@ namespace Google.ProtocolBuffers.Examples.AddressBook { ...@@ -392,6 +426,14 @@ namespace Google.ProtocolBuffers.Examples.AddressBook {
return new AddressBook(this); return new AddressBook(this);
} }
public void Freeze() {
if (IsFrozen) {
return;
}
_frozen = true;
person_.Freeze();
}
public const int PersonFieldNumber = 1; public const int PersonFieldNumber = 1;
private readonly pbc::RepeatedField<global::Google.ProtocolBuffers.Examples.AddressBook.Person> person_ = new pbc::RepeatedField<global::Google.ProtocolBuffers.Examples.AddressBook.Person>(); private readonly pbc::RepeatedField<global::Google.ProtocolBuffers.Examples.AddressBook.Person> person_ = new pbc::RepeatedField<global::Google.ProtocolBuffers.Examples.AddressBook.Person>();
public pbc::RepeatedField<global::Google.ProtocolBuffers.Examples.AddressBook.Person> Person { public pbc::RepeatedField<global::Google.ProtocolBuffers.Examples.AddressBook.Person> Person {
......
using Google.Protobuf.TestProtos; using System;
using Google.Protobuf.TestProtos;
using NUnit.Framework; using NUnit.Framework;
namespace Google.Protobuf namespace Google.Protobuf
...@@ -257,5 +258,22 @@ namespace Google.Protobuf ...@@ -257,5 +258,22 @@ namespace Google.Protobuf
original.OneofNestedMessage.Bb = 30; original.OneofNestedMessage.Bb = 30;
Assert.AreNotEqual(original, clone); Assert.AreNotEqual(original, clone);
} }
[Test]
public void Freeze()
{
var frozen = new TestAllTypes();
frozen.Freeze();
Assert.IsTrue(frozen.IsFrozen);
Assert.Throws<InvalidOperationException>(() => frozen.ClearOneofField());
Assert.Throws<InvalidOperationException>(() => frozen.SingleInt32 = 0);
Assert.Throws<InvalidOperationException>(() => frozen.SingleNestedMessage = null);
Assert.Throws<InvalidOperationException>(() => frozen.SingleNestedEnum = 0);
Assert.Throws<InvalidOperationException>(() => frozen.OneofString = null);
Assert.Throws<InvalidOperationException>(() => frozen.OneofUint32 = 0U);
Assert.Throws<InvalidOperationException>(() => frozen.RepeatedDouble.Add(0.0));
Assert.Throws<InvalidOperationException>(() => frozen.RepeatedNestedMessage.Add(new TestAllTypes.Types.NestedMessage()));
}
} }
} }
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Google.Protobuf.Collections; using Google.Protobuf.Collections;
using Google.Protobuf.TestProtos;
using NUnit.Framework; using NUnit.Framework;
namespace Google.Protobuf namespace Google.Protobuf
...@@ -11,8 +12,8 @@ namespace Google.Protobuf ...@@ -11,8 +12,8 @@ namespace Google.Protobuf
public void NullValuesRejected() public void NullValuesRejected()
{ {
var list = new RepeatedField<string>(); var list = new RepeatedField<string>();
Assert.Throws<ArgumentNullException>(() => list.Add((string) null)); Assert.Throws<ArgumentNullException>(() => list.Add((string)null));
Assert.Throws<ArgumentNullException>(() => list.Add((IEnumerable<string>) null)); Assert.Throws<ArgumentNullException>(() => list.Add((IEnumerable<string>)null));
Assert.Throws<ArgumentNullException>(() => list.Add((RepeatedField<string>)null)); Assert.Throws<ArgumentNullException>(() => list.Add((RepeatedField<string>)null));
Assert.Throws<ArgumentNullException>(() => list.Contains(null)); Assert.Throws<ArgumentNullException>(() => list.Contains(null));
Assert.Throws<ArgumentNullException>(() => list.IndexOf(null)); Assert.Throws<ArgumentNullException>(() => list.IndexOf(null));
...@@ -47,5 +48,47 @@ namespace Google.Protobuf ...@@ -47,5 +48,47 @@ namespace Google.Protobuf
Assert.AreEqual("foo", list[1]); Assert.AreEqual("foo", list[1]);
Assert.AreEqual("bar", list[2]); Assert.AreEqual("bar", list[2]);
} }
[Test]
public void Freeze_FreezesElements()
{
var list = new RepeatedField<TestAllTypes> { new TestAllTypes() };
Assert.IsFalse(list[0].IsFrozen);
list.Freeze();
Assert.IsTrue(list[0].IsFrozen);
}
[Test]
public void Freeze_PreventsMutations()
{
var list = new RepeatedField<int> { 0 };
list.Freeze();
Assert.Throws<InvalidOperationException>(() => list.Add(1));
Assert.Throws<InvalidOperationException>(() => list[0] = 1);
Assert.Throws<InvalidOperationException>(() => list.Clear());
Assert.Throws<InvalidOperationException>(() => list.RemoveAt(0));
Assert.Throws<InvalidOperationException>(() => list.Remove(0));
Assert.Throws<InvalidOperationException>(() => list.Insert(0, 0));
}
[Test]
public void Freeze_ReportsFrozen()
{
var list = new RepeatedField<int> { 0 };
Assert.IsFalse(list.IsFrozen);
Assert.IsFalse(list.IsReadOnly);
list.Freeze();
Assert.IsTrue(list.IsFrozen);
Assert.IsTrue(list.IsReadOnly);
}
[Test]
public void Clone_ReturnsMutable()
{
var list = new RepeatedField<int> { 0 };
list.Freeze();
var clone = list.Clone();
clone[0] = 1;
}
} }
} }
...@@ -74,6 +74,9 @@ namespace Google.Protobuf.TestProtos { ...@@ -74,6 +74,9 @@ namespace Google.Protobuf.TestProtos {
get { return global::Google.Protobuf.TestProtos.UnittestImportProto3.internal__static_protobuf_unittest_import_ImportMessage__FieldAccessorTable; } get { return global::Google.Protobuf.TestProtos.UnittestImportProto3.internal__static_protobuf_unittest_import_ImportMessage__FieldAccessorTable; }
} }
private bool _frozen = false;
public bool IsFrozen { get { return _frozen; } }
public ImportMessage() { } public ImportMessage() { }
public ImportMessage(ImportMessage other) { public ImportMessage(ImportMessage other) {
...@@ -84,13 +87,22 @@ namespace Google.Protobuf.TestProtos { ...@@ -84,13 +87,22 @@ namespace Google.Protobuf.TestProtos {
return new ImportMessage(this); return new ImportMessage(this);
} }
public void Freeze() {
if (IsFrozen) {
return;
}
_frozen = true;
}
public const int DFieldNumber = 1; public const int DFieldNumber = 1;
private int d_; private int d_;
public int D { public int D {
get { return d_; } get { return d_; }
set { d_ = value; } set {
pb::Freezable.CheckMutable(this);
d_ = value;
}
} }
public override bool Equals(object other) { public override bool Equals(object other) {
return Equals(other as ImportMessage); return Equals(other as ImportMessage);
......
...@@ -59,6 +59,9 @@ namespace Google.Protobuf.TestProtos { ...@@ -59,6 +59,9 @@ namespace Google.Protobuf.TestProtos {
get { return global::Google.Protobuf.TestProtos.UnittestImportPublicProto3.internal__static_protobuf_unittest_import_PublicImportMessage__FieldAccessorTable; } get { return global::Google.Protobuf.TestProtos.UnittestImportPublicProto3.internal__static_protobuf_unittest_import_PublicImportMessage__FieldAccessorTable; }
} }
private bool _frozen = false;
public bool IsFrozen { get { return _frozen; } }
public PublicImportMessage() { } public PublicImportMessage() { }
public PublicImportMessage(PublicImportMessage other) { public PublicImportMessage(PublicImportMessage other) {
...@@ -69,13 +72,22 @@ namespace Google.Protobuf.TestProtos { ...@@ -69,13 +72,22 @@ namespace Google.Protobuf.TestProtos {
return new PublicImportMessage(this); return new PublicImportMessage(this);
} }
public void Freeze() {
if (IsFrozen) {
return;
}
_frozen = true;
}
public const int EFieldNumber = 1; public const int EFieldNumber = 1;
private int e_; private int e_;
public int E { public int E {
get { return e_; } get { return e_; }
set { e_ = value; } set {
pb::Freezable.CheckMutable(this);
e_ = value;
}
} }
public override bool Equals(object other) { public override bool Equals(object other) {
return Equals(other as PublicImportMessage); return Equals(other as PublicImportMessage);
......
...@@ -104,6 +104,9 @@ namespace UnitTest.Issues.TestProtos { ...@@ -104,6 +104,9 @@ namespace UnitTest.Issues.TestProtos {
get { return global::UnitTest.Issues.TestProtos.UnittestIssues.internal__static_unittest_issues_NegativeEnumMessage__FieldAccessorTable; } get { return global::UnitTest.Issues.TestProtos.UnittestIssues.internal__static_unittest_issues_NegativeEnumMessage__FieldAccessorTable; }
} }
private bool _frozen = false;
public bool IsFrozen { get { return _frozen; } }
public NegativeEnumMessage() { } public NegativeEnumMessage() { }
public NegativeEnumMessage(NegativeEnumMessage other) { public NegativeEnumMessage(NegativeEnumMessage other) {
...@@ -116,13 +119,24 @@ namespace UnitTest.Issues.TestProtos { ...@@ -116,13 +119,24 @@ namespace UnitTest.Issues.TestProtos {
return new NegativeEnumMessage(this); return new NegativeEnumMessage(this);
} }
public void Freeze() {
if (IsFrozen) {
return;
}
_frozen = true;
values_.Freeze();
packedValues_.Freeze();
}
public const int ValueFieldNumber = 1; public const int ValueFieldNumber = 1;
private global::UnitTest.Issues.TestProtos.NegativeEnum value_ = global::UnitTest.Issues.TestProtos.NegativeEnum.NEGATIVE_ENUM_ZERO; private global::UnitTest.Issues.TestProtos.NegativeEnum value_ = global::UnitTest.Issues.TestProtos.NegativeEnum.NEGATIVE_ENUM_ZERO;
public global::UnitTest.Issues.TestProtos.NegativeEnum Value { public global::UnitTest.Issues.TestProtos.NegativeEnum Value {
get { return value_; } get { return value_; }
set { value_ = value; } set {
pb::Freezable.CheckMutable(this);
value_ = value;
}
} }
public const int ValuesFieldNumber = 2; public const int ValuesFieldNumber = 2;
private readonly pbc::RepeatedField<global::UnitTest.Issues.TestProtos.NegativeEnum> values_ = new pbc::RepeatedField<global::UnitTest.Issues.TestProtos.NegativeEnum>(); private readonly pbc::RepeatedField<global::UnitTest.Issues.TestProtos.NegativeEnum> values_ = new pbc::RepeatedField<global::UnitTest.Issues.TestProtos.NegativeEnum>();
...@@ -255,6 +269,9 @@ namespace UnitTest.Issues.TestProtos { ...@@ -255,6 +269,9 @@ namespace UnitTest.Issues.TestProtos {
get { return global::UnitTest.Issues.TestProtos.UnittestIssues.internal__static_unittest_issues_DeprecatedChild__FieldAccessorTable; } get { return global::UnitTest.Issues.TestProtos.UnittestIssues.internal__static_unittest_issues_DeprecatedChild__FieldAccessorTable; }
} }
private bool _frozen = false;
public bool IsFrozen { get { return _frozen; } }
public DeprecatedChild() { } public DeprecatedChild() { }
public DeprecatedChild(DeprecatedChild other) { public DeprecatedChild(DeprecatedChild other) {
...@@ -264,6 +281,13 @@ namespace UnitTest.Issues.TestProtos { ...@@ -264,6 +281,13 @@ namespace UnitTest.Issues.TestProtos {
return new DeprecatedChild(this); return new DeprecatedChild(this);
} }
public void Freeze() {
if (IsFrozen) {
return;
}
_frozen = true;
}
public override bool Equals(object other) { public override bool Equals(object other) {
return Equals(other as DeprecatedChild); return Equals(other as DeprecatedChild);
} }
...@@ -328,6 +352,9 @@ namespace UnitTest.Issues.TestProtos { ...@@ -328,6 +352,9 @@ namespace UnitTest.Issues.TestProtos {
get { return global::UnitTest.Issues.TestProtos.UnittestIssues.internal__static_unittest_issues_DeprecatedFieldsMessage__FieldAccessorTable; } get { return global::UnitTest.Issues.TestProtos.UnittestIssues.internal__static_unittest_issues_DeprecatedFieldsMessage__FieldAccessorTable; }
} }
private bool _frozen = false;
public bool IsFrozen { get { return _frozen; } }
public DeprecatedFieldsMessage() { } public DeprecatedFieldsMessage() { }
public DeprecatedFieldsMessage(DeprecatedFieldsMessage other) { public DeprecatedFieldsMessage(DeprecatedFieldsMessage other) {
...@@ -343,14 +370,27 @@ namespace UnitTest.Issues.TestProtos { ...@@ -343,14 +370,27 @@ namespace UnitTest.Issues.TestProtos {
return new DeprecatedFieldsMessage(this); return new DeprecatedFieldsMessage(this);
} }
public void Freeze() {
if (IsFrozen) {
return;
}
_frozen = true;
primitiveArray_.Freeze();
if (messageValue_ != null) MessageValue.Freeze();
messageArray_.Freeze();
enumArray_.Freeze();
}
public const int PrimitiveValueFieldNumber = 1; public const int PrimitiveValueFieldNumber = 1;
private int primitiveValue_; private int primitiveValue_;
[global::System.ObsoleteAttribute()] [global::System.ObsoleteAttribute()]
public int PrimitiveValue { public int PrimitiveValue {
get { return primitiveValue_; } get { return primitiveValue_; }
set { primitiveValue_ = value; } set {
pb::Freezable.CheckMutable(this);
primitiveValue_ = value;
}
} }
public const int PrimitiveArrayFieldNumber = 2; public const int PrimitiveArrayFieldNumber = 2;
private readonly pbc::RepeatedField<int> primitiveArray_ = new pbc::RepeatedField<int>(); private readonly pbc::RepeatedField<int> primitiveArray_ = new pbc::RepeatedField<int>();
...@@ -364,7 +404,10 @@ namespace UnitTest.Issues.TestProtos { ...@@ -364,7 +404,10 @@ namespace UnitTest.Issues.TestProtos {
[global::System.ObsoleteAttribute()] [global::System.ObsoleteAttribute()]
public global::UnitTest.Issues.TestProtos.DeprecatedChild MessageValue { public global::UnitTest.Issues.TestProtos.DeprecatedChild MessageValue {
get { return messageValue_; } get { return messageValue_; }
set { messageValue_ = value; } set {
pb::Freezable.CheckMutable(this);
messageValue_ = value;
}
} }
public const int MessageArrayFieldNumber = 4; public const int MessageArrayFieldNumber = 4;
...@@ -379,9 +422,11 @@ namespace UnitTest.Issues.TestProtos { ...@@ -379,9 +422,11 @@ namespace UnitTest.Issues.TestProtos {
[global::System.ObsoleteAttribute()] [global::System.ObsoleteAttribute()]
public global::UnitTest.Issues.TestProtos.DeprecatedEnum EnumValue { public global::UnitTest.Issues.TestProtos.DeprecatedEnum EnumValue {
get { return enumValue_; } get { return enumValue_; }
set { enumValue_ = value; } set {
pb::Freezable.CheckMutable(this);
enumValue_ = value;
}
} }
public const int EnumArrayFieldNumber = 6; public const int EnumArrayFieldNumber = 6;
private readonly pbc::RepeatedField<global::UnitTest.Issues.TestProtos.DeprecatedEnum> enumArray_ = new pbc::RepeatedField<global::UnitTest.Issues.TestProtos.DeprecatedEnum>(); private readonly pbc::RepeatedField<global::UnitTest.Issues.TestProtos.DeprecatedEnum> enumArray_ = new pbc::RepeatedField<global::UnitTest.Issues.TestProtos.DeprecatedEnum>();
...@@ -403,7 +448,8 @@ namespace UnitTest.Issues.TestProtos { ...@@ -403,7 +448,8 @@ namespace UnitTest.Issues.TestProtos {
} }
if (PrimitiveValue != other.PrimitiveValue) return false; if (PrimitiveValue != other.PrimitiveValue) return false;
if(!primitiveArray_.Equals(other.primitiveArray_)) return false; if(!primitiveArray_.Equals(other.primitiveArray_)) return false;
if (!object.Equals(MessageValue, other.MessageValue)) return false;if(!messageArray_.Equals(other.messageArray_)) return false; if (!object.Equals(MessageValue, other.MessageValue)) return false;
if(!messageArray_.Equals(other.messageArray_)) return false;
if (EnumValue != other.EnumValue) return false; if (EnumValue != other.EnumValue) return false;
if(!enumArray_.Equals(other.enumArray_)) return false; if(!enumArray_.Equals(other.enumArray_)) return false;
return true; return true;
...@@ -563,6 +609,9 @@ namespace UnitTest.Issues.TestProtos { ...@@ -563,6 +609,9 @@ namespace UnitTest.Issues.TestProtos {
get { return global::UnitTest.Issues.TestProtos.UnittestIssues.internal__static_unittest_issues_ItemField__FieldAccessorTable; } get { return global::UnitTest.Issues.TestProtos.UnittestIssues.internal__static_unittest_issues_ItemField__FieldAccessorTable; }
} }
private bool _frozen = false;
public bool IsFrozen { get { return _frozen; } }
public ItemField() { } public ItemField() { }
public ItemField(ItemField other) { public ItemField(ItemField other) {
...@@ -573,13 +622,22 @@ namespace UnitTest.Issues.TestProtos { ...@@ -573,13 +622,22 @@ namespace UnitTest.Issues.TestProtos {
return new ItemField(this); return new ItemField(this);
} }
public void Freeze() {
if (IsFrozen) {
return;
}
_frozen = true;
}
public const int ItemFieldNumber = 1; public const int ItemFieldNumber = 1;
private int item_; private int item_;
public int Item { public int Item {
get { return item_; } get { return item_; }
set { item_ = value; } set {
pb::Freezable.CheckMutable(this);
item_ = value;
}
} }
public override bool Equals(object other) { public override bool Equals(object other) {
return Equals(other as ItemField); return Equals(other as ItemField);
......
...@@ -4,10 +4,16 @@ using System.Collections.Generic; ...@@ -4,10 +4,16 @@ using System.Collections.Generic;
namespace Google.Protobuf.Collections namespace Google.Protobuf.Collections
{ {
public sealed class RepeatedField<T> : IList<T>, IEquatable<RepeatedField<T>> /// <summary>
/// The contents of a repeated field: essentially, a collection with some extra
/// restrictions (no null values) and capabilities (deep cloning and freezing).
/// </summary>
/// <typeparam name="T">The element type of the repeated field.</typeparam>
public sealed class RepeatedField<T> : IList<T>, IDeepCloneable<RepeatedField<T>>, IEquatable<RepeatedField<T>>, IFreezable
{ {
private static readonly T[] EmptyArray = new T[0]; private static readonly T[] EmptyArray = new T[0];
private bool frozen;
private const int MinArraySize = 8; private const int MinArraySize = 8;
private T[] array = EmptyArray; private T[] array = EmptyArray;
private int count = 0; private int count = 0;
...@@ -26,6 +32,7 @@ namespace Google.Protobuf.Collections ...@@ -26,6 +32,7 @@ namespace Google.Protobuf.Collections
public RepeatedField<T> Clone() public RepeatedField<T> Clone()
{ {
RepeatedField<T> clone = new RepeatedField<T>(); RepeatedField<T> clone = new RepeatedField<T>();
// Clone is implicitly *not* frozen, even if this object is.
if (array != EmptyArray) if (array != EmptyArray)
{ {
clone.array = (T[])array.Clone(); clone.array = (T[])array.Clone();
...@@ -42,6 +49,21 @@ namespace Google.Protobuf.Collections ...@@ -42,6 +49,21 @@ namespace Google.Protobuf.Collections
return clone; return clone;
} }
public bool IsFrozen { get { return frozen; } }
public void Freeze()
{
frozen = true;
IFreezable[] freezableArray = array as IFreezable[];
if (freezableArray != null)
{
for (int i = 0; i < count; i++)
{
freezableArray[i].Freeze();
}
}
}
private void EnsureSize(int size) private void EnsureSize(int size)
{ {
size = Math.Max(size, MinArraySize); size = Math.Max(size, MinArraySize);
...@@ -60,6 +82,7 @@ namespace Google.Protobuf.Collections ...@@ -60,6 +82,7 @@ namespace Google.Protobuf.Collections
{ {
throw new ArgumentNullException("item"); throw new ArgumentNullException("item");
} }
this.CheckMutable();
EnsureSize(count + 1); EnsureSize(count + 1);
array[count++] = item; array[count++] = item;
} }
...@@ -70,6 +93,7 @@ namespace Google.Protobuf.Collections ...@@ -70,6 +93,7 @@ namespace Google.Protobuf.Collections
/// <param name="readEnum"></param> /// <param name="readEnum"></param>
internal void AddInt32(int item) internal void AddInt32(int item)
{ {
this.CheckMutable();
EnsureSize(count + 1); EnsureSize(count + 1);
int[] castArray = (int[]) (object) array; int[] castArray = (int[]) (object) array;
castArray[count++] = item; castArray[count++] = item;
...@@ -77,6 +101,7 @@ namespace Google.Protobuf.Collections ...@@ -77,6 +101,7 @@ namespace Google.Protobuf.Collections
public void Clear() public void Clear()
{ {
this.CheckMutable();
array = EmptyArray; array = EmptyArray;
count = 0; count = 0;
} }
...@@ -93,6 +118,7 @@ namespace Google.Protobuf.Collections ...@@ -93,6 +118,7 @@ namespace Google.Protobuf.Collections
public bool Remove(T item) public bool Remove(T item)
{ {
this.CheckMutable();
int index = IndexOf(item); int index = IndexOf(item);
if (index == -1) if (index == -1)
{ {
...@@ -107,7 +133,7 @@ namespace Google.Protobuf.Collections ...@@ -107,7 +133,7 @@ namespace Google.Protobuf.Collections
public int Count { get { return count; } } public int Count { get { return count; } }
// TODO(jonskeet): If we implement freezing, make this reflect it. // TODO(jonskeet): If we implement freezing, make this reflect it.
public bool IsReadOnly { get { return false; } } public bool IsReadOnly { get { return IsFrozen; } }
public void Add(RepeatedField<T> values) public void Add(RepeatedField<T> values)
{ {
...@@ -115,6 +141,7 @@ namespace Google.Protobuf.Collections ...@@ -115,6 +141,7 @@ namespace Google.Protobuf.Collections
{ {
throw new ArgumentNullException("values"); throw new ArgumentNullException("values");
} }
this.CheckMutable();
EnsureSize(count + values.count); EnsureSize(count + values.count);
// We know that all the values will be valid, because it's a RepeatedField. // We know that all the values will be valid, because it's a RepeatedField.
Array.Copy(values.array, 0, array, count, values.count); Array.Copy(values.array, 0, array, count, values.count);
...@@ -127,6 +154,7 @@ namespace Google.Protobuf.Collections ...@@ -127,6 +154,7 @@ namespace Google.Protobuf.Collections
{ {
throw new ArgumentNullException("values"); throw new ArgumentNullException("values");
} }
this.CheckMutable();
// TODO: Check for ICollection and get the Count? // TODO: Check for ICollection and get the Count?
foreach (T item in values) foreach (T item in values)
{ {
...@@ -227,6 +255,7 @@ namespace Google.Protobuf.Collections ...@@ -227,6 +255,7 @@ namespace Google.Protobuf.Collections
{ {
throw new ArgumentOutOfRangeException("index"); throw new ArgumentOutOfRangeException("index");
} }
this.CheckMutable();
EnsureSize(count + 1); EnsureSize(count + 1);
Array.Copy(array, index, array, index + 1, count - index); Array.Copy(array, index, array, index + 1, count - index);
count++; count++;
...@@ -238,6 +267,7 @@ namespace Google.Protobuf.Collections ...@@ -238,6 +267,7 @@ namespace Google.Protobuf.Collections
{ {
throw new ArgumentOutOfRangeException("index"); throw new ArgumentOutOfRangeException("index");
} }
this.CheckMutable();
Array.Copy(array, index + 1, array, index, count - index - 1); Array.Copy(array, index + 1, array, index, count - index - 1);
count--; count--;
array[count] = default(T); array[count] = default(T);
...@@ -259,6 +289,7 @@ namespace Google.Protobuf.Collections ...@@ -259,6 +289,7 @@ namespace Google.Protobuf.Collections
{ {
throw new ArgumentOutOfRangeException("index"); throw new ArgumentOutOfRangeException("index");
} }
this.CheckMutable();
if (value == null) if (value == null)
{ {
throw new ArgumentNullException("value"); throw new ArgumentNullException("value");
......
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 Google Inc. All rights reserved.
// http://github.com/google/protobuf
//
// 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;
namespace Google.Protobuf
{
/// <summary>
/// Extension methods for <see cref="IFreezable"/> types.
/// </summary>
public static class Freezable
{
/// <summary>
/// Throws an <see cref="InvalidOperationException"/> if <paramref name="target"/>
/// is frozen.
/// </summary>
/// <remarks>
/// This is a convenience methods that freezable types can call before all
/// mutations, to protect frozen objects.
/// </remarks>
public static void CheckMutable(this IFreezable target)
{
if (target.IsFrozen)
{
throw new InvalidOperationException("Attempt to mutate frozen object");
}
}
}
}
...@@ -41,6 +41,7 @@ namespace Google.Protobuf ...@@ -41,6 +41,7 @@ namespace Google.Protobuf
{ {
// TODO(jonskeet): Do we want a "weak" (non-generic) version of IReflectedMessage? // TODO(jonskeet): Do we want a "weak" (non-generic) version of IReflectedMessage?
// TODO(jonskeet): Split these interfaces into separate files when we're happy with them.
/// <summary> /// <summary>
/// Reflection support for a specific message type. message /// Reflection support for a specific message type. message
...@@ -85,7 +86,7 @@ namespace Google.Protobuf ...@@ -85,7 +86,7 @@ namespace Google.Protobuf
/// the implementation class. /// the implementation class.
/// </summary> /// </summary>
/// <typeparam name="T">The message type.</typeparam> /// <typeparam name="T">The message type.</typeparam>
public interface IMessage<T> : IMessage, IEquatable<T>, IDeepCloneable<T> where T : IMessage<T> public interface IMessage<T> : IMessage, IEquatable<T>, IDeepCloneable<T>, IFreezable where T : IMessage<T>
{ {
/// <summary> /// <summary>
/// Merges the given message into this one. /// Merges the given message into this one.
...@@ -102,6 +103,11 @@ namespace Google.Protobuf ...@@ -102,6 +103,11 @@ namespace Google.Protobuf
/// All generated messages implement this interface, but so do some non-message types. /// All generated messages implement this interface, but so do some non-message types.
/// Additionally, due to the type constraint on <c>T</c> in <see cref="IMessage{T}"/>, /// Additionally, due to the type constraint on <c>T</c> in <see cref="IMessage{T}"/>,
/// it is simpler to keep this as a separate interface. /// it is simpler to keep this as a separate interface.
/// </para>
/// <para>
/// Freezable types which implement this interface should always return a mutable clone,
/// even if the original object is frozen.
/// </para>
/// </remarks> /// </remarks>
/// <typeparam name="T">The type itself, returned by the <see cref="Clone"/> method.</typeparam> /// <typeparam name="T">The type itself, returned by the <see cref="Clone"/> method.</typeparam>
public interface IDeepCloneable<T> public interface IDeepCloneable<T>
...@@ -112,4 +118,32 @@ namespace Google.Protobuf ...@@ -112,4 +118,32 @@ namespace Google.Protobuf
/// <returns>A deep clone of this object.</returns> /// <returns>A deep clone of this object.</returns>
T Clone(); T Clone();
} }
/// <summary>
/// Provides a mechanism for freezing a message (or repeated field collection)
/// to make it immutable.
/// </summary>
/// <remarks>
/// Implementations are under no obligation to make this thread-safe: if a freezable
/// type instance is shared between threads before being frozen, and one thread then
/// freezes it, it is possible for other threads to make changes during the freezing
/// operation and also to observe stale values for mutated fields. Objects should be
/// frozen before being made available to other threads.
/// </remarks>
public interface IFreezable
{
/// <summary>
/// Freezes this object.
/// </summary>
/// <remarks>
/// If the object is already frozen, this method has no effect.
/// </remarks>
void Freeze();
/// <summary>
/// Returns whether or not this object is frozen (and therefore immutable).
/// </summary>
/// <value><c>true</c> if this object is frozen; <c>false</c> otherwise.</value>
bool IsFrozen { get; }
}
} }
...@@ -85,6 +85,7 @@ ...@@ -85,6 +85,7 @@
<Compile Include="Descriptors\PackageDescriptor.cs" /> <Compile Include="Descriptors\PackageDescriptor.cs" />
<Compile Include="Descriptors\ServiceDescriptor.cs" /> <Compile Include="Descriptors\ServiceDescriptor.cs" />
<Compile Include="FrameworkPortability.cs" /> <Compile Include="FrameworkPortability.cs" />
<Compile Include="Freezable.cs" />
<Compile Include="MessageExtensions.cs" /> <Compile Include="MessageExtensions.cs" />
<Compile Include="FieldAccess\FieldAccessorBase.cs" /> <Compile Include="FieldAccess\FieldAccessorBase.cs" />
<Compile Include="FieldAccess\ReflectionUtil.cs" /> <Compile Include="FieldAccess\ReflectionUtil.cs" />
......
...@@ -107,6 +107,11 @@ FieldGeneratorBase::FieldGeneratorBase(const FieldDescriptor* descriptor, ...@@ -107,6 +107,11 @@ FieldGeneratorBase::FieldGeneratorBase(const FieldDescriptor* descriptor,
FieldGeneratorBase::~FieldGeneratorBase() { FieldGeneratorBase::~FieldGeneratorBase() {
} }
void FieldGeneratorBase::GenerateFreezingCode(io::Printer* printer) {
// No-op: only message fields and repeated fields need
// special handling for freezing, so default to not generating any code.
}
void FieldGeneratorBase::AddDeprecatedFlag(io::Printer* printer) { void FieldGeneratorBase::AddDeprecatedFlag(io::Printer* printer) {
if (descriptor_->options().deprecated()) if (descriptor_->options().deprecated())
{ {
......
...@@ -48,6 +48,7 @@ class FieldGeneratorBase : public SourceGeneratorBase { ...@@ -48,6 +48,7 @@ class FieldGeneratorBase : public SourceGeneratorBase {
~FieldGeneratorBase(); ~FieldGeneratorBase();
virtual void GenerateCloningCode(io::Printer* printer) = 0; virtual void GenerateCloningCode(io::Printer* printer) = 0;
virtual void GenerateFreezingCode(io::Printer* printer);
virtual void GenerateMembers(io::Printer* printer) = 0; virtual void GenerateMembers(io::Printer* printer) = 0;
virtual void GenerateMergingCode(io::Printer* printer) = 0; virtual void GenerateMergingCode(io::Printer* printer) = 0;
virtual void GenerateParsingCode(io::Printer* printer) = 0; virtual void GenerateParsingCode(io::Printer* printer) = 0;
......
...@@ -211,7 +211,9 @@ void MessageGenerator::Generate(io::Printer* printer) { ...@@ -211,7 +211,9 @@ void MessageGenerator::Generate(io::Printer* printer) {
"public pb::FieldAccess.FieldAccessorTable<$class_name$> Fields {\n" "public pb::FieldAccess.FieldAccessorTable<$class_name$> Fields {\n"
" get { return $umbrella_class_name$.internal__$identifier$__FieldAccessorTable; }\n" " get { return $umbrella_class_name$.internal__$identifier$__FieldAccessorTable; }\n"
"}\n" "}\n"
"\n"); "\n"
"private bool _frozen = false;\n"
"public bool IsFrozen { get { return _frozen; } }\n\n");
// Parameterless constructor // Parameterless constructor
printer->Print( printer->Print(
...@@ -219,6 +221,7 @@ void MessageGenerator::Generate(io::Printer* printer) { ...@@ -219,6 +221,7 @@ void MessageGenerator::Generate(io::Printer* printer) {
"public $class_name$() { }\n\n"); "public $class_name$() { }\n\n");
GenerateCloningCode(printer); GenerateCloningCode(printer);
GenerateFreezingCode(printer);
// Fields/properties // Fields/properties
for (int i = 0; i < descriptor_->field_count(); i++) { for (int i = 0; i < descriptor_->field_count(); i++) {
...@@ -260,6 +263,7 @@ void MessageGenerator::Generate(io::Printer* printer) { ...@@ -260,6 +263,7 @@ void MessageGenerator::Generate(io::Printer* printer) {
" get { return $name$Case_; }\n" " get { return $name$Case_; }\n"
"}\n\n" "}\n\n"
"public void Clear$property_name$() {\n" "public void Clear$property_name$() {\n"
" pb::Freezable.CheckMutable(this);\n"
" $name$Case_ = $property_name$OneofCase.None;\n" " $name$Case_ = $property_name$OneofCase.None;\n"
" $name$_ = null;\n" " $name$_ = null;\n"
"}\n\n"); "}\n\n");
...@@ -346,6 +350,36 @@ void MessageGenerator::GenerateCloningCode(io::Printer* printer) { ...@@ -346,6 +350,36 @@ void MessageGenerator::GenerateCloningCode(io::Printer* printer) {
"}\n\n"); "}\n\n");
} }
void MessageGenerator::GenerateFreezingCode(io::Printer* printer) {
map<string, string> vars;
vars["class_name"] = class_name();
printer->Print(
"public void Freeze() {\n"
" if (IsFrozen) {\n"
" return;\n"
" }\n"
" _frozen = true;\n");
printer->Indent();
// Freeze non-oneof fields first (only messages and repeated fields will actually generate any code)
for (int i = 0; i < descriptor_->field_count(); i++) {
if (!descriptor_->field(i)->containing_oneof()) {
scoped_ptr<FieldGeneratorBase> generator(
CreateFieldGeneratorInternal(descriptor_->field(i)));
generator->GenerateFreezingCode(printer);
}
}
// For each oneof, if the value is freezable, freeze it. We don't actually need to know which type it was.
for (int i = 0; i < descriptor_->oneof_decl_count(); ++i) {
vars["name"] = UnderscoresToCamelCase(descriptor_->oneof_decl(i)->name(), false);
printer->Print(vars,
"if ($name$_ is IFreezable) ((IFreezable) $name$_).Freeze();\n");
}
printer->Outdent();
printer->Print("}\n\n");
}
void MessageGenerator::GenerateFrameworkMethods(io::Printer* printer) { void MessageGenerator::GenerateFrameworkMethods(io::Printer* printer) {
map<string, string> vars; map<string, string> vars;
vars["class_name"] = class_name(); vars["class_name"] = class_name();
......
...@@ -51,6 +51,7 @@ class MessageGenerator : public SourceGeneratorBase { ...@@ -51,6 +51,7 @@ class MessageGenerator : public SourceGeneratorBase {
~MessageGenerator(); ~MessageGenerator();
void GenerateCloningCode(io::Printer* printer); void GenerateCloningCode(io::Printer* printer);
void GenerateFreezingCode(io::Printer* printer);
void GenerateFrameworkMethods(io::Printer* printer); void GenerateFrameworkMethods(io::Printer* printer);
void GenerateStaticVariables(io::Printer* printer); void GenerateStaticVariables(io::Printer* printer);
void GenerateStaticVariableInitializers(io::Printer* printer); void GenerateStaticVariableInitializers(io::Printer* printer);
......
...@@ -66,7 +66,10 @@ void MessageFieldGenerator::GenerateMembers(io::Printer* printer) { ...@@ -66,7 +66,10 @@ void MessageFieldGenerator::GenerateMembers(io::Printer* printer) {
variables_, variables_,
"public $type_name$ $property_name$ {\n" "public $type_name$ $property_name$ {\n"
" get { return $name$_; }\n" " get { return $name$_; }\n"
" set { $name$_ = value; }\n" " set {\n"
" pb::Freezable.CheckMutable(this);\n"
" $name$_ = value;\n"
" }\n"
"}\n"); "}\n");
} }
...@@ -116,7 +119,7 @@ void MessageFieldGenerator::WriteHash(io::Printer* printer) { ...@@ -116,7 +119,7 @@ void MessageFieldGenerator::WriteHash(io::Printer* printer) {
void MessageFieldGenerator::WriteEquals(io::Printer* printer) { void MessageFieldGenerator::WriteEquals(io::Printer* printer) {
printer->Print( printer->Print(
variables_, variables_,
"if (!object.Equals($property_name$, other.$property_name$)) return false;"); "if (!object.Equals($property_name$, other.$property_name$)) return false;\n");
} }
void MessageFieldGenerator::WriteToString(io::Printer* printer) { void MessageFieldGenerator::WriteToString(io::Printer* printer) {
variables_["field_name"] = GetFieldName(descriptor_); variables_["field_name"] = GetFieldName(descriptor_);
...@@ -130,6 +133,11 @@ void MessageFieldGenerator::GenerateCloningCode(io::Printer* printer) { ...@@ -130,6 +133,11 @@ void MessageFieldGenerator::GenerateCloningCode(io::Printer* printer) {
"$property_name$ = other.$has_property_check$ ? other.$property_name$.Clone() : null;\n"); "$property_name$ = other.$has_property_check$ ? other.$property_name$.Clone() : null;\n");
} }
void MessageFieldGenerator::GenerateFreezingCode(io::Printer* printer) {
printer->Print(variables_,
"if ($has_property_check$) $property_name$.Freeze();\n");
}
MessageOneofFieldGenerator::MessageOneofFieldGenerator(const FieldDescriptor* descriptor, MessageOneofFieldGenerator::MessageOneofFieldGenerator(const FieldDescriptor* descriptor,
int fieldOrdinal) int fieldOrdinal)
: MessageFieldGenerator(descriptor, fieldOrdinal) { : MessageFieldGenerator(descriptor, fieldOrdinal) {
...@@ -147,6 +155,7 @@ void MessageOneofFieldGenerator::GenerateMembers(io::Printer* printer) { ...@@ -147,6 +155,7 @@ void MessageOneofFieldGenerator::GenerateMembers(io::Printer* printer) {
"public $type_name$ $property_name$ {\n" "public $type_name$ $property_name$ {\n"
" get { return $has_property_check$ ? ($type_name$) $oneof_name$_ : null; }\n" " get { return $has_property_check$ ? ($type_name$) $oneof_name$_ : null; }\n"
" set {\n" " set {\n"
" pb::Freezable.CheckMutable(this);\n"
" $oneof_name$_ = value;\n" " $oneof_name$_ = value;\n"
" $oneof_name$Case_ = value == null ? $oneof_property_name$OneofCase.None : $oneof_property_name$OneofCase.$property_name$;\n" " $oneof_name$Case_ = value == null ? $oneof_property_name$OneofCase.None : $oneof_property_name$OneofCase.$property_name$;\n"
" }\n" " }\n"
......
...@@ -47,6 +47,7 @@ class MessageFieldGenerator : public FieldGeneratorBase { ...@@ -47,6 +47,7 @@ class MessageFieldGenerator : public FieldGeneratorBase {
~MessageFieldGenerator(); ~MessageFieldGenerator();
virtual void GenerateCloningCode(io::Printer* printer); virtual void GenerateCloningCode(io::Printer* printer);
virtual void GenerateFreezingCode(io::Printer* printer);
virtual void GenerateMembers(io::Printer* printer); virtual void GenerateMembers(io::Printer* printer);
virtual void GenerateMergingCode(io::Printer* printer); virtual void GenerateMergingCode(io::Printer* printer);
virtual void GenerateParsingCode(io::Printer* printer); virtual void GenerateParsingCode(io::Printer* printer);
......
...@@ -72,17 +72,21 @@ void PrimitiveFieldGenerator::GenerateMembers(io::Printer* printer) { ...@@ -72,17 +72,21 @@ void PrimitiveFieldGenerator::GenerateMembers(io::Printer* printer) {
printer->Print( printer->Print(
variables_, variables_,
"public $type_name$ $property_name$ {\n" "public $type_name$ $property_name$ {\n"
" get { return $name$_; }\n"); " get { return $name$_; }\n"
" set {\n"
" pb::Freezable.CheckMutable(this);\n");
if (is_value_type) { if (is_value_type) {
printer->Print( printer->Print(
variables_, variables_,
" set { $name$_ = value; }\n"); " $name$_ = value;\n");
} else { } else {
printer->Print( printer->Print(
variables_, variables_,
" set { $name$_ = value ?? $default_value$; }\n"); " $name$_ = value ?? $default_value$;\n");
} }
printer->Print("}\n\n"); printer->Print(
" }\n"
"}\n");
} }
void PrimitiveFieldGenerator::GenerateMergingCode(io::Printer* printer) { void PrimitiveFieldGenerator::GenerateMergingCode(io::Printer* printer) {
...@@ -166,7 +170,8 @@ void PrimitiveOneofFieldGenerator::GenerateMembers(io::Printer* printer) { ...@@ -166,7 +170,8 @@ void PrimitiveOneofFieldGenerator::GenerateMembers(io::Printer* printer) {
variables_, variables_,
"public $type_name$ $property_name$ {\n" "public $type_name$ $property_name$ {\n"
" get { return $has_property_check$ ? ($type_name$) $oneof_name$_ : $default_value$; }\n" " get { return $has_property_check$ ? ($type_name$) $oneof_name$_ : $default_value$; }\n"
" set {\n"); " set {\n"
" pb::Freezable.CheckMutable(this);\n");
if (is_value_type) { if (is_value_type) {
printer->Print( printer->Print(
variables_, variables_,
......
...@@ -147,6 +147,11 @@ void RepeatedEnumFieldGenerator::GenerateCloningCode(io::Printer* printer) { ...@@ -147,6 +147,11 @@ void RepeatedEnumFieldGenerator::GenerateCloningCode(io::Printer* printer) {
"$name$_ = other.$name$_.Clone();\n"); "$name$_ = other.$name$_.Clone();\n");
} }
void RepeatedEnumFieldGenerator::GenerateFreezingCode(io::Printer* printer) {
printer->Print(variables_,
"$name$_.Freeze();\n");
}
} // namespace csharp } // namespace csharp
} // namespace compiler } // namespace compiler
} // namespace protobuf } // namespace protobuf
......
...@@ -49,6 +49,7 @@ class RepeatedEnumFieldGenerator : public FieldGeneratorBase { ...@@ -49,6 +49,7 @@ class RepeatedEnumFieldGenerator : public FieldGeneratorBase {
~RepeatedEnumFieldGenerator(); ~RepeatedEnumFieldGenerator();
virtual void GenerateCloningCode(io::Printer* printer); virtual void GenerateCloningCode(io::Printer* printer);
virtual void GenerateFreezingCode(io::Printer* printer);
virtual void GenerateMembers(io::Printer* printer); virtual void GenerateMembers(io::Printer* printer);
virtual void GenerateMergingCode(io::Printer* printer); virtual void GenerateMergingCode(io::Printer* printer);
virtual void GenerateParsingCode(io::Printer* printer); virtual void GenerateParsingCode(io::Printer* printer);
......
...@@ -123,6 +123,11 @@ void RepeatedMessageFieldGenerator::GenerateCloningCode(io::Printer* printer) { ...@@ -123,6 +123,11 @@ void RepeatedMessageFieldGenerator::GenerateCloningCode(io::Printer* printer) {
"$name$_ = other.$name$_.Clone();\n"); "$name$_ = other.$name$_.Clone();\n");
} }
void RepeatedMessageFieldGenerator::GenerateFreezingCode(io::Printer* printer) {
printer->Print(variables_,
"$name$_.Freeze();\n");
}
} // namespace csharp } // namespace csharp
} // namespace compiler } // namespace compiler
} // namespace protobuf } // namespace protobuf
......
...@@ -47,6 +47,7 @@ class RepeatedMessageFieldGenerator : public FieldGeneratorBase { ...@@ -47,6 +47,7 @@ class RepeatedMessageFieldGenerator : public FieldGeneratorBase {
~RepeatedMessageFieldGenerator(); ~RepeatedMessageFieldGenerator();
virtual void GenerateCloningCode(io::Printer* printer); virtual void GenerateCloningCode(io::Printer* printer);
virtual void GenerateFreezingCode(io::Printer* printer);
virtual void GenerateMembers(io::Printer* printer); virtual void GenerateMembers(io::Printer* printer);
virtual void GenerateMergingCode(io::Printer* printer); virtual void GenerateMergingCode(io::Printer* printer);
virtual void GenerateParsingCode(io::Printer* printer); virtual void GenerateParsingCode(io::Printer* printer);
......
...@@ -153,6 +153,11 @@ void RepeatedPrimitiveFieldGenerator::GenerateCloningCode(io::Printer* printer) ...@@ -153,6 +153,11 @@ void RepeatedPrimitiveFieldGenerator::GenerateCloningCode(io::Printer* printer)
"$name$_ = other.$name$_.Clone();\n"); "$name$_ = other.$name$_.Clone();\n");
} }
void RepeatedPrimitiveFieldGenerator::GenerateFreezingCode(io::Printer* printer) {
printer->Print(variables_,
"$name$_.Freeze();\n");
}
} // namespace csharp } // namespace csharp
} // namespace compiler } // namespace compiler
} // namespace protobuf } // namespace protobuf
......
...@@ -47,6 +47,7 @@ class RepeatedPrimitiveFieldGenerator : public FieldGeneratorBase { ...@@ -47,6 +47,7 @@ class RepeatedPrimitiveFieldGenerator : public FieldGeneratorBase {
~RepeatedPrimitiveFieldGenerator(); ~RepeatedPrimitiveFieldGenerator();
virtual void GenerateCloningCode(io::Printer* printer); virtual void GenerateCloningCode(io::Printer* printer);
virtual void GenerateFreezingCode(io::Printer* printer);
virtual void GenerateMembers(io::Printer* printer); virtual void GenerateMembers(io::Printer* printer);
virtual void GenerateMergingCode(io::Printer* printer); virtual void GenerateMergingCode(io::Printer* printer);
virtual void GenerateParsingCode(io::Printer* printer); virtual void GenerateParsingCode(io::Printer* printer);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment