• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #region Copyright notice and license
2 // Protocol Buffers - Google's data interchange format
3 // Copyright 2015 Google Inc.  All rights reserved.
4 //
5 // Use of this source code is governed by a BSD-style
6 // license that can be found in the LICENSE file or at
7 // https://developers.google.com/open-source/licenses/bsd
8 #endregion
9 
10 using System;
11 using System.Diagnostics.CodeAnalysis;
12 using System.Reflection;
13 using Google.Protobuf.Compatibility;
14 
15 namespace Google.Protobuf.Reflection
16 {
17     /// <summary>
18     /// Accessor for single fields.
19     /// </summary>
20     internal sealed class SingleFieldAccessor : FieldAccessorBase
21     {
22         // All the work here is actually done in the constructor - it creates the appropriate delegates.
23         // There are various cases to consider, based on the property type (message, string/bytes, or "genuine" primitive)
24         // and proto2 vs proto3 for non-message types, as proto3 doesn't support "full" presence detection or default
25         // values.
26 
27         private readonly Action<IMessage, object> setValueDelegate;
28         private readonly Action<IMessage> clearDelegate;
29         private readonly Func<IMessage, bool> hasDelegate;
30 
SingleFieldAccessor( [DynamicallyAccessedMembers(GeneratedClrTypeInfo.MessageAccessibility)] Type messageType, PropertyInfo property, FieldDescriptor descriptor)31         internal SingleFieldAccessor(
32             [DynamicallyAccessedMembers(GeneratedClrTypeInfo.MessageAccessibility)]
33             Type messageType, PropertyInfo property, FieldDescriptor descriptor) : base(property, descriptor)
34         {
35             if (!property.CanWrite)
36             {
37                 throw new ArgumentException("Not all required properties/methods available");
38             }
39             setValueDelegate = ReflectionUtil.CreateActionIMessageObject(property.GetSetMethod());
40 
41             // Note: this looks worrying in that we access the containing oneof, which isn't valid until cross-linking
42             // is complete... but field accessors aren't created until after cross-linking.
43             // The oneof itself won't be cross-linked yet, but that's okay: the oneof accessor is created
44             // earlier.
45 
46             // Message fields always support presence, via null checks.
47             if (descriptor.FieldType == FieldType.Message)
48             {
49                 hasDelegate = message => GetValue(message) != null;
50                 clearDelegate = message => SetValue(message, null);
51             }
52             // Oneof fields always support presence, via case checks.
53             // Note that clearing the field is a no-op unless that specific field is the current "case".
54             else if (descriptor.RealContainingOneof != null)
55             {
56                 var oneofAccessor = descriptor.RealContainingOneof.Accessor;
57                 hasDelegate = message => oneofAccessor.GetCaseFieldDescriptor(message) == descriptor;
58                 clearDelegate = message =>
59                 {
60                     // Clear on a field only affects the oneof itself if the current case is the field we're accessing.
61                     if (oneofAccessor.GetCaseFieldDescriptor(message) == descriptor)
62                     {
63                         oneofAccessor.Clear(message);
64                     }
65                 };
66             }
67             // Anything else that supports presence should have a "HasXyz" property and a "ClearXyz"
68             // method.
69             else if (descriptor.HasPresence)
70             {
71                 MethodInfo hasMethod = messageType.GetRuntimeProperty("Has" + property.Name).GetMethod;
72                 if (hasMethod == null)
73                 {
74                     throw new ArgumentException("Not all required properties/methods are available");
75                 }
76                 hasDelegate = ReflectionUtil.CreateFuncIMessageBool(hasMethod);
77                 MethodInfo clearMethod = messageType.GetRuntimeMethod("Clear" + property.Name, ReflectionUtil.EmptyTypes);
78                 if (clearMethod == null)
79                 {
80                     throw new ArgumentException("Not all required properties/methods are available");
81                 }
82                 clearDelegate = ReflectionUtil.CreateActionIMessage(clearMethod);
83             }
84             // Otherwise, we don't support presence.
85             else
86             {
87                 hasDelegate = message => throw new InvalidOperationException("Presence is not implemented for this field");
88 
89                 // While presence isn't supported, clearing still is; it's just setting to a default value.
90                 object defaultValue = GetDefaultValue(descriptor);
91                 clearDelegate = message => SetValue(message, defaultValue);
92             }
93         }
94 
95         private static object GetDefaultValue(FieldDescriptor descriptor) =>
96             descriptor.FieldType switch
97             {
98                 FieldType.Bool => false,
99                 FieldType.Bytes => ByteString.Empty,
100                 FieldType.String => "",
101                 FieldType.Double => 0.0,
102                 FieldType.SInt32 or FieldType.Int32 or FieldType.SFixed32 or FieldType.Enum => 0,
103                 FieldType.Fixed32 or FieldType.UInt32 => (uint)0,
104                 FieldType.Fixed64 or FieldType.UInt64 => 0UL,
105                 FieldType.SFixed64 or FieldType.Int64 or FieldType.SInt64 => 0L,
106                 FieldType.Float => 0f,
107                 FieldType.Message or FieldType.Group => null,
108                 _ => throw new ArgumentException("Invalid field type"),
109             };
110 
111         public override void Clear(IMessage message) => clearDelegate(message);
112         public override bool HasValue(IMessage message) => hasDelegate(message);
SetValue(IMessage message, object value)113         public override void SetValue(IMessage message, object value) => setValueDelegate(message, value);
114     }
115 }
116