1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2008 Google Inc. All rights reserved. 4 // https://developers.google.com/protocol-buffers/ 5 // 6 // Redistribution and use in source and binary forms, with or without 7 // modification, are permitted provided that the following conditions are 8 // met: 9 // 10 // * Redistributions of source code must retain the above copyright 11 // notice, this list of conditions and the following disclaimer. 12 // * Redistributions in binary form must reproduce the above 13 // copyright notice, this list of conditions and the following disclaimer 14 // in the documentation and/or other materials provided with the 15 // distribution. 16 // * Neither the name of Google Inc. nor the names of its 17 // contributors may be used to endorse or promote products derived from 18 // this software without specific prior written permission. 19 // 20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 #endregion 32 33 using Google.Protobuf.Collections; 34 using System; 35 using System.Collections.Generic; 36 using System.Linq; 37 using System.Security; 38 39 namespace Google.Protobuf 40 { 41 /// <summary> 42 /// Methods for managing <see cref="ExtensionSet{TTarget}"/>s with null checking. 43 /// 44 /// Most users will not use this class directly and its API is experimental and subject to change. 45 /// </summary> 46 public static class ExtensionSet 47 { 48 private static bool TryGetValue<TTarget>(ref ExtensionSet<TTarget> set, Extension extension, out IExtensionValue value) where TTarget : IExtendableMessage<TTarget> 49 { 50 if (set == null) 51 { 52 value = null; 53 return false; 54 } 55 return set.ValuesByNumber.TryGetValue(extension.FieldNumber, out value); 56 } 57 58 /// <summary> 59 /// Gets the value of the specified extension 60 /// </summary> 61 public static TValue Get<TTarget, TValue>(ref ExtensionSet<TTarget> set, Extension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget> 62 { 63 IExtensionValue value; 64 if (TryGetValue(ref set, extension, out value)) 65 { 66 return ((ExtensionValue<TValue>)value).GetValue(); 67 } 68 else 69 { 70 return extension.DefaultValue; 71 } 72 } 73 74 /// <summary> 75 /// Gets the value of the specified repeated extension or null if it doesn't exist in this set 76 /// </summary> 77 public static RepeatedField<TValue> Get<TTarget, TValue>(ref ExtensionSet<TTarget> set, RepeatedExtension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget> 78 { 79 IExtensionValue value; 80 if (TryGetValue(ref set, extension, out value)) 81 { 82 return ((RepeatedExtensionValue<TValue>)value).GetValue(); 83 } 84 else 85 { 86 return null; 87 } 88 } 89 90 /// <summary> 91 /// Gets the value of the specified repeated extension, registering it if it doesn't exist 92 /// </summary> 93 public static RepeatedField<TValue> GetOrInitialize<TTarget, TValue>(ref ExtensionSet<TTarget> set, RepeatedExtension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget> 94 { 95 IExtensionValue value; 96 if (set == null) 97 { 98 value = extension.CreateValue(); 99 set = new ExtensionSet<TTarget>(); 100 set.ValuesByNumber.Add(extension.FieldNumber, value); 101 } 102 else 103 { 104 if (!set.ValuesByNumber.TryGetValue(extension.FieldNumber, out value)) 105 { 106 value = extension.CreateValue(); 107 set.ValuesByNumber.Add(extension.FieldNumber, value); 108 } 109 } 110 111 return ((RepeatedExtensionValue<TValue>)value).GetValue(); 112 } 113 114 /// <summary> 115 /// Sets the value of the specified extension. This will make a new instance of ExtensionSet if the set is null. 116 /// </summary> 117 public static void Set<TTarget, TValue>(ref ExtensionSet<TTarget> set, Extension<TTarget, TValue> extension, TValue value) where TTarget : IExtendableMessage<TTarget> 118 { 119 ProtoPreconditions.CheckNotNullUnconstrained(value, nameof(value)); 120 121 IExtensionValue extensionValue; 122 if (set == null) 123 { 124 extensionValue = extension.CreateValue(); 125 set = new ExtensionSet<TTarget>(); 126 set.ValuesByNumber.Add(extension.FieldNumber, extensionValue); 127 } 128 else 129 { 130 if (!set.ValuesByNumber.TryGetValue(extension.FieldNumber, out extensionValue)) 131 { 132 extensionValue = extension.CreateValue(); 133 set.ValuesByNumber.Add(extension.FieldNumber, extensionValue); 134 } 135 } 136 137 ((ExtensionValue<TValue>)extensionValue).SetValue(value); 138 } 139 140 /// <summary> 141 /// Gets whether the value of the specified extension is set 142 /// </summary> 143 public static bool Has<TTarget, TValue>(ref ExtensionSet<TTarget> set, Extension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget> 144 { 145 IExtensionValue value; 146 return TryGetValue(ref set, extension, out value); 147 } 148 149 /// <summary> 150 /// Clears the value of the specified extension 151 /// </summary> 152 public static void Clear<TTarget, TValue>(ref ExtensionSet<TTarget> set, Extension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget> 153 { 154 if (set == null) 155 { 156 return; 157 } 158 set.ValuesByNumber.Remove(extension.FieldNumber); 159 if (set.ValuesByNumber.Count == 0) 160 { 161 set = null; 162 } 163 } 164 165 /// <summary> 166 /// Clears the value of the specified extension 167 /// </summary> 168 public static void Clear<TTarget, TValue>(ref ExtensionSet<TTarget> set, RepeatedExtension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget> 169 { 170 if (set == null) 171 { 172 return; 173 } 174 set.ValuesByNumber.Remove(extension.FieldNumber); 175 if (set.ValuesByNumber.Count == 0) 176 { 177 set = null; 178 } 179 } 180 181 /// <summary> 182 /// Tries to merge a field from the coded input, returning true if the field was merged. 183 /// If the set is null or the field was not otherwise merged, this returns false. 184 /// </summary> 185 public static bool TryMergeFieldFrom<TTarget>(ref ExtensionSet<TTarget> set, CodedInputStream stream) where TTarget : IExtendableMessage<TTarget> 186 { 187 ParseContext.Initialize(stream, out ParseContext ctx); 188 try 189 { 190 return TryMergeFieldFrom<TTarget>(ref set, ref ctx); 191 } 192 finally 193 { 194 ctx.CopyStateTo(stream); 195 } 196 } 197 198 /// <summary> 199 /// Tries to merge a field from the coded input, returning true if the field was merged. 200 /// If the set is null or the field was not otherwise merged, this returns false. 201 /// </summary> 202 public static bool TryMergeFieldFrom<TTarget>(ref ExtensionSet<TTarget> set, ref ParseContext ctx) where TTarget : IExtendableMessage<TTarget> 203 { 204 Extension extension; 205 int lastFieldNumber = WireFormat.GetTagFieldNumber(ctx.LastTag); 206 207 IExtensionValue extensionValue; 208 if (set != null && set.ValuesByNumber.TryGetValue(lastFieldNumber, out extensionValue)) 209 { 210 extensionValue.MergeFrom(ref ctx); 211 return true; 212 } 213 else if (ctx.ExtensionRegistry != null && ctx.ExtensionRegistry.ContainsInputField(ctx.LastTag, typeof(TTarget), out extension)) 214 { 215 IExtensionValue value = extension.CreateValue(); 216 value.MergeFrom(ref ctx); 217 set = (set ?? new ExtensionSet<TTarget>()); 218 set.ValuesByNumber.Add(extension.FieldNumber, value); 219 return true; 220 } 221 else 222 { 223 return false; 224 } 225 } 226 227 /// <summary> 228 /// Merges the second set into the first set, creating a new instance if first is null 229 /// </summary> 230 public static void MergeFrom<TTarget>(ref ExtensionSet<TTarget> first, ExtensionSet<TTarget> second) where TTarget : IExtendableMessage<TTarget> 231 { 232 if (second == null) 233 { 234 return; 235 } 236 if (first == null) 237 { 238 first = new ExtensionSet<TTarget>(); 239 } 240 foreach (var pair in second.ValuesByNumber) 241 { 242 IExtensionValue value; 243 if (first.ValuesByNumber.TryGetValue(pair.Key, out value)) 244 { 245 value.MergeFrom(pair.Value); 246 } 247 else 248 { 249 var cloned = pair.Value.Clone(); 250 first.ValuesByNumber[pair.Key] = cloned; 251 } 252 } 253 } 254 255 /// <summary> 256 /// Clones the set into a new set. If the set is null, this returns null 257 /// </summary> 258 public static ExtensionSet<TTarget> Clone<TTarget>(ExtensionSet<TTarget> set) where TTarget : IExtendableMessage<TTarget> 259 { 260 if (set == null) 261 { 262 return null; 263 } 264 265 var newSet = new ExtensionSet<TTarget>(); 266 foreach (var pair in set.ValuesByNumber) 267 { 268 var cloned = pair.Value.Clone(); 269 newSet.ValuesByNumber[pair.Key] = cloned; 270 } 271 return newSet; 272 } 273 } 274 275 /// <summary> 276 /// Used for keeping track of extensions in messages. 277 /// <see cref="IExtendableMessage{T}"/> methods route to this set. 278 /// 279 /// Most users will not need to use this class directly 280 /// </summary> 281 /// <typeparam name="TTarget">The message type that extensions in this set target</typeparam> 282 public sealed class ExtensionSet<TTarget> where TTarget : IExtendableMessage<TTarget> 283 { 284 internal Dictionary<int, IExtensionValue> ValuesByNumber { get; } = new Dictionary<int, IExtensionValue>(); 285 286 /// <summary> 287 /// Gets a hash code of the set 288 /// </summary> GetHashCode()289 public override int GetHashCode() 290 { 291 int ret = typeof(TTarget).GetHashCode(); 292 foreach (KeyValuePair<int, IExtensionValue> field in ValuesByNumber) 293 { 294 // Use ^ here to make the field order irrelevant. 295 int hash = field.Key.GetHashCode() ^ field.Value.GetHashCode(); 296 ret ^= hash; 297 } 298 return ret; 299 } 300 301 /// <summary> 302 /// Returns whether this set is equal to the other object 303 /// </summary> Equals(object other)304 public override bool Equals(object other) 305 { 306 if (ReferenceEquals(this, other)) 307 { 308 return true; 309 } 310 ExtensionSet<TTarget> otherSet = other as ExtensionSet<TTarget>; 311 if (ValuesByNumber.Count != otherSet.ValuesByNumber.Count) 312 { 313 return false; 314 } 315 foreach (var pair in ValuesByNumber) 316 { 317 IExtensionValue secondValue; 318 if (!otherSet.ValuesByNumber.TryGetValue(pair.Key, out secondValue)) 319 { 320 return false; 321 } 322 if (!pair.Value.Equals(secondValue)) 323 { 324 return false; 325 } 326 } 327 return true; 328 } 329 330 /// <summary> 331 /// Calculates the size of this extension set 332 /// </summary> CalculateSize()333 public int CalculateSize() 334 { 335 int size = 0; 336 foreach (var value in ValuesByNumber.Values) 337 { 338 size += value.CalculateSize(); 339 } 340 return size; 341 } 342 343 /// <summary> 344 /// Writes the extension values in this set to the output stream 345 /// </summary> WriteTo(CodedOutputStream stream)346 public void WriteTo(CodedOutputStream stream) 347 { 348 349 WriteContext.Initialize(stream, out WriteContext ctx); 350 try 351 { 352 WriteTo(ref ctx); 353 } 354 finally 355 { 356 ctx.CopyStateTo(stream); 357 } 358 } 359 360 /// <summary> 361 /// Writes the extension values in this set to the write context 362 /// </summary> 363 [SecuritySafeCritical] WriteTo(ref WriteContext ctx)364 public void WriteTo(ref WriteContext ctx) 365 { 366 foreach (var value in ValuesByNumber.Values) 367 { 368 value.WriteTo(ref ctx); 369 } 370 } 371 IsInitialized()372 internal bool IsInitialized() 373 { 374 return ValuesByNumber.Values.All(v => v.IsInitialized()); 375 } 376 } 377 } 378