1// Protocol Buffers - Google's data interchange format
2// Copyright 2008 Google Inc.  All rights reserved.
3// https://developers.google.com/protocol-buffers/
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are
7// met:
8//
9//     * Redistributions of source code must retain the above copyright
10// notice, this list of conditions and the following disclaimer.
11//     * Redistributions in binary form must reproduce the above
12// copyright notice, this list of conditions and the following disclaimer
13// in the documentation and/or other materials provided with the
14// distribution.
15//     * Neither the name of Google Inc. nor the names of its
16// contributors may be used to endorse or promote products derived from
17// this software without specific prior written permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31package com.google.protobuf;
32
33import java.io.IOException;
34import java.util.Arrays;
35
36/**
37 * {@code UnknownFieldSetLite} is used to keep track of fields which were seen
38 * when parsing a protocol message but whose field numbers or types are
39 * unrecognized. This most frequently occurs when new fields are added to a
40 * message type and then messages containing those fields are read by old
41 * software that was compiled before the new types were added.
42 *
43 * <p>For use by generated code only.
44 *
45 * @author dweis@google.com (Daniel Weis)
46 */
47public final class UnknownFieldSetLite {
48
49  // Arbitrarily chosen.
50  // TODO(dweis): Tune this number?
51  private static final int MIN_CAPACITY = 8;
52
53  private static final UnknownFieldSetLite DEFAULT_INSTANCE =
54      new UnknownFieldSetLite(0, new int[0], new Object[0], false /* isMutable */);
55
56  /**
57   * Get an empty {@code UnknownFieldSetLite}.
58   *
59   * <p>For use by generated code only.
60   */
61  public static UnknownFieldSetLite getDefaultInstance() {
62    return DEFAULT_INSTANCE;
63  }
64
65  /**
66   * Returns a new mutable instance.
67   */
68  static UnknownFieldSetLite newInstance() {
69    return new UnknownFieldSetLite();
70  }
71
72  /**
73   * Returns a mutable {@code UnknownFieldSetLite} that is the composite of {@code first} and
74   * {@code second}.
75   */
76  static UnknownFieldSetLite mutableCopyOf(UnknownFieldSetLite first, UnknownFieldSetLite second) {
77    int count = first.count + second.count;
78    int[] tags = Arrays.copyOf(first.tags, count);
79    System.arraycopy(second.tags, 0, tags, first.count, second.count);
80    Object[] objects = Arrays.copyOf(first.objects, count);
81    System.arraycopy(second.objects, 0, objects, first.count, second.count);
82    return new UnknownFieldSetLite(count, tags, objects, true /* isMutable */);
83  }
84
85  /**
86   * The number of elements in the set.
87   */
88  private int count;
89
90  /**
91   * The tag numbers for the elements in the set.
92   */
93  private int[] tags;
94
95  /**
96   * The boxed values of the elements in the set.
97   */
98  private Object[] objects;
99
100  /**
101   * The lazily computed serialized size of the set.
102   */
103  private int memoizedSerializedSize = -1;
104
105  /**
106   * Indicates that this object is mutable.
107   */
108  private boolean isMutable;
109
110  /**
111   * Constructs a mutable {@code UnknownFieldSetLite}.
112   */
113  private UnknownFieldSetLite() {
114    this(0, new int[MIN_CAPACITY], new Object[MIN_CAPACITY], true /* isMutable */);
115  }
116
117  /**
118   * Constructs the {@code UnknownFieldSetLite}.
119   */
120  private UnknownFieldSetLite(int count, int[] tags, Object[] objects, boolean isMutable) {
121    this.count = count;
122    this.tags = tags;
123    this.objects = objects;
124    this.isMutable = isMutable;
125  }
126
127  /**
128   * Marks this object as immutable.
129   *
130   * <p>Future calls to methods that attempt to modify this object will throw.
131   */
132  public void makeImmutable() {
133    this.isMutable = false;
134  }
135
136  /**
137   * Throws an {@link UnsupportedOperationException} if immutable.
138   */
139  void checkMutable() {
140    if (!isMutable) {
141      throw new UnsupportedOperationException();
142    }
143  }
144
145  /**
146   * Serializes the set and writes it to {@code output}.
147   *
148   * <p>For use by generated code only.
149   */
150  public void writeTo(CodedOutputStream output) throws IOException {
151    for (int i = 0; i < count; i++) {
152      int tag = tags[i];
153      int fieldNumber = WireFormat.getTagFieldNumber(tag);
154      switch (WireFormat.getTagWireType(tag)) {
155        case WireFormat.WIRETYPE_VARINT:
156          output.writeUInt64(fieldNumber, (Long) objects[i]);
157          break;
158        case WireFormat.WIRETYPE_FIXED32:
159          output.writeFixed32(fieldNumber, (Integer) objects[i]);
160          break;
161        case WireFormat.WIRETYPE_FIXED64:
162          output.writeFixed64(fieldNumber, (Long) objects[i]);
163          break;
164        case WireFormat.WIRETYPE_LENGTH_DELIMITED:
165          output.writeBytes(fieldNumber, (ByteString) objects[i]);
166          break;
167        case WireFormat.WIRETYPE_START_GROUP:
168          output.writeTag(fieldNumber, WireFormat.WIRETYPE_START_GROUP);
169          ((UnknownFieldSetLite) objects[i]).writeTo(output);
170          output.writeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP);
171          break;
172        default:
173          throw InvalidProtocolBufferException.invalidWireType();
174      }
175    }
176  }
177
178  /**
179   * Get the number of bytes required to encode this set.
180   *
181   * <p>For use by generated code only.
182   */
183  public int getSerializedSize() {
184    int size = memoizedSerializedSize;
185    if (size != -1) {
186      return size;
187    }
188
189    size = 0;
190    for (int i = 0; i < count; i++) {
191      int tag = tags[i];
192      int fieldNumber = WireFormat.getTagFieldNumber(tag);
193      switch (WireFormat.getTagWireType(tag)) {
194        case WireFormat.WIRETYPE_VARINT:
195          size += CodedOutputStream.computeUInt64Size(fieldNumber, (Long) objects[i]);
196          break;
197        case WireFormat.WIRETYPE_FIXED32:
198          size += CodedOutputStream.computeFixed32Size(fieldNumber, (Integer) objects[i]);
199          break;
200        case WireFormat.WIRETYPE_FIXED64:
201          size += CodedOutputStream.computeFixed64Size(fieldNumber, (Long) objects[i]);
202          break;
203        case WireFormat.WIRETYPE_LENGTH_DELIMITED:
204          size += CodedOutputStream.computeBytesSize(fieldNumber, (ByteString) objects[i]);
205          break;
206        case WireFormat.WIRETYPE_START_GROUP:
207          size +=  CodedOutputStream.computeTagSize(fieldNumber) * 2
208              + ((UnknownFieldSetLite) objects[i]).getSerializedSize();
209          break;
210        default:
211          throw new IllegalStateException(InvalidProtocolBufferException.invalidWireType());
212      }
213    }
214
215    memoizedSerializedSize = size;
216
217    return size;
218  }
219
220  @Override
221  public boolean equals(Object obj) {
222    if (this == obj) {
223      return true;
224    }
225
226    if (obj == null) {
227      return false;
228    }
229
230    if (!(obj instanceof UnknownFieldSetLite)) {
231      return false;
232    }
233
234    UnknownFieldSetLite other = (UnknownFieldSetLite) obj;
235    if (count != other.count
236        // TODO(dweis): Only have to compare up to count but at worst 2x worse than we need to do.
237        || !Arrays.equals(tags, other.tags)
238        || !Arrays.deepEquals(objects, other.objects)) {
239      return false;
240    }
241
242    return true;
243  }
244
245  @Override
246  public int hashCode() {
247    int hashCode = 17;
248
249    hashCode = 31 * hashCode + count;
250    hashCode = 31 * hashCode + Arrays.hashCode(tags);
251    hashCode = 31 * hashCode + Arrays.deepHashCode(objects);
252
253    return hashCode;
254  }
255
256  /**
257   * Prints a String representation of the unknown field set.
258   *
259   * <p>For use by generated code only.
260   *
261   * @param buffer the buffer to write to
262   * @param indent the number of spaces the fields should be indented by
263   */
264  final void printWithIndent(StringBuilder buffer, int indent) {
265    for (int i = 0; i < count; i++) {
266      int fieldNumber = WireFormat.getTagFieldNumber(tags[i]);
267      MessageLiteToString.printField(buffer, indent, String.valueOf(fieldNumber), objects[i]);
268    }
269  }
270
271  private void storeField(int tag, Object value) {
272    ensureCapacity();
273
274    tags[count] = tag;
275    objects[count] = value;
276    count++;
277  }
278
279  /**
280   * Ensures that our arrays are long enough to store more metadata.
281   */
282  private void ensureCapacity() {
283    if (count == tags.length) {
284      int increment = count < (MIN_CAPACITY / 2) ? MIN_CAPACITY : count >> 1;
285      int newLength = count + increment;
286
287      tags = Arrays.copyOf(tags, newLength);
288      objects = Arrays.copyOf(objects, newLength);
289    }
290  }
291
292  /**
293   * Parse a single field from {@code input} and merge it into this set.
294   *
295   * <p>For use by generated code only.
296   *
297   * @param tag The field's tag number, which was already parsed.
298   * @return {@code false} if the tag is an end group tag.
299   */
300  boolean mergeFieldFrom(final int tag, final CodedInputStream input) throws IOException {
301    checkMutable();
302    final int fieldNumber = WireFormat.getTagFieldNumber(tag);
303    switch (WireFormat.getTagWireType(tag)) {
304      case WireFormat.WIRETYPE_VARINT:
305        storeField(tag, input.readInt64());
306        return true;
307      case WireFormat.WIRETYPE_FIXED32:
308        storeField(tag, input.readFixed32());
309        return true;
310      case WireFormat.WIRETYPE_FIXED64:
311        storeField(tag, input.readFixed64());
312        return true;
313      case WireFormat.WIRETYPE_LENGTH_DELIMITED:
314        storeField(tag, input.readBytes());
315        return true;
316      case WireFormat.WIRETYPE_START_GROUP:
317        final UnknownFieldSetLite subFieldSet = new UnknownFieldSetLite();
318        subFieldSet.mergeFrom(input);
319        input.checkLastTagWas(
320            WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP));
321        storeField(tag, subFieldSet);
322        return true;
323      case WireFormat.WIRETYPE_END_GROUP:
324        return false;
325      default:
326        throw InvalidProtocolBufferException.invalidWireType();
327    }
328  }
329
330  /**
331   * Convenience method for merging a new field containing a single varint
332   * value. This is used in particular when an unknown enum value is
333   * encountered.
334   *
335   * <p>For use by generated code only.
336   */
337  UnknownFieldSetLite mergeVarintField(int fieldNumber, int value) {
338    checkMutable();
339    if (fieldNumber == 0) {
340      throw new IllegalArgumentException("Zero is not a valid field number.");
341    }
342
343    storeField(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_VARINT), (long) value);
344
345    return this;
346  }
347
348  /**
349   * Convenience method for merging a length-delimited field.
350   *
351   * <p>For use by generated code only.
352   */
353  UnknownFieldSetLite mergeLengthDelimitedField(final int fieldNumber, final ByteString value) {
354    checkMutable();
355    if (fieldNumber == 0) {
356      throw new IllegalArgumentException("Zero is not a valid field number.");
357    }
358
359    storeField(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED), value);
360
361    return this;
362  }
363
364  /**
365   * Parse an entire message from {@code input} and merge its fields into
366   * this set.
367   */
368  private UnknownFieldSetLite mergeFrom(final CodedInputStream input) throws IOException {
369    // Ensures initialization in mergeFieldFrom.
370    while (true) {
371      final int tag = input.readTag();
372      if (tag == 0 || !mergeFieldFrom(tag, input)) {
373        break;
374      }
375    }
376    return this;
377  }
378}
379