From 3351bb63dacdf437486e034337a47fa8e31a0078 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Thu, 14 Aug 2008 20:35:15 +0100 Subject: [PATCH] Descriptors are pretty much complete, with a slight issue of how to find the default type for a repeated field. --- CSHARP-README.txt | 6 +- .../DescriptorProtos/Autogenerated.cs | 149 ------- .../Descriptors/DescriptorBase.cs | 27 +- .../Descriptors/DescriptorPool.cs | 252 +++++++++++- .../Descriptors/DescriptorUtil.cs | 27 ++ .../DescriptorValidationException.cs | 53 +++ .../Descriptors/EnumDescriptor.cs | 56 ++- .../Descriptors/EnumValueDescriptor.cs | 11 +- .../Descriptors/FieldDescriptor.cs | 386 ++++++++++++++++-- .../Descriptors/FileDescriptor.cs | 159 +++++++- .../Descriptors/IDescriptor.cs | 25 ++ .../Descriptors/IndexedDescriptorBase.cs | 4 +- .../Descriptors/MessageDescriptor.cs | 108 ++++- .../Descriptors/MethodDescriptor.cs | 55 +++ .../Descriptors/PackageDescriptor.cs | 44 ++ .../Descriptors/ServiceDescriptor.cs | 27 +- csharp/ProtocolBuffers/ProtocolBuffers.csproj | 5 + csharp/ProtocolBuffers/TextFormat.cs | 20 + 18 files changed, 1198 insertions(+), 216 deletions(-) delete mode 100644 csharp/ProtocolBuffers/DescriptorProtos/Autogenerated.cs create mode 100644 csharp/ProtocolBuffers/Descriptors/DescriptorUtil.cs create mode 100644 csharp/ProtocolBuffers/Descriptors/DescriptorValidationException.cs create mode 100644 csharp/ProtocolBuffers/Descriptors/IDescriptor.cs create mode 100644 csharp/ProtocolBuffers/Descriptors/MethodDescriptor.cs create mode 100644 csharp/ProtocolBuffers/Descriptors/PackageDescriptor.cs diff --git a/CSHARP-README.txt b/CSHARP-README.txt index 9283577f91..445587a8e4 100644 --- a/CSHARP-README.txt +++ b/CSHARP-README.txt @@ -34,5 +34,7 @@ o There's a mixture of generic and non-generic code. This IEnumerable. As an aside, this becomes a pain when trying to create "the read-only version of this list, whose type I don't know but I know it's a List". Oh for mumble types. - - +o Nested types always end up under a "Types" static class which + is merely present to avoid name clashes. +o FileDescriptor.FindByName has been made generic to allow simple + type-safe searching for any nested type. \ No newline at end of file diff --git a/csharp/ProtocolBuffers/DescriptorProtos/Autogenerated.cs b/csharp/ProtocolBuffers/DescriptorProtos/Autogenerated.cs deleted file mode 100644 index 86cfdae7cc..0000000000 --- a/csharp/ProtocolBuffers/DescriptorProtos/Autogenerated.cs +++ /dev/null @@ -1,149 +0,0 @@ -// This file contains quick hacks to represent code that will be autogenerated. - -namespace Google.ProtocolBuffers.DescriptorProtos { - - /// - /// This only exists until we've got the real code... - /// - /* - public abstract class TemporaryMessage : IMessage where T : IMessage { - #region IMessage Members - - public IMessage DefaultInstanceForType { - get { throw new System.NotImplementedException(); } - } - - public IBuilder CreateBuilderForType() { - throw new System.NotImplementedException(); - } - - #endregion - - #region IMessage Members - - public Google.ProtocolBuffers.Descriptors.MessageDescriptor DescriptorForType { - get { throw new System.NotImplementedException(); } - } - - public System.Collections.Generic.IDictionary AllFields { - get { throw new System.NotImplementedException(); } - } - - public bool HasField(Google.ProtocolBuffers.Descriptors.FieldDescriptor field) { - throw new System.NotImplementedException(); - } - - public object this[Google.ProtocolBuffers.Descriptors.FieldDescriptor field] { - get { throw new System.NotImplementedException(); } - } - - public int GetRepeatedFieldCount(Google.ProtocolBuffers.Descriptors.FieldDescriptor field) { - throw new System.NotImplementedException(); - } - - public object this[Google.ProtocolBuffers.Descriptors.FieldDescriptor field, int index] { - get { throw new System.NotImplementedException(); } - } - - public UnknownFieldSet UnknownFields { - get { throw new System.NotImplementedException(); } - } - - public bool IsInitialized { - get { throw new System.NotImplementedException(); } - } - - public void WriteTo(CodedOutputStream output) { - throw new System.NotImplementedException(); - } - - public int SerializedSize { - get { throw new System.NotImplementedException(); } - } - - public ByteString ToByteString() { - throw new System.NotImplementedException(); - } - - public byte[] ToByteArray() { - throw new System.NotImplementedException(); - } - - public void WriteTo(System.IO.Stream output) { - throw new System.NotImplementedException(); - } - - IMessage IMessage.DefaultInstanceForType { - get { throw new System.NotImplementedException(); } - } - - IBuilder IMessage.CreateBuilderForType() { - throw new System.NotImplementedException(); - } - - #endregion - } - - public partial class MessageOptions : TemporaryMessage { - public bool MessageSetWireFormat; - } - - public partial class DescriptorProto : TemporaryMessage { - public string Name { get; set; } - public string FullName { get; set; } - public MessageOptions Options { get; set; } - } - - public partial class EnumDescriptorProto : TemporaryMessage { - public string Name { get; set; } - public string FullName { get; set; } - public EnumOptions Options { get; set; } - } - - public partial class EnumOptions : TemporaryMessage { } - - public partial class EnumValueDescriptorProto : TemporaryMessage { - public string Name { get; set; } - public string FullName { get; set; } - public EnumValueOptions Options { get; set; } - } - - public partial class EnumValueOptions : TemporaryMessage { } - - public partial class FieldDescriptorProto : TemporaryMessage { - public string Name { get; set; } - public string FullName { get; set; } - public FieldOptions Options { get; set; } - - } - - public partial class FieldOptions : TemporaryMessage { } - - public partial class FileDescriptorProto : TemporaryMessage { - public string Name { get; set; } - public string FullName { get; set; } - public FileOptions Options { get; set; } - - public string Package { get; set; } - } - - public partial class FileOptions : TemporaryMessage { } - - public partial class MethodDescriptorProto : TemporaryMessage { - public string Name { get; set; } - public string FullName { get; set; } - public MethodOptions Options { get; set; } - - } - - public partial class MethodOptions : TemporaryMessage { } - - public partial class ServiceDescriptorProto : TemporaryMessage { - public string Name { get; set; } - public string FullName { get; set; } - public ServiceOptions Options { get; set; } - - } - - public partial class ServiceOptions : TemporaryMessage { } */ -} diff --git a/csharp/ProtocolBuffers/Descriptors/DescriptorBase.cs b/csharp/ProtocolBuffers/Descriptors/DescriptorBase.cs index e6be75470e..0678cb6da8 100644 --- a/csharp/ProtocolBuffers/Descriptors/DescriptorBase.cs +++ b/csharp/ProtocolBuffers/Descriptors/DescriptorBase.cs @@ -2,21 +2,39 @@ using System.Collections.Generic; using System.Text; using Google.ProtocolBuffers.DescriptorProtos; +using Google.ProtocolBuffers.Collections; namespace Google.ProtocolBuffers.Descriptors { /// - /// Base class for all descriptors, providing common functionality. + /// Base class for nearly all descriptors, providing common functionality. /// /// Type of the protocol buffer form of this descriptor /// Type of the options protocol buffer for this descriptor - public abstract class DescriptorBase where TProto : IMessage, IDescriptorProto { + public abstract class DescriptorBase : IDescriptor + where TProto : IMessage, IDescriptorProto { private readonly TProto proto; private readonly FileDescriptor file; + private readonly string fullName; - protected DescriptorBase(TProto proto, FileDescriptor file) { + protected DescriptorBase(TProto proto, FileDescriptor file, string fullName) { this.proto = proto; this.file = file; + this.fullName = fullName; + } + + protected static string ComputeFullName(FileDescriptor file, MessageDescriptor parent, string name) { + if (parent != null) { + return parent.FullName + "." + name; + } + if (file.Package.Length > 0) { + return file.Package + "." + name; + } + return name; + } + + IMessage IDescriptor.Proto { + get { return proto; } } /// @@ -32,10 +50,9 @@ namespace Google.ProtocolBuffers.Descriptors { /// /// The fully qualified name of the descriptor's target. - /// TODO(jonskeet): Implement! /// public string FullName { - get { return null; } + get { return fullName; } } /// diff --git a/csharp/ProtocolBuffers/Descriptors/DescriptorPool.cs b/csharp/ProtocolBuffers/Descriptors/DescriptorPool.cs index f28769761c..fc30295e5c 100644 --- a/csharp/ProtocolBuffers/Descriptors/DescriptorPool.cs +++ b/csharp/ProtocolBuffers/Descriptors/DescriptorPool.cs @@ -1,4 +1,7 @@ using System.Collections.Generic; +using System; +using System.Text; +using System.Text.RegularExpressions; namespace Google.ProtocolBuffers.Descriptors { /// @@ -6,17 +9,258 @@ namespace Google.ProtocolBuffers.Descriptors { /// internal class DescriptorPool { - IDictionary byName; + private readonly IDictionary descriptorsByName = + new Dictionary(); + private readonly IDictionary fieldsByNumber = + new Dictionary(); + private readonly IDictionary enumValuesByNumber = + new Dictionary(); + private readonly DescriptorPool[] dependencies; + + internal DescriptorPool(FileDescriptor[] dependencyFiles) { + dependencies = new DescriptorPool[dependencyFiles.Length]; + for (int i = 0; i < dependencyFiles.Length; i++) { + dependencies[i] = dependencyFiles[i].DescriptorPool; + } + + foreach (FileDescriptor dependency in dependencyFiles) { + AddPackage(dependency.Package, dependency); + } + } /// /// Finds a symbol of the given name within the pool. /// /// The type of symbol to look for - /// Name to look up + /// Fully-qualified name to look up /// The symbol with the given name and type, /// or null if the symbol doesn't exist or has the wrong type - internal T FindSymbol(string name) where T : class { - return default(T); + internal T FindSymbol(string fullName) where T : class, IDescriptor { + IDescriptor result; + descriptorsByName.TryGetValue(fullName, out result); + T descriptor = result as T; + if (descriptor != null) { + return descriptor; + } + + foreach (DescriptorPool dependency in dependencies) { + dependency.descriptorsByName.TryGetValue(fullName, out result); + descriptor = result as T; + if (descriptor != null) { + return descriptor; + } + } + + return null; + } + + /// + /// Adds a package to the symbol tables. If a package by the same name + /// already exists, that is fine, but if some other kind of symbol + /// exists under the same name, an exception is thrown. If the package + /// has multiple components, this also adds the parent package(s). + /// + internal void AddPackage(string fullName, FileDescriptor file) { + int dotpos = fullName.LastIndexOf('.'); + String name; + if (dotpos != -1) { + AddPackage(fullName.Substring(0, dotpos), file); + name = fullName.Substring(dotpos + 1); + } else { + name = fullName; + } + + IDescriptor old; + if (descriptorsByName.TryGetValue(fullName, out old)) { + if (!(old is PackageDescriptor)) { + throw new DescriptorValidationException(file, + "\"" + name + "\" is already defined (as something other than a " + + "package) in file \"" + old.File.Name + "\"."); + } + } + // TODO(jonskeet): Check issue 25 wrt the ordering of these parameters + descriptorsByName[fullName] = new PackageDescriptor(fullName, name, file); + } + + /// + /// Adds a symbol to the symbol table. + /// + /// The symbol already existed + /// in the symbol table. + internal void AddSymbol(IDescriptor descriptor) { + ValidateSymbolName(descriptor); + String fullName = descriptor.FullName; + + IDescriptor old; + if (descriptorsByName.TryGetValue(fullName, out old)) { + int dotPos = fullName.LastIndexOf('.'); + string message; + if (descriptor.File == old.File) { + if (dotPos == -1) { + message = "\"" + fullName + "\" is already defined."; + } else { + message = "\"" + fullName.Substring(dotPos + 1) + "\" is already defined in \"" + fullName.Substring(0, dotPos) + "\"."; + } + } else { + message = "\"" + fullName + "\" is already defined in file \"" + old.File.Name + "\"."; + } + throw new DescriptorValidationException(descriptor, message); + } + descriptorsByName[fullName] = descriptor; + } + + private static readonly Regex ValidationRegex = new Regex("^[_A-Za-z][_A-Za-z0-9]*$", RegexOptions.Compiled); + + /// + /// Verifies that the descriptor's name is valid (i.e. it contains + /// only letters, digits and underscores, and does not start with a digit). + /// + /// + private static void ValidateSymbolName(IDescriptor descriptor) { + if (descriptor.Name == "") { + throw new DescriptorValidationException(descriptor, "Missing name."); + } + if (!ValidationRegex.IsMatch(descriptor.Name)) { + throw new DescriptorValidationException(descriptor, + "\"" + descriptor.Name + "\" is not a valid identifier."); + } + } + + /// + /// Returns the field with the given number in the given descriptor, + /// or null if it can't be found. + /// + internal FieldDescriptor FindFieldByNumber(MessageDescriptor messageDescriptor, int number) { + FieldDescriptor ret; + fieldsByNumber.TryGetValue(new DescriptorIntPair(messageDescriptor, number), out ret); + return ret; + } + + internal EnumValueDescriptor FindEnumValueByNumber(EnumDescriptor enumDescriptor, int number) { + EnumValueDescriptor ret; + enumValuesByNumber.TryGetValue(new DescriptorIntPair(enumDescriptor, number), out ret); + return ret; + } + + /// + /// Adds a field to the fieldsByNumber table. + /// + /// A field with the same + /// containing type and number already exists. + internal void AddFieldByNumber(FieldDescriptor field) { + DescriptorIntPair key = new DescriptorIntPair(field.ContainingType, field.FieldNumber); + FieldDescriptor old; + if (fieldsByNumber.TryGetValue(key, out old)) { + throw new DescriptorValidationException(field, "Field number " + field.FieldNumber + + "has already been used in \"" + field.ContainingType.FullName + + "\" by field \"" + old.Name + "\"."); + } + fieldsByNumber[key] = field; + } + + /// + /// Adds an enum value to the enumValuesByNumber table. If an enum value + /// with the same type and number already exists, this method does nothing. + /// (This is allowed; the first value defined with the number takes precedence.) + /// + internal void AddEnumValueByNumber(EnumValueDescriptor enumValue) { + DescriptorIntPair key = new DescriptorIntPair(enumValue.EnumDescriptor, enumValue.Number); + if (!enumValuesByNumber.ContainsKey(key)) { + enumValuesByNumber[key] = enumValue; + } + } + + /// + /// Looks up a descriptor by name, relative to some other descriptor. + /// The name may be fully-qualified (with a leading '.'), partially-qualified, + /// or unqualified. C++-like name lookup semantics are used to search for the + /// matching descriptor. + /// + public IDescriptor LookupSymbol(string name, IDescriptor relativeTo) { + // TODO(jonskeet): This could be optimized in a number of ways. + + IDescriptor result; + if (name.StartsWith(".")) { + // Fully-qualified name. + result = FindSymbol(name.Substring(1)); + } else { + // If "name" is a compound identifier, we want to search for the + // first component of it, then search within it for the rest. + int firstPartLength = name.IndexOf('.'); + string firstPart = firstPartLength == -1 ? name : name.Substring(0, firstPartLength); + + // We will search each parent scope of "relativeTo" looking for the + // symbol. + StringBuilder scopeToTry = new StringBuilder(relativeTo.FullName); + + while (true) { + // Chop off the last component of the scope. + + // TODO(jonskeet): Make this more efficient. May not be worth using StringBuilder at all + int dotpos = scopeToTry.ToString().LastIndexOf("."); + if (dotpos == -1) { + result = FindSymbol(name); + break; + } else { + scopeToTry.Length = dotpos + 1; + + // Append firstPart and try to find. + scopeToTry.Append(firstPart); + result = FindSymbol(scopeToTry.ToString()); + + if (result != null) { + if (firstPartLength != -1) { + // We only found the first part of the symbol. Now look for + // the whole thing. If this fails, we *don't* want to keep + // searching parent scopes. + scopeToTry.Length = dotpos + 1; + scopeToTry.Append(name); + result = FindSymbol(scopeToTry.ToString()); + } + break; + } + + // Not found. Remove the name so we can try again. + scopeToTry.Length = dotpos; + } + } + } + + if (result == null) { + throw new DescriptorValidationException(relativeTo, "\"" + name + "\" is not defined."); + } else { + return result; + } + } + + /// + /// Struct used to hold the keys for the fieldByNumber table. + /// + struct DescriptorIntPair : IEquatable { + + private readonly int number; + private readonly IDescriptor descriptor; + + internal DescriptorIntPair(IDescriptor descriptor, int number) { + this.number = number; + this.descriptor = descriptor; + } + + public bool Equals(DescriptorIntPair other) { + return descriptor == other.descriptor + && number == other.number; + } + + public override bool Equals(object obj) { + if (obj is DescriptorIntPair) { + return Equals((DescriptorIntPair)obj); + } + return false; + } + + public override int GetHashCode() { + return descriptor.GetHashCode() * ((1 << 16) - 1) + number; + } } } } diff --git a/csharp/ProtocolBuffers/Descriptors/DescriptorUtil.cs b/csharp/ProtocolBuffers/Descriptors/DescriptorUtil.cs new file mode 100644 index 0000000000..a75d5f92ce --- /dev/null +++ b/csharp/ProtocolBuffers/Descriptors/DescriptorUtil.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using Google.ProtocolBuffers.Collections; +namespace Google.ProtocolBuffers.Descriptors { + /// + /// Internal class containing utility methods when working with descriptors. + /// + static class DescriptorUtil { + /// + /// Equivalent to Func[TInput, int, TOutput] but usable in .NET 2.0. Only used to convert + /// arrays. + /// + internal delegate TOutput IndexedConverter(TInput element, int index); + + /// + /// Converts the given array into a read-only list, applying the specified conversion to + /// each input element. + /// + internal static IList ConvertAndMakeReadOnly(IList input, + IndexedConverter converter) { + TOutput[] array = new TOutput[input.Count]; + for (int i = 0; i < array.Length; i++) { + array[i] = converter(input[i], i); + } + return Lists.AsReadOnly(array); + } + } +} diff --git a/csharp/ProtocolBuffers/Descriptors/DescriptorValidationException.cs b/csharp/ProtocolBuffers/Descriptors/DescriptorValidationException.cs new file mode 100644 index 0000000000..bc0c207851 --- /dev/null +++ b/csharp/ProtocolBuffers/Descriptors/DescriptorValidationException.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Google.ProtocolBuffers.Descriptors { + public class DescriptorValidationException : Exception { + + private readonly String name; + private readonly IMessage proto; + private readonly string description; + + /// + /// The full name of the descriptor where the error occurred. + /// + public String ProblemSymbolName { + get { return name; } + } + + /// + /// The protocol message representation of the invalid descriptor. + /// + public IMessage ProblemProto { + get { return proto; } + } + + /// + /// A human-readable description of the error. (The Message property + /// is made up of the descriptor's name and this description.) + /// + public string Description { + get { return description; } + } + + internal DescriptorValidationException(IDescriptor problemDescriptor, string description) : + base(problemDescriptor.FullName + ": " + description) { + + // Note that problemDescriptor may be partially uninitialized, so we + // don't want to expose it directly to the user. So, we only provide + // the name and the original proto. + name = problemDescriptor.FullName; + proto = problemDescriptor.Proto; + this.description = description; + } + + internal DescriptorValidationException(IDescriptor problemDescriptor, string description, Exception cause) : + base(problemDescriptor.FullName + ": " + description, cause) { + + name = problemDescriptor.FullName; + proto = problemDescriptor.Proto; + this.description = description; + } + } +} diff --git a/csharp/ProtocolBuffers/Descriptors/EnumDescriptor.cs b/csharp/ProtocolBuffers/Descriptors/EnumDescriptor.cs index 1ebe5a7e8b..3dccc2871d 100644 --- a/csharp/ProtocolBuffers/Descriptors/EnumDescriptor.cs +++ b/csharp/ProtocolBuffers/Descriptors/EnumDescriptor.cs @@ -1,13 +1,61 @@  +using System; +using System.Collections.Generic; using Google.ProtocolBuffers.DescriptorProtos; namespace Google.ProtocolBuffers.Descriptors { + public class EnumDescriptor : IndexedDescriptorBase { - internal EnumDescriptor(EnumDescriptorProto proto, EnumOptions options, FileDescriptor file, int index) - : base(proto, file, index) { + + private readonly MessageDescriptor containingType; + private readonly IList values; + + internal EnumDescriptor(EnumDescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int index) + : base(proto, file, ComputeFullName(file, parent, proto.Name), index) { + containingType = parent; + + if (proto.ValueCount == 0) { + // We cannot allow enums with no values because this would mean there + // would be no valid default value for fields of this type. + throw new DescriptorValidationException(this, "Enums must contain at least one value."); + } + + values = DescriptorUtil.ConvertAndMakeReadOnly(proto.ValueList, + (value, i) => new EnumValueDescriptor(value, file, this, i)); + + File.DescriptorPool.AddSymbol(this); + } + + /// + /// If this is a nested type, get the outer descriptor, otherwise null. + /// + public MessageDescriptor ContainingType { + get { return containingType; } + } + + /// + /// An unmodifiable list of defined value descriptors for this enum. + /// + public IList Values { + get { return values; } + } + + /// + /// Finds an enum value by number. If multiple enum values have the + /// same number, this returns the first defined value with that number. + /// + /// + /// + internal EnumValueDescriptor FindValueByNumber(int number) { + return File.DescriptorPool.FindEnumValueByNumber(this, number); } - internal EnumValueDescriptor FindValueByNumber(int rawValue) { - throw new System.NotImplementedException(); + /// + /// Finds an enum value by name. + /// + /// The unqualified name of the value (e.g. "FOO"). + /// The value's descriptor, or null if not found. + internal EnumValueDescriptor FindValueByName(string name) { + return File.DescriptorPool.FindSymbol(FullName + "." + name); } } } diff --git a/csharp/ProtocolBuffers/Descriptors/EnumValueDescriptor.cs b/csharp/ProtocolBuffers/Descriptors/EnumValueDescriptor.cs index 94bd68bf3f..15b901bd9b 100644 --- a/csharp/ProtocolBuffers/Descriptors/EnumValueDescriptor.cs +++ b/csharp/ProtocolBuffers/Descriptors/EnumValueDescriptor.cs @@ -6,15 +6,16 @@ namespace Google.ProtocolBuffers.Descriptors { private readonly EnumDescriptor enumDescriptor; - internal EnumValueDescriptor(EnumValueDescriptorProto proto, - FileDescriptor file, - EnumDescriptor parent, - int index) : base (proto, file, index) { + internal EnumValueDescriptor(EnumValueDescriptorProto proto, FileDescriptor file, + EnumDescriptor parent, int index) + : base (proto, file, parent.FullName + "." + proto.Name, index) { enumDescriptor = parent; + file.DescriptorPool.AddSymbol(this); + file.DescriptorPool.AddEnumValueByNumber(this); } public int Number { - get { throw new NotImplementedException(); } + get { return Proto.Number; } } public EnumDescriptor EnumDescriptor { diff --git a/csharp/ProtocolBuffers/Descriptors/FieldDescriptor.cs b/csharp/ProtocolBuffers/Descriptors/FieldDescriptor.cs index 0cfc723d01..1750ce1d5a 100644 --- a/csharp/ProtocolBuffers/Descriptors/FieldDescriptor.cs +++ b/csharp/ProtocolBuffers/Descriptors/FieldDescriptor.cs @@ -5,43 +5,215 @@ using Google.ProtocolBuffers.Collections; using Google.ProtocolBuffers.DescriptorProtos; namespace Google.ProtocolBuffers.Descriptors { - public class FieldDescriptor : IndexedDescriptorBase { + public class FieldDescriptor : IndexedDescriptorBase, IComparable { - private readonly EnumDescriptor enumType; - private readonly MessageDescriptor parent; + private readonly MessageDescriptor extensionScope; + private EnumDescriptor enumType; + private MessageDescriptor messageType; + private MessageDescriptor containingType; + private object defaultValue; + private FieldType fieldType; + private MappedType mappedType; - internal FieldDescriptor(FieldDescriptorProto proto, - FileDescriptor file, - MessageDescriptor parent, - int index, - bool isExtension) : base(proto, file, index) { - enumType = null; - this.parent = parent; + internal FieldDescriptor(FieldDescriptorProto proto, FileDescriptor file, + MessageDescriptor parent, int index, bool isExtension) + : base(proto, file, ComputeFullName(file, parent, proto.Name), index) { + + if (proto.HasType) { + fieldType = GetFieldTypeFromProtoType(proto.Type); + //type = proto.Type; + } + + if (FieldNumber <= 0) { + throw new DescriptorValidationException(this, + "Field numbers must be positive integers."); + } + + if (isExtension) { + if (!proto.HasExtendee) { + throw new DescriptorValidationException(this, + "FieldDescriptorProto.Extendee not set for extension field."); + } + containingType = null; // Will be filled in when cross-linking + if (parent != null) { + extensionScope = parent; + } else { + extensionScope = null; + } + } else { + if (proto.HasExtendee) { + throw new DescriptorValidationException(this, + "FieldDescriptorProto.Extendee set for non-extension field."); + } + containingType = parent; + extensionScope = null; + } + + file.DescriptorPool.AddSymbol(this); + } + + /// + /// Maps a field type as included in the .proto file to a FieldType. + /// + private static FieldType GetFieldTypeFromProtoType(FieldDescriptorProto.Types.Type type) { + switch (type) { + case FieldDescriptorProto.Types.Type.TYPE_DOUBLE: return FieldType.Double; + case FieldDescriptorProto.Types.Type.TYPE_FLOAT: return FieldType.Float; + case FieldDescriptorProto.Types.Type.TYPE_INT64: return FieldType.Int64; + case FieldDescriptorProto.Types.Type.TYPE_UINT64: return FieldType.UInt64; + case FieldDescriptorProto.Types.Type.TYPE_INT32: return FieldType.Int32; + case FieldDescriptorProto.Types.Type.TYPE_FIXED64: return FieldType.Fixed64; + case FieldDescriptorProto.Types.Type.TYPE_FIXED32: return FieldType.Fixed32; + case FieldDescriptorProto.Types.Type.TYPE_BOOL: return FieldType.Bool; + case FieldDescriptorProto.Types.Type.TYPE_STRING: return FieldType.String; + case FieldDescriptorProto.Types.Type.TYPE_GROUP: return FieldType.Group; + case FieldDescriptorProto.Types.Type.TYPE_MESSAGE: return FieldType.Message; + case FieldDescriptorProto.Types.Type.TYPE_BYTES: return FieldType.Bytes; + case FieldDescriptorProto.Types.Type.TYPE_UINT32: return FieldType.UInt32; + case FieldDescriptorProto.Types.Type.TYPE_ENUM: return FieldType.Enum; + case FieldDescriptorProto.Types.Type.TYPE_SFIXED32: return FieldType.SFixed32; + case FieldDescriptorProto.Types.Type.TYPE_SFIXED64: return FieldType.SFixed64; + case FieldDescriptorProto.Types.Type.TYPE_SINT32: return FieldType.SInt32; + case FieldDescriptorProto.Types.Type.TYPE_SINT64: return FieldType.SInt64; + default: + throw new ArgumentException("Invalid type specified"); + } + } + + /// + /// Returns the default value for a mapped type. + /// + private static object GetDefaultValueForMappedType(MappedType type) { + switch (type) { + case MappedType.Int32: return 0; + case MappedType.Int64: return (long) 0; + case MappedType.UInt32: return (uint) 0; + case MappedType.UInt64: return (ulong) 0; + case MappedType.Single: return (float) 0; + case MappedType.Double: return (double) 0; + case MappedType.Boolean: return false; + case MappedType.String: return ""; + case MappedType.ByteString: return ByteString.Empty; + case MappedType.Message: return null; + case MappedType.Enum: return null; + default: + throw new ArgumentException("Invalid type specified"); + } } public bool IsRequired { - get; - set; + get { return Proto.Label == FieldDescriptorProto.Types.Label.LABEL_REQUIRED; } + } + + public bool IsOptional { + get { return Proto.Label == FieldDescriptorProto.Types.Label.LABEL_OPTIONAL; } } - public MappedType MappedType { get; set; } + public bool IsRepeated { + get { return Proto.Label == FieldDescriptorProto.Types.Label.LABEL_REPEATED; } + } - public bool IsRepeated { get; set; } + /// + /// Indicates whether or not the field had an explicitly-defined default value. + /// + public bool HasDefaultValue { + get { return Proto.HasDefaultValue; } + } - public FieldType FieldType { get; set; } - public int FieldNumber { get; set; } + /// + /// The field's default value. Valid for all types except messages + /// and groups. For all other types, the object returned is of the + /// same class that would be returned by IMessage[this]. + /// 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. + /// + public object DefaultValue { + get { + if (MappedType == MappedType.Message) { + throw new InvalidOperationException("FieldDescriptor.DefaultValue called on an embedded message field."); + } + return defaultValue; + } + } - public bool IsExtension { get; set; } + /// + /// Indicates whether or not this field is an extension. + /// + public bool IsExtension { + get { return Proto.HasExtendee; } + } + /* + * Get the field's containing type. For extensions, this is the type being + * extended, not the location where the extension was defined. See + * {@link #getExtensionScope()}. + */ + /// + /// Get the field's containing type. For extensions, this is the type being + /// extended, not the location where the extension was defined. See + /// . + /// public MessageDescriptor ContainingType { - get { return parent; } + get { return containingType; } + } + + /// + /// For extensions defined nested within message types, gets + /// the outer type. Not valid for non-extension fields. + /// + /// + /// + /// message Foo { + /// extensions 1000 to max; + /// } + /// extend Foo { + /// optional int32 baz = 1234; + /// } + /// message Bar { + /// extend Foo { + /// optional int32 qux = 4321; + /// } + /// } + /// + /// The containing type for both baz and qux is Foo. + /// However, the extension scope for baz is null while + /// the extension scope for qux is Bar. + /// + public MessageDescriptor ExtensionScope { + get { + if (!IsExtension) { + throw new InvalidOperationException("This field is not an extension."); + } + return extensionScope; + } } - public bool IsOptional { get; set; } + public MappedType MappedType { + get { return mappedType; } + } - public MessageDescriptor MessageType { get; set; } + public FieldType FieldType { + get { return fieldType; } + } - public MessageDescriptor ExtensionScope { get; set; } + public int FieldNumber { + get { return Proto.Number; } + } + + /// + /// Compares this descriptor with another one, ordering in "canonical" order + /// which simply means ascending order by field number. + /// must be a field of the same type, i.e. the of + /// both fields must be the same. + /// + public int CompareTo(FieldDescriptor other) { + if (other.containingType != containingType) { + throw new ArgumentException("FieldDescriptors can only be compared to other FieldDescriptors " + + "for fields of the same message type."); + } + return FieldNumber - other.FieldNumber; + } + /// /// For enum fields, returns the field's type. @@ -53,15 +225,18 @@ namespace Google.ProtocolBuffers.Descriptors { } return enumType; } - } + } /// - /// 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. + /// For embedded message and group fields, returns the field's type. /// - public object DefaultValue { - get { throw new NotImplementedException(); } + public MessageDescriptor MessageType { + get { + if (MappedType != MappedType.Message) { + throw new InvalidOperationException("MessageType is only valid for enum fields."); + } + return messageType; + } } /// @@ -70,6 +245,7 @@ namespace Google.ProtocolBuffers.Descriptors { /// public static readonly IDictionary FieldTypeToWireFormatMap = MapFieldTypes(); + private static IDictionary MapFieldTypes() { var map = new Dictionary(); foreach (FieldInfo field in typeof(FieldType).GetFields(BindingFlags.Static | BindingFlags.Public)) { @@ -79,5 +255,161 @@ namespace Google.ProtocolBuffers.Descriptors { } return Dictionaries.AsReadOnly(map); } + + /// + /// Look up and cross-link all field types etc. + /// + internal void CrossLink() { + if (Proto.HasExtendee) { + IDescriptor extendee = File.DescriptorPool.LookupSymbol(Proto.Extendee, this); + if (!(extendee is MessageDescriptor)) { + throw new DescriptorValidationException(this, "\"" + Proto.Extendee + "\" is not a message type."); + } + containingType = (MessageDescriptor) extendee; + + if (!containingType.IsExtensionNumber(FieldNumber)) { + throw new DescriptorValidationException(this, + "\"" + containingType.FullName + "\" does not declare " + FieldNumber + " as an extension number."); + } + } + + if (Proto.HasTypeName) { + IDescriptor typeDescriptor = + File.DescriptorPool.LookupSymbol(Proto.TypeName, this); + + if (!Proto.HasType) { + // Choose field type based on symbol. + if (typeDescriptor is MessageDescriptor) { + fieldType = FieldType.Message; + mappedType = MappedType.Message; + } else if (typeDescriptor is EnumDescriptor) { + fieldType = FieldType.Enum; + mappedType = MappedType.Enum; + } else { + throw new DescriptorValidationException(this, "\"" + Proto.TypeName + "\" is not a type."); + } + } + + if (MappedType == MappedType.Message) { + if (!(typeDescriptor is MessageDescriptor)) { + throw new DescriptorValidationException(this, "\"" + Proto.TypeName + "\" is not a message type."); + } + messageType = (MessageDescriptor) typeDescriptor; + + if (Proto.HasDefaultValue) { + throw new DescriptorValidationException(this, "Messages can't have default values."); + } + } else if (MappedType == Descriptors.MappedType.Enum) { + if (!(typeDescriptor is EnumDescriptor)) { + throw new DescriptorValidationException(this, "\"" + Proto.TypeName + "\" is not an enum type."); + } + enumType = (EnumDescriptor)typeDescriptor; + } else { + throw new DescriptorValidationException(this, "Field with primitive type has type_name."); + } + } else { + if (MappedType == MappedType.Message || MappedType == MappedType.Enum) { + throw new DescriptorValidationException(this, "Field with message or enum type missing type_name."); + } + } + + // We don't attempt to parse the default value until here because for + // enums we need the enum type's descriptor. + if (Proto.HasDefaultValue) { + if (IsRepeated) { + throw new DescriptorValidationException(this, "Repeated fields cannot have default values."); + } + + try { + // TODO(jonskeet): Check signage for Int32 and Int64. + switch (FieldType) { + case FieldType.Int32: + case FieldType.SInt32: + case FieldType.SFixed32: + defaultValue = TextFormat.ParseInt32(Proto.DefaultValue); + break; + case FieldType.UInt32: + case FieldType.Fixed32: + defaultValue = TextFormat.ParseUInt32(Proto.DefaultValue); + break; + case FieldType.Int64: + case FieldType.SInt64: + case FieldType.SFixed64: + defaultValue = TextFormat.ParseInt64(Proto.DefaultValue); + break; + case FieldType.UInt64: + case FieldType.Fixed64: + defaultValue = TextFormat.ParseUInt64(Proto.DefaultValue); + break; + case FieldType.Float: + defaultValue = float.Parse(Proto.DefaultValue); + break; + case FieldType.Double: + defaultValue = double.Parse(Proto.DefaultValue); + break; + case FieldType.Bool: + defaultValue = bool.Parse(Proto.DefaultValue); // TODO(jonskeet): Check this will work + break; + case FieldType.String: + defaultValue = Proto.DefaultValue; + break; + case FieldType.Bytes: + try { + defaultValue = TextFormat.UnescapeBytes(Proto.DefaultValue); + } catch (FormatException e) { + throw new DescriptorValidationException(this, "Couldn't parse default value: " + e.Message); + } + break; + case FieldType.Enum: + defaultValue = enumType.FindValueByName(Proto.DefaultValue); + if (defaultValue == null) { + throw new DescriptorValidationException(this, "Unknown enum default value: \"" + Proto.DefaultValue + "\""); + } + break; + case FieldType.Message: + case FieldType.Group: + throw new DescriptorValidationException(this, "Message type had default value."); + } + } catch (FormatException e) { + DescriptorValidationException validationException = + new DescriptorValidationException(this, "Could not parse default value: \"" + Proto.DefaultValue + "\"", e); + throw validationException; + } + } else { + // Determine the default default for this field. + if (IsRepeated) { + // FIXME(jonskeet): Find out the correct list type and use that instead. + defaultValue = new List(); + } else { + switch (MappedType) { + case MappedType.Enum: + // We guarantee elsewhere that an enum type always has at least + // one possible value. + defaultValue = enumType.Values[0]; + break; + case MappedType.Message: + defaultValue = null; + break; + default: + defaultValue = GetDefaultValueForMappedType(MappedType); + break; + } + } + } + + if (!IsExtension) { + File.DescriptorPool.AddFieldByNumber(this); + } + + if (containingType != null && containingType.Options.MessageSetWireFormat) { + if (IsExtension) { + if (!IsOptional || FieldType != FieldType.Message) { + throw new DescriptorValidationException(this, "Extensions of MessageSets must be optional messages."); + } + } else { + throw new DescriptorValidationException(this, "MessageSets cannot have fields, only extensions."); + } + } + } } } diff --git a/csharp/ProtocolBuffers/Descriptors/FileDescriptor.cs b/csharp/ProtocolBuffers/Descriptors/FileDescriptor.cs index d552d2f0a3..9e065c86a0 100644 --- a/csharp/ProtocolBuffers/Descriptors/FileDescriptor.cs +++ b/csharp/ProtocolBuffers/Descriptors/FileDescriptor.cs @@ -1,17 +1,64 @@ using System; +using System.Collections.ObjectModel; using Google.ProtocolBuffers.DescriptorProtos; using System.Collections.Generic; +using Google.ProtocolBuffers.Collections; namespace Google.ProtocolBuffers.Descriptors { - public class FileDescriptor : DescriptorBase { + /// + /// Describes a .proto file, including everything defined within. + /// IDescriptor is implemented such that the File property returns this descriptor, + /// and the FullName is the same as the Name. + /// + public class FileDescriptor : IDescriptor { + + private readonly FileDescriptorProto proto; private readonly IList messageTypes; private readonly IList enumTypes; private readonly IList services; private readonly IList extensions; private readonly IList dependencies; private readonly DescriptorPool pool; + + private FileDescriptor(FileDescriptorProto proto, FileDescriptor[] dependencies, DescriptorPool pool) { + this.pool = pool; + this.proto = proto; + this.dependencies = new ReadOnlyCollection((FileDescriptor[]) dependencies.Clone()); + + pool.AddPackage(Package, this); + + messageTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.MessageTypeList, + (message, index) => new MessageDescriptor(message, this, null, index)); + + enumTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.EnumTypeList, + (enumType, index) => new EnumDescriptor(enumType, this, null, index)); + + services = DescriptorUtil.ConvertAndMakeReadOnly(proto.ServiceList, + (service, index) => new ServiceDescriptor(service, this, index)); + + extensions = DescriptorUtil.ConvertAndMakeReadOnly(proto.ExtensionList, + (field, index) => new FieldDescriptor(field, this, null, index, true)); + } + + /// + /// The descriptor in its protocol message representation. + /// + public FileDescriptorProto Proto { + get { return proto; } + } - public FileDescriptor(FileDescriptorProto proto, FileDescriptor file) : base(proto, file) { + /// + /// The defined in descriptor.proto. + /// + public FileOptions Options { + get { return proto.Options; } + } + + /// + /// The file name. + /// + public string Name { + get { return proto.Name; } } /// @@ -19,7 +66,7 @@ namespace Google.ProtocolBuffers.Descriptors { /// be equivalent to the .NET namespace of the generated classes. /// public string Package { - get { return Proto.Package; } + get { return proto.Package; } } /// @@ -57,10 +104,110 @@ namespace Google.ProtocolBuffers.Descriptors { get { return dependencies; } } - public static FileDescriptor BuildFrom(FileDescriptorProto proto, - FileDescriptor[] dependencies) { - throw new NotImplementedException(); + /// + /// Implementation of IDescriptor.FullName - just returns the same as Name. + /// + string IDescriptor.FullName { + get { return Name; } + } + + /// + /// Implementation of IDescriptor.File - just returns this descriptor. + /// + FileDescriptor IDescriptor.File { + get { return this; } + } + + /// + /// Protocol buffer describing this descriptor. + /// + IMessage IDescriptor.Proto { + get { return Proto; } + } + + /// + /// Pool containing symbol descriptors. + /// + internal DescriptorPool DescriptorPool { + get { return pool; } + } + + /// + /// Finds a type (message, enum, service or extension) in the file by name. Does not find nested types. + /// + /// The unqualified type name to look for. + /// The type of descriptor to look for (or ITypeDescriptor for any) + /// The type's descriptor, or null if not found. + public T FindTypeByName(String name) + where T : class, IDescriptor { + // Don't allow looking up nested types. This will make optimization + // easier later. + if (name.IndexOf('.') != -1) { + return null; + } + if (Package.Length > 0) { + name = Package + "." + name; + } + T result = pool.FindSymbol(name); + if (result != null && result.File == this) { + return result; + } + return null; + } + + /// + /// Builds a FileDescriptor from its protocol buffer representation. + /// + /// The protocol message form of the FileDescriptor. + /// FileDescriptors corresponding to all of the + /// file's dependencies, in the exact order listed in the .proto file + /// If is not + /// a valid descriptor. This can occur for a number of reasons, such as a field + /// having an undefined type or because two messages were defined with the same name. + public static FileDescriptor BuildFrom(FileDescriptorProto proto, FileDescriptor[] dependencies) { + // Building decsriptors involves two steps: translating and linking. + // In the translation step (implemented by FileDescriptor's + // constructor), we build an object tree mirroring the + // FileDescriptorProto's tree and put all of the descriptors into the + // DescriptorPool's lookup tables. In the linking step, we look up all + // type references in the DescriptorPool, so that, for example, a + // FieldDescriptor for an embedded message contains a pointer directly + // to the Descriptor for that message's type. We also detect undefined + // types in the linking step. + DescriptorPool pool = new DescriptorPool(dependencies); + FileDescriptor result = new FileDescriptor(proto, dependencies, pool); + + if (dependencies.Length != proto.DependencyCount) { + throw new DescriptorValidationException(result, + "Dependencies passed to FileDescriptor.BuildFrom() don't match " + + "those listed in the FileDescriptorProto."); + } + for (int i = 0; i < proto.DependencyCount; i++) { + if (dependencies[i].Name != proto.DependencyList[i]) { + throw new DescriptorValidationException(result, + "Dependencies passed to FileDescriptor.BuildFrom() don't match " + + "those listed in the FileDescriptorProto."); + } + } + + result.CrossLink(); + return result; + } + + private void CrossLink() { + foreach (MessageDescriptor message in messageTypes) { + message.CrossLink(); + } + + foreach (ServiceDescriptor service in services) { + service.CrossLink(); + } + + foreach (FieldDescriptor extension in extensions) { + extension.CrossLink(); + } } + /// /// This method is to be called by generated code only. It is equivalent /// to BuilderFrom except that the FileDescriptorProto is encoded in diff --git a/csharp/ProtocolBuffers/Descriptors/IDescriptor.cs b/csharp/ProtocolBuffers/Descriptors/IDescriptor.cs new file mode 100644 index 0000000000..ff5628409d --- /dev/null +++ b/csharp/ProtocolBuffers/Descriptors/IDescriptor.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Google.ProtocolBuffers.Descriptors { + + /// + /// The non-generic form of the IDescriptor interface. Useful for describing a general + /// descriptor. + /// + public interface IDescriptor { + string Name { get; } + string FullName { get; } + FileDescriptor File { get; } + IMessage Proto { get; } + } + + /// + /// Strongly-typed form of the IDescriptor interface. + /// + /// Protocol buffer type underlying this descriptor type + public interface IDescriptor : IDescriptor where TProto : IMessage { + new TProto Proto { get; } + } +} diff --git a/csharp/ProtocolBuffers/Descriptors/IndexedDescriptorBase.cs b/csharp/ProtocolBuffers/Descriptors/IndexedDescriptorBase.cs index b94521635b..5415357d69 100644 --- a/csharp/ProtocolBuffers/Descriptors/IndexedDescriptorBase.cs +++ b/csharp/ProtocolBuffers/Descriptors/IndexedDescriptorBase.cs @@ -13,8 +13,8 @@ namespace Google.ProtocolBuffers.Descriptors { private readonly int index; - protected IndexedDescriptorBase(TProto proto, FileDescriptor file, int index) - : base(proto, file) { + protected IndexedDescriptorBase(TProto proto, FileDescriptor file, string fullName, int index) + : base(proto, file, fullName) { this.index = index; } diff --git a/csharp/ProtocolBuffers/Descriptors/MessageDescriptor.cs b/csharp/ProtocolBuffers/Descriptors/MessageDescriptor.cs index fd8c6e5b47..548f226b80 100644 --- a/csharp/ProtocolBuffers/Descriptors/MessageDescriptor.cs +++ b/csharp/ProtocolBuffers/Descriptors/MessageDescriptor.cs @@ -1,27 +1,113 @@ - -using System.Collections.Generic; +using System.Collections.Generic; using Google.ProtocolBuffers.DescriptorProtos; namespace Google.ProtocolBuffers.Descriptors { - public class MessageDescriptor : DescriptorBase { - public IList Fields; - internal MessageDescriptor(DescriptorProto proto, FileDescriptor file) : base(proto, file) { + /// + /// Describes a message type. + /// + public class MessageDescriptor : IndexedDescriptorBase { + + private readonly MessageDescriptor containingType; + private readonly IList nestedTypes; + private readonly IList enumTypes; + private readonly IList fields; + private readonly IList extensions; + + internal MessageDescriptor(DescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int typeIndex) + : base(proto, file, ComputeFullName(file, parent, proto.Name), typeIndex) { + containingType = parent; + + nestedTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.NestedTypeList, + (type, index) => new MessageDescriptor(type, file, null, index)); + + enumTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.EnumTypeList, + (type, index) => new EnumDescriptor(type, file, null, index)); + + fields = DescriptorUtil.ConvertAndMakeReadOnly(proto.FieldList, + (field, index) => new FieldDescriptor(field, file, null, index, false)); + + extensions = DescriptorUtil.ConvertAndMakeReadOnly(proto.ExtensionList, + (field, index) => new FieldDescriptor(field, file, null, index, true)); + + file.DescriptorPool.AddSymbol(this); } - internal bool IsExtensionNumber(int fieldNumber) { - throw new System.NotImplementedException(); + /// + /// An unmodifiable list of this message type's fields. + /// + public IList Fields { + get { return fields; } } - internal FieldDescriptor FindFieldByNumber(int fieldNumber) { - throw new System.NotImplementedException(); + /// + /// An unmodifiable list of this message type's extensions. + /// + public IList Extensions { + get { return extensions; } } /// - /// List of message types nested within this one. + /// An unmodifiable list of this message type's nested types. /// public IList NestedTypes { - get { throw new System.NotImplementedException(); } + get { return nestedTypes; } + } + + /// + /// An unmodifiable list of this message type's enum types. + /// + public IList EnumTypes { + get { return enumTypes; } + } + + /// + /// Determines if the given field number is an extension. + /// + public bool IsExtensionNumber(int number) { + foreach (DescriptorProto.Types.ExtensionRange range in Proto.ExtensionRangeList) { + if (range.Start <= number && number < range.End) { + return true; + } + } + return false; + } + + /// + /// Finds a field by field number. + /// + /// The field number within this message type. + /// The field's descriptor, or null if not found. + public FieldDescriptor FindFieldByNumber(int number) { + return File.DescriptorPool.FindFieldByNumber(this, number); + } + + /// + /// Finds a nested descriptor by name. The is valid for fields, nested + /// message types and enums. + /// + /// The unqualified name of the descriptor, e.g. "Foo" + /// The descriptor, or null if not found. + public T FindDescriptor(string name) + where T : class, IDescriptor { + return File.DescriptorPool.FindSymbol(FullName + "." + name); + } + + /// + /// Looks up and cross-links all fields, nested types, and extensions. + /// + internal void CrossLink() { + foreach (MessageDescriptor message in nestedTypes) { + message.CrossLink(); + } + + foreach (FieldDescriptor field in fields) { + field.CrossLink(); + } + + foreach (FieldDescriptor extension in extensions) { + extension.CrossLink(); + } } } } diff --git a/csharp/ProtocolBuffers/Descriptors/MethodDescriptor.cs b/csharp/ProtocolBuffers/Descriptors/MethodDescriptor.cs new file mode 100644 index 0000000000..efc3fa4cf3 --- /dev/null +++ b/csharp/ProtocolBuffers/Descriptors/MethodDescriptor.cs @@ -0,0 +1,55 @@ +using Google.ProtocolBuffers.DescriptorProtos; + +namespace Google.ProtocolBuffers.Descriptors { + /// + /// Describes a single method in a service. + /// + public class MethodDescriptor : IndexedDescriptorBase { + + private readonly ServiceDescriptor service; + private MessageDescriptor inputType; + private MessageDescriptor outputType; + + /// + /// The service this method belongs to. + /// + public ServiceDescriptor Service { + get { return service; } + } + + /// + /// The method's input type. + /// + public MessageDescriptor InputType { + get { return inputType; } + } + + /// + /// The method's input type. + /// + public MessageDescriptor OutputType { + get { return inputType; } + } + + internal MethodDescriptor(MethodDescriptorProto proto, FileDescriptor file, + ServiceDescriptor parent, int index) + : base(proto, file, parent.FullName + "." + proto.Name, index) { + service = parent; + file.DescriptorPool.AddSymbol(this); + } + + internal void CrossLink() { + IDescriptor lookup = File.DescriptorPool.LookupSymbol(Proto.InputType, this); + if (!(lookup is MessageDescriptor)) { + throw new DescriptorValidationException(this, "\"" + Proto.InputType + "\" is not a message type."); + } + inputType = (MessageDescriptor) lookup; + + lookup = File.DescriptorPool.LookupSymbol(Proto.OutputType, this); + if (!(lookup is MessageDescriptor)) { + throw new DescriptorValidationException(this, "\"" + Proto.OutputType + "\" is not a message type."); + } + outputType = (MessageDescriptor) lookup; + } + } +} diff --git a/csharp/ProtocolBuffers/Descriptors/PackageDescriptor.cs b/csharp/ProtocolBuffers/Descriptors/PackageDescriptor.cs new file mode 100644 index 0000000000..acef6200eb --- /dev/null +++ b/csharp/ProtocolBuffers/Descriptors/PackageDescriptor.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Google.ProtocolBuffers.Descriptors { + /* + * Represents a package in the symbol table. We use PackageDescriptors + * just as placeholders so that someone cannot define, say, a message type + * that has the same name as an existing package. + */ + /// + /// Represents a package in the symbol table. We use PackageDescriptors + /// just as placeholders so that someone cannot define, say, a message type + /// that has the same name as an existing package. + /// + internal class PackageDescriptor : IDescriptor { + + private readonly string name; + private readonly string fullName; + private readonly FileDescriptor file; + + internal PackageDescriptor(string name, string fullName, FileDescriptor file) { + this.file = file; + this.fullName = fullName; + this.name = name; + } + + public IMessage Proto { + get { return file.Proto; } + } + + public string Name { + get { return name; } + } + + public string FullName { + get { return fullName; } + } + + public FileDescriptor File { + get { return file; } + } + } +} diff --git a/csharp/ProtocolBuffers/Descriptors/ServiceDescriptor.cs b/csharp/ProtocolBuffers/Descriptors/ServiceDescriptor.cs index dbe93f99f1..d7c6f3a3a7 100644 --- a/csharp/ProtocolBuffers/Descriptors/ServiceDescriptor.cs +++ b/csharp/ProtocolBuffers/Descriptors/ServiceDescriptor.cs @@ -4,9 +4,34 @@ using System.Text; using Google.ProtocolBuffers.DescriptorProtos; namespace Google.ProtocolBuffers.Descriptors { + + /// + /// Describes a service type. + /// public class ServiceDescriptor : IndexedDescriptorBase { + + private readonly IList methods; + public ServiceDescriptor(ServiceDescriptorProto proto, FileDescriptor file, int index) - : base(proto, file, index) { + : base(proto, file, ComputeFullName(file, null, proto.Name), index) { + + methods = DescriptorUtil.ConvertAndMakeReadOnly(proto.MethodList, + (method, i) => new MethodDescriptor(method, file, this, i)); + + file.DescriptorPool.AddSymbol(this); + } + + /// + /// An unmodifiable list of methods in this service. + /// + public IList Methods { + get { return methods; } + } + + internal void CrossLink() { + foreach (MethodDescriptor method in methods) { + method.CrossLink(); + } } } } diff --git a/csharp/ProtocolBuffers/ProtocolBuffers.csproj b/csharp/ProtocolBuffers/ProtocolBuffers.csproj index b510673856..d1f7ad26ba 100644 --- a/csharp/ProtocolBuffers/ProtocolBuffers.csproj +++ b/csharp/ProtocolBuffers/ProtocolBuffers.csproj @@ -52,6 +52,8 @@ + + @@ -59,9 +61,12 @@ + + + diff --git a/csharp/ProtocolBuffers/TextFormat.cs b/csharp/ProtocolBuffers/TextFormat.cs index e789d0d7d9..87c5e2112d 100644 --- a/csharp/ProtocolBuffers/TextFormat.cs +++ b/csharp/ProtocolBuffers/TextFormat.cs @@ -11,5 +11,25 @@ namespace Google.ProtocolBuffers { internal static string PrintToString(UnknownFieldSet unknownFieldSet) { throw new NotImplementedException(); } + + internal static object ParseUInt64(string p) { + throw new NotImplementedException(); + } + + internal static object ParseInt64(string p) { + throw new NotImplementedException(); + } + + internal static object ParseUInt32(string p) { + throw new NotImplementedException(); + } + + internal static object ParseInt32(string p) { + throw new NotImplementedException(); + } + + internal static object UnescapeBytes(string p) { + throw new NotImplementedException(); + } } }