diff options
author | Jon Skeet <skeet@pobox.com> | 2008-08-14 20:33:34 +0100 |
---|---|---|
committer | Jon Skeet <skeet@pobox.com> | 2008-08-14 20:33:34 +0100 |
commit | ec8c395517ad407cbe869927a0c92e9a32724b9c (patch) | |
tree | c696f4df7a2e375203fb01ae7d5f4d6cb8692472 /csharp | |
parent | b83aee759acd079c5cd42c77c36697771de15118 (diff) | |
download | protobuf-ec8c395517ad407cbe869927a0c92e9a32724b9c.tar.gz protobuf-ec8c395517ad407cbe869927a0c92e9a32724b9c.tar.bz2 protobuf-ec8c395517ad407cbe869927a0c92e9a32724b9c.zip |
Gradually implementing FieldSet
Diffstat (limited to 'csharp')
-rw-r--r-- | csharp/ProtocolBuffers/AbstractBuilder.cs | 22 | ||||
-rw-r--r-- | csharp/ProtocolBuffers/Collections/Dictionaries.cs | 16 | ||||
-rw-r--r-- | csharp/ProtocolBuffers/Collections/Lists.cs | 20 | ||||
-rw-r--r-- | csharp/ProtocolBuffers/Collections/ReadOnlyDictionary.cs | 99 | ||||
-rw-r--r-- | csharp/ProtocolBuffers/Descriptors/EnumDescriptor.cs | 5 | ||||
-rw-r--r-- | csharp/ProtocolBuffers/Descriptors/EnumValueDescriptor.cs | 6 | ||||
-rw-r--r-- | csharp/ProtocolBuffers/Descriptors/FieldDescriptor.cs | 31 | ||||
-rw-r--r-- | csharp/ProtocolBuffers/Descriptors/MappedType.cs | 2 | ||||
-rw-r--r-- | csharp/ProtocolBuffers/FieldAccess/IFieldAccessor.cs | 47 | ||||
-rw-r--r-- | csharp/ProtocolBuffers/FieldAccess/SingularFieldAccessor.cs | 65 | ||||
-rw-r--r-- | csharp/ProtocolBuffers/FieldSet.cs | 305 | ||||
-rw-r--r-- | csharp/ProtocolBuffers/GeneratedBuilder.cs | 153 | ||||
-rw-r--r-- | csharp/ProtocolBuffers/GeneratedMessage.cs | 18 | ||||
-rw-r--r-- | csharp/ProtocolBuffers/IBuilder.cs | 4 | ||||
-rw-r--r-- | csharp/ProtocolBuffers/ProtocolBuffers.csproj | 5 | ||||
-rw-r--r-- | csharp/ProtocolBuffers/UnknownFieldSet.cs | 11 |
16 files changed, 698 insertions, 111 deletions
diff --git a/csharp/ProtocolBuffers/AbstractBuilder.cs b/csharp/ProtocolBuffers/AbstractBuilder.cs index 01088f5d..f8ed554d 100644 --- a/csharp/ProtocolBuffers/AbstractBuilder.cs +++ b/csharp/ProtocolBuffers/AbstractBuilder.cs @@ -25,8 +25,8 @@ namespace Google.ProtocolBuffers { protected abstract IMessage BuildPartialImpl(); protected abstract IBuilder CloneImpl(); protected abstract IMessage DefaultInstanceForTypeImpl { get; } - protected abstract IBuilder NewBuilderForFieldImpl<TField>(FieldDescriptor field); - protected abstract IBuilder ClearFieldImpl(); + protected abstract IBuilder NewBuilderForFieldImpl(FieldDescriptor field); + protected abstract IBuilder ClearFieldImpl(FieldDescriptor field); protected abstract IBuilder AddRepeatedFieldImpl(FieldDescriptor field, object value); #endregion @@ -39,30 +39,30 @@ namespace Google.ProtocolBuffers { return BuildPartialImpl(); } - public IBuilder Clone() { + IBuilder IBuilder.Clone() { return CloneImpl(); } - public IMessage DefaultInstanceForType { + IMessage IBuilder.DefaultInstanceForType { get { return DefaultInstanceForTypeImpl; } } - public IBuilder NewBuilderForField<TField>(FieldDescriptor field) { - return NewBuilderForFieldImpl<TField>(field); + IBuilder IBuilder.NewBuilderForField(FieldDescriptor field) { + return NewBuilderForFieldImpl(field); } - public IBuilder ClearField(FieldDescriptor field) { - return ClearFieldImpl(); + IBuilder IBuilder.ClearField(FieldDescriptor field) { + return ClearFieldImpl(field); } - public IBuilder AddRepeatedField(FieldDescriptor field, object value) { + IBuilder IBuilder.AddRepeatedField(FieldDescriptor field, object value) { return AddRepeatedFieldImpl(field, value); } #endregion public IBuilder Clear() { foreach(FieldDescriptor field in AllFields.Keys) { - ClearField(field); + ClearFieldImpl(field); } return this; } @@ -85,7 +85,7 @@ namespace Google.ProtocolBuffers { if (field.IsRepeated) { // Concatenate repeated fields foreach (object element in (IEnumerable) entry.Value) { - AddRepeatedField(field, element); + AddRepeatedFieldImpl(field, element); } } else if (field.MappedType == MappedType.Message) { // Merge singular messages diff --git a/csharp/ProtocolBuffers/Collections/Dictionaries.cs b/csharp/ProtocolBuffers/Collections/Dictionaries.cs new file mode 100644 index 00000000..fb0ebdd2 --- /dev/null +++ b/csharp/ProtocolBuffers/Collections/Dictionaries.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Google.ProtocolBuffers.Collections { + + /// <summary> + /// Non-generic class with generic methods which proxy to the non-generic methods + /// in the generic class. + /// </summary> + public static class Dictionaries { + public static IDictionary<TKey, TValue> AsReadOnly<TKey, TValue> (IDictionary<TKey, TValue> dictionary) { + return dictionary.IsReadOnly ? dictionary : new ReadOnlyDictionary<TKey, TValue>(dictionary); + } + } +} diff --git a/csharp/ProtocolBuffers/Collections/Lists.cs b/csharp/ProtocolBuffers/Collections/Lists.cs index 6d27142f..0fba3edb 100644 --- a/csharp/ProtocolBuffers/Collections/Lists.cs +++ b/csharp/ProtocolBuffers/Collections/Lists.cs @@ -4,18 +4,34 @@ using System.Collections.ObjectModel; using System.Text; namespace Google.ProtocolBuffers.Collections { + + public static class Lists { + + public static IList<T> AsReadOnly<T>(IList<T> list) { + return Lists<T>.AsReadOnly(list); + } + } + /// <summary> /// Utilities class for dealing with lists. /// </summary> - static class Lists<T> { + public static class Lists<T> { static readonly ReadOnlyCollection<T> empty = new ReadOnlyCollection<T>(new T[0]); /// <summary> /// Returns an immutable empty list. /// </summary> - internal static ReadOnlyCollection<T> Empty { + public static ReadOnlyCollection<T> Empty { get { return empty; } } + + /// <summary> + /// Returns either the original reference if it's already read-only, + /// or a new ReadOnlyCollection wrapping the original list. + /// </summary> + public static IList<T> AsReadOnly(IList<T> list) { + return list.IsReadOnly ? list : new ReadOnlyCollection<T>(list); + } } } diff --git a/csharp/ProtocolBuffers/Collections/ReadOnlyDictionary.cs b/csharp/ProtocolBuffers/Collections/ReadOnlyDictionary.cs new file mode 100644 index 00000000..b67d5cd3 --- /dev/null +++ b/csharp/ProtocolBuffers/Collections/ReadOnlyDictionary.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using IEnumerable=System.Collections.IEnumerable; + +namespace Google.ProtocolBuffers.Collections { + /// <summary> + /// Read-only wrapper around another dictionary. + /// </summary> + public class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue> { + readonly IDictionary<TKey, TValue> wrapped; + + public ReadOnlyDictionary(IDictionary<TKey, TValue> wrapped) { + this.wrapped = wrapped; + } + + public void Add(TKey key, TValue value) { + throw new InvalidOperationException(); + } + + public bool ContainsKey(TKey key) { + return wrapped.ContainsKey(key); + } + + public ICollection<TKey> Keys { + get { return wrapped.Keys; } + } + + public bool Remove(TKey key) { + throw new InvalidOperationException(); + } + + public bool TryGetValue(TKey key, out TValue value) { + return wrapped.TryGetValue(key, out value); + } + + public ICollection<TValue> Values { + get { return wrapped.Values; } + } + + public TValue this[TKey key] { + get { + return wrapped[key]; + } + set { + throw new InvalidOperationException(); + } + } + + public void Add(KeyValuePair<TKey, TValue> item) { + throw new InvalidOperationException(); + } + + public void Clear() { + throw new InvalidOperationException(); + } + + public bool Contains(KeyValuePair<TKey, TValue> item) { + return wrapped.Contains(item); + } + + public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { + wrapped.CopyTo(array, arrayIndex); + } + + public int Count { + get { return wrapped.Count; } + } + + public bool IsReadOnly { + get { return true; } + } + + public bool Remove(KeyValuePair<TKey, TValue> item) { + throw new InvalidOperationException(); + } + + public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { + return wrapped.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() { + return ((IEnumerable) wrapped).GetEnumerator(); + } + + public override bool Equals(object obj) { + return wrapped.Equals(obj); + } + + public override int GetHashCode() { + return wrapped.GetHashCode(); + } + + public override string ToString() { + return wrapped.ToString(); + } + } +} diff --git a/csharp/ProtocolBuffers/Descriptors/EnumDescriptor.cs b/csharp/ProtocolBuffers/Descriptors/EnumDescriptor.cs new file mode 100644 index 00000000..c4f5c796 --- /dev/null +++ b/csharp/ProtocolBuffers/Descriptors/EnumDescriptor.cs @@ -0,0 +1,5 @@ + +namespace Google.ProtocolBuffers.Descriptors { + public class EnumDescriptor { + } +} diff --git a/csharp/ProtocolBuffers/Descriptors/EnumValueDescriptor.cs b/csharp/ProtocolBuffers/Descriptors/EnumValueDescriptor.cs index df3a5db1..48b3fb5c 100644 --- a/csharp/ProtocolBuffers/Descriptors/EnumValueDescriptor.cs +++ b/csharp/ProtocolBuffers/Descriptors/EnumValueDescriptor.cs @@ -2,8 +2,14 @@ namespace Google.ProtocolBuffers.Descriptors { public class EnumValueDescriptor { + private EnumDescriptor enumDescriptor; + public int Number { get { throw new NotImplementedException(); } } + + public EnumDescriptor EnumDescriptor { + get { return enumDescriptor; } + } } } diff --git a/csharp/ProtocolBuffers/Descriptors/FieldDescriptor.cs b/csharp/ProtocolBuffers/Descriptors/FieldDescriptor.cs index 8eb19d53..45e7f964 100644 --- a/csharp/ProtocolBuffers/Descriptors/FieldDescriptor.cs +++ b/csharp/ProtocolBuffers/Descriptors/FieldDescriptor.cs @@ -1,7 +1,11 @@ +using System; + namespace Google.ProtocolBuffers.Descriptors { public class FieldDescriptor { + private EnumDescriptor enumType; + public bool IsRequired { get; set; @@ -25,5 +29,32 @@ namespace Google.ProtocolBuffers.Descriptors { public MessageDescriptor MessageType { get; set; } public MessageDescriptor ExtensionScope { get; set; } + + /// <summary> + /// For enum fields, returns the field's type. + /// </summary> + public EnumDescriptor EnumType { + get { + if (MappedType != MappedType.Enum) { + throw new InvalidOperationException("EnumType is only valid for enum fields."); + } + return enumType; + } + } + + /// <summary> + /// The default value for this field. For repeated fields + /// this will always be an empty list. For message fields it will + /// always be null. For singular values, it will depend on the descriptor. + /// </summary> + public object DefaultValue + { + get { throw new NotImplementedException(); } + } + + public string Name + { + get { throw new NotImplementedException(); } + } } } diff --git a/csharp/ProtocolBuffers/Descriptors/MappedType.cs b/csharp/ProtocolBuffers/Descriptors/MappedType.cs index 4d2d8e51..8d6c8ced 100644 --- a/csharp/ProtocolBuffers/Descriptors/MappedType.cs +++ b/csharp/ProtocolBuffers/Descriptors/MappedType.cs @@ -9,6 +9,8 @@ namespace Google.ProtocolBuffers.Descriptors { public enum MappedType { Int32, Int64, + UInt32, + UInt64, Single, Double, Boolean, diff --git a/csharp/ProtocolBuffers/FieldAccess/IFieldAccessor.cs b/csharp/ProtocolBuffers/FieldAccess/IFieldAccessor.cs index b667fd7f..002d9739 100644 --- a/csharp/ProtocolBuffers/FieldAccess/IFieldAccessor.cs +++ b/csharp/ProtocolBuffers/FieldAccess/IFieldAccessor.cs @@ -1,34 +1,59 @@ -using System; -using System.Collections.Generic; -using System.Text; +namespace Google.ProtocolBuffers.FieldAccess { -namespace Google.ProtocolBuffers.FieldAccess { + /// <summary> + /// Allows fields to be reflectively accessed in a smart manner. + /// The property descriptors for each field are created once and then cached. + /// In addition, this interface holds knowledge of repeated fields, builders etc. + /// </summary> internal interface IFieldAccessor<TMessage, TBuilder> where TMessage : IMessage<TMessage> where TBuilder : IBuilder<TMessage> { - void AddRepeated(IBuilder<TMessage> builder, object value); + /// <summary> + /// Indicates whether the specified message contains the field. + /// </summary> bool Has(IMessage<TMessage> message); + + /// <summary> + /// Gets the count of the repeated field in the specified message. + /// </summary> int GetRepeatedCount(IMessage<TMessage> message); - void Clear(TBuilder builder); - TBuilder CreateBuilder(); + + /// <summary> + /// Clears the field in the specified builder. + /// </summary> + /// <param name="builder"></param> + void Clear(IBuilder<TMessage> builder); + + /// <summary> + /// Creates a builder for the type of this field (which must be a message field). + /// </summary> + IBuilder CreateBuilder(); /// <summary> /// Accessor for single fields /// </summary> - object this[IMessage<TMessage> message] { get; } + object GetValue(IMessage<TMessage> message); /// <summary> /// Mutator for single fields /// </summary> - object this[IBuilder<TMessage> builder] { set; } + void SetValue(IBuilder<TMessage> builder, object value); /// <summary> /// Accessor for repeated fields /// </summary> - object this[IMessage<TMessage> message, int index] { get; } + object GetRepeatedValue(IMessage<TMessage> message, int index); /// <summary> /// Mutator for repeated fields /// </summary> - object this[IBuilder<TMessage> builder, int index] { set; } + void SetRepeated(IBuilder<TMessage> builder, int index, object value); + /// <summary> + /// Adds the specified value to the field in the given builder. + /// </summary> + void AddRepeated(IBuilder<TMessage> builder, object value); + /// <summary> + /// Returns a read-only wrapper around the value of a repeated field. + /// </summary> + object GetRepeatedWrapper(IBuilder<TMessage> builder); } } diff --git a/csharp/ProtocolBuffers/FieldAccess/SingularFieldAccessor.cs b/csharp/ProtocolBuffers/FieldAccess/SingularFieldAccessor.cs deleted file mode 100644 index 62b3c48d..00000000 --- a/csharp/ProtocolBuffers/FieldAccess/SingularFieldAccessor.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Google.ProtocolBuffers.Descriptors; - -namespace Google.ProtocolBuffers.FieldAccess { - internal class SingularFieldAccessor<TMessage, TBuilder> : IFieldAccessor<TMessage, TBuilder> - where TMessage : IMessage<TMessage> - where TBuilder : IBuilder<TMessage> { - - readonly HasFunction<TMessage> hasProxy; - readonly Action<TBuilder> clearProxy; - - internal SingularFieldAccessor(FieldDescriptor descriptor, String pascalCaseName) { - - /* Class<? extends GeneratedMessage> messageClass, - Class<? extends GeneratedMessage.Builder> builderClass) { - getMethod = getMethodOrDie(messageClass, "get" + camelCaseName); - type = getMethod.getReturnType(); - setMethod = getMethodOrDie(builderClass, "set" + camelCaseName, type); - hasMethod = - getMethodOrDie(messageClass, "has" + camelCaseName); - clearMethod = getMethodOrDie(builderClass, "clear" + camelCaseName); */ - } - - public bool Has(IMessage<TMessage> message) { - return false;// hasProxy(message); - } - - public void Clear(TBuilder builder) { -// clearProxy(builder); - } - - public TBuilder CreateBuilder() { -// return createBuilderProxy(builder); - return default(TBuilder); - } - - public object this[IMessage<TMessage> message] { - get { return null;/* getProxy(message);*/ } - } - - public object this[IBuilder<TMessage> builder] { - set { /*setProxy(builder, value);*/ } - } - - #region Repeated operations (which just throw an exception) - public object this[IMessage<TMessage> message, int index] { - get { throw new InvalidOperationException("Repeated operation called on singular field"); } - } - - public object this[IBuilder<TMessage> builder, int index] { - set { throw new InvalidOperationException("Repeated operation called on singular field"); } - } - - public int GetRepeatedCount(IMessage<TMessage> message) { - throw new InvalidOperationException("Repeated operation called on singular field"); - } - - public void AddRepeated(IBuilder<TMessage> builder, object value) { - throw new InvalidOperationException("Repeated operation called on singular field"); - } - #endregion - } -} diff --git a/csharp/ProtocolBuffers/FieldSet.cs b/csharp/ProtocolBuffers/FieldSet.cs index 0efde4b1..9ab13c65 100644 --- a/csharp/ProtocolBuffers/FieldSet.cs +++ b/csharp/ProtocolBuffers/FieldSet.cs @@ -1,10 +1,91 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Text; +using Google.ProtocolBuffers.Descriptors; +using Google.ProtocolBuffers.Collections; namespace Google.ProtocolBuffers { - public class FieldSet { - public static void MergeFrom(CodedInputStream input, + /// <summary> + /// A class which represents an arbitrary set of fields of some message type. + /// This is used to implement DynamicMessage, and also to represent extensions + /// in GeneratedMessage. This class is internal, since outside users should probably + /// be using DynamicMessage. + /// + /// As in the Java implementation, this class goes against the rest of the framework + /// in terms of mutability. Instead of having a mutable Builder class and an immutable + /// FieldSet class, FieldSet just has a MakeImmutable() method. This is safe so long as + /// all callers are careful not to let a mutable FieldSet escape into the open. This would + /// be impossible to guarantee if this were a public class, of course. + /// </summary> + internal class FieldSet { + + private static readonly FieldSet defaultInstance = new FieldSet(new Dictionary<FieldDescriptor, object>()).MakeImmutable(); + + private IDictionary<FieldDescriptor, object> fields; + + private FieldSet(IDictionary<FieldDescriptor, object> fields) { + this.fields = fields; + } + + /// <summary> + /// Makes this FieldSet immutable, and returns it for convenience. Any + /// mutable repeated fields are made immutable, as well as the map itself. + /// </summary> + internal FieldSet MakeImmutable() { + // First check if we have any repeated values + bool hasRepeats = false; + foreach (object value in fields.Values) { + IList<object> list = value as IList<object>; + if (list != null && !list.IsReadOnly) { + hasRepeats = true; + break; + } + } + + if (hasRepeats) { + var tmp = new SortedList<FieldDescriptor, object>(); + foreach (KeyValuePair<FieldDescriptor, object> entry in fields) { + IList<object> list = entry.Value as IList<object>; + tmp[entry.Key] = list == null ? entry.Value : Lists.AsReadOnly(list); + } + fields = tmp; + } + + fields = Dictionaries.AsReadOnly(fields); + + return this; + } + + /// <summary> + /// Returns the default, immutable instance with no fields defined. + /// </summary> + internal static FieldSet DefaultInstance { + get { return defaultInstance; } + } + + /// <summary> + /// Returns an immutable mapping of fields. Note that although the mapping itself + /// is immutable, the entries may not be (i.e. any repeated values are represented by + /// mutable lists). The behaviour is not specified if the contents are mutated. + /// </summary> + internal IDictionary<FieldDescriptor, object> AllFields { + get { return Dictionaries.AsReadOnly(fields); } + } + + /// <summary> + /// See <see cref="IMessage.HasField"/>. + /// </summary> + public bool HasField(FieldDescriptor field) { + if (field.IsRepeated) { + throw new ArgumentException("HasField() can only be called on non-repeated fields."); + } + + return fields.ContainsKey(field); + } + + // TODO(jonskeet): Should this be in UnknownFieldSet.Builder really? + internal static void MergeFrom(CodedInputStream input, UnknownFieldSet.Builder unknownFields, ExtensionRegistry extensionRegistry, IBuilder builder) { @@ -15,19 +96,227 @@ namespace Google.ProtocolBuffers { break; } if (!MergeFieldFrom(input, unknownFields, extensionRegistry, - builder, tag)) { + builder, tag)) { // end group tag break; } } } - public static bool MergeFieldFrom(CodedInputStream input, - UnknownFieldSet.Builder unknownFields, - ExtensionRegistry extensionRegistry, - IBuilder builder, - uint tag) { + // TODO(jonskeet): Should this be in UnknownFieldSet.Builder really? + internal static bool MergeFieldFrom(CodedInputStream input, + UnknownFieldSet.Builder unknownFields, + ExtensionRegistry extensionRegistry, + IBuilder builder, + uint tag) { throw new NotImplementedException(); } + + /// <summary> + /// Clears all fields. + /// </summary> + internal void Clear() { + fields.Clear(); + } + + /// <summary> + /// <see cref="IMessage.Item(FieldDescriptor)"/> + /// </summary> + /// <remarks> + /// If the field is not set, the behaviour when fetching this property varies by field type: + /// <list> + /// <item>For singular message values, null is returned.</item> + /// <item>For singular non-message values, the default value of the field is returned.</item> + /// <item>For repeated values, an empty immutable list is returned.</item> + /// </list> + /// This method returns null if the field is a singular message type + /// and is not set; in this case it is up to the caller to fetch the + /// message's default instance. For repeated fields of message types, + /// an empty collection is returned. For repeated fields of non-message + /// types, null is returned. + /// <para /> + /// When setting this property, any list values are copied, and each element is checked + /// to ensure it is of an appropriate type. + /// </remarks> + /// + internal object this[FieldDescriptor field] { + get { + object result; + if (fields.TryGetValue(field, out result)) { + return result; + } + + // This will just do the right thing + return field.DefaultValue; + } + set { + if (field.IsRepeated) { + List<object> list = value as List<object>; + if (list == null) { + throw new ArgumentException("Wrong object type used with protocol message reflection."); + } + + // Wrap the contents in a new list so that the caller cannot change + // the list's contents after setting it. + List<object> newList = new List<object>(list); + foreach (object element in newList) { + VerifyType(field, element); + } + value = newList; + } + else { + VerifyType(field, value); + } + fields[field] = value; + } + } + + /// <summary> + /// <see cref="IMessage.Item(FieldDescriptor,int)" /> + /// </summary> + internal object this[FieldDescriptor field, int index] { + get { + if (!field.IsRepeated) { + throw new ArgumentException("Indexer specifying field and index can only be called on repeated fields."); + } + + return ((List<object>)this[field])[index]; + } + set { + if (!field.IsRepeated) { + throw new ArgumentException("Indexer specifying field and index can only be called on repeated fields."); + } + VerifyType(field, value); + object list; + if (!fields.TryGetValue(field, out list)) { + throw new ArgumentOutOfRangeException(); + } + ((List<object>) list)[index] = value; + } + } + + /// <summary> + /// <see cref="IBuilder.AddRepeatedField" /> + /// </summary> + /// <param name="field"></param> + /// <param name="value"></param> + internal void AddRepeatedField(FieldDescriptor field, object value) { + if (!field.IsRepeated) { + throw new ArgumentException("AddRepeatedField can only be called on repeated fields."); + } + VerifyType(field, value); + object list; + if (!fields.TryGetValue(field, out list)) { + list = new List<object>(); + fields[field] = list; + } + ((List<object>) list).Add(value); + } + + /// <summary> + /// <see cref="IMessage.IsInitialized" /> + /// </summary> + /// <remarks> + /// Since FieldSet itself does not have any way of knowing about + /// required fields that aren't actually present in the set, it is up + /// to the caller to check for genuinely required fields. This property + /// merely checks that any messages present are themselves initialized. + /// </remarks> + internal bool IsInitialized { + get { + foreach (KeyValuePair<FieldDescriptor, object> entry in fields) { + FieldDescriptor field = entry.Key; + if (field.MappedType == MappedType.Message) { + if (field.IsRepeated) { + foreach(IMessage message in (IEnumerable) entry.Value) { + if (!message.IsInitialized) { + return false; + } + } + } else { + if (!((IMessage) entry.Value).IsInitialized) { + return false; + } + } + } + } + return true; + } + } + + /// <summary> + /// Verifies whether all the required fields in the specified message + /// descriptor are present in this field set, as well as whether + /// all the embedded messages are themselves initialized. + /// </summary> + internal bool IsInitializedWithRespectTo(MessageDescriptor type) { + foreach (FieldDescriptor field in type.Fields) { + if (field.IsRequired && !HasField(field)) { + return false; + } + } + return IsInitialized; + } + + /// <summary> + /// <see cref="IBuilder.ClearField" /> + /// </summary> + public void ClearField(FieldDescriptor field) { + fields.Remove(field); + } + + /// <summary> + /// <see cref="IMessage.GetRepeatedFieldCount" /> + /// </summary> + public int GetRepeatedFieldCount(FieldDescriptor field) { + if (!field.IsRepeated) { + throw new ArgumentException("GetRepeatedFieldCount() can only be called on repeated fields."); + } + + return ((List<object>) this[field]).Count; + } + + /// <summary> + /// Verifies that the given object is of the correct type to be a valid + /// value for the given field. + /// </summary> + /// <remarks> + /// For repeated fields, this checks if the object is of the right + /// element type, not whether it's a list. + /// </remarks> + /// <exception cref="ArgumentException">The value is not of the right type.</exception> + private static void VerifyType(FieldDescriptor field, object value) { + bool isValid = false; + switch (field.MappedType) { + case MappedType.Int32: isValid = value is int; break; + case MappedType.Int64: isValid = value is long; break; + case MappedType.UInt32: isValid = value is uint; break; + case MappedType.UInt64: isValid = value is ulong; break; + case MappedType.Single: isValid = value is float; break; + case MappedType.Double: isValid = value is double; break; + case MappedType.Boolean: isValid = value is bool; break; + case MappedType.String: isValid = value is string; break; + case MappedType.ByteString: isValid = value is ByteString; break; + case MappedType.Enum: + EnumValueDescriptor enumValue = value as EnumValueDescriptor; + isValid = enumValue != null && enumValue.EnumDescriptor == field.EnumType; + break; + case MappedType.Message: + IMessage messageValue = value as IMessage; + isValid = messageValue != null && messageValue.DescriptorForType == field.MessageType; + break; + } + + if (!isValid) { + // When chaining calls to SetField(), it can be hard to tell from + // the stack trace which exact call failed, since the whole chain is + // considered one line of code. So, let's make sure to include the + // field name and other useful info in the exception. + throw new ArgumentException("Wrong object type used with protocol message reflection. " + + "Message type \"" + field.ContainingType.FullName + + "\", field \"" + (field.IsExtension ? field.FullName : field.Name) + + "\", value was type \"" + value.GetType().Name + "\"."); + } + } } } diff --git a/csharp/ProtocolBuffers/GeneratedBuilder.cs b/csharp/ProtocolBuffers/GeneratedBuilder.cs new file mode 100644 index 00000000..ffb794f6 --- /dev/null +++ b/csharp/ProtocolBuffers/GeneratedBuilder.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Google.ProtocolBuffers.Collections; +using Google.ProtocolBuffers.Descriptors; + +namespace Google.ProtocolBuffers { + /// <summary> + /// All generated protocol message builder classes extend this class. It implements + /// most of the IBuilder interface using reflection. Users can ignore this class + /// as an implementation detail. + /// </summary> + public abstract class GeneratedBuilder<TMessage, TBuilder> : AbstractBuilder, IBuilder<TMessage> + where TMessage : GeneratedMessage <TMessage, TBuilder> + where TBuilder : GeneratedBuilder<TMessage, TBuilder>, IBuilder<TMessage> { + + /// <summary> + /// Returns the message being built at the moment. + /// </summary> + protected abstract GeneratedMessage<TMessage,TBuilder> MessageBeingBuilt { get; } + + public override bool Initialized { + get { return MessageBeingBuilt.IsInitialized; } + } + + public override IDictionary<FieldDescriptor, object> AllFields { + get { return MessageBeingBuilt.AllFields; } + } + + public override object this[FieldDescriptor field] { + get { + // For repeated fields, the underlying list object is still modifiable at this point. + // Make sure not to expose the modifiable list to the caller. + return field.IsRepeated + ? MessageBeingBuilt.InternalFieldAccessors[field].GetRepeatedWrapper(this) + : MessageBeingBuilt[field]; + } + set { + MessageBeingBuilt.InternalFieldAccessors[field].SetValue(this, value); + } + } + + public override MessageDescriptor DescriptorForType { + get { return MessageBeingBuilt.DescriptorForType; } + } + + public override int GetRepeatedFieldCount(FieldDescriptor field) { + return MessageBeingBuilt.GetRepeatedFieldCount(field); + } + + public override object this[FieldDescriptor field, int index] { + get { return MessageBeingBuilt[field, index]; } + set { MessageBeingBuilt.InternalFieldAccessors[field].SetRepeated(this, index, value); } + } + + public override bool HasField(FieldDescriptor field) { + return MessageBeingBuilt.HasField(field); + } + + protected override IMessage BuildImpl() { + return Build(); + } + + protected override IMessage BuildPartialImpl() { + return BuildPartial(); + } + + protected override IBuilder CloneImpl() { + return Clone(); + } + + protected override IMessage DefaultInstanceForTypeImpl { + get { return DefaultInstanceForType; } + } + + protected override IBuilder NewBuilderForFieldImpl(FieldDescriptor field) { + return NewBuilderForField(field); + } + + protected override IBuilder ClearFieldImpl(FieldDescriptor field) { + return ClearField(field); + } + + protected override IBuilder AddRepeatedFieldImpl(FieldDescriptor field, object value) { + return AddRepeatedField(field, value); + } + + public new abstract IBuilder<TMessage> Clear(); + + public IBuilder<TMessage> MergeFrom(IMessage<TMessage> other) { + throw new NotImplementedException(); + } + + public abstract IMessage<TMessage> Build(); + + public abstract IMessage<TMessage> BuildPartial(); + + public abstract IBuilder<TMessage> Clone(); + + IBuilder<TMessage> IBuilder<TMessage>.MergeFrom(CodedInputStream input) { + throw new NotImplementedException(); + } + + IBuilder<TMessage> IBuilder<TMessage>.MergeFrom(CodedInputStream input, ExtensionRegistry extensionRegistry) { + throw new NotImplementedException(); + } + + public IMessage<TMessage> DefaultInstanceForType { + get { throw new NotImplementedException(); } + } + + public IBuilder NewBuilderForField(FieldDescriptor field) { + throw new NotImplementedException(); + } + + public IBuilder<TMessage> ClearField(FieldDescriptor field) { + MessageBeingBuilt.InternalFieldAccessors[field].Clear(this); + return this; + } + + public IBuilder<TMessage> AddRepeatedField(FieldDescriptor field, object value) { + return this; + } + + public new IBuilder<TMessage> MergeUnknownFields(UnknownFieldSet unknownFields) { + throw new NotImplementedException(); + } + + IBuilder<TMessage> IBuilder<TMessage>.MergeFrom(ByteString data) { + throw new NotImplementedException(); + } + + IBuilder<TMessage> IBuilder<TMessage>.MergeFrom(ByteString data, ExtensionRegistry extensionRegistry) { + throw new NotImplementedException(); + } + + IBuilder<TMessage> IBuilder<TMessage>.MergeFrom(byte[] data) { + throw new NotImplementedException(); + } + + IBuilder<TMessage> IBuilder<TMessage>.MergeFrom(byte[] data, ExtensionRegistry extensionRegistry) { + throw new NotImplementedException(); + } + + IBuilder<TMessage> IBuilder<TMessage>.MergeFrom(System.IO.Stream input) { + throw new NotImplementedException(); + } + + IBuilder<TMessage> IBuilder<TMessage>.MergeFrom(System.IO.Stream input, ExtensionRegistry extensionRegistry) { + throw new NotImplementedException(); + } + } +} diff --git a/csharp/ProtocolBuffers/GeneratedMessage.cs b/csharp/ProtocolBuffers/GeneratedMessage.cs index d10531eb..90d4a19f 100644 --- a/csharp/ProtocolBuffers/GeneratedMessage.cs +++ b/csharp/ProtocolBuffers/GeneratedMessage.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Google.ProtocolBuffers.Collections; using Google.ProtocolBuffers.Descriptors; using Google.ProtocolBuffers.FieldAccess; @@ -7,7 +8,7 @@ namespace Google.ProtocolBuffers { /// <summary> /// All generated protocol message classes extend this class. It implements - /// most of the IMessage and IBuilder interfaces using reflection. Users + /// most of the IMessage interface using reflection. Users /// can ignore this class as an implementation detail. /// </summary> public abstract class GeneratedMessage<TMessage, TBuilder> : AbstractMessage, IMessage<TMessage> @@ -15,7 +16,7 @@ namespace Google.ProtocolBuffers { private readonly UnknownFieldSet unknownFields = UnknownFieldSet.DefaultInstance; - protected abstract FieldAccessorTable<TMessage, TBuilder> InternalFieldAccessors { get; } + protected internal abstract FieldAccessorTable<TMessage, TBuilder> InternalFieldAccessors { get; } public override MessageDescriptor DescriptorForType { get { return InternalFieldAccessors.Descriptor; } @@ -38,21 +39,22 @@ namespace Google.ProtocolBuffers { } private IDictionary<FieldDescriptor, Object> GetMutableFieldMap() { - var ret = new Dictionary<FieldDescriptor, object>(); + + // Use a SortedList so we'll end up serializing fields in order + var ret = new SortedList<FieldDescriptor, object>(); MessageDescriptor descriptor = DescriptorForType; foreach (FieldDescriptor field in descriptor.Fields) { IFieldAccessor<TMessage, TBuilder> accessor = InternalFieldAccessors[field]; if ((field.IsRepeated && accessor.GetRepeatedCount(this) != 0) || accessor.Has(this)) { - ret[field] = accessor[this]; + ret[field] = accessor.GetValue(this); } } return ret; } public override IDictionary<FieldDescriptor, object> AllFields { - // FIXME: Make it immutable - get { return GetMutableFieldMap(); } + get { return Dictionaries.AsReadOnly(GetMutableFieldMap()); } } public override bool HasField(FieldDescriptor field) { @@ -64,11 +66,11 @@ namespace Google.ProtocolBuffers { } public override object this[FieldDescriptor field, int index] { - get { return InternalFieldAccessors[field][this, index]; } + get { return InternalFieldAccessors[field].GetRepeatedValue(this, index); } } public override object this[FieldDescriptor field] { - get { return InternalFieldAccessors[field][this]; } + get { return InternalFieldAccessors[field].GetValue(this); } } public override UnknownFieldSet UnknownFields { diff --git a/csharp/ProtocolBuffers/IBuilder.cs b/csharp/ProtocolBuffers/IBuilder.cs index 50279039..f1366988 100644 --- a/csharp/ProtocolBuffers/IBuilder.cs +++ b/csharp/ProtocolBuffers/IBuilder.cs @@ -88,7 +88,7 @@ namespace Google.ProtocolBuffers { IBuilder MergeFrom(CodedInputStream input); IBuilder MergeFrom(CodedInputStream codedInputStream, ExtensionRegistry extensionRegistry); IMessage DefaultInstanceForType { get; } - IBuilder NewBuilderForField<TField>(FieldDescriptor field); + IBuilder NewBuilderForField(FieldDescriptor field); IBuilder ClearField(FieldDescriptor field); IBuilder AddRepeatedField(FieldDescriptor field, object value); IBuilder MergeUnknownFields(UnknownFieldSet unknownFields); @@ -191,7 +191,7 @@ namespace Google.ProtocolBuffers { /// Messages built with this can then be passed to the various mutation properties /// and methods. /// </summary> - new IBuilder<TField> NewBuilderForField<TField>(FieldDescriptor field) where TField : IMessage<TField>; + //new IBuilder<TField> NewBuilderForField<TField>(FieldDescriptor field) where TField : IMessage<TField>; /// <summary> /// Clears the field. This is exactly equivalent to calling the generated diff --git a/csharp/ProtocolBuffers/ProtocolBuffers.csproj b/csharp/ProtocolBuffers/ProtocolBuffers.csproj index c22720f4..ea30503b 100644 --- a/csharp/ProtocolBuffers/ProtocolBuffers.csproj +++ b/csharp/ProtocolBuffers/ProtocolBuffers.csproj @@ -42,7 +42,10 @@ <Compile Include="ByteString.cs" /> <Compile Include="CodedInputStream.cs" /> <Compile Include="CodedOutputStream.cs" /> + <Compile Include="Collections\Dictionaries.cs" /> <Compile Include="Collections\Lists.cs" /> + <Compile Include="Collections\ReadOnlyDictionary.cs" /> + <Compile Include="Descriptors\EnumDescriptor.cs" /> <Compile Include="Descriptors\EnumValueDescriptor.cs" /> <Compile Include="Descriptors\FieldDescriptor.cs" /> <Compile Include="Descriptors\FieldType.cs" /> @@ -53,8 +56,8 @@ <Compile Include="FieldAccess\Delegates.cs" /> <Compile Include="FieldAccess\IFieldAccessor.cs" /> <Compile Include="FieldAccess\FieldAccessorTable.cs" /> - <Compile Include="FieldAccess\SingularFieldAccessor.cs" /> <Compile Include="FieldSet.cs" /> + <Compile Include="GeneratedBuilder.cs" /> <Compile Include="GeneratedExtension.cs" /> <Compile Include="GeneratedMessage.cs" /> <Compile Include="IBuilder.cs" /> diff --git a/csharp/ProtocolBuffers/UnknownFieldSet.cs b/csharp/ProtocolBuffers/UnknownFieldSet.cs index a0d84d40..87297d85 100644 --- a/csharp/ProtocolBuffers/UnknownFieldSet.cs +++ b/csharp/ProtocolBuffers/UnknownFieldSet.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; using System.IO; +using Google.ProtocolBuffers.Collections; namespace Google.ProtocolBuffers { public class UnknownFieldSet { @@ -48,10 +49,10 @@ namespace Google.ProtocolBuffers { } /// <summary> - /// Creates and returns a copy of the mapping from field numbers to values. + /// Returns a read-only view of the mapping from field numbers to values. /// </summary> public IDictionary<int, UnknownField> FieldDictionary { - get { return new Dictionary<int, UnknownField>(fields); } + get { return Dictionaries.AsReadOnly(fields); } } /// <summary> @@ -195,7 +196,11 @@ namespace Google.ProtocolBuffers { public class Builder { - private Dictionary<int, UnknownField> fields = new Dictionary<int, UnknownField>(); + /// <summary> + /// Mapping from number to field. Note that by using a SortedList we ensure + /// that the fields will be serialized in ascending order. + /// </summary> + private IDictionary<int, UnknownField> fields = new SortedList<int, UnknownField>(); // Optimization: We keep around a builder for the last field that was // modified so that we can efficiently add to it multiple times in a |