diff options
Diffstat (limited to 'src/ProtocolBuffers/UnknownFieldSet.cs')
-rw-r--r-- | src/ProtocolBuffers/UnknownFieldSet.cs | 626 |
1 files changed, 626 insertions, 0 deletions
diff --git a/src/ProtocolBuffers/UnknownFieldSet.cs b/src/ProtocolBuffers/UnknownFieldSet.cs new file mode 100644 index 00000000..18eacbe8 --- /dev/null +++ b/src/ProtocolBuffers/UnknownFieldSet.cs @@ -0,0 +1,626 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using System; +using System.Collections.Generic; +using System.IO; +using Google.ProtocolBuffers.Collections; +using Google.ProtocolBuffers.Descriptors; +using Google.ProtocolBuffers.DescriptorProtos; + +namespace Google.ProtocolBuffers { + /// <summary> + /// Used to keep track of fields which were seen when parsing a protocol message + /// but whose field numbers or types are unrecognized. This most frequently + /// occurs when new fields are added to a message type and then messages containing + /// those fields are read by old software that was built before the new types were + /// added. + /// + /// Every message contains an UnknownFieldSet. + /// + /// Most users will never need to use this class directly. + /// </summary> + public sealed class UnknownFieldSet { + + private static readonly UnknownFieldSet defaultInstance = new UnknownFieldSet(new Dictionary<int, UnknownField>()); + + private readonly IDictionary<int, UnknownField> fields; + + private UnknownFieldSet(IDictionary<int, UnknownField> fields) { + this.fields = fields; + } + + /// <summary> + /// Creates a new unknown field set builder. + /// </summary> + public static Builder CreateBuilder() { + return new Builder(); + } + + /// <summary> + /// Creates a new unknown field set builder + /// and initialize it from <paramref name="original"/>. + /// </summary> + public static Builder CreateBuilder(UnknownFieldSet original) { + return new Builder().MergeFrom(original); + } + + public static UnknownFieldSet DefaultInstance { + get { return defaultInstance; } + } + + /// <summary> + /// Returns a read-only view of the mapping from field numbers to values. + /// </summary> + public IDictionary<int, UnknownField> FieldDictionary { + get { return Dictionaries.AsReadOnly(fields); } + } + + /// <summary> + /// Checks whether or not the given field number is present in the set. + /// </summary> + public bool HasField(int field) { + return fields.ContainsKey(field); + } + + /// <summary> + /// Fetches a field by number, returning an empty field if not present. + /// Never returns null. + /// </summary> + public UnknownField this[int number] { + get { + UnknownField ret; + if (!fields.TryGetValue(number, out ret)) { + ret = UnknownField.DefaultInstance; + } + return ret; + } + } + + /// <summary> + /// Serializes the set and writes it to <paramref name="output"/>. + /// </summary> + public void WriteTo(CodedOutputStream output) { + foreach (KeyValuePair<int, UnknownField> entry in fields) { + entry.Value.WriteTo(entry.Key, output); + } + } + + /// <summary> + /// Gets the number of bytes required to encode this set. + /// </summary> + public int SerializedSize { + get { + int result = 0; + foreach (KeyValuePair<int, UnknownField> entry in fields) { + result += entry.Value.GetSerializedSize(entry.Key); + } + return result; + } + } + + /// <summary> + /// Converts the set to a string in protocol buffer text format. This + /// is just a trivial wrapper around TextFormat.PrintToString. + /// </summary> + public override String ToString() { + return TextFormat.PrintToString(this); + } + + /// <summary> + /// Serializes the message to a ByteString and returns it. This is + /// just a trivial wrapper around WriteTo(CodedOutputStream). + /// </summary> + /// <returns></returns> + public ByteString ToByteString() { + ByteString.CodedBuilder codedBuilder = new ByteString.CodedBuilder(SerializedSize); + WriteTo(codedBuilder.CodedOutput); + return codedBuilder.Build(); + } + + /// <summary> + /// Serializes the message to a byte array and returns it. This is + /// just a trivial wrapper around WriteTo(CodedOutputStream). + /// </summary> + /// <returns></returns> + public byte[] ToByteArray() { + byte[] data = new byte[SerializedSize]; + CodedOutputStream output = CodedOutputStream.CreateInstance(data); + WriteTo(output); + output.CheckNoSpaceLeft(); + return data; + } + + /// <summary> + /// Serializes the message and writes it to <paramref name="output"/>. This is + /// just a trivial wrapper around WriteTo(CodedOutputStream). + /// </summary> + /// <param name="output"></param> + public void WriteTo(Stream output) { + CodedOutputStream codedOutput = CodedOutputStream.CreateInstance(output); + WriteTo(codedOutput); + codedOutput.Flush(); + } + + /// <summary> + /// Serializes the set and writes it to <paramref name="output"/> using + /// the MessageSet wire format. + /// </summary> + public void WriteAsMessageSetTo(CodedOutputStream output) { + foreach (KeyValuePair<int, UnknownField> entry in fields) { + entry.Value.WriteAsMessageSetExtensionTo(entry.Key, output); + } + } + + /// <summary> + /// Gets the number of bytes required to encode this set using the MessageSet + /// wire format. + /// </summary> + public int SerializedSizeAsMessageSet { + get { + int result = 0; + foreach (KeyValuePair<int, UnknownField> entry in fields) { + result += entry.Value.GetSerializedSizeAsMessageSetExtension(entry.Key); + } + return result; + } + } + + /// <summary> + /// Parses an UnknownFieldSet from the given input. + /// </summary> + public static UnknownFieldSet ParseFrom(CodedInputStream input) { + return CreateBuilder().MergeFrom(input).Build(); + } + + /// <summary> + /// Parses an UnknownFieldSet from the given data. + /// </summary> + public static UnknownFieldSet ParseFrom(ByteString data) { + return CreateBuilder().MergeFrom(data).Build(); + } + + /// <summary> + /// Parses an UnknownFieldSet from the given data. + /// </summary> + public static UnknownFieldSet ParseFrom(byte[] data) { + return CreateBuilder().MergeFrom(data).Build(); + } + + /// <summary> + /// Parses an UnknownFieldSet from the given input. + /// </summary> + public static UnknownFieldSet ParseFrom(Stream input) { + return CreateBuilder().MergeFrom(input).Build(); + } + + /// <summary> + /// Builder for UnknownFieldSets. + /// </summary> + public sealed class Builder + { + /// <summary> + /// Mapping from number to field. Note that by using a SortedList we ensure + /// that the fields will be serialized in ascending order. + /// </summary> + private IDictionary<int, UnknownField> fields = new SortedList<int, UnknownField>(); + + // Optimization: We keep around a builder for the last field that was + // modified so that we can efficiently add to it multiple times in a + // row (important when parsing an unknown repeated field). + int lastFieldNumber; + UnknownField.Builder lastField; + + internal Builder() { + } + + /// <summary> + /// Returns a field builder for the specified field number, including any values + /// which already exist. + /// </summary> + private UnknownField.Builder GetFieldBuilder(int number) { + if (lastField != null) { + if (number == lastFieldNumber) { + return lastField; + } + // Note: AddField() will reset lastField and lastFieldNumber. + AddField(lastFieldNumber, lastField.Build()); + } + if (number == 0) { + return null; + } + + lastField = UnknownField.CreateBuilder(); + UnknownField existing; + if (fields.TryGetValue(number, out existing)) { + lastField.MergeFrom(existing); + } + lastFieldNumber = number; + return lastField; + } + + /// <summary> + /// Build the UnknownFieldSet and return it. Once this method has been called, + /// this instance will no longer be usable. Calling any method after this + /// will throw a NullReferenceException. + /// </summary> + public UnknownFieldSet Build() { + GetFieldBuilder(0); // Force lastField to be built. + UnknownFieldSet result = fields.Count == 0 ? DefaultInstance : new UnknownFieldSet(fields); + fields = null; + return result; + } + + /// <summary> + /// Adds a field to the set. If a field with the same number already exists, it + /// is replaced. + /// </summary> + public Builder AddField(int number, UnknownField field) { + if (number == 0) { + throw new ArgumentOutOfRangeException("number", "Zero is not a valid field number."); + } + if (lastField != null && lastFieldNumber == number) { + // Discard this. + lastField = null; + lastFieldNumber = 0; + } + fields[number] = field; + return this; + } + + /// <summary> + /// Resets the builder to an empty set. + /// </summary> + public Builder Clear() { + fields.Clear(); + lastFieldNumber = 0; + lastField = null; + return this; + } + + /// <summary> + /// Parse an entire message from <paramref name="input"/> and merge + /// its fields into this set. + /// </summary> + public Builder MergeFrom(CodedInputStream input) { + while (true) { + uint tag = input.ReadTag(); + if (tag == 0 || !MergeFieldFrom(tag, input)) { + break; + } + } + return this; + } + + /// <summary> + /// Parse a single field from <paramref name="input"/> and merge it + /// into this set. + /// </summary> + /// <param name="tag">The field's tag number, which was already parsed.</param> + /// <param name="input">The coded input stream containing the field</param> + /// <returns>false if the tag is an "end group" tag, true otherwise</returns> + public bool MergeFieldFrom(uint tag, CodedInputStream input) { + int number = WireFormat.GetTagFieldNumber(tag); + switch (WireFormat.GetTagWireType(tag)) { + case WireFormat.WireType.Varint: + GetFieldBuilder(number).AddVarint(input.ReadUInt64()); + return true; + case WireFormat.WireType.Fixed64: + GetFieldBuilder(number).AddFixed64(input.ReadFixed64()); + return true; + case WireFormat.WireType.LengthDelimited: + GetFieldBuilder(number).AddLengthDelimited(input.ReadBytes()); + return true; + case WireFormat.WireType.StartGroup: { + Builder subBuilder = CreateBuilder(); + input.ReadUnknownGroup(number, subBuilder); + GetFieldBuilder(number).AddGroup(subBuilder.Build()); + return true; + } + case WireFormat.WireType.EndGroup: + return false; + case WireFormat.WireType.Fixed32: + GetFieldBuilder(number).AddFixed32(input.ReadFixed32()); + return true; + default: + throw InvalidProtocolBufferException.InvalidWireType(); + } + } + + /// <summary> + /// Parses <paramref name="input"/> as an UnknownFieldSet and merge it + /// with the set being built. This is just a small wrapper around + /// MergeFrom(CodedInputStream). + /// </summary> + public Builder MergeFrom(Stream input) { + CodedInputStream codedInput = CodedInputStream.CreateInstance(input); + MergeFrom(codedInput); + codedInput.CheckLastTagWas(0); + return this; + } + + /// <summary> + /// Parses <paramref name="data"/> as an UnknownFieldSet and merge it + /// with the set being built. This is just a small wrapper around + /// MergeFrom(CodedInputStream). + /// </summary> + public Builder MergeFrom(ByteString data) { + CodedInputStream input = data.CreateCodedInput(); + MergeFrom(input); + input.CheckLastTagWas(0); + return this; + } + + /// <summary> + /// Parses <paramref name="data"/> as an UnknownFieldSet and merge it + /// with the set being built. This is just a small wrapper around + /// MergeFrom(CodedInputStream). + /// </summary> + public Builder MergeFrom(byte[] data) { + CodedInputStream input = CodedInputStream.CreateInstance(data); + MergeFrom(input); + input.CheckLastTagWas(0); + return this; + } + + /// <summary> + /// Convenience method for merging a new field containing a single varint + /// value. This is used in particular when an unknown enum value is + /// encountered. + /// </summary> + public Builder MergeVarintField(int number, ulong value) { + if (number == 0) { + throw new ArgumentOutOfRangeException("number", "Zero is not a valid field number."); + } + GetFieldBuilder(number).AddVarint(value); + return this; + } + + /// <summary> + /// Merges the fields from <paramref name="other"/> into this set. + /// If a field number exists in both sets, the values in <paramref name="other"/> + /// will be appended to the values in this set. + /// </summary> + public Builder MergeFrom(UnknownFieldSet other) { + if (other != DefaultInstance) { + foreach(KeyValuePair<int, UnknownField> entry in other.fields) { + MergeField(entry.Key, entry.Value); + } + } + return this; + } + + /// <summary> + /// Checks if the given field number is present in the set. + /// </summary> + public bool HasField(int number) { + if (number == 0) { + throw new ArgumentOutOfRangeException("number", "Zero is not a valid field number."); + } + return number == lastFieldNumber || fields.ContainsKey(number); + } + + /// <summary> + /// Adds a field to the unknown field set. If a field with the same + /// number already exists, the two are merged. + /// </summary> + public Builder MergeField(int number, UnknownField field) { + if (number == 0) { + throw new ArgumentOutOfRangeException("number", "Zero is not a valid field number."); + } + if (HasField(number)) { + GetFieldBuilder(number).MergeFrom(field); + } else { + // Optimization: We could call getFieldBuilder(number).mergeFrom(field) + // in this case, but that would create a copy of the Field object. + // We'd rather reuse the one passed to us, so call AddField() instead. + AddField(number, field); + } + return this; + } + + internal void MergeFrom(CodedInputStream input, ExtensionRegistry extensionRegistry, IBuilder builder) { + while (true) { + uint tag = input.ReadTag(); + if (tag == 0) { + break; + } + if (!MergeFieldFrom(input, extensionRegistry, builder, tag)) { + // end group tag + break; + } + } + } + + /// <summary> + /// Like <see cref="MergeFrom(CodedInputStream, ExtensionRegistry, IBuilder)" /> + /// but parses a single field. + /// </summary> + /// <param name="input">The input to read the field from</param> + /// <param name="extensionRegistry">Registry to use when an extension field is encountered</param> + /// <param name="builder">Builder to merge field into, if it's a known field</param> + /// <param name="tag">The tag, which should already have been read from the input</param> + /// <returns>true unless the tag is an end-group tag</returns> + internal bool MergeFieldFrom(CodedInputStream input, + ExtensionRegistry extensionRegistry, IBuilder builder, uint tag) { + + if (DescriptorProtoFile.Bootstrapping) { + return MergeFieldFrom(tag, input); + } + + MessageDescriptor type = builder.DescriptorForType; + if (type.Options.MessageSetWireFormat && tag == WireFormat.MessageSetTag.ItemStart) { + MergeMessageSetExtensionFromCodedStream(input, extensionRegistry, builder); + return true; + } + + WireFormat.WireType wireType = WireFormat.GetTagWireType(tag); + int fieldNumber = WireFormat.GetTagFieldNumber(tag); + + FieldDescriptor field; + IMessage defaultFieldInstance = null; + + if (type.IsExtensionNumber(fieldNumber)) { + ExtensionInfo extension = extensionRegistry[type, fieldNumber]; + if (extension == null) { + field = null; + } else { + field = extension.Descriptor; + defaultFieldInstance = extension.DefaultInstance; + } + } else { + field = type.FindFieldByNumber(fieldNumber); + } + + // Unknown field or wrong wire type. Skip. + if (field == null || wireType != WireFormat.GetWireType(field.FieldType)) { + return MergeFieldFrom(tag, input); + } + + object value; + switch (field.FieldType) { + case FieldType.Group: + case FieldType.Message: { + IBuilder subBuilder; + if (defaultFieldInstance != null) { + subBuilder = defaultFieldInstance.WeakCreateBuilderForType(); + } else { + subBuilder = builder.CreateBuilderForField(field); + } + if (!field.IsRepeated) { + subBuilder.WeakMergeFrom((IMessage)builder[field]); + } + if (field.FieldType == FieldType.Group) { + input.ReadGroup(field.FieldNumber, subBuilder, extensionRegistry); + } else { + input.ReadMessage(subBuilder, extensionRegistry); + } + value = subBuilder.WeakBuild(); + break; + } + case FieldType.Enum: { + int rawValue = input.ReadEnum(); + value = field.EnumType.FindValueByNumber(rawValue); + // If the number isn't recognized as a valid value for this enum, + // drop it. + if (value == null) { + MergeVarintField(fieldNumber, (ulong)rawValue); + return true; + } + break; + } + default: + value = input.ReadPrimitiveField(field.FieldType); + break; + } + if (field.IsRepeated) { + builder.WeakAddRepeatedField(field, value); + } else { + builder[field] = value; + } + return true; + } + + /// <summary> + /// Called by MergeFieldFrom to parse a MessageSet extension. + /// </summary> + private void MergeMessageSetExtensionFromCodedStream(CodedInputStream input, + ExtensionRegistry extensionRegistry, IBuilder builder) { + MessageDescriptor type = builder.DescriptorForType; + + // The wire format for MessageSet is: + // message MessageSet { + // repeated group Item = 1 { + // required int32 typeId = 2; + // required bytes message = 3; + // } + // } + // "typeId" is the extension's field number. The extension can only be + // a message type, where "message" contains the encoded bytes of that + // message. + // + // In practice, we will probably never see a MessageSet item in which + // the message appears before the type ID, or where either field does not + // appear exactly once. However, in theory such cases are valid, so we + // should be prepared to accept them. + + int typeId = 0; + ByteString rawBytes = null; // If we encounter "message" before "typeId" + IBuilder subBuilder = null; + FieldDescriptor field = null; + + while (true) { + uint tag = input.ReadTag(); + if (tag == 0) { + break; + } + + if (tag == WireFormat.MessageSetTag.TypeID) { + typeId = input.ReadInt32(); + // Zero is not a valid type ID. + if (typeId != 0) { + ExtensionInfo extension = extensionRegistry[type, typeId]; + if (extension != null) { + field = extension.Descriptor; + subBuilder = extension.DefaultInstance.WeakCreateBuilderForType(); + IMessage originalMessage = (IMessage)builder[field]; + if (originalMessage != null) { + subBuilder.WeakMergeFrom(originalMessage); + } + if (rawBytes != null) { + // We already encountered the message. Parse it now. + // TODO(jonskeet): Check this is okay. It's subtly different from the Java, as it doesn't create an input stream from rawBytes. + // In fact, why don't we just call MergeFrom(rawBytes)? And what about the extension registry? + subBuilder.WeakMergeFrom(rawBytes.CreateCodedInput()); + rawBytes = null; + } + } else { + // Unknown extension number. If we already saw data, put it + // in rawBytes. + if (rawBytes != null) { + MergeField(typeId, UnknownField.CreateBuilder().AddLengthDelimited(rawBytes).Build()); + rawBytes = null; + } + } + } + } else if (tag == WireFormat.MessageSetTag.Message) { + if (typeId == 0) { + // We haven't seen a type ID yet, so we have to store the raw bytes for now. + rawBytes = input.ReadBytes(); + } else if (subBuilder == null) { + // We don't know how to parse this. Ignore it. + MergeField(typeId, UnknownField.CreateBuilder().AddLengthDelimited(input.ReadBytes()).Build()); + } else { + // We already know the type, so we can parse directly from the input + // with no copying. Hooray! + input.ReadMessage(subBuilder, extensionRegistry); + } + } else { + // Unknown tag. Skip it. + if (!input.SkipField(tag)) { + break; // end of group + } + } + } + + input.CheckLastTagWas(WireFormat.MessageSetTag.ItemEnd); + + if (subBuilder != null) { + builder[field] = subBuilder.WeakBuild(); + } + } + } + } +} |