Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ProtobufCS/0.9.1/ProtobufCS/src/ProtocolBuffers/UnknownFieldSet.cs @ 4032

Last change on this file since 4032 was 3857, checked in by abeham, 15 years ago

#866

  • Added protobuf-csharp-port project source to ExtLibs
File size: 25.3 KB
Line 
1#region Copyright notice and license
2// Protocol Buffers - Google's data interchange format
3// Copyright 2008 Google Inc.  All rights reserved.
4// http://github.com/jskeet/dotnet-protobufs/
5// Original C++/Java/Python code:
6// http://code.google.com/p/protobuf/
7//
8// Redistribution and use in source and binary forms, with or without
9// modification, are permitted provided that the following conditions are
10// met:
11//
12//     * Redistributions of source code must retain the above copyright
13// notice, this list of conditions and the following disclaimer.
14//     * Redistributions in binary form must reproduce the above
15// copyright notice, this list of conditions and the following disclaimer
16// in the documentation and/or other materials provided with the
17// distribution.
18//     * Neither the name of Google Inc. nor the names of its
19// contributors may be used to endorse or promote products derived from
20// this software without specific prior written permission.
21//
22// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33#endregion
34
35using System;
36using System.Collections.Generic;
37using System.IO;
38using Google.ProtocolBuffers.Collections;
39using Google.ProtocolBuffers.Descriptors;
40using Google.ProtocolBuffers.DescriptorProtos;
41
42namespace Google.ProtocolBuffers {
43  /// <summary>
44  /// Used to keep track of fields which were seen when parsing a protocol message
45  /// but whose field numbers or types are unrecognized. This most frequently
46  /// occurs when new fields are added to a message type and then messages containing
47  /// those fields are read by old software that was built before the new types were
48  /// added.
49  ///
50  /// Every message contains an UnknownFieldSet.
51  ///
52  /// Most users will never need to use this class directly.
53  /// </summary>
54  public sealed class UnknownFieldSet {
55
56    private static readonly UnknownFieldSet defaultInstance = new UnknownFieldSet(new Dictionary<int, UnknownField>());
57
58    private readonly IDictionary<int, UnknownField> fields;
59
60    private UnknownFieldSet(IDictionary<int, UnknownField> fields) {
61      this.fields = fields;
62    }
63
64    /// <summary>
65    /// Creates a new unknown field set builder.
66    /// </summary>
67    public static Builder CreateBuilder() {
68      return new Builder();
69    }
70
71    /// <summary>
72    /// Creates a new unknown field set builder
73    /// and initialize it from <paramref name="original"/>.
74    /// </summary>
75    public static Builder CreateBuilder(UnknownFieldSet original) {
76      return new Builder().MergeFrom(original);
77    }
78
79    public static UnknownFieldSet DefaultInstance {
80      get { return defaultInstance; }
81    }
82
83    /// <summary>
84    /// Returns a read-only view of the mapping from field numbers to values.
85    /// </summary>
86    public IDictionary<int, UnknownField> FieldDictionary {
87      get { return Dictionaries.AsReadOnly(fields); }
88    }
89
90    /// <summary>
91    /// Checks whether or not the given field number is present in the set.
92    /// </summary>
93    public bool HasField(int field) {
94      return fields.ContainsKey(field);
95    }
96
97    /// <summary>
98    /// Fetches a field by number, returning an empty field if not present.
99    /// Never returns null.
100    /// </summary>
101    public UnknownField this[int number] {
102      get {
103        UnknownField ret;
104        if (!fields.TryGetValue(number, out ret)) {
105          ret = UnknownField.DefaultInstance;
106        }
107        return ret;
108      }
109    }
110
111    /// <summary>
112    /// Serializes the set and writes it to <paramref name="output"/>.
113    /// </summary>
114    public void WriteTo(CodedOutputStream output) {
115      foreach (KeyValuePair<int, UnknownField> entry in fields) {
116        entry.Value.WriteTo(entry.Key, output);
117      }
118    }
119
120    /// <summary>
121    /// Gets the number of bytes required to encode this set.
122    /// </summary>
123    public int SerializedSize {
124      get {
125        int result = 0;
126        foreach (KeyValuePair<int, UnknownField> entry in fields) {
127          result += entry.Value.GetSerializedSize(entry.Key);
128        }
129        return result;
130      }
131    }
132
133    /// <summary>
134    /// Converts the set to a string in protocol buffer text format. This
135    /// is just a trivial wrapper around TextFormat.PrintToString.
136    /// </summary>
137    public override String ToString() {
138      return TextFormat.PrintToString(this);
139    }
140
141    /// <summary>
142    /// Serializes the message to a ByteString and returns it. This is
143    /// just a trivial wrapper around WriteTo(CodedOutputStream).
144    /// </summary>
145    /// <returns></returns>
146    public ByteString ToByteString() {
147      ByteString.CodedBuilder codedBuilder = new ByteString.CodedBuilder(SerializedSize);
148      WriteTo(codedBuilder.CodedOutput);
149      return codedBuilder.Build();
150    }
151
152    /// <summary>
153    /// Serializes the message to a byte array and returns it. This is
154    /// just a trivial wrapper around WriteTo(CodedOutputStream).
155    /// </summary>
156    /// <returns></returns>
157    public byte[] ToByteArray() {
158      byte[] data = new byte[SerializedSize];
159      CodedOutputStream output = CodedOutputStream.CreateInstance(data);
160      WriteTo(output);
161      output.CheckNoSpaceLeft();
162      return data;
163    }
164
165    /// <summary>
166    /// Serializes the message and writes it to <paramref name="output"/>. This is
167    /// just a trivial wrapper around WriteTo(CodedOutputStream).
168    /// </summary>
169    /// <param name="output"></param>
170    public void WriteTo(Stream output) {
171      CodedOutputStream codedOutput = CodedOutputStream.CreateInstance(output);
172      WriteTo(codedOutput);
173      codedOutput.Flush();
174    }
175
176    /// <summary>
177    /// Serializes the set and writes it to <paramref name="output"/> using
178    /// the MessageSet wire format.
179    /// </summary>
180    public void WriteAsMessageSetTo(CodedOutputStream output) {
181      foreach (KeyValuePair<int, UnknownField> entry in fields) {
182        entry.Value.WriteAsMessageSetExtensionTo(entry.Key, output);
183      }
184    }
185
186    /// <summary>
187    /// Gets the number of bytes required to encode this set using the MessageSet
188    /// wire format.
189    /// </summary>
190    public int SerializedSizeAsMessageSet {
191      get {
192        int result = 0;
193        foreach (KeyValuePair<int, UnknownField> entry in fields) {
194          result += entry.Value.GetSerializedSizeAsMessageSetExtension(entry.Key);
195        }
196        return result;
197      }
198    }
199
200    public override bool Equals(object other) {
201      if (ReferenceEquals(this, other)) {
202        return true;
203      }
204      UnknownFieldSet otherSet = other as UnknownFieldSet;
205      return otherSet != null && Dictionaries.Equals(fields, otherSet.fields);
206    }
207
208    public override int GetHashCode() {
209      return Dictionaries.GetHashCode(fields);
210    }
211
212    /// <summary>
213    /// Parses an UnknownFieldSet from the given input.
214    /// </summary>
215    public static UnknownFieldSet ParseFrom(CodedInputStream input) {
216      return CreateBuilder().MergeFrom(input).Build();
217    }
218
219    /// <summary>
220    /// Parses an UnknownFieldSet from the given data.
221    /// </summary>
222    public static UnknownFieldSet ParseFrom(ByteString data) {
223      return CreateBuilder().MergeFrom(data).Build();
224    }
225
226    /// <summary>
227    /// Parses an UnknownFieldSet from the given data.
228    /// </summary>
229    public static UnknownFieldSet ParseFrom(byte[] data) {
230      return CreateBuilder().MergeFrom(data).Build();
231    }
232
233    /// <summary>
234    /// Parses an UnknownFieldSet from the given input.
235    /// </summary>
236    public static UnknownFieldSet ParseFrom(Stream input) {
237      return CreateBuilder().MergeFrom(input).Build();
238    }
239
240    /// <summary>
241    /// Builder for UnknownFieldSets.
242    /// </summary>
243    public sealed class Builder
244    {
245      /// <summary>
246      /// Mapping from number to field. Note that by using a SortedList we ensure
247      /// that the fields will be serialized in ascending order.
248      /// </summary>
249      private IDictionary<int, UnknownField> fields = new SortedList<int, UnknownField>();
250      // Optimization:  We keep around a builder for the last field that was
251      // modified so that we can efficiently add to it multiple times in a
252      // row (important when parsing an unknown repeated field).
253      private int lastFieldNumber;
254      private UnknownField.Builder lastField;
255
256      internal Builder() {
257      }
258
259      /// <summary>
260      /// Returns a field builder for the specified field number, including any values
261      /// which already exist.
262      /// </summary>
263      private UnknownField.Builder GetFieldBuilder(int number) {
264        if (lastField != null) {
265          if (number == lastFieldNumber) {
266            return lastField;
267          }
268          // Note: AddField() will reset lastField and lastFieldNumber.
269          AddField(lastFieldNumber, lastField.Build());
270        }
271        if (number == 0) {
272          return null;
273        }
274
275        lastField = UnknownField.CreateBuilder();
276        UnknownField existing;
277        if (fields.TryGetValue(number, out existing)) {
278          lastField.MergeFrom(existing);
279        }
280        lastFieldNumber = number;
281        return lastField;
282      }
283
284      /// <summary>
285      /// Build the UnknownFieldSet and return it. Once this method has been called,
286      /// this instance will no longer be usable. Calling any method after this
287      /// will throw a NullReferenceException.
288      /// </summary>
289      public UnknownFieldSet Build() {
290        GetFieldBuilder(0);  // Force lastField to be built.
291        UnknownFieldSet result = fields.Count == 0 ? DefaultInstance : new UnknownFieldSet(fields);
292        fields = null;
293        return result;
294      }
295
296      /// <summary>
297      /// Adds a field to the set. If a field with the same number already exists, it
298      /// is replaced.
299      /// </summary>
300      public Builder AddField(int number, UnknownField field) {
301        if (number == 0) {
302          throw new ArgumentOutOfRangeException("number", "Zero is not a valid field number.");
303        }
304        if (lastField != null && lastFieldNumber == number) {
305          // Discard this.
306          lastField = null;
307          lastFieldNumber = 0;
308        }
309        fields[number] = field;
310        return this;
311      }
312
313      /// <summary>
314      /// Resets the builder to an empty set.
315      /// </summary>
316      public Builder Clear() {
317        fields.Clear();
318        lastFieldNumber = 0;
319        lastField = null;
320        return this;
321      }
322     
323      /// <summary>
324      /// Parse an entire message from <paramref name="input"/> and merge
325      /// its fields into this set.
326      /// </summary>
327      public Builder MergeFrom(CodedInputStream input) {
328        while (true) {
329          uint tag = input.ReadTag();
330          if (tag == 0 || !MergeFieldFrom(tag, input)) {
331            break;
332          }
333        }
334        return this;
335      }
336
337        /// <summary>
338        /// Parse a single field from <paramref name="input"/> and merge it
339        /// into this set.
340        /// </summary>
341        /// <param name="tag">The field's tag number, which was already parsed.</param>
342        /// <param name="input">The coded input stream containing the field</param>
343        /// <returns>false if the tag is an "end group" tag, true otherwise</returns>
344      [CLSCompliant(false)]
345      public bool MergeFieldFrom(uint tag, CodedInputStream input) {
346        int number = WireFormat.GetTagFieldNumber(tag);
347        switch (WireFormat.GetTagWireType(tag)) {
348          case WireFormat.WireType.Varint:
349            GetFieldBuilder(number).AddVarint(input.ReadUInt64());
350            return true;
351          case WireFormat.WireType.Fixed64:
352            GetFieldBuilder(number).AddFixed64(input.ReadFixed64());
353            return true;
354          case WireFormat.WireType.LengthDelimited:
355            GetFieldBuilder(number).AddLengthDelimited(input.ReadBytes());
356            return true;
357          case WireFormat.WireType.StartGroup: {
358            Builder subBuilder = CreateBuilder();
359            input.ReadUnknownGroup(number, subBuilder);
360            GetFieldBuilder(number).AddGroup(subBuilder.Build());
361            return true;
362          }
363          case WireFormat.WireType.EndGroup:
364            return false;
365          case WireFormat.WireType.Fixed32:
366            GetFieldBuilder(number).AddFixed32(input.ReadFixed32());
367            return true;
368          default:
369            throw InvalidProtocolBufferException.InvalidWireType();
370        }
371      }
372
373      /// <summary>
374      /// Parses <paramref name="input"/> as an UnknownFieldSet and merge it
375      /// with the set being built. This is just a small wrapper around
376      /// MergeFrom(CodedInputStream).
377      /// </summary>
378      public Builder MergeFrom(Stream input) {
379        CodedInputStream codedInput = CodedInputStream.CreateInstance(input);
380        MergeFrom(codedInput);
381        codedInput.CheckLastTagWas(0);
382        return this;
383      }
384
385      /// <summary>
386      /// Parses <paramref name="data"/> as an UnknownFieldSet and merge it
387      /// with the set being built. This is just a small wrapper around
388      /// MergeFrom(CodedInputStream).
389      /// </summary>
390      public Builder MergeFrom(ByteString data) {
391        CodedInputStream input = data.CreateCodedInput();
392        MergeFrom(input);
393        input.CheckLastTagWas(0);
394        return this;
395      }
396
397      /// <summary>
398      /// Parses <paramref name="data"/> as an UnknownFieldSet and merge it
399      /// with the set being built. This is just a small wrapper around
400      /// MergeFrom(CodedInputStream).
401      /// </summary>
402      public Builder MergeFrom(byte[] data) {
403        CodedInputStream input = CodedInputStream.CreateInstance(data);
404        MergeFrom(input);
405        input.CheckLastTagWas(0);
406        return this;
407      }
408
409      /// <summary>
410      /// Convenience method for merging a new field containing a single varint
411      /// value.  This is used in particular when an unknown enum value is
412      /// encountered.
413      /// </summary>
414      [CLSCompliant(false)]
415      public Builder MergeVarintField(int number, ulong value) {
416        if (number == 0) {
417          throw new ArgumentOutOfRangeException("number", "Zero is not a valid field number.");
418        }
419        GetFieldBuilder(number).AddVarint(value);
420        return this;
421      }
422
423      /// <summary>
424      /// Merges the fields from <paramref name="other"/> into this set.
425      /// If a field number exists in both sets, the values in <paramref name="other"/>
426      /// will be appended to the values in this set.
427      /// </summary>
428      public Builder MergeFrom(UnknownFieldSet other) {
429        if (other != DefaultInstance) {
430          foreach(KeyValuePair<int, UnknownField> entry in other.fields) {
431            MergeField(entry.Key, entry.Value);
432          }
433        }
434        return this;
435      }
436
437      /// <summary>
438      /// Checks if the given field number is present in the set.
439      /// </summary>
440      public bool HasField(int number) {
441        if (number == 0) {
442          throw new ArgumentOutOfRangeException("number", "Zero is not a valid field number.");
443        }
444        return number == lastFieldNumber || fields.ContainsKey(number);
445      }
446
447      /// <summary>
448      /// Adds a field to the unknown field set. If a field with the same
449      /// number already exists, the two are merged.
450      /// </summary>
451      public Builder MergeField(int number, UnknownField field) {
452        if (number == 0) {
453          throw new ArgumentOutOfRangeException("number", "Zero is not a valid field number.");
454        }
455        if (HasField(number)) {
456          GetFieldBuilder(number).MergeFrom(field);
457        } else {
458          // Optimization:  We could call getFieldBuilder(number).mergeFrom(field)
459          // in this case, but that would create a copy of the Field object.
460          // We'd rather reuse the one passed to us, so call AddField() instead.
461          AddField(number, field);
462        }
463        return this;
464      }
465
466      internal void MergeFrom(CodedInputStream input, ExtensionRegistry extensionRegistry, IBuilder builder) {
467        while (true) {
468          uint tag = input.ReadTag();
469          if (tag == 0) {
470            break;
471          }
472          if (!MergeFieldFrom(input, extensionRegistry, builder, tag)) {
473            // end group tag
474            break;
475          }
476        }
477      }
478
479      /// <summary>
480      /// Like <see cref="MergeFrom(CodedInputStream, ExtensionRegistry, IBuilder)" />
481      /// but parses a single field.
482      /// </summary>
483      /// <param name="input">The input to read the field from</param>
484      /// <param name="extensionRegistry">Registry to use when an extension field is encountered</param>
485      /// <param name="builder">Builder to merge field into, if it's a known field</param>
486      /// <param name="tag">The tag, which should already have been read from the input</param>
487      /// <returns>true unless the tag is an end-group tag</returns>
488      internal bool MergeFieldFrom(CodedInputStream input,
489          ExtensionRegistry extensionRegistry, IBuilder builder, uint tag) {
490
491        MessageDescriptor type = builder.DescriptorForType;
492        if (type.Options.MessageSetWireFormat && tag == WireFormat.MessageSetTag.ItemStart) {
493          MergeMessageSetExtensionFromCodedStream(input, extensionRegistry, builder);
494          return true;
495        }
496
497        WireFormat.WireType wireType = WireFormat.GetTagWireType(tag);
498        int fieldNumber = WireFormat.GetTagFieldNumber(tag);
499
500        FieldDescriptor field;
501        IMessage defaultFieldInstance = null;
502
503        if (type.IsExtensionNumber(fieldNumber)) {
504          ExtensionInfo extension = extensionRegistry[type, fieldNumber];
505          if (extension == null) {
506            field = null;
507          } else {
508            field = extension.Descriptor;
509            defaultFieldInstance = extension.DefaultInstance;
510          }
511        } else {
512          field = type.FindFieldByNumber(fieldNumber);
513        }
514
515        // Unknown field or wrong wire type. Skip.
516        if (field == null || wireType != WireFormat.GetWireType(field)) {
517          return MergeFieldFrom(tag, input);
518        }
519
520        if (field.IsPacked) {
521          int length = (int)input.ReadRawVarint32();
522          int limit = input.PushLimit(length);
523          if (field.FieldType == FieldType.Enum) {
524            while (!input.ReachedLimit) {
525              int rawValue = input.ReadEnum();
526              object value = field.EnumType.FindValueByNumber(rawValue);
527              if (value == null) {
528                // If the number isn't recognized as a valid value for this
529                // enum, drop it (don't even add it to unknownFields).
530                return true;
531              }
532              builder.WeakAddRepeatedField(field, value);
533            }
534          } else {
535            while (!input.ReachedLimit) {
536              Object value = input.ReadPrimitiveField(field.FieldType);
537              builder.WeakAddRepeatedField(field, value);
538            }
539          }
540          input.PopLimit(limit);
541        } else {
542          object value;
543          switch (field.FieldType) {
544            case FieldType.Group:
545            case FieldType.Message: {
546                IBuilder subBuilder;
547                if (defaultFieldInstance != null) {
548                  subBuilder = defaultFieldInstance.WeakCreateBuilderForType();
549                } else {
550                  subBuilder = builder.CreateBuilderForField(field);
551                }
552                if (!field.IsRepeated) {
553                  subBuilder.WeakMergeFrom((IMessage)builder[field]);
554                }
555                if (field.FieldType == FieldType.Group) {
556                  input.ReadGroup(field.FieldNumber, subBuilder, extensionRegistry);
557                } else {
558                  input.ReadMessage(subBuilder, extensionRegistry);
559                }
560                value = subBuilder.WeakBuild();
561                break;
562              }
563            case FieldType.Enum: {
564                int rawValue = input.ReadEnum();
565                value = field.EnumType.FindValueByNumber(rawValue);
566                // If the number isn't recognized as a valid value for this enum,
567                // drop it.
568                if (value == null) {
569                  MergeVarintField(fieldNumber, (ulong)rawValue);
570                  return true;
571                }
572                break;
573              }
574            default:
575              value = input.ReadPrimitiveField(field.FieldType);
576              break;
577          }
578          if (field.IsRepeated) {
579            builder.WeakAddRepeatedField(field, value);
580          } else {
581            builder[field] = value;
582          }
583        }
584        return true;
585      }
586
587      /// <summary>
588      /// Called by MergeFieldFrom to parse a MessageSet extension.
589      /// </summary>
590      private void MergeMessageSetExtensionFromCodedStream(CodedInputStream input,
591          ExtensionRegistry extensionRegistry, IBuilder builder) {
592        MessageDescriptor type = builder.DescriptorForType;
593
594        // The wire format for MessageSet is:
595        //   message MessageSet {
596        //     repeated group Item = 1 {
597        //       required int32 typeId = 2;
598        //       required bytes message = 3;
599        //     }
600        //   }
601        // "typeId" is the extension's field number.  The extension can only be
602        // a message type, where "message" contains the encoded bytes of that
603        // message.
604        //
605        // In practice, we will probably never see a MessageSet item in which
606        // the message appears before the type ID, or where either field does not
607        // appear exactly once.  However, in theory such cases are valid, so we
608        // should be prepared to accept them.
609
610        int typeId = 0;
611        ByteString rawBytes = null;  // If we encounter "message" before "typeId"
612        IBuilder subBuilder = null;
613        FieldDescriptor field = null;
614
615        while (true) {
616          uint tag = input.ReadTag();
617          if (tag == 0) {
618            break;
619          }
620
621          if (tag == WireFormat.MessageSetTag.TypeID) {
622            typeId = input.ReadInt32();
623            // Zero is not a valid type ID.
624            if (typeId != 0) {
625              ExtensionInfo extension = extensionRegistry[type, typeId];
626              if (extension != null) {
627                field = extension.Descriptor;
628                subBuilder = extension.DefaultInstance.WeakCreateBuilderForType();
629                IMessage originalMessage = (IMessage)builder[field];
630                if (originalMessage != null) {
631                  subBuilder.WeakMergeFrom(originalMessage);
632                }
633                if (rawBytes != null) {
634                  // We already encountered the message.  Parse it now.
635                  // TODO(jonskeet): Check this is okay. It's subtly different from the Java, as it doesn't create an input stream from rawBytes.
636                  // In fact, why don't we just call MergeFrom(rawBytes)? And what about the extension registry?
637                  subBuilder.WeakMergeFrom(rawBytes.CreateCodedInput());
638                  rawBytes = null;
639                }
640              } else {
641                // Unknown extension number.  If we already saw data, put it
642                // in rawBytes.
643                if (rawBytes != null) {
644                  MergeField(typeId, UnknownField.CreateBuilder().AddLengthDelimited(rawBytes).Build());
645                  rawBytes = null;
646                }
647              }
648            }
649          } else if (tag == WireFormat.MessageSetTag.Message) {
650            if (typeId == 0) {
651              // We haven't seen a type ID yet, so we have to store the raw bytes for now.
652              rawBytes = input.ReadBytes();
653            } else if (subBuilder == null) {
654              // We don't know how to parse this.  Ignore it.
655              MergeField(typeId, UnknownField.CreateBuilder().AddLengthDelimited(input.ReadBytes()).Build());
656            } else {
657              // We already know the type, so we can parse directly from the input
658              // with no copying.  Hooray!
659              input.ReadMessage(subBuilder, extensionRegistry);
660            }
661          } else {
662            // Unknown tag.  Skip it.
663            if (!input.SkipField(tag)) {
664              break;  // end of group
665            }
666          }
667        }
668
669        input.CheckLastTagWas(WireFormat.MessageSetTag.ItemEnd);
670
671        if (subBuilder != null) {
672          builder[field] = subBuilder.WeakBuild();
673        }
674      }
675    }
676  }
677}
Note: See TracBrowser for help on using the repository browser.