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