Commit 72ec3367 authored by Jon Skeet's avatar Jon Skeet

Tidy up reflection in advance of attempting to implement DynamicMessage.

There are corner cases where MessageDescriptor.{ClrType,Parser} will return null, and these are now documented. However, normally they *should* be implemented, even for descriptors of for dynamic messages. Ditto FieldDescriptor.Accessor.
We'll still need a fair amount of work to implement dynamic messages, but this change means that the public API will be remain intact.

Additionally, this change starts making use of C# 6 features in the files that it touches. This is far from exhaustive, and later PRs will have more.

Generated code changes coming in the next commit.
parent d6202a9b
...@@ -63,7 +63,7 @@ namespace Google.Protobuf.Reflection ...@@ -63,7 +63,7 @@ namespace Google.Protobuf.Reflection
Assert.AreEqual(UnittestImportProto3Reflection.Descriptor, file.Dependencies[0]); Assert.AreEqual(UnittestImportProto3Reflection.Descriptor, file.Dependencies[0]);
MessageDescriptor messageType = TestAllTypes.Descriptor; MessageDescriptor messageType = TestAllTypes.Descriptor;
Assert.AreSame(typeof(TestAllTypes), messageType.GeneratedType); Assert.AreSame(typeof(TestAllTypes), messageType.ClrType);
Assert.AreSame(TestAllTypes.Parser, messageType.Parser); Assert.AreSame(TestAllTypes.Parser, messageType.Parser);
Assert.AreEqual(messageType, file.MessageTypes[0]); Assert.AreEqual(messageType, file.MessageTypes[0]);
Assert.AreEqual(messageType, file.FindTypeByName<MessageDescriptor>("TestAllTypes")); Assert.AreEqual(messageType, file.FindTypeByName<MessageDescriptor>("TestAllTypes"));
...@@ -227,18 +227,12 @@ namespace Google.Protobuf.Reflection ...@@ -227,18 +227,12 @@ namespace Google.Protobuf.Reflection
} }
[Test] [Test]
public void ConstructionWithoutGeneratedCodeInfo() public void MapEntryMessageDescriptor()
{ {
var data = UnittestIssuesReflection.Descriptor.Proto.ToByteArray(); var descriptor = MapWellKnownTypes.Descriptor.NestedTypes[0];
var newDescriptor = Google.Protobuf.Reflection.FileDescriptor.InternalBuildGeneratedFileFrom(data, new Reflection.FileDescriptor[] { }, null); Assert.IsNull(descriptor.Parser);
Assert.IsNull(descriptor.ClrType);
// We should still be able to get at a field... Assert.IsNull(descriptor.Fields[1].Accessor);
var messageDescriptor = newDescriptor.FindTypeByName<MessageDescriptor>("ItemField");
var fieldDescriptor = messageDescriptor.FindFieldByName("item");
// But there shouldn't be an accessor (or a generated type for the message, or parser)
Assert.IsNull(fieldDescriptor.Accessor);
Assert.IsNull(messageDescriptor.GeneratedType);
Assert.IsNull(messageDescriptor.Parser);
} }
// From TestFieldOrdering: // From TestFieldOrdering:
......
...@@ -388,8 +388,7 @@ namespace Google.Protobuf ...@@ -388,8 +388,7 @@ namespace Google.Protobuf
// If it's the message form, we can extract the value first, which *will* be the (possibly boxed) native value, // If it's the message form, we can extract the value first, which *will* be the (possibly boxed) native value,
// and then proceed, writing it as if we were definitely in a field. (We never need to wrap it in an extra string... // and then proceed, writing it as if we were definitely in a field. (We never need to wrap it in an extra string...
// WriteValue will do the right thing.) // WriteValue will do the right thing.)
// TODO: Detect this differently when we have dynamic messages. if (descriptor.IsWrapperType)
if (descriptor.File == Int32Value.Descriptor.File)
{ {
if (value is IMessage) if (value is IMessage)
{ {
......
...@@ -291,8 +291,7 @@ namespace Google.Protobuf ...@@ -291,8 +291,7 @@ namespace Google.Protobuf
{ {
// Parse wrapper types as their constituent types. // Parse wrapper types as their constituent types.
// TODO: What does this mean for null? // TODO: What does this mean for null?
// TODO: Detect this differently when we have dynamic messages, and put it in one place... if (field.MessageType.IsWrapperType)
if (field.MessageType.IsWellKnownType && field.MessageType.File == Int32Value.Descriptor.File)
{ {
field = field.MessageType.Fields[WrappersReflection.WrapperValueFieldNumber]; field = field.MessageType.Fields[WrappersReflection.WrapperValueFieldNumber];
fieldType = field.FieldType; fieldType = field.FieldType;
......
...@@ -43,13 +43,13 @@ namespace Google.Protobuf.Reflection ...@@ -43,13 +43,13 @@ namespace Google.Protobuf.Reflection
private readonly EnumDescriptorProto proto; private readonly EnumDescriptorProto proto;
private readonly MessageDescriptor containingType; private readonly MessageDescriptor containingType;
private readonly IList<EnumValueDescriptor> values; private readonly IList<EnumValueDescriptor> values;
private readonly Type generatedType; private readonly Type clrType;
internal EnumDescriptor(EnumDescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int index, Type generatedType) internal EnumDescriptor(EnumDescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int index, Type generatedType)
: base(file, file.ComputeFullName(parent, proto.Name), index) : base(file, file.ComputeFullName(parent, proto.Name), index)
{ {
this.proto = proto; this.proto = proto;
this.generatedType = generatedType; this.clrType = generatedType;
containingType = parent; containingType = parent;
if (proto.Value.Count == 0) if (proto.Value.Count == 0)
...@@ -73,9 +73,9 @@ namespace Google.Protobuf.Reflection ...@@ -73,9 +73,9 @@ namespace Google.Protobuf.Reflection
public override string Name { get { return proto.Name; } } public override string Name { get { return proto.Name; } }
/// <summary> /// <summary>
/// The generated type for this enum, or <c>null</c> if the descriptor does not represent a generated type. /// The CLR type for this enum. For generated code, this will be a CLR enum type.
/// </summary> /// </summary>
public Type GeneratedType { get { return generatedType; } } public Type ClrType { get { return clrType; } }
/// <value> /// <value>
/// If this is a nested type, get the outer descriptor, otherwise null. /// If this is a nested type, get the outer descriptor, otherwise null.
......
...@@ -62,8 +62,7 @@ namespace Google.Protobuf.Reflection ...@@ -62,8 +62,7 @@ namespace Google.Protobuf.Reflection
if (FieldNumber <= 0) if (FieldNumber <= 0)
{ {
throw new DescriptorValidationException(this, throw new DescriptorValidationException(this, "Field numbers must be positive integers.");
"Field numbers must be positive integers.");
} }
containingType = parent; containingType = parent;
// OneofIndex "defaults" to -1 due to a hack in FieldDescriptor.OnConstruction. // OneofIndex "defaults" to -1 due to a hack in FieldDescriptor.OnConstruction.
...@@ -72,7 +71,7 @@ namespace Google.Protobuf.Reflection ...@@ -72,7 +71,7 @@ namespace Google.Protobuf.Reflection
if (proto.OneofIndex < 0 || proto.OneofIndex >= parent.Proto.OneofDecl.Count) if (proto.OneofIndex < 0 || proto.OneofIndex >= parent.Proto.OneofDecl.Count)
{ {
throw new DescriptorValidationException(this, throw new DescriptorValidationException(this,
"FieldDescriptorProto.oneof_index is out of range for type " + parent.Name); $"FieldDescriptorProto.oneof_index is out of range for type {parent.Name}");
} }
containingOneof = parent.Oneofs[proto.OneofIndex]; containingOneof = parent.Oneofs[proto.OneofIndex];
} }
...@@ -94,13 +93,22 @@ namespace Google.Protobuf.Reflection ...@@ -94,13 +93,22 @@ namespace Google.Protobuf.Reflection
internal FieldDescriptorProto Proto { get { return proto; } } internal FieldDescriptorProto Proto { get { return proto; } }
/// <summary> /// <summary>
/// Returns the accessor for this field, or <c>null</c> if this descriptor does /// Returns the accessor for this field.
/// not support reflective access.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <para>
/// While a <see cref="FieldDescriptor"/> describes the field, it does not provide /// While a <see cref="FieldDescriptor"/> describes the field, it does not provide
/// any way of obtaining or changing the value of the field within a specific message; /// any way of obtaining or changing the value of the field within a specific message;
/// that is the responsibility of the accessor. /// that is the responsibility of the accessor.
/// </para>
/// <para>
/// The value returned by this property will be non-null for all regular fields. However,
/// if a message containing a map field is introspected, the list of nested messages will include
/// an auto-generated nested key/value pair message for the field. This is not represented in any
/// generated type, and the value of the map field itself is represented by a dictionary in the
/// reflection API. There are never instances of those "hidden" messages, so no accessor is provided
/// and this property will return null.
/// </para>
/// </remarks> /// </remarks>
public IFieldAccessor Accessor { get { return accessor; } } public IFieldAccessor Accessor { get { return accessor; } }
...@@ -281,7 +289,7 @@ namespace Google.Protobuf.Reflection ...@@ -281,7 +289,7 @@ namespace Google.Protobuf.Reflection
} }
else else
{ {
throw new DescriptorValidationException(this, "\"" + Proto.TypeName + "\" is not a type."); throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not a type.");
} }
} }
...@@ -289,8 +297,7 @@ namespace Google.Protobuf.Reflection ...@@ -289,8 +297,7 @@ namespace Google.Protobuf.Reflection
{ {
if (!(typeDescriptor is MessageDescriptor)) if (!(typeDescriptor is MessageDescriptor))
{ {
throw new DescriptorValidationException(this, throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not a message type.");
"\"" + Proto.TypeName + "\" is not a message type.");
} }
messageType = (MessageDescriptor) typeDescriptor; messageType = (MessageDescriptor) typeDescriptor;
...@@ -303,7 +310,7 @@ namespace Google.Protobuf.Reflection ...@@ -303,7 +310,7 @@ namespace Google.Protobuf.Reflection
{ {
if (!(typeDescriptor is EnumDescriptor)) if (!(typeDescriptor is EnumDescriptor))
{ {
throw new DescriptorValidationException(this, "\"" + Proto.TypeName + "\" is not an enum type."); throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not an enum type.");
} }
enumType = (EnumDescriptor) typeDescriptor; enumType = (EnumDescriptor) typeDescriptor;
} }
...@@ -333,14 +340,16 @@ namespace Google.Protobuf.Reflection ...@@ -333,14 +340,16 @@ namespace Google.Protobuf.Reflection
private IFieldAccessor CreateAccessor(string propertyName) private IFieldAccessor CreateAccessor(string propertyName)
{ {
if (containingType.GeneratedType == null || propertyName == null) // If we're given no property name, that's because we really don't want an accessor.
// (At the moment, that means it's a map entry message...)
if (propertyName == null)
{ {
return null; return null;
} }
var property = containingType.GeneratedType.GetProperty(propertyName); var property = containingType.ClrType.GetProperty(propertyName);
if (property == null) if (property == null)
{ {
throw new DescriptorValidationException(this, "Property " + propertyName + " not found in " + containingType.GeneratedType); throw new DescriptorValidationException(this, $"Property {propertyName} not found in {containingType.ClrType}");
} }
return IsMap ? new MapFieldAccessor(property, this) return IsMap ? new MapFieldAccessor(property, this)
: IsRepeated ? new RepeatedFieldAccessor(property, this) : IsRepeated ? new RepeatedFieldAccessor(property, this)
......
...@@ -43,35 +43,26 @@ namespace Google.Protobuf.Reflection ...@@ -43,35 +43,26 @@ namespace Google.Protobuf.Reflection
/// </summary> /// </summary>
public sealed class FileDescriptor : IDescriptor public sealed class FileDescriptor : IDescriptor
{ {
private readonly ByteString descriptorData;
private readonly FileDescriptorProto proto;
private readonly IList<MessageDescriptor> messageTypes;
private readonly IList<EnumDescriptor> enumTypes;
private readonly IList<ServiceDescriptor> services;
private readonly IList<FileDescriptor> dependencies;
private readonly IList<FileDescriptor> publicDependencies;
private readonly DescriptorPool pool;
private FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, FileDescriptor[] dependencies, DescriptorPool pool, bool allowUnknownDependencies, GeneratedCodeInfo generatedCodeInfo) private FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, FileDescriptor[] dependencies, DescriptorPool pool, bool allowUnknownDependencies, GeneratedCodeInfo generatedCodeInfo)
{ {
this.descriptorData = descriptorData; SerializedData = descriptorData;
this.pool = pool; DescriptorPool = pool;
this.proto = proto; Proto = proto;
this.dependencies = new ReadOnlyCollection<FileDescriptor>((FileDescriptor[]) dependencies.Clone()); Dependencies = new ReadOnlyCollection<FileDescriptor>((FileDescriptor[]) dependencies.Clone());
publicDependencies = DeterminePublicDependencies(this, proto, dependencies, allowUnknownDependencies); PublicDependencies = DeterminePublicDependencies(this, proto, dependencies, allowUnknownDependencies);
pool.AddPackage(Package, this); pool.AddPackage(Package, this);
messageTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.MessageType, MessageTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.MessageType,
(message, index) => (message, index) =>
new MessageDescriptor(message, this, null, index, generatedCodeInfo == null ? null : generatedCodeInfo.NestedTypes[index])); new MessageDescriptor(message, this, null, index, generatedCodeInfo.NestedTypes[index]));
enumTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.EnumType, EnumTypes = DescriptorUtil.ConvertAndMakeReadOnly(proto.EnumType,
(enumType, index) => (enumType, index) =>
new EnumDescriptor(enumType, this, null, index, generatedCodeInfo == null ? null : generatedCodeInfo.NestedEnums[index])); new EnumDescriptor(enumType, this, null, index, generatedCodeInfo.NestedEnums[index]));
services = DescriptorUtil.ConvertAndMakeReadOnly(proto.Service, Services = DescriptorUtil.ConvertAndMakeReadOnly(proto.Service,
(service, index) => (service, index) =>
new ServiceDescriptor(service, this, index)); new ServiceDescriptor(service, this, index));
} }
...@@ -132,99 +123,63 @@ namespace Google.Protobuf.Reflection ...@@ -132,99 +123,63 @@ namespace Google.Protobuf.Reflection
/// <value> /// <value>
/// The descriptor in its protocol message representation. /// The descriptor in its protocol message representation.
/// </value> /// </value>
internal FileDescriptorProto Proto internal FileDescriptorProto Proto { get; }
{
get { return proto; }
}
/// <value> /// <value>
/// The file name. /// The file name.
/// </value> /// </value>
public string Name public string Name => Proto.Name;
{
get { return proto.Name; }
}
/// <summary> /// <summary>
/// The package as declared in the .proto file. This may or may not /// The package as declared in the .proto file. This may or may not
/// be equivalent to the .NET namespace of the generated classes. /// be equivalent to the .NET namespace of the generated classes.
/// </summary> /// </summary>
public string Package public string Package => Proto.Package;
{
get { return proto.Package; }
}
/// <value> /// <value>
/// Unmodifiable list of top-level message types declared in this file. /// Unmodifiable list of top-level message types declared in this file.
/// </value> /// </value>
public IList<MessageDescriptor> MessageTypes public IList<MessageDescriptor> MessageTypes { get; }
{
get { return messageTypes; }
}
/// <value> /// <value>
/// Unmodifiable list of top-level enum types declared in this file. /// Unmodifiable list of top-level enum types declared in this file.
/// </value> /// </value>
public IList<EnumDescriptor> EnumTypes public IList<EnumDescriptor> EnumTypes { get; }
{
get { return enumTypes; }
}
/// <value> /// <value>
/// Unmodifiable list of top-level services declared in this file. /// Unmodifiable list of top-level services declared in this file.
/// </value> /// </value>
public IList<ServiceDescriptor> Services public IList<ServiceDescriptor> Services { get; }
{
get { return services; }
}
/// <value> /// <value>
/// Unmodifiable list of this file's dependencies (imports). /// Unmodifiable list of this file's dependencies (imports).
/// </value> /// </value>
public IList<FileDescriptor> Dependencies public IList<FileDescriptor> Dependencies { get; }
{
get { return dependencies; }
}
/// <value> /// <value>
/// Unmodifiable list of this file's public dependencies (public imports). /// Unmodifiable list of this file's public dependencies (public imports).
/// </value> /// </value>
public IList<FileDescriptor> PublicDependencies public IList<FileDescriptor> PublicDependencies { get; }
{
get { return publicDependencies; }
}
/// <value> /// <value>
/// The original serialized binary form of this descriptor. /// The original serialized binary form of this descriptor.
/// </value> /// </value>
public ByteString SerializedData public ByteString SerializedData { get; }
{
get { return descriptorData; }
}
/// <value> /// <value>
/// Implementation of IDescriptor.FullName - just returns the same as Name. /// Implementation of IDescriptor.FullName - just returns the same as Name.
/// </value> /// </value>
string IDescriptor.FullName string IDescriptor.FullName => Name;
{
get { return Name; }
}
/// <value> /// <value>
/// Implementation of IDescriptor.File - just returns this descriptor. /// Implementation of IDescriptor.File - just returns this descriptor.
/// </value> /// </value>
FileDescriptor IDescriptor.File FileDescriptor IDescriptor.File => this;
{
get { return this; }
}
/// <value> /// <value>
/// Pool containing symbol descriptors. /// Pool containing symbol descriptors.
/// </value> /// </value>
internal DescriptorPool DescriptorPool internal DescriptorPool DescriptorPool { get; }
{
get { return pool; }
}
/// <summary> /// <summary>
/// Finds a type (message, enum, service or extension) in the file by name. Does not find nested types. /// Finds a type (message, enum, service or extension) in the file by name. Does not find nested types.
...@@ -245,7 +200,7 @@ namespace Google.Protobuf.Reflection ...@@ -245,7 +200,7 @@ namespace Google.Protobuf.Reflection
{ {
name = Package + "." + name; name = Package + "." + name;
} }
T result = pool.FindSymbol<T>(name); T result = DescriptorPool.FindSymbol<T>(name);
if (result != null && result.File == this) if (result != null && result.File == this)
{ {
return result; return result;
...@@ -264,7 +219,7 @@ namespace Google.Protobuf.Reflection ...@@ -264,7 +219,7 @@ namespace Google.Protobuf.Reflection
/// file's dependencies, in the exact order listed in the .proto file. May be null, /// 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> /// in which case it is treated as an empty array.</param>
/// <param name="allowUnknownDependencies">Whether unknown dependencies are ignored (true) or cause an exception to be thrown (false).</param> /// <param name="allowUnknownDependencies">Whether unknown dependencies are ignored (true) or cause an exception to be thrown (false).</param>
/// <param name="generatedCodeInfo">Reflection information, if any. May be null, specifically for non-generated code.</param> /// <param name="generatedCodeInfo">Details about generated code, for the purposes of reflection.</param>
/// <exception cref="DescriptorValidationException">If <paramref name="proto"/> is not /// <exception cref="DescriptorValidationException">If <paramref name="proto"/> is not
/// a valid descriptor. This can occur for a number of reasons, such as a field /// 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> /// having an undefined type or because two messages were defined with the same name.</exception>
...@@ -291,15 +246,17 @@ namespace Google.Protobuf.Reflection ...@@ -291,15 +246,17 @@ namespace Google.Protobuf.Reflection
// need. // need.
if (dependencies.Length != proto.Dependency.Count) if (dependencies.Length != proto.Dependency.Count)
{ {
throw new DescriptorValidationException(result, throw new DescriptorValidationException(
"Dependencies passed to FileDescriptor.BuildFrom() don't match " + result,
"those listed in the FileDescriptorProto."); "Dependencies passed to FileDescriptor.BuildFrom() don't match " +
"those listed in the FileDescriptorProto.");
} }
for (int i = 0; i < proto.Dependency.Count; i++) for (int i = 0; i < proto.Dependency.Count; i++)
{ {
if (dependencies[i].Name != proto.Dependency[i]) if (dependencies[i].Name != proto.Dependency[i])
{ {
throw new DescriptorValidationException(result, throw new DescriptorValidationException(
result,
"Dependencies passed to FileDescriptor.BuildFrom() don't match " + "Dependencies passed to FileDescriptor.BuildFrom() don't match " +
"those listed in the FileDescriptorProto. Expected: " + "those listed in the FileDescriptorProto. Expected: " +
proto.Dependency[i] + " but was: " + dependencies[i].Name); proto.Dependency[i] + " but was: " + dependencies[i].Name);
...@@ -312,28 +269,29 @@ namespace Google.Protobuf.Reflection ...@@ -312,28 +269,29 @@ namespace Google.Protobuf.Reflection
private void CrossLink() private void CrossLink()
{ {
foreach (MessageDescriptor message in messageTypes) foreach (MessageDescriptor message in MessageTypes)
{ {
message.CrossLink(); message.CrossLink();
} }
foreach (ServiceDescriptor service in services) foreach (ServiceDescriptor service in Services)
{ {
service.CrossLink(); service.CrossLink();
} }
} }
/// <summary> /// <summary>
/// Creates an instance for generated code. /// Creates a descriptor for generated code.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The <paramref name="generatedCodeInfo"/> parameter should be null for descriptors which don't correspond to /// This method is only designed to be used by the results of generating code with protoc,
/// generated types. Otherwise, it should be a <see cref="GeneratedCodeInfo"/> with nested types and nested /// which creates the appropriate dependencies etc. It has to be public because the generated
/// enums corresponding to the types and enums contained within the file descriptor. /// code is "external", but should not be called directly by end users.
/// </remarks> /// </remarks>
public static FileDescriptor InternalBuildGeneratedFileFrom(byte[] descriptorData, public static FileDescriptor FromGeneratedCode(
FileDescriptor[] dependencies, byte[] descriptorData,
GeneratedCodeInfo generatedCodeInfo) FileDescriptor[] dependencies,
GeneratedCodeInfo generatedCodeInfo)
{ {
FileDescriptorProto proto; FileDescriptorProto proto;
try try
...@@ -345,8 +303,6 @@ namespace Google.Protobuf.Reflection ...@@ -345,8 +303,6 @@ namespace Google.Protobuf.Reflection
throw new ArgumentException("Failed to parse protocol buffer descriptor for generated code.", e); throw new ArgumentException("Failed to parse protocol buffer descriptor for generated code.", e);
} }
try try
{ {
// When building descriptors for generated code, we allow unknown // When building descriptors for generated code, we allow unknown
...@@ -355,7 +311,7 @@ namespace Google.Protobuf.Reflection ...@@ -355,7 +311,7 @@ namespace Google.Protobuf.Reflection
} }
catch (DescriptorValidationException e) catch (DescriptorValidationException e)
{ {
throw new ArgumentException("Invalid embedded descriptor for \"" + proto.Name + "\".", e); throw new ArgumentException($"Invalid embedded descriptor for \"{proto.Name}\".", e);
} }
} }
...@@ -367,7 +323,7 @@ namespace Google.Protobuf.Reflection ...@@ -367,7 +323,7 @@ namespace Google.Protobuf.Reflection
/// </returns> /// </returns>
public override string ToString() public override string ToString()
{ {
return "FileDescriptor for " + proto.Name; return $"FileDescriptor for {Name}";
} }
/// <summary> /// <summary>
......
#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; using System;
namespace Google.Protobuf.Reflection namespace Google.Protobuf.Reflection
...@@ -20,31 +51,31 @@ namespace Google.Protobuf.Reflection ...@@ -20,31 +51,31 @@ namespace Google.Protobuf.Reflection
/// <summary> /// <summary>
/// Irrelevant for file descriptors; the parser for message descriptors. /// Irrelevant for file descriptors; the parser for message descriptors.
/// </summary> /// </summary>
public MessageParser Parser { get; private set; } public MessageParser Parser { get; }
/// <summary> /// <summary>
/// Irrelevant for file descriptors; the CLR property names (in message descriptor field order) /// Irrelevant for file descriptors; the CLR property names (in message descriptor field order)
/// for fields in the message for message descriptors. /// for fields in the message for message descriptors.
/// </summary> /// </summary>
public string[] PropertyNames { get; private set; } public string[] PropertyNames { get; }
/// <summary> /// <summary>
/// Irrelevant for file descriptors; the CLR property "base" names (in message descriptor oneof order) /// Irrelevant for file descriptors; the CLR property "base" names (in message descriptor oneof order)
/// for oneofs in the message for message descriptors. It is expected that for a oneof name of "Foo", /// for oneofs in the message for message descriptors. It is expected that for a oneof name of "Foo",
/// there will be a "FooCase" property and a "ClearFoo" method. /// there will be a "FooCase" property and a "ClearFoo" method.
/// </summary> /// </summary>
public string[] OneofNames { get; private set; } public string[] OneofNames { get; }
/// <summary> /// <summary>
/// The reflection information for types within this file/message descriptor. Elements may be null /// The reflection information for types within this file/message descriptor. Elements may be null
/// if there is no corresponding generated type, e.g. for map entry types. /// if there is no corresponding generated type, e.g. for map entry types.
/// </summary> /// </summary>
public GeneratedCodeInfo[] NestedTypes { get; private set; } public GeneratedCodeInfo[] NestedTypes { get; }
/// <summary> /// <summary>
/// The CLR types for enums within this file/message descriptor. /// The CLR types for enums within this file/message descriptor.
/// </summary> /// </summary>
public Type[] NestedEnums { get; private set; } public Type[] NestedEnums { get; }
/// <summary> /// <summary>
/// Creates a GeneratedCodeInfo for a message descriptor, with nested types, nested enums, the CLR type, property names and oneof names. /// Creates a GeneratedCodeInfo for a message descriptor, with nested types, nested enums, the CLR type, property names and oneof names.
......
...@@ -56,153 +56,145 @@ namespace Google.Protobuf.Reflection ...@@ -56,153 +56,145 @@ namespace Google.Protobuf.Reflection
"google/protobuf/type.proto", "google/protobuf/type.proto",
}; };
private readonly DescriptorProto proto;
private readonly MessageDescriptor containingType;
private readonly IList<MessageDescriptor> nestedTypes;
private readonly IList<EnumDescriptor> enumTypes;
private readonly IList<FieldDescriptor> fieldsInDeclarationOrder; private readonly IList<FieldDescriptor> fieldsInDeclarationOrder;
private readonly IList<FieldDescriptor> fieldsInNumberOrder; private readonly IList<FieldDescriptor> fieldsInNumberOrder;
private readonly IDictionary<string, FieldDescriptor> jsonFieldMap; private readonly IDictionary<string, FieldDescriptor> jsonFieldMap;
private readonly FieldCollection fields;
private readonly IList<OneofDescriptor> oneofs;
// CLR representation of the type described by this descriptor, if any.
private readonly Type generatedType;
private readonly MessageParser parser;
internal MessageDescriptor(DescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int typeIndex, GeneratedCodeInfo generatedCodeInfo) internal MessageDescriptor(DescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int typeIndex, GeneratedCodeInfo generatedCodeInfo)
: base(file, file.ComputeFullName(parent, proto.Name), typeIndex) : base(file, file.ComputeFullName(parent, proto.Name), typeIndex)
{ {
this.proto = proto; Proto = proto;
parser = generatedCodeInfo == null ? null : generatedCodeInfo.Parser; Parser = generatedCodeInfo?.Parser;
generatedType = generatedCodeInfo == null ? null : generatedCodeInfo.ClrType; ClrType = generatedCodeInfo?.ClrType;
ContainingType = parent;
containingType = parent;
// Note use of generatedCodeInfo. rather than generatedCodeInfo?. here... we don't expect
oneofs = DescriptorUtil.ConvertAndMakeReadOnly( // to see any nested oneofs, types or enums in "not actually generated" code... we do
// expect fields though (for map entry messages).
Oneofs = DescriptorUtil.ConvertAndMakeReadOnly(
proto.OneofDecl, proto.OneofDecl,
(oneof, index) => (oneof, index) =>
new OneofDescriptor(oneof, file, this, index, generatedCodeInfo == null ? null : generatedCodeInfo.OneofNames[index])); new OneofDescriptor(oneof, file, this, index, generatedCodeInfo.OneofNames[index]));
nestedTypes = DescriptorUtil.ConvertAndMakeReadOnly( NestedTypes = DescriptorUtil.ConvertAndMakeReadOnly(
proto.NestedType, proto.NestedType,
(type, index) => (type, index) =>
new MessageDescriptor(type, file, this, index, generatedCodeInfo == null ? null : generatedCodeInfo.NestedTypes[index])); new MessageDescriptor(type, file, this, index, generatedCodeInfo.NestedTypes[index]));
enumTypes = DescriptorUtil.ConvertAndMakeReadOnly( EnumTypes = DescriptorUtil.ConvertAndMakeReadOnly(
proto.EnumType, proto.EnumType,
(type, index) => (type, index) =>
new EnumDescriptor(type, file, this, index, generatedCodeInfo == null ? null : generatedCodeInfo.NestedEnums[index])); new EnumDescriptor(type, file, this, index, generatedCodeInfo.NestedEnums[index]));
fieldsInDeclarationOrder = DescriptorUtil.ConvertAndMakeReadOnly( fieldsInDeclarationOrder = DescriptorUtil.ConvertAndMakeReadOnly(
proto.Field, proto.Field,
(field, index) => (field, index) =>
new FieldDescriptor(field, file, this, index, generatedCodeInfo == null ? null : generatedCodeInfo.PropertyNames[index])); new FieldDescriptor(field, file, this, index, generatedCodeInfo?.PropertyNames[index]));
fieldsInNumberOrder = new ReadOnlyCollection<FieldDescriptor>(fieldsInDeclarationOrder.OrderBy(field => field.FieldNumber).ToArray()); fieldsInNumberOrder = new ReadOnlyCollection<FieldDescriptor>(fieldsInDeclarationOrder.OrderBy(field => field.FieldNumber).ToArray());
// TODO: Use field => field.Proto.JsonName when we're confident it's appropriate. (And then use it in the formatter, too.) // TODO: Use field => field.Proto.JsonName when we're confident it's appropriate. (And then use it in the formatter, too.)
jsonFieldMap = new ReadOnlyDictionary<string, FieldDescriptor>(fieldsInNumberOrder.ToDictionary(field => JsonFormatter.ToCamelCase(field.Name))); jsonFieldMap = new ReadOnlyDictionary<string, FieldDescriptor>(fieldsInNumberOrder.ToDictionary(field => JsonFormatter.ToCamelCase(field.Name)));
file.DescriptorPool.AddSymbol(this); file.DescriptorPool.AddSymbol(this);
fields = new FieldCollection(this); Fields = new FieldCollection(this);
}
/// <summary>
/// Returns the total number of nested types and enums, recursively.
/// </summary>
private int CountTotalGeneratedTypes()
{
return nestedTypes.Sum(nested => nested.CountTotalGeneratedTypes()) + enumTypes.Count;
} }
/// <summary> /// <summary>
/// The brief name of the descriptor's target. /// The brief name of the descriptor's target.
/// </summary> /// </summary>
public override string Name { get { return proto.Name; } } public override string Name => Proto.Name;
internal DescriptorProto Proto { get { return proto; } } internal DescriptorProto Proto { get; }
/// <summary> /// <summary>
/// The generated type for this message, or <c>null</c> if the descriptor does not represent a generated type. /// The CLR type used to represent message instances from this descriptor.
/// </summary> /// </summary>
public Type GeneratedType { get { return generatedType; } } /// <remarks>
/// <para>
/// The value returned by this property will be non-null for all regular fields. However,
/// if a message containing a map field is introspected, the list of nested messages will include
/// an auto-generated nested key/value pair message for the field. This is not represented in any
/// generated type, so this property will return null in such cases.
/// </para>
/// <para>
/// For wrapper types (<see cref="Google.Protobuf.WellKnownTypes.StringValue"/> and the like), the type returned here
/// will be the generated message type, not the native type used by reflection for fields of those types. Code
/// using reflection should call <see cref="IsWrapperType"/> to determine whether a message descriptor represents
/// a wrapper type, and handle the result appropriately.
/// </para>
/// </remarks>
public Type ClrType { get; }
/// <summary> /// <summary>
/// A parser for this message type. /// A parser for this message type.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <para>
/// As <see cref="MessageDescriptor"/> is not generic, this cannot be statically /// As <see cref="MessageDescriptor"/> is not generic, this cannot be statically
/// typed to the relevant type, but if <see cref="GeneratedType"/> returns a non-null value, the parser returned /// typed to the relevant type, but it should produce objects of a type compatible with <see cref="ClrType"/>.
/// </para>
/// <para>
/// The value returned by this property will be non-null for all regular fields. However,
/// if a message containing a map field is introspected, the list of nested messages will include
/// an auto-generated nested key/value pair message for the field. No message parser object is created for
/// such messages, so this property will return null in such cases.
/// </para>
/// <para>
/// For wrapper types (<see cref="Google.Protobuf.WellKnownTypes.StringValue"/> and the like), the parser returned here
/// will be the generated message type, not the native type used by reflection for fields of those types. Code
/// using reflection should call <see cref="IsWrapperType"/> to determine whether a message descriptor represents
/// a wrapper type, and handle the result appropriately.
/// </para>
/// </remarks> /// </remarks>
public MessageParser Parser { get { return parser; } } public MessageParser Parser { get; }
/// <summary> /// <summary>
/// Returns whether this message is one of the "well known types" which may have runtime/protoc support. /// Returns whether this message is one of the "well known types" which may have runtime/protoc support.
/// </summary> /// </summary>
internal bool IsWellKnownType internal bool IsWellKnownType => File.Package == "google.protobuf" && WellKnownTypeNames.Contains(File.Name);
{
get /// <summary>
{ /// Returns whether this message is one of the "wrapper types" used for fields which represent primitive values
return File.Package == "google.protobuf" && WellKnownTypeNames.Contains(File.Name); /// with the addition of presence.
} /// </summary>
} internal bool IsWrapperType => File.Package == "google.protobuf" && File.Name == "google/protobuf/wrappers.proto";
/// <value> /// <value>
/// If this is a nested type, get the outer descriptor, otherwise null. /// If this is a nested type, get the outer descriptor, otherwise null.
/// </value> /// </value>
public MessageDescriptor ContainingType public MessageDescriptor ContainingType { get; }
{
get { return containingType; }
}
/// <value> /// <value>
/// A collection of fields, which can be retrieved by name or field number. /// A collection of fields, which can be retrieved by name or field number.
/// </value> /// </value>
public FieldCollection Fields public FieldCollection Fields { get; }
{
get { return fields; }
}
/// <value> /// <value>
/// An unmodifiable list of this message type's nested types. /// An unmodifiable list of this message type's nested types.
/// </value> /// </value>
public IList<MessageDescriptor> NestedTypes public IList<MessageDescriptor> NestedTypes { get; }
{
get { return nestedTypes; }
}
/// <value> /// <value>
/// An unmodifiable list of this message type's enum types. /// An unmodifiable list of this message type's enum types.
/// </value> /// </value>
public IList<EnumDescriptor> EnumTypes public IList<EnumDescriptor> EnumTypes { get; }
{
get { return enumTypes; }
}
/// <value> /// <value>
/// An unmodifiable list of the "oneof" field collections in this message type. /// An unmodifiable list of the "oneof" field collections in this message type.
/// </value> /// </value>
public IList<OneofDescriptor> Oneofs public IList<OneofDescriptor> Oneofs { get; }
{
get { return oneofs; }
}
/// <summary> /// <summary>
/// Finds a field by field name. /// Finds a field by field name.
/// </summary> /// </summary>
/// <param name="name">The unqualified name of the field (e.g. "foo").</param> /// <param name="name">The unqualified name of the field (e.g. "foo").</param>
/// <returns>The field's descriptor, or null if not found.</returns> /// <returns>The field's descriptor, or null if not found.</returns>
public FieldDescriptor FindFieldByName(String name) public FieldDescriptor FindFieldByName(String name) =>File.DescriptorPool.FindSymbol<FieldDescriptor>(FullName + "." + name);
{
return File.DescriptorPool.FindSymbol<FieldDescriptor>(FullName + "." + name);
}
/// <summary> /// <summary>
/// Finds a field by field number. /// Finds a field by field number.
/// </summary> /// </summary>
/// <param name="number">The field number within this message type.</param> /// <param name="number">The field number within this message type.</param>
/// <returns>The field's descriptor, or null if not found.</returns> /// <returns>The field's descriptor, or null if not found.</returns>
public FieldDescriptor FindFieldByNumber(int number) public FieldDescriptor FindFieldByNumber(int number) => File.DescriptorPool.FindFieldByNumber(this, number);
{
return File.DescriptorPool.FindFieldByNumber(this, number);
}
/// <summary> /// <summary>
/// Finds a nested descriptor by name. The is valid for fields, nested /// Finds a nested descriptor by name. The is valid for fields, nested
...@@ -210,18 +202,15 @@ namespace Google.Protobuf.Reflection ...@@ -210,18 +202,15 @@ namespace Google.Protobuf.Reflection
/// </summary> /// </summary>
/// <param name="name">The unqualified name of the descriptor, e.g. "Foo"</param> /// <param name="name">The unqualified name of the descriptor, e.g. "Foo"</param>
/// <returns>The descriptor, or null if not found.</returns> /// <returns>The descriptor, or null if not found.</returns>
public T FindDescriptor<T>(string name) public T FindDescriptor<T>(string name) where T : class, IDescriptor =>
where T : class, IDescriptor File.DescriptorPool.FindSymbol<T>(FullName + "." + name);
{
return File.DescriptorPool.FindSymbol<T>(FullName + "." + name);
}
/// <summary> /// <summary>
/// Looks up and cross-links all fields and nested types. /// Looks up and cross-links all fields and nested types.
/// </summary> /// </summary>
internal void CrossLink() internal void CrossLink()
{ {
foreach (MessageDescriptor message in nestedTypes) foreach (MessageDescriptor message in NestedTypes)
{ {
message.CrossLink(); message.CrossLink();
} }
...@@ -231,7 +220,7 @@ namespace Google.Protobuf.Reflection ...@@ -231,7 +220,7 @@ namespace Google.Protobuf.Reflection
field.CrossLink(); field.CrossLink();
} }
foreach (OneofDescriptor oneof in oneofs) foreach (OneofDescriptor oneof in Oneofs)
{ {
oneof.CrossLink(); oneof.CrossLink();
} }
...@@ -253,10 +242,7 @@ namespace Google.Protobuf.Reflection ...@@ -253,10 +242,7 @@ namespace Google.Protobuf.Reflection
/// Returns the fields in the message as an immutable list, in the order in which they /// Returns the fields in the message as an immutable list, in the order in which they
/// are declared in the source .proto file. /// are declared in the source .proto file.
/// </value> /// </value>
public IList<FieldDescriptor> InDeclarationOrder() public IList<FieldDescriptor> InDeclarationOrder() => messageDescriptor.fieldsInDeclarationOrder;
{
return messageDescriptor.fieldsInDeclarationOrder;
}
/// <value> /// <value>
/// Returns the fields in the message as an immutable list, in ascending field number /// Returns the fields in the message as an immutable list, in ascending field number
...@@ -264,10 +250,7 @@ namespace Google.Protobuf.Reflection ...@@ -264,10 +250,7 @@ namespace Google.Protobuf.Reflection
/// index in the list to the field number; to retrieve a field by field number, it is better /// index in the list to the field number; to retrieve a field by field number, it is better
/// to use the <see cref="FieldCollection"/> indexer. /// to use the <see cref="FieldCollection"/> indexer.
/// </value> /// </value>
public IList<FieldDescriptor> InFieldNumberOrder() public IList<FieldDescriptor> InFieldNumberOrder() => messageDescriptor.fieldsInNumberOrder;
{
return messageDescriptor.fieldsInNumberOrder;
}
// TODO: consider making this public in the future. (Being conservative for now...) // TODO: consider making this public in the future. (Being conservative for now...)
...@@ -276,10 +259,7 @@ namespace Google.Protobuf.Reflection ...@@ -276,10 +259,7 @@ namespace Google.Protobuf.Reflection
/// in the JSON representation to the field descriptors. For example, a field <c>foo_bar</c> /// in the JSON representation to the field descriptors. For example, a field <c>foo_bar</c>
/// in the message would result in an entry with a key <c>fooBar</c>. /// in the message would result in an entry with a key <c>fooBar</c>.
/// </value> /// </value>
internal IDictionary<string, FieldDescriptor> ByJsonName() internal IDictionary<string, FieldDescriptor> ByJsonName() => messageDescriptor.jsonFieldMap;
{
return messageDescriptor.jsonFieldMap;
}
/// <summary> /// <summary>
/// Retrieves the descriptor for the field with the given number. /// Retrieves the descriptor for the field with the given number.
......
...@@ -86,8 +86,7 @@ namespace Google.Protobuf.Reflection ...@@ -86,8 +86,7 @@ namespace Google.Protobuf.Reflection
/// in a particular message. /// in a particular message.
/// </summary> /// </summary>
/// <value> /// <value>
/// The accessor used for reflective access, or <c>null</c> if reflection is not /// The accessor used for reflective access.
/// supported by this descriptor.
/// </value> /// </value>
public OneofAccessor Accessor { get { return accessor; } } public OneofAccessor Accessor { get { return accessor; } }
...@@ -106,19 +105,15 @@ namespace Google.Protobuf.Reflection ...@@ -106,19 +105,15 @@ namespace Google.Protobuf.Reflection
private OneofAccessor CreateAccessor(string clrName) private OneofAccessor CreateAccessor(string clrName)
{ {
if (containingType.GeneratedType == null || clrName == null) var caseProperty = containingType.ClrType.GetProperty(clrName + "Case");
{
return null;
}
var caseProperty = containingType.GeneratedType.GetProperty(clrName + "Case");
if (caseProperty == null) if (caseProperty == null)
{ {
throw new DescriptorValidationException(this, "Property " + clrName + "Case not found in " + containingType.GeneratedType); throw new DescriptorValidationException(this, $"Property {clrName}Case not found in {containingType.ClrType}");
} }
var clearMethod = containingType.GeneratedType.GetMethod("Clear" + clrName); var clearMethod = containingType.ClrType.GetMethod("Clear" + clrName);
if (clearMethod == null) if (clearMethod == null)
{ {
throw new DescriptorValidationException(this, "Method Clear" + clrName + " not found in " + containingType.GeneratedType); throw new DescriptorValidationException(this, $"Method Clear{clrName} not found in {containingType.ClrType}");
} }
return new OneofAccessor(caseProperty, clearMethod, this); return new OneofAccessor(caseProperty, clearMethod, this);
......
...@@ -166,7 +166,7 @@ void ReflectionClassGenerator::WriteDescriptor(io::Printer* printer) { ...@@ -166,7 +166,7 @@ void ReflectionClassGenerator::WriteDescriptor(io::Printer* printer) {
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// Invoke InternalBuildGeneratedFileFrom() to build the file. // Invoke InternalBuildGeneratedFileFrom() to build the file.
printer->Print( printer->Print(
"descriptor = pbr::FileDescriptor.InternalBuildGeneratedFileFrom(descriptorData,\n"); "descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,\n");
printer->Print(" new pbr::FileDescriptor[] { "); printer->Print(" new pbr::FileDescriptor[] { ");
for (int i = 0; i < file_->dependency_count(); i++) { for (int i = 0; i < file_->dependency_count(); i++) {
// descriptor.proto is special: we don't allow access to the generated code, but there's // descriptor.proto is special: we don't allow access to the generated code, but there's
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment