// 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; namespace Google.ProtocolBuffers { /// /// 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. /// public sealed class UnknownFieldSet { private static readonly UnknownFieldSet defaultInstance = new UnknownFieldSet(new Dictionary()); private readonly IDictionary fields; private UnknownFieldSet(IDictionary fields) { this.fields = fields; } /// /// Creates a new unknown field set builder. /// public static Builder CreateBuilder() { return new Builder(); } /// /// Creates a new unknown field set builder /// and initialize it from . /// public static Builder CreateBuilder(UnknownFieldSet original) { return new Builder().MergeFrom(original); } public static UnknownFieldSet DefaultInstance { get { return defaultInstance; } } /// /// Returns a read-only view of the mapping from field numbers to values. /// public IDictionary FieldDictionary { get { return Dictionaries.AsReadOnly(fields); } } /// /// Checks whether or not the given field number is present in the set. /// public bool HasField(int field) { return fields.ContainsKey(field); } /// /// Fetches a field by number, returning an empty field if not present. /// Never returns null. /// public UnknownField this[int number] { get { UnknownField ret; if (!fields.TryGetValue(number, out ret)) { ret = UnknownField.DefaultInstance; } return ret; } } /// /// Serializes the set and writes it to . /// public void WriteTo(CodedOutputStream output) { foreach (KeyValuePair entry in fields) { entry.Value.WriteTo(entry.Key, output); } } /// /// Gets the number of bytes required to encode this set. /// public int SerializedSize { get { int result = 0; foreach (KeyValuePair entry in fields) { result += entry.Value.GetSerializedSize(entry.Key); } return result; } } /// /// Converts the set to a string in protocol buffer text format. This /// is just a trivial wrapper around TextFormat.PrintToString. /// public override String ToString() { return TextFormat.PrintToString(this); } /// /// Serializes the message to a ByteString and returns it. This is /// just a trivial wrapper around WriteTo(CodedOutputStream). /// /// public ByteString ToByteString() { ByteString.CodedBuilder codedBuilder = new ByteString.CodedBuilder(SerializedSize); WriteTo(codedBuilder.CodedOutput); return codedBuilder.Build(); } /// /// Serializes the message to a byte array and returns it. This is /// just a trivial wrapper around WriteTo(CodedOutputStream). /// /// public byte[] ToByteArray() { byte[] data = new byte[SerializedSize]; CodedOutputStream output = CodedOutputStream.CreateInstance(data); WriteTo(output); output.CheckNoSpaceLeft(); return data; } /// /// Serializes the message and writes it to . This is /// just a trivial wrapper around WriteTo(CodedOutputStream). /// /// public void WriteTo(Stream output) { CodedOutputStream codedOutput = CodedOutputStream.CreateInstance(output); WriteTo(codedOutput); codedOutput.Flush(); } /// /// Serializes the set and writes it to using /// the MessageSet wire format. /// public void WriteAsMessageSetTo(CodedOutputStream output) { foreach (KeyValuePair entry in fields) { entry.Value.WriteAsMessageSetExtensionTo(entry.Key, output); } } /// /// Gets the number of bytes required to encode this set using the MessageSet /// wire format. /// public int SerializedSizeAsMessageSet { get { int result = 0; foreach (KeyValuePair entry in fields) { result += entry.Value.GetSerializedSizeAsMessageSetExtension(entry.Key); } return result; } } /// /// Parses an UnknownFieldSet from the given input. /// public static UnknownFieldSet ParseFrom(CodedInputStream input) { return CreateBuilder().MergeFrom(input).Build(); } /// /// Parses an UnknownFieldSet from the given data. /// public static UnknownFieldSet ParseFrom(ByteString data) { return CreateBuilder().MergeFrom(data).Build(); } /// /// Parses an UnknownFieldSet from the given data. /// public static UnknownFieldSet ParseFrom(byte[] data) { return CreateBuilder().MergeFrom(data).Build(); } /// /// Parses an UnknownFieldSet from the given input. /// public static UnknownFieldSet ParseFrom(Stream input) { return CreateBuilder().MergeFrom(input).Build(); } /// /// Builder for UnknownFieldSets. /// public sealed class Builder { /// /// Mapping from number to field. Note that by using a SortedList we ensure /// that the fields will be serialized in ascending order. /// private IDictionary fields = new SortedList(); // 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() { } /// /// Returns a field builder for the specified field number, including any values /// which already exist. /// 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; } /// /// 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. /// public UnknownFieldSet Build() { GetFieldBuilder(0); // Force lastField to be built. UnknownFieldSet result = fields.Count == 0 ? DefaultInstance : new UnknownFieldSet(fields); fields = null; return result; } /// /// Adds a field to the set. If a field with the same number already exists, it /// is replaced. /// 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; } /// /// Resets the builder to an empty set. /// public Builder Clear() { fields.Clear(); lastFieldNumber = 0; lastField = null; return this; } /// /// Parse an entire message from and merge /// its fields into this set. /// public Builder MergeFrom(CodedInputStream input) { while (true) { uint tag = input.ReadTag(); if (tag == 0 || !MergeFieldFrom(tag, input)) { break; } } return this; } /// /// Parse a single field from and merge it /// into this set. /// /// The field's tag number, which was already parsed. /// The coded input stream containing the field /// false if the tag is an "end group" tag, true otherwise public bool MergeFieldFrom(uint tag, CodedInputStream input) { int number = WireFormat.GetTagFieldNumber(tag); switch (WireFormat.GetTagWireType(tag)) { case WireFormat.WireType.Varint: // TODO(jonskeet): Check this is correct (different to Java) 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(); } } /// /// Parses as an UnknownFieldSet and merge it /// with the set being built. This is just a small wrapper around /// MergeFrom(CodedInputStream). /// public Builder MergeFrom(Stream input) { CodedInputStream codedInput = CodedInputStream.CreateInstance(input); MergeFrom(codedInput); codedInput.CheckLastTagWas(0); return this; } /// /// Parses as an UnknownFieldSet and merge it /// with the set being built. This is just a small wrapper around /// MergeFrom(CodedInputStream). /// public Builder MergeFrom(ByteString data) { CodedInputStream input = data.CreateCodedInput(); MergeFrom(input); input.CheckLastTagWas(0); return this; } /// /// Parses as an UnknownFieldSet and merge it /// with the set being built. This is just a small wrapper around /// MergeFrom(CodedInputStream). /// public Builder MergeFrom(byte[] data) { CodedInputStream input = CodedInputStream.CreateInstance(data); MergeFrom(input); input.CheckLastTagWas(0); return this; } /// /// Convenience method for merging a new field containing a single varint /// value. This is used in particular when an unknown enum value is /// encountered. /// 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; } /// /// Merges the fields from into this set. /// If a field number exists in both sets, the values in /// will be appended to the values in this set. /// public Builder MergeFrom(UnknownFieldSet other) { if (other != DefaultInstance) { foreach(KeyValuePair entry in other.fields) { MergeField(entry.Key, entry.Value); } } return this; } /// /// Checks if the given field number is present in the set. /// 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); } /// /// Adds a field to the unknown field set. If a field with the same /// number already exists, the two are merged. /// 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; } } } }