From f7e43c6f80a29530242f64b5a13790aac5679db7 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Fri, 20 Nov 2015 14:23:34 +0000 Subject: Added the type registry in advance of implementing Any support. Biting off just this bit first as I don't need the changes from a previous PR for this part. --- .../Google.Protobuf.Test.csproj | 1 + .../Reflection/TypeRegistryTest.cs | 94 +++++++++++ csharp/src/Google.Protobuf/Google.Protobuf.csproj | 1 + .../src/Google.Protobuf/Reflection/TypeRegistry.cs | 183 +++++++++++++++++++++ 4 files changed, 279 insertions(+) create mode 100644 csharp/src/Google.Protobuf.Test/Reflection/TypeRegistryTest.cs create mode 100644 csharp/src/Google.Protobuf/Reflection/TypeRegistry.cs (limited to 'csharp') diff --git a/csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj b/csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj index cd15b968..903454eb 100644 --- a/csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj +++ b/csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj @@ -99,6 +99,7 @@ + diff --git a/csharp/src/Google.Protobuf.Test/Reflection/TypeRegistryTest.cs b/csharp/src/Google.Protobuf.Test/Reflection/TypeRegistryTest.cs new file mode 100644 index 00000000..5be7ca23 --- /dev/null +++ b/csharp/src/Google.Protobuf.Test/Reflection/TypeRegistryTest.cs @@ -0,0 +1,94 @@ +#region Copyright notice and license +// Protocol Buffers - Google's data interchange format +// Copyright 2015 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#endregion + +using Google.Protobuf.TestProtos; +using Google.Protobuf.WellKnownTypes; +using NUnit.Framework; + +namespace Google.Protobuf.Reflection +{ + public class TypeRegistryTest + { + // Most of our tests use messages. Simple test that we really can use files... + [Test] + public void CreateWithFileDescriptor() + { + var registry = TypeRegistry.FromFiles(DurationReflection.Descriptor, StructReflection.Descriptor); + AssertDescriptorPresent(registry, Duration.Descriptor); + AssertDescriptorPresent(registry, ListValue.Descriptor); + AssertDescriptorAbsent(registry, Timestamp.Descriptor); + } + + [Test] + public void TypesFromSameFile() + { + // Just for kicks, let's start with a nested type + var registry = TypeRegistry.FromMessages(TestAllTypes.Types.NestedMessage.Descriptor); + // Top-level... + AssertDescriptorPresent(registry, TestFieldOrderings.Descriptor); + // ... and nested (not the same as the original NestedMessage!) + AssertDescriptorPresent(registry, TestFieldOrderings.Types.NestedMessage.Descriptor); + } + + [Test] + public void DependenciesAreIncluded() + { + var registry = TypeRegistry.FromMessages(TestAllTypes.Descriptor); + // Direct dependencies + AssertDescriptorPresent(registry, ImportMessage.Descriptor); + // Public dependencies + AssertDescriptorPresent(registry, PublicImportMessage.Descriptor); + } + + [Test] + public void DuplicateFiles() + { + // Duplicates via dependencies and simply via repetition + var registry = TypeRegistry.FromFiles( + UnittestProto3Reflection.Descriptor, UnittestImportProto3Reflection.Descriptor, + TimestampReflection.Descriptor, TimestampReflection.Descriptor); + AssertDescriptorPresent(registry, TestAllTypes.Descriptor); + AssertDescriptorPresent(registry, ImportMessage.Descriptor); + AssertDescriptorPresent(registry, Timestamp.Descriptor); + } + + private static void AssertDescriptorPresent(TypeRegistry registry, MessageDescriptor descriptor) + { + Assert.AreSame(descriptor, registry.Find(descriptor.FullName)); + } + + private static void AssertDescriptorAbsent(TypeRegistry registry, MessageDescriptor descriptor) + { + Assert.IsNull(registry.Find(descriptor.FullName)); + } + } +} diff --git a/csharp/src/Google.Protobuf/Google.Protobuf.csproj b/csharp/src/Google.Protobuf/Google.Protobuf.csproj index 9179ab17..36807b82 100644 --- a/csharp/src/Google.Protobuf/Google.Protobuf.csproj +++ b/csharp/src/Google.Protobuf/Google.Protobuf.csproj @@ -121,6 +121,7 @@ + diff --git a/csharp/src/Google.Protobuf/Reflection/TypeRegistry.cs b/csharp/src/Google.Protobuf/Reflection/TypeRegistry.cs new file mode 100644 index 00000000..31d5a30f --- /dev/null +++ b/csharp/src/Google.Protobuf/Reflection/TypeRegistry.cs @@ -0,0 +1,183 @@ +#region Copyright notice and license +// Protocol Buffers - Google's data interchange format +// Copyright 2015 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#endregion +using System.Collections.Generic; +using System.Linq; + +namespace Google.Protobuf.Reflection +{ + /// + /// An immutable registry of types which can be looked up by their full name. + /// + public sealed class TypeRegistry + { + /// + /// An empty type registry, containing no types. + /// + public static TypeRegistry Empty { get; } = new TypeRegistry(new Dictionary()); + + private readonly Dictionary fullNameToMessageMap; + + private TypeRegistry(Dictionary fullNameToMessageMap) + { + this.fullNameToMessageMap = fullNameToMessageMap; + } + + /// + /// Attempts to find a message descriptor by its full name. + /// + /// The full name of the message, which is the dot-separated + /// combination of package, containing messages and message name + /// The message descriptor corresponding to or null + /// if there is no such message descriptor. + public MessageDescriptor Find(string fullName) + { + MessageDescriptor ret; + // Ignore the return value as ret will end up with the right value either way. + fullNameToMessageMap.TryGetValue(fullName, out ret); + return ret; + } + + /// + /// Creates a type registry from the specified set of file descriptors. + /// + /// + /// This is a convenience overload for + /// to allow calls such as TypeRegistry.FromFiles(descriptor1, descriptor2). + /// + /// The set of files to include in the registry. Must not contain null values. + /// A type registry for the given files. + public static TypeRegistry FromFiles(params FileDescriptor[] fileDescriptors) + { + return FromFiles((IEnumerable) fileDescriptors); + } + + /// + /// Creates a type registry from the specified set of file descriptors. + /// + /// + /// All message types within all the specified files are added to the registry, and + /// the dependencies of the specified files are also added, recursively. + /// + /// The set of files to include in the registry. Must not contain null values. + /// A type registry for the given files. + public static TypeRegistry FromFiles(IEnumerable fileDescriptors) + { + Preconditions.CheckNotNull(fileDescriptors, nameof(fileDescriptors)); + var builder = new Builder(); + foreach (var file in fileDescriptors) + { + builder.AddFile(file); + } + return builder.Build(); + } + + /// + /// Creates a type registry from the file descriptor parents of the specified set of message descriptors. + /// + /// + /// This is a convenience overload for + /// to allow calls such as TypeRegistry.FromFiles(descriptor1, descriptor2). + /// + /// The set of message descriptors to use to identify file descriptors to include in the registry. + /// Must not contain null values. + /// A type registry for the given files. + public static TypeRegistry FromMessages(params MessageDescriptor[] messageDescriptors) + { + return FromMessages((IEnumerable) messageDescriptors); + } + + /// + /// Creates a type registry from the file descriptor parents of the specified set of message descriptors. + /// + /// + /// The specified message descriptors are only used to identify their file descriptors; the returned registry + /// contains all the types within the file descriptors which contain the specified message descriptors (and + /// the dependencies of those files), not just the specified messages. + /// + /// The set of message descriptors to use to identify file descriptors to include in the registry. + /// Must not contain null values. + /// A type registry for the given files. + public static TypeRegistry FromMessages(IEnumerable messageDescriptors) + { + Preconditions.CheckNotNull(messageDescriptors, nameof(messageDescriptors)); + return FromFiles(messageDescriptors.Select(md => md.File)); + } + + /// + /// Builder class which isn't exposed, but acts as a convenient alternative to passing round two dictionaries in recursive calls. + /// + private class Builder + { + private readonly Dictionary types; + private readonly HashSet fileDescriptorNames; + + internal Builder() + { + types = new Dictionary(); + fileDescriptorNames = new HashSet(); + } + + internal void AddFile(FileDescriptor fileDescriptor) + { + if (!fileDescriptorNames.Add(fileDescriptor.Name)) + { + return; + } + foreach (var dependency in fileDescriptor.Dependencies) + { + AddFile(dependency); + } + foreach (var message in fileDescriptor.MessageTypes) + { + AddMessage(message); + } + } + + private void AddMessage(MessageDescriptor messageDescriptor) + { + foreach (var nestedType in messageDescriptor.NestedTypes) + { + AddMessage(nestedType); + } + // This will overwrite any previous entry. Given that each file should + // only be added once, this could be a problem such as package A.B with type C, + // and package A with type B.C... it's unclear what we should do in that case. + types[messageDescriptor.FullName] = messageDescriptor; + } + + internal TypeRegistry Build() + { + return new TypeRegistry(types); + } + } + } +} -- cgit v1.2.3