diff options
author | Jon Skeet <jonskeet@google.com> | 2018-08-30 14:53:06 +0100 |
---|---|---|
committer | Jon Skeet <skeet@pobox.com> | 2018-09-22 09:09:15 +0100 |
commit | 1711999078ca1d435de3958bf963e95a742e972f (patch) | |
tree | 6ea6797a824da7cf26b2535ea4e3a9be9cd5ec95 /csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs | |
parent | a6e1cc7e328c45a0cb9856c530c8f6cd23314163 (diff) | |
download | protobuf-1711999078ca1d435de3958bf963e95a742e972f.tar.gz protobuf-1711999078ca1d435de3958bf963e95a742e972f.tar.bz2 protobuf-1711999078ca1d435de3958bf963e95a742e972f.zip |
Provide simple access to descriptor declarations in C#
This is primarily for access to comments, which would be expected to be available in a protoc plugin.
The implementation has two fiddly aspects:
- We use a Lazy<T> to avoid building the map before cross-linking. An alternative would be to crosslink at the end of the constructor, and remove the calls to CrossLink elsewhere. This would be generally better IMO, but deviate from the Java code.
- The casts to IReadOnlyList<DescriptorBase> are unfortunate. They'll always work, because these lists are always ReadOnlyCollection<T> for a descriptor type... but we can't use IList<DescriptorBase> as that's not covariant, and it's annoyingly fiddly to change the field to be of type ReadOnlyCollection<T>.
Diffstat (limited to 'csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs')
-rw-r--r-- | csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs | 80 |
1 files changed, 80 insertions, 0 deletions
diff --git a/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs index 216e03cc..6d4520c0 100644 --- a/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs +++ b/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs @@ -34,7 +34,10 @@ using Google.Protobuf.WellKnownTypes; using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics; using System.Linq; +using System.Threading; +using static Google.Protobuf.Reflection.SourceCodeInfo.Types; namespace Google.Protobuf.Reflection { @@ -55,6 +58,8 @@ namespace Google.Protobuf.Reflection ForceReflectionInitialization<Value.KindOneofCase>(); } + private readonly Lazy<Dictionary<IDescriptor, DescriptorDeclaration>> declarations; + private FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, IEnumerable<FileDescriptor> dependencies, DescriptorPool pool, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo) { SerializedData = descriptorData; @@ -77,6 +82,81 @@ namespace Google.Protobuf.Reflection Services = DescriptorUtil.ConvertAndMakeReadOnly(proto.Service, (service, index) => new ServiceDescriptor(service, this, index)); + + declarations = new Lazy<Dictionary<IDescriptor, DescriptorDeclaration>>(CreateDeclarationMap, LazyThreadSafetyMode.ExecutionAndPublication); + } + + private Dictionary<IDescriptor, DescriptorDeclaration> CreateDeclarationMap() + { + var dictionary = new Dictionary<IDescriptor, DescriptorDeclaration>(); + foreach (var location in Proto.SourceCodeInfo?.Location ?? Enumerable.Empty<Location>()) + { + var descriptor = FindDescriptorForPath(location.Path); + if (descriptor != null) + { + dictionary[descriptor] = DescriptorDeclaration.FromProto(descriptor, location); + } + } + return dictionary; + + IDescriptor FindDescriptorForPath(IList<int> path) + { + // All complete declarations have an even, non-empty path length + // (There can be an empty path for a descriptor declaration, but that can't have any comments, + // so we currently ignore it.) + if (path.Count == 0 || (path.Count & 1) != 0) + { + return null; + } + IReadOnlyList<DescriptorBase> topLevelList = GetNestedDescriptorListForField(path[0]); + DescriptorBase current = GetDescriptorFromList(topLevelList, path[1]); + + for (int i = 2; current != null && i < path.Count; i += 2) + { + var list = current.GetNestedDescriptorListForField(path[i]); + current = GetDescriptorFromList(list, path[i + 1]); + } + return current; + } + + DescriptorBase GetDescriptorFromList(IReadOnlyList<DescriptorBase> list, int index) + { + // This is fine: it may be a newer version of protobuf than we understand, with a new descriptor + // field. + if (list == null) + { + return null; + } + // We *could* return null to silently continue, but this is basically data corruption. + if (index < 0 || index >= list.Count) + { + // We don't have much extra information to give at this point unfortunately. If this becomes a problem, + // we can pass in the complete path and report that and the file name. + throw new InvalidProtocolBufferException($"Invalid descriptor location path: index out of range"); + } + return list[index]; + } + + IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber) + { + switch (fieldNumber) + { + case FileDescriptorProto.ServiceFieldNumber: + return (IReadOnlyList<DescriptorBase>) Services; + case FileDescriptorProto.MessageTypeFieldNumber: + return (IReadOnlyList<DescriptorBase>) MessageTypes; + case FileDescriptorProto.EnumTypeFieldNumber: + return (IReadOnlyList<DescriptorBase>) EnumTypes; + default: + return null; + } + } + } + + internal DescriptorDeclaration GetDeclaration(IDescriptor descriptor) + { + declarations.Value.TryGetValue(descriptor, out var declaration); + return declaration; } /// <summary> |