• 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.Collections.Generic;
12 using System.Collections.ObjectModel;
13 using System.Linq;
14 using Google.Protobuf.Collections;
15 using Google.Protobuf.Compatibility;
16 
17 namespace Google.Protobuf.Reflection
18 {
19     /// <summary>
20     /// Describes a "oneof" field collection in a message type: a set of
21     /// fields of which at most one can be set in any particular message.
22     /// </summary>
23     public sealed class OneofDescriptor : DescriptorBase
24     {
25         private MessageDescriptor containingType;
26         private IList<FieldDescriptor> fields;
27         private readonly OneofAccessor accessor;
28 
OneofDescriptor(OneofDescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int index, string clrName)29         internal OneofDescriptor(OneofDescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int index, string clrName)
30             : base(file, file.ComputeFullName(parent, proto.Name), index, parent.Features.MergedWith(proto.Options?.Features))
31         {
32             this.Proto = proto;
33             containingType = parent;
34             file.DescriptorPool.AddSymbol(this);
35 
36             // It's useful to determine whether or not this is a synthetic oneof before cross-linking. That means
37             // diving into the proto directly rather than using FieldDescriptor, but that's okay.
38             var firstFieldInOneof = parent.Proto.Field.FirstOrDefault(fieldProto => fieldProto.HasOneofIndex && fieldProto.OneofIndex == index);
39             IsSynthetic = firstFieldInOneof?.Proto3Optional ?? false;
40 
41             accessor = CreateAccessor(clrName);
42         }
43 
44         /// <summary>
45         /// The brief name of the descriptor's target.
46         /// </summary>
47         public override string Name => Proto.Name;
48 
49         // Visible for testing
50         internal OneofDescriptorProto Proto { get; }
51 
52         /// <summary>
53         /// Returns a clone of the underlying <see cref="OneofDescriptorProto"/> describing this oneof.
54         /// Note that a copy is taken every time this method is called, so clients using it frequently
55         /// (and not modifying it) may want to cache the returned value.
56         /// </summary>
57         /// <returns>A protobuf representation of this oneof descriptor.</returns>
ToProto()58         public OneofDescriptorProto ToProto() => Proto.Clone();
59 
60         /// <summary>
61         /// Gets the message type containing this oneof.
62         /// </summary>
63         /// <value>
64         /// The message type containing this oneof.
65         /// </value>
66         public MessageDescriptor ContainingType
67         {
68             get { return containingType; }
69         }
70 
71         /// <summary>
72         /// Gets the fields within this oneof, in declaration order.
73         /// </summary>
74         /// <value>
75         /// The fields within this oneof, in declaration order.
76         /// </value>
77         public IList<FieldDescriptor> Fields { get { return fields; } }
78 
79         /// <summary>
80         /// Returns <c>true</c> if this oneof is a synthetic oneof containing a proto3 optional field;
81         /// <c>false</c> otherwise.
82         /// </summary>
83         public bool IsSynthetic { get; }
84 
85         /// <summary>
86         /// Gets an accessor for reflective access to the values associated with the oneof
87         /// in a particular message.
88         /// </summary>
89         /// <remarks>
90         /// <para>
91         /// In descriptors for generated code, the value returned by this property will always be non-null.
92         /// </para>
93         /// <para>
94         /// In dynamically loaded descriptors, the value returned by this property will current be null;
95         /// if and when dynamic messages are supported, it will return a suitable accessor to work with
96         /// them.
97         /// </para>
98         /// </remarks>
99         /// <value>
100         /// The accessor used for reflective access.
101         /// </value>
102         public OneofAccessor Accessor { get { return accessor; } }
103 
104         /// <summary>
105         /// The (possibly empty) set of custom options for this oneof.
106         /// </summary>
107         [Obsolete("CustomOptions are obsolete. Use the GetOptions method.")]
108         public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
109 
110         /// <summary>
111         /// The <c>OneofOptions</c>, defined in <c>descriptor.proto</c>.
112         /// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
113         /// Custom options can be retrieved as extensions of the returned message.
114         /// NOTE: A defensive copy is created each time this property is retrieved.
115         /// </summary>
GetOptions()116         public OneofOptions GetOptions()
117         {
118             var clone = Proto.Options?.Clone();
119             if (clone is null)
120             {
121                 return null;
122             }
123             // Clients should be using feature accessor methods, not accessing features on the
124             // options proto.
125             clone.Features = null;
126             return clone;
127         }
128 
129         /// <summary>
130         /// Gets a single value oneof option for this descriptor
131         /// </summary>
132         [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
GetOption(Extension<OneofOptions, T> extension)133         public T GetOption<T>(Extension<OneofOptions, T> extension)
134         {
135             var value = Proto.Options.GetExtension(extension);
136             return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value;
137         }
138 
139         /// <summary>
140         /// Gets a repeated value oneof option for this descriptor
141         /// </summary>
142         [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
GetOption(RepeatedExtension<OneofOptions, T> extension)143         public RepeatedField<T> GetOption<T>(RepeatedExtension<OneofOptions, T> extension)
144         {
145             return Proto.Options.GetExtension(extension).Clone();
146         }
147 
CrossLink()148         internal void CrossLink()
149         {
150             List<FieldDescriptor> fieldCollection = new List<FieldDescriptor>();
151             foreach (var field in ContainingType.Fields.InDeclarationOrder())
152             {
153                 if (field.ContainingOneof == this)
154                 {
155                     fieldCollection.Add(field);
156                 }
157             }
158             fields = new ReadOnlyCollection<FieldDescriptor>(fieldCollection);
159         }
160 
CreateAccessor(string clrName)161         private OneofAccessor CreateAccessor(string clrName)
162         {
163             // We won't have a CLR name if this is from a dynamically-loaded FileDescriptor.
164             // TODO: Support dynamic messages.
165             if (clrName == null)
166             {
167                 return null;
168             }
169             if (IsSynthetic)
170             {
171                 return OneofAccessor.ForSyntheticOneof(this);
172             }
173             else
174             {
175                 var caseProperty = containingType.ClrType.GetProperty(clrName + "Case");
176                 if (caseProperty == null)
177                 {
178                     throw new DescriptorValidationException(this, $"Property {clrName}Case not found in {containingType.ClrType}");
179                 }
180                 if (!caseProperty.CanRead)
181                 {
182                     throw new ArgumentException($"Cannot read from property {clrName}Case in {containingType.ClrType}");
183                 }
184                 var clearMethod = containingType.ClrType.GetMethod("Clear" + clrName);
185                 if (clearMethod == null)
186                 {
187                     throw new DescriptorValidationException(this, $"Method Clear{clrName} not found in {containingType.ClrType}");
188                 }
189                 return OneofAccessor.ForRegularOneof(this, caseProperty, clearMethod);
190             }
191         }
192     }
193 }
194