diff options
Diffstat (limited to 'src/ProtocolBuffers/Descriptors')
17 files changed, 1826 insertions, 0 deletions
diff --git a/src/ProtocolBuffers/Descriptors/DescriptorBase.cs b/src/ProtocolBuffers/Descriptors/DescriptorBase.cs new file mode 100644 index 00000000..0c1414ae --- /dev/null +++ b/src/ProtocolBuffers/Descriptors/DescriptorBase.cs @@ -0,0 +1,83 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using Google.ProtocolBuffers.DescriptorProtos; + +namespace Google.ProtocolBuffers.Descriptors { + /// <summary> + /// Base class for nearly all descriptors, providing common functionality. + /// </summary> + /// <typeparam name="TProto">Type of the protocol buffer form of this descriptor</typeparam> + /// <typeparam name="TOptions">Type of the options protocol buffer for this descriptor</typeparam> + public abstract class DescriptorBase<TProto, TOptions> : IDescriptor<TProto> + where TProto : IMessage, IDescriptorProto<TOptions> { + + private readonly TProto proto; + private readonly FileDescriptor file; + private readonly string fullName; + + 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; } + } + + /// <summary> + /// Returns the protocol buffer form of this descriptor + /// </summary> + public TProto Proto { + get { return proto; } + } + + public TOptions Options { + get { return proto.Options; } + } + + /// <summary> + /// The fully qualified name of the descriptor's target. + /// </summary> + public string FullName { + get { return fullName; } + } + + /// <summary> + /// The brief name of the descriptor's target. + /// </summary> + public string Name { + get { return proto.Name; } + } + + /// <value> + /// The file this descriptor was declared in. + /// </value> + public FileDescriptor File { + get { return file; } + } + } +} diff --git a/src/ProtocolBuffers/Descriptors/DescriptorPool.cs b/src/ProtocolBuffers/Descriptors/DescriptorPool.cs new file mode 100644 index 00000000..19a5b6a7 --- /dev/null +++ b/src/ProtocolBuffers/Descriptors/DescriptorPool.cs @@ -0,0 +1,281 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using System.Collections.Generic; +using System; +using System.Text; +using System.Text.RegularExpressions; + +namespace Google.ProtocolBuffers.Descriptors { + /// <summary> + /// Contains lookup tables containing all the descriptors defined in a particular file. + /// </summary> + internal sealed class DescriptorPool { + + private readonly IDictionary<string, IDescriptor> descriptorsByName = + new Dictionary<string, IDescriptor>(); + private readonly IDictionary<DescriptorIntPair, FieldDescriptor> fieldsByNumber = + new Dictionary<DescriptorIntPair, FieldDescriptor>(); + private readonly IDictionary<DescriptorIntPair, EnumValueDescriptor> enumValuesByNumber = + new Dictionary<DescriptorIntPair, EnumValueDescriptor>(); + 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); + } + } + + /// <summary> + /// Finds a symbol of the given name within the pool. + /// </summary> + /// <typeparam name="T">The type of symbol to look for</typeparam> + /// <param name="fullName">Fully-qualified name to look up</param> + /// <returns>The symbol with the given name and type, + /// or null if the symbol doesn't exist or has the wrong type</returns> + internal T FindSymbol<T>(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; + } + + /// <summary> + /// 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). + /// </summary> + 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); + } + + /// <summary> + /// Adds a symbol to the symbol table. + /// </summary> + /// <exception cref="DescriptorValidationException">The symbol already existed + /// in the symbol table.</exception> + 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); + + /// <summary> + /// Verifies that the descriptor's name is valid (i.e. it contains + /// only letters, digits and underscores, and does not start with a digit). + /// </summary> + /// <param name="descriptor"></param> + 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."); + } + } + + /// <summary> + /// Returns the field with the given number in the given descriptor, + /// or null if it can't be found. + /// </summary> + 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; + } + + /// <summary> + /// Adds a field to the fieldsByNumber table. + /// </summary> + /// <exception cref="DescriptorValidationException">A field with the same + /// containing type and number already exists.</exception> + 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; + } + + /// <summary> + /// 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.) + /// </summary> + internal void AddEnumValueByNumber(EnumValueDescriptor enumValue) { + DescriptorIntPair key = new DescriptorIntPair(enumValue.EnumDescriptor, enumValue.Number); + if (!enumValuesByNumber.ContainsKey(key)) { + enumValuesByNumber[key] = enumValue; + } + } + + /// <summary> + /// 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. + /// </summary> + 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<IDescriptor>(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<IDescriptor>(name); + break; + } else { + scopeToTry.Length = dotpos + 1; + + // Append firstPart and try to find. + scopeToTry.Append(firstPart); + result = FindSymbol<IDescriptor>(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<IDescriptor>(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; + } + } + + /// <summary> + /// Struct used to hold the keys for the fieldByNumber table. + /// </summary> + struct DescriptorIntPair : IEquatable<DescriptorIntPair> { + + 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/src/ProtocolBuffers/Descriptors/DescriptorUtil.cs b/src/ProtocolBuffers/Descriptors/DescriptorUtil.cs new file mode 100644 index 00000000..c945616d --- /dev/null +++ b/src/ProtocolBuffers/Descriptors/DescriptorUtil.cs @@ -0,0 +1,43 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using System.Collections.Generic; +using Google.ProtocolBuffers.Collections; + +namespace Google.ProtocolBuffers.Descriptors { + /// <summary> + /// Internal class containing utility methods when working with descriptors. + /// </summary> + internal static class DescriptorUtil { + /// <summary> + /// Equivalent to Func[TInput, int, TOutput] but usable in .NET 2.0. Only used to convert + /// arrays. + /// </summary> + internal delegate TOutput IndexedConverter<TInput, TOutput>(TInput element, int index); + + /// <summary> + /// Converts the given array into a read-only list, applying the specified conversion to + /// each input element. + /// </summary> + internal static IList<TOutput> ConvertAndMakeReadOnly<TInput, TOutput>(IList<TInput> input, + IndexedConverter<TInput, TOutput> converter) { + TOutput[] array = new TOutput[input.Count]; + for (int i = 0; i < array.Length; i++) { + array[i] = converter(input[i], i); + } + return Lists<TOutput>.AsReadOnly(array); + } + } +} diff --git a/src/ProtocolBuffers/Descriptors/DescriptorValidationException.cs b/src/ProtocolBuffers/Descriptors/DescriptorValidationException.cs new file mode 100644 index 00000000..62c723f3 --- /dev/null +++ b/src/ProtocolBuffers/Descriptors/DescriptorValidationException.cs @@ -0,0 +1,70 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using System; + +namespace Google.ProtocolBuffers.Descriptors { + /// <summary> + /// Thrown when building descriptors fails because the source DescriptorProtos + /// are not valid. + /// </summary> + public sealed class DescriptorValidationException : Exception { + + private readonly String name; + private readonly IMessage proto; + private readonly string description; + + /// <value> + /// The full name of the descriptor where the error occurred. + /// </value> + public String ProblemSymbolName { + get { return name; } + } + + /// <value> + /// The protocol message representation of the invalid descriptor. + /// </value> + public IMessage ProblemProto { + get { return proto; } + } + + /// <value> + /// A human-readable description of the error. (The Message property + /// is made up of the descriptor's name and this description.) + /// </value> + 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/src/ProtocolBuffers/Descriptors/EnumDescriptor.cs b/src/ProtocolBuffers/Descriptors/EnumDescriptor.cs new file mode 100644 index 00000000..576051ab --- /dev/null +++ b/src/ProtocolBuffers/Descriptors/EnumDescriptor.cs @@ -0,0 +1,76 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using System.Collections.Generic; +using Google.ProtocolBuffers.DescriptorProtos; + +namespace Google.ProtocolBuffers.Descriptors { + + /// <summary> + /// Descriptor for an enum type in a .proto file. + /// </summary> + public sealed class EnumDescriptor : IndexedDescriptorBase<EnumDescriptorProto, EnumOptions> { + + private readonly MessageDescriptor containingType; + private readonly IList<EnumValueDescriptor> 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); + } + + /// <value> + /// If this is a nested type, get the outer descriptor, otherwise null. + /// </value> + public MessageDescriptor ContainingType { + get { return containingType; } + } + + /// <value> + /// An unmodifiable list of defined value descriptors for this enum. + /// </value> + public IList<EnumValueDescriptor> Values { + get { return values; } + } + + /// <summary> + /// Finds an enum value by number. If multiple enum values have the + /// same number, this returns the first defined value with that number. + /// </summary> + internal EnumValueDescriptor FindValueByNumber(int number) { + return File.DescriptorPool.FindEnumValueByNumber(this, number); + } + + /// <summary> + /// Finds an enum value by name. + /// </summary> + /// <param name="name">The unqualified name of the value (e.g. "FOO").</param> + /// <returns>The value's descriptor, or null if not found.</returns> + internal EnumValueDescriptor FindValueByName(string name) { + return File.DescriptorPool.FindSymbol<EnumValueDescriptor>(FullName + "." + name); + } + } +} diff --git a/src/ProtocolBuffers/Descriptors/EnumValueDescriptor.cs b/src/ProtocolBuffers/Descriptors/EnumValueDescriptor.cs new file mode 100644 index 00000000..112a7fb6 --- /dev/null +++ b/src/ProtocolBuffers/Descriptors/EnumValueDescriptor.cs @@ -0,0 +1,43 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using Google.ProtocolBuffers.DescriptorProtos; + +namespace Google.ProtocolBuffers.Descriptors { + + /// <summary> + /// Descriptor for a single enum value within an enum in a .proto file. + /// </summary> + public sealed class EnumValueDescriptor : IndexedDescriptorBase<EnumValueDescriptorProto, EnumValueOptions> { + + private readonly EnumDescriptor enumDescriptor; + + 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 { return Proto.Number; } + } + + public EnumDescriptor EnumDescriptor { + get { return enumDescriptor; } + } + } +} diff --git a/src/ProtocolBuffers/Descriptors/FieldDescriptor.cs b/src/ProtocolBuffers/Descriptors/FieldDescriptor.cs new file mode 100644 index 00000000..0cd552e4 --- /dev/null +++ b/src/ProtocolBuffers/Descriptors/FieldDescriptor.cs @@ -0,0 +1,437 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using System; +using System.Collections.Generic; +using System.Reflection; +using Google.ProtocolBuffers.Collections; +using Google.ProtocolBuffers.DescriptorProtos; + +namespace Google.ProtocolBuffers.Descriptors { + + /// <summary> + /// Descriptor for a field or extension within a message in a .proto file. + /// </summary> + public sealed class FieldDescriptor : IndexedDescriptorBase<FieldDescriptorProto, FieldOptions>, IComparable<FieldDescriptor> { + + 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, ComputeFullName(file, parent, proto.Name), index) { + + if (proto.HasType) { + fieldType = GetFieldTypeFromProtoType(proto.Type); + mappedType = FieldTypeToMappedTypeMap[fieldType]; + } + + 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); + } + + /// <summary> + /// Maps a field type as included in the .proto file to a FieldType. + /// </summary> + 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"); + } + } + + /// <summary> + /// Returns the default value for a mapped type. + /// </summary> + 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 { return Proto.Label == FieldDescriptorProto.Types.Label.LABEL_REQUIRED; } + } + + public bool IsOptional { + get { return Proto.Label == FieldDescriptorProto.Types.Label.LABEL_OPTIONAL; } + } + + public bool IsRepeated { + get { return Proto.Label == FieldDescriptorProto.Types.Label.LABEL_REPEATED; } + } + + /// <valule> + /// Indicates whether or not the field had an explicitly-defined default value. + /// </value> + public bool HasDefaultValue { + get { return Proto.HasDefaultValue; } + } + + /// <value> + /// 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 immutable list compatible with IList[object]. + /// For message fields it will always be null. For singular values, it will depend on the descriptor. + /// </value> + public object DefaultValue { + get { + if (MappedType == MappedType.Message) { + throw new InvalidOperationException("FieldDescriptor.DefaultValue called on an embedded message field."); + } + return defaultValue; + } + } + + /// <value> + /// Indicates whether or not this field is an extension. + /// </value> + 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()}. + */ + /// <summary> + /// Get the field's containing type. For extensions, this is the type being + /// extended, not the location where the extension was defined. See + /// <see cref="ExtensionScope" />. + /// </summary> + public MessageDescriptor ContainingType { + get { return containingType; } + } + + /// <summary> + /// For extensions defined nested within message types, gets + /// the outer type. Not valid for non-extension fields. + /// </summary> + /// <example> + /// <code> + /// message Foo { + /// extensions 1000 to max; + /// } + /// extend Foo { + /// optional int32 baz = 1234; + /// } + /// message Bar { + /// extend Foo { + /// optional int32 qux = 4321; + /// } + /// } + /// </code> + /// The containing type for both <c>baz</c> and <c>qux</c> is <c>Foo</c>. + /// However, the extension scope for <c>baz</c> is <c>null</c> while + /// the extension scope for <c>qux</c> is <c>Bar</c>. + /// </example> + public MessageDescriptor ExtensionScope { + get { + if (!IsExtension) { + throw new InvalidOperationException("This field is not an extension."); + } + return extensionScope; + } + } + + public MappedType MappedType { + get { return mappedType; } + } + + public FieldType FieldType { + get { return fieldType; } + } + + public int FieldNumber { + get { return Proto.Number; } + } + + /// <summary> + /// Compares this descriptor with another one, ordering in "canonical" order + /// which simply means ascending order by field number. <paramref name="other"/> + /// must be a field of the same type, i.e. the <see cref="ContainingType"/> of + /// both fields must be the same. + /// </summary> + 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; + } + + + /// <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> + /// For embedded message and group fields, returns the field's type. + /// </summary> + public MessageDescriptor MessageType { + get { + if (MappedType != MappedType.Message) { + throw new InvalidOperationException("MessageType is only valid for enum fields."); + } + return messageType; + } + } + + /// <summary> + /// Immutable mapping from field type to mapped type. Built using the attributes on + /// FieldType values. + /// </summary> + public static readonly IDictionary<FieldType, MappedType> FieldTypeToMappedTypeMap = MapFieldTypes(); + + private static IDictionary<FieldType, MappedType> MapFieldTypes() { + var map = new Dictionary<FieldType, MappedType>(); + foreach (FieldInfo field in typeof(FieldType).GetFields(BindingFlags.Static | BindingFlags.Public)) { + FieldType fieldType = (FieldType)field.GetValue(null); + FieldMappingAttribute mapping = (FieldMappingAttribute)field.GetCustomAttributes(typeof(FieldMappingAttribute), false)[0]; + map[fieldType] = mapping.MappedType; + } + return Dictionaries.AsReadOnly(map); + } + + /// <summary> + /// Look up and cross-link all field types etc. + /// </summary> + 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 { + 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: + if (Proto.DefaultValue == "true") { + defaultValue = true; + } else if (Proto.DefaultValue == "false") { + defaultValue = false; + } else { + throw new FormatException("Boolean values must be \"true\" or \"false\""); + } + 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) { + defaultValue = Lists<object>.Empty; + } 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/src/ProtocolBuffers/Descriptors/FieldMappingAttribute.cs b/src/ProtocolBuffers/Descriptors/FieldMappingAttribute.cs new file mode 100644 index 00000000..18f88a31 --- /dev/null +++ b/src/ProtocolBuffers/Descriptors/FieldMappingAttribute.cs @@ -0,0 +1,34 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using System; + +namespace Google.ProtocolBuffers.Descriptors { + + /// <summary> + /// Defined specifically for the <see cref="FieldType" /> enumeration, + /// this allows each field type to specify the mapped type and wire type. + /// </summary> + [AttributeUsage(AttributeTargets.Field)] + internal sealed class FieldMappingAttribute : Attribute { + internal FieldMappingAttribute(MappedType mappedType, WireFormat.WireType wireType) { + MappedType = mappedType; + WireType = wireType; + } + + internal MappedType MappedType { get; private set; } + internal WireFormat.WireType WireType { get; private set; } + } +} diff --git a/src/ProtocolBuffers/Descriptors/FieldType.cs b/src/ProtocolBuffers/Descriptors/FieldType.cs new file mode 100644 index 00000000..7c15c0cc --- /dev/null +++ b/src/ProtocolBuffers/Descriptors/FieldType.cs @@ -0,0 +1,42 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Google.ProtocolBuffers.Descriptors { + /// <summary> + /// Enumeration of all the possible field types. The odd formatting is to make it very clear + /// which attribute applies to which value, while maintaining a compact format. + /// </summary> + public enum FieldType { + [FieldMapping(MappedType.Double, WireFormat.WireType.Fixed64)] Double, + [FieldMapping(MappedType.Single, WireFormat.WireType.Fixed32)] Float, + [FieldMapping(MappedType.Int64, WireFormat.WireType.Varint)] Int64, + [FieldMapping(MappedType.UInt64, WireFormat.WireType.Varint)] UInt64, + [FieldMapping(MappedType.Int32, WireFormat.WireType.Varint)] Int32, + [FieldMapping(MappedType.UInt64, WireFormat.WireType.Fixed64)] Fixed64, + [FieldMapping(MappedType.UInt32, WireFormat.WireType.Fixed32)] Fixed32, + [FieldMapping(MappedType.Boolean, WireFormat.WireType.Varint)] Bool, + [FieldMapping(MappedType.String, WireFormat.WireType.LengthDelimited)] String, + [FieldMapping(MappedType.Message, WireFormat.WireType.StartGroup)] Group, + [FieldMapping(MappedType.Message, WireFormat.WireType.LengthDelimited)] Message, + [FieldMapping(MappedType.ByteString, WireFormat.WireType.LengthDelimited)] Bytes, + [FieldMapping(MappedType.UInt32, WireFormat.WireType.Varint)] UInt32, + [FieldMapping(MappedType.Int32, WireFormat.WireType.Fixed32)] SFixed32, + [FieldMapping(MappedType.Int64, WireFormat.WireType.Fixed64)] SFixed64, + [FieldMapping(MappedType.Int32, WireFormat.WireType.Varint)] SInt32, + [FieldMapping(MappedType.Int64, WireFormat.WireType.Varint)] SInt64, + [FieldMapping(MappedType.Enum, WireFormat.WireType.Varint)] Enum + } +} diff --git a/src/ProtocolBuffers/Descriptors/FileDescriptor.cs b/src/ProtocolBuffers/Descriptors/FileDescriptor.cs new file mode 100644 index 00000000..abd54e76 --- /dev/null +++ b/src/ProtocolBuffers/Descriptors/FileDescriptor.cs @@ -0,0 +1,246 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Google.ProtocolBuffers.DescriptorProtos; + +namespace Google.ProtocolBuffers.Descriptors { + + /// <summary> + /// 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. + /// </summary> + public sealed class FileDescriptor : IDescriptor<FileDescriptorProto> { + + private readonly FileDescriptorProto proto; + private readonly IList<MessageDescriptor> messageTypes; + private readonly IList<EnumDescriptor> enumTypes; + private readonly IList<ServiceDescriptor> services; + private readonly IList<FieldDescriptor> extensions; + private readonly IList<FileDescriptor> dependencies; + private readonly DescriptorPool pool; + + private FileDescriptor(FileDescriptorProto proto, FileDescriptor[] dependencies, DescriptorPool pool) { + this.pool = pool; + this.proto = proto; + this.dependencies = new ReadOnlyCollection<FileDescriptor>((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)); + } + + /// <value> + /// The descriptor in its protocol message representation. + /// </value> + public FileDescriptorProto Proto { + get { return proto; } + } + + /// <value> + /// The <see cref="FileOptions" /> defined in <c>descriptor.proto</c>. + /// </value> + public FileOptions Options { + get { return proto.Options; } + } + + /// <value> + /// The file name. + /// </value> + public string Name { + get { return proto.Name; } + } + + /// <summary> + /// The package as declared in the .proto file. This may or may not + /// be equivalent to the .NET namespace of the generated classes. + /// </summary> + public string Package { + get { return proto.Package; } + } + + /// <value> + /// Unmodifiable list of top-level message types declared in this file. + /// </value> + public IList<MessageDescriptor> MessageTypes { + get { return messageTypes; } + } + + /// <value> + /// Unmodifiable list of top-level enum types declared in this file. + /// </value> + public IList<EnumDescriptor> EnumTypes { + get { return enumTypes; } + } + + /// <value> + /// Unmodifiable list of top-level services declared in this file. + /// </value> + public IList<ServiceDescriptor> Services { + get { return services; } + } + + /// <value> + /// Unmodifiable list of top-level extensions declared in this file. + /// </value> + public IList<FieldDescriptor> Extensions { + get { return extensions; } + } + + /// <value> + /// Unmodifiable list of this file's dependencies (imports). + /// </value> + public IList<FileDescriptor> Dependencies { + get { return dependencies; } + } + + /// <value> + /// Implementation of IDescriptor.FullName - just returns the same as Name. + /// </value> + string IDescriptor.FullName { + get { return Name; } + } + + /// <value> + /// Implementation of IDescriptor.File - just returns this descriptor. + /// </value> + FileDescriptor IDescriptor.File { + get { return this; } + } + + /// <value> + /// Protocol buffer describing this descriptor. + /// </value> + IMessage IDescriptor.Proto { + get { return Proto; } + } + + /// <value> + /// Pool containing symbol descriptors. + /// </value> + internal DescriptorPool DescriptorPool { + get { return pool; } + } + + /// <summary> + /// Finds a type (message, enum, service or extension) in the file by name. Does not find nested types. + /// </summary> + /// <param name="name">The unqualified type name to look for.</param> + /// <typeparam name="T">The type of descriptor to look for (or ITypeDescriptor for any)</typeparam> + /// <returns>The type's descriptor, or null if not found.</returns> + public T FindTypeByName<T>(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<T>(name); + if (result != null && result.File == this) { + return result; + } + return null; + } + + /// <summary> + /// Builds a FileDescriptor from its protocol buffer representation. + /// </summary> + /// <param name="proto">The protocol message form of the FileDescriptor.</param> + /// <param name="dependencies">FileDescriptors corresponding to all of the + /// file's dependencies, in the exact order listed in the .proto file. May be null, + /// in which case it is treated as an empty array.</param> + /// <exception cref="DescriptorValidationException">If <paramref name="proto"/> 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.</exception> + public static FileDescriptor BuildFrom(FileDescriptorProto proto, FileDescriptor[] dependencies) { + // Building descriptors 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. + if (dependencies == null) { + dependencies = new FileDescriptor[0]; + } + + 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(); + } + + foreach (MessageDescriptor message in messageTypes) { + message.CheckRequiredFields(); + } + } + + /// <summary> + /// This method is to be called by generated code only. It is equivalent + /// to BuilderFrom except that the FileDescriptorProto is encoded in + /// protocol buffer wire format. + /// </summary> + public static FileDescriptor InternalBuildGeneratedFileFrom(byte[] descriptorData, + FileDescriptor[] dependencies) { + FileDescriptorProto proto = FileDescriptorProto.ParseFrom(descriptorData); + return BuildFrom(proto, dependencies); + } + } +} diff --git a/src/ProtocolBuffers/Descriptors/IDescriptor.cs b/src/ProtocolBuffers/Descriptors/IDescriptor.cs new file mode 100644 index 00000000..2143f39a --- /dev/null +++ b/src/ProtocolBuffers/Descriptors/IDescriptor.cs @@ -0,0 +1,37 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Google.ProtocolBuffers.Descriptors { + + /// <summary> + /// The non-generic form of the IDescriptor interface. Useful for describing a general + /// descriptor. + /// </summary> + public interface IDescriptor { + string Name { get; } + string FullName { get; } + FileDescriptor File { get; } + IMessage Proto { get; } + } + + /// <summary> + /// Strongly-typed form of the IDescriptor interface. + /// </summary> + /// <typeparam name="TProto">Protocol buffer type underlying this descriptor type</typeparam> + public interface IDescriptor<TProto> : IDescriptor where TProto : IMessage { + new TProto Proto { get; } + } +} diff --git a/src/ProtocolBuffers/Descriptors/IndexedDescriptorBase.cs b/src/ProtocolBuffers/Descriptors/IndexedDescriptorBase.cs new file mode 100644 index 00000000..d04d40db --- /dev/null +++ b/src/ProtocolBuffers/Descriptors/IndexedDescriptorBase.cs @@ -0,0 +1,45 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using Google.ProtocolBuffers.DescriptorProtos; + +namespace Google.ProtocolBuffers.Descriptors { + /// <summary> + /// Base class for descriptors which are also indexed. This is all of them other than + /// <see cref="FileDescriptor" />. + /// </summary> + public abstract class IndexedDescriptorBase<TProto, TOptions> : DescriptorBase<TProto, TOptions> + where TProto : IMessage<TProto>, IDescriptorProto<TOptions> { + + private readonly int index; + + protected IndexedDescriptorBase(TProto proto, FileDescriptor file, string fullName, int index) + : base(proto, file, fullName) { + this.index = index; + } + + /// <value> + /// The index of this descriptor within its parent descriptor. + /// </value> + /// <remarks> + /// This returns the index of this descriptor within its parent, for + /// this descriptor's type. (There can be duplicate values for different + /// types, e.g. one enum type with index 0 and one message type with index 0.) + /// </remarks> + public int Index { + get { return index; } + } + } +} diff --git a/src/ProtocolBuffers/Descriptors/MappedType.cs b/src/ProtocolBuffers/Descriptors/MappedType.cs new file mode 100644 index 00000000..ca7a7b43 --- /dev/null +++ b/src/ProtocolBuffers/Descriptors/MappedType.cs @@ -0,0 +1,34 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Google.ProtocolBuffers.Descriptors { + /// <summary> + /// Type as it's mapped onto a .NET type. + /// </summary> + public enum MappedType { + Int32, + Int64, + UInt32, + UInt64, + Single, + Double, + Boolean, + String, + ByteString, + Message, + Enum + } +} diff --git a/src/ProtocolBuffers/Descriptors/MessageDescriptor.cs b/src/ProtocolBuffers/Descriptors/MessageDescriptor.cs new file mode 100644 index 00000000..f387a325 --- /dev/null +++ b/src/ProtocolBuffers/Descriptors/MessageDescriptor.cs @@ -0,0 +1,186 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using System.Collections.Generic; +using Google.ProtocolBuffers.DescriptorProtos; + +namespace Google.ProtocolBuffers.Descriptors { + + /// <summary> + /// Describes a message type. + /// </summary> + public sealed class MessageDescriptor : IndexedDescriptorBase<DescriptorProto, MessageOptions> { + + private readonly MessageDescriptor containingType; + private readonly IList<MessageDescriptor> nestedTypes; + private readonly IList<EnumDescriptor> enumTypes; + private readonly IList<FieldDescriptor> fields; + private readonly IList<FieldDescriptor> extensions; + private bool hasRequiredFields; + + 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, this, index)); + + enumTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.EnumTypeList, + (type, index) => new EnumDescriptor(type, file, this, index)); + + fields = DescriptorUtil.ConvertAndMakeReadOnly(proto.FieldList, + (field, index) => new FieldDescriptor(field, file, this, index, false)); + + extensions = DescriptorUtil.ConvertAndMakeReadOnly(proto.ExtensionList, + (field, index) => new FieldDescriptor(field, file, this, index, true)); + + file.DescriptorPool.AddSymbol(this); + } + + /// <value> + /// If this is a nested type, get the outer descriptor, otherwise null. + /// </value> + public MessageDescriptor ContainingType { + get { return containingType; } + } + + /// <value> + /// An unmodifiable list of this message type's fields. + /// </value> + public IList<FieldDescriptor> Fields { + get { return fields; } + } + + /// <value> + /// An unmodifiable list of this message type's extensions. + /// </value> + public IList<FieldDescriptor> Extensions { + get { return extensions; } + } + + /// <value> + /// An unmodifiable list of this message type's nested types. + /// </value> + public IList<MessageDescriptor> NestedTypes { + get { return nestedTypes; } + } + + /// <value> + /// An unmodifiable list of this message type's enum types. + /// </value> + public IList<EnumDescriptor> EnumTypes { + get { return enumTypes; } + } + + /// <summary> + /// Returns a pre-computed result as to whether this message + /// has required fields. This includes optional fields which are + /// message types which in turn have required fields, and any + /// extension fields. + /// </summary> + internal bool HasRequiredFields { + get { return hasRequiredFields; } + } + + /// <summary> + /// Determines if the given field number is an extension. + /// </summary> + public bool IsExtensionNumber(int number) { + foreach (DescriptorProto.Types.ExtensionRange range in Proto.ExtensionRangeList) { + if (range.Start <= number && number < range.End) { + return true; + } + } + return false; + } + + /// <summary> + /// Finds a field by field number. + /// </summary> + /// <param name="number">The field number within this message type.</param> + /// <returns>The field's descriptor, or null if not found.</returns> + public FieldDescriptor FindFieldByNumber(int number) { + return File.DescriptorPool.FindFieldByNumber(this, number); + } + + /// <summary> + /// Finds a nested descriptor by name. The is valid for fields, nested + /// message types and enums. + /// </summary> + /// <param name="name">The unqualified name of the descriptor, e.g. "Foo"</param> + /// <returns>The descriptor, or null if not found.</returns> + public T FindDescriptor<T>(string name) + where T : class, IDescriptor { + return File.DescriptorPool.FindSymbol<T>(FullName + "." + name); + } + + /// <summary> + /// Looks up and cross-links all fields, nested types, and extensions. + /// </summary> + internal void CrossLink() { + foreach (MessageDescriptor message in nestedTypes) { + message.CrossLink(); + } + + foreach (FieldDescriptor field in fields) { + field.CrossLink(); + } + + foreach (FieldDescriptor extension in extensions) { + extension.CrossLink(); + } + } + + internal void CheckRequiredFields() { + IDictionary<MessageDescriptor, byte> alreadySeen = new Dictionary<MessageDescriptor, byte>(); + hasRequiredFields = CheckRequiredFields(alreadySeen); + } + + private bool CheckRequiredFields(IDictionary<MessageDescriptor,byte> alreadySeen) { + + if (alreadySeen.ContainsKey(this)) { + // The type is already in the cache. This means that either: + // a. The type has no required fields. + // b. We are in the midst of checking if the type has required fields, + // somewhere up the stack. In this case, we know that if the type + // has any required fields, they'll be found when we return to it, + // and the whole call to HasRequiredFields() will return true. + // Therefore, we don't have to check if this type has required fields + // here. + return false; + } + alreadySeen[this] = 0; // Value is irrelevant; we want set semantics + + // If the type allows extensions, an extension with message type could contain + // required fields, so we have to be conservative and assume such an + // extension exists. + if (Proto.ExtensionRangeCount != 0) { + return true; + } + + foreach (FieldDescriptor field in Fields) { + if (field.IsRequired) { + return true; + } + if (field.MappedType == MappedType.Message) { + if (field.MessageType.CheckRequiredFields(alreadySeen)) { + return true; + } + } + } + return false; + } + } +} diff --git a/src/ProtocolBuffers/Descriptors/MethodDescriptor.cs b/src/ProtocolBuffers/Descriptors/MethodDescriptor.cs new file mode 100644 index 00000000..13e6d141 --- /dev/null +++ b/src/ProtocolBuffers/Descriptors/MethodDescriptor.cs @@ -0,0 +1,70 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using Google.ProtocolBuffers.DescriptorProtos; + +namespace Google.ProtocolBuffers.Descriptors { + /// <summary> + /// Describes a single method in a service. + /// </summary> + public sealed class MethodDescriptor : IndexedDescriptorBase<MethodDescriptorProto, MethodOptions> { + + private readonly ServiceDescriptor service; + private MessageDescriptor inputType; + private MessageDescriptor outputType; + + /// <value> + /// The service this method belongs to. + /// </value> + public ServiceDescriptor Service { + get { return service; } + } + + /// <value> + /// The method's input type. + /// </value> + public MessageDescriptor InputType { + get { return inputType; } + } + + /// <value> + /// The method's input type. + /// </value> + public MessageDescriptor OutputType { + get { return outputType; } + } + + 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/src/ProtocolBuffers/Descriptors/PackageDescriptor.cs b/src/ProtocolBuffers/Descriptors/PackageDescriptor.cs new file mode 100644 index 00000000..9884c4a6 --- /dev/null +++ b/src/ProtocolBuffers/Descriptors/PackageDescriptor.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Google.ProtocolBuffers.Descriptors { + /// <summary> + /// 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. + /// </summary> + internal sealed class PackageDescriptor : IDescriptor<IMessage> { + + 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/src/ProtocolBuffers/Descriptors/ServiceDescriptor.cs b/src/ProtocolBuffers/Descriptors/ServiceDescriptor.cs new file mode 100644 index 00000000..cb152637 --- /dev/null +++ b/src/ProtocolBuffers/Descriptors/ServiceDescriptor.cs @@ -0,0 +1,60 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using System; +using System.Collections.Generic; +using Google.ProtocolBuffers.DescriptorProtos; + +namespace Google.ProtocolBuffers.Descriptors { + + /// <summary> + /// Describes a service type. + /// </summary> + public sealed class ServiceDescriptor : IndexedDescriptorBase<ServiceDescriptorProto, ServiceOptions> { + + private readonly IList<MethodDescriptor> methods; + + public ServiceDescriptor(ServiceDescriptorProto proto, FileDescriptor file, int 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); + } + + /// <value> + /// An unmodifiable list of methods in this service. + /// </value> + public IList<MethodDescriptor> Methods { + get { return methods; } + } + + /// <summary> + /// Finds a method by name. + /// </summary> + /// <param name="name">The unqualified name of the method (e.g. "Foo").</param> + /// <returns>The method's decsriptor, or null if not found.</returns> + public MethodDescriptor FindMethodByName(String name) { + return File.DescriptorPool.FindSymbol<MethodDescriptor>(FullName + "." + name); + } + + internal void CrossLink() { + foreach (MethodDescriptor method in methods) { + method.CrossLink(); + } + } + } +} |