1327 lines
45 KiB
Java
1327 lines
45 KiB
Java
// Protocol Buffers - Google's data interchange format
|
|
// Copyright 2008 Google Inc. All rights reserved.
|
|
// https://developers.google.com/protocol-buffers/
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are
|
|
// met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above
|
|
// copyright notice, this list of conditions and the following disclaimer
|
|
// in the documentation and/or other materials provided with the
|
|
// distribution.
|
|
// * Neither the name of Google Inc. nor the names of its
|
|
// contributors may be used to endorse or promote products derived from
|
|
// this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
package com.google.protobuf;
|
|
|
|
import static com.google.protobuf.Internal.checkNotNull;
|
|
|
|
import com.google.protobuf.LazyField.LazyIterator;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Map.Entry;
|
|
|
|
/**
|
|
* A class which represents an arbitrary set of fields of some message type. This is used to
|
|
* implement {@link DynamicMessage}, and also to represent extensions in {@link GeneratedMessage}.
|
|
* This class is package-private, since outside users should probably be using {@link
|
|
* DynamicMessage}.
|
|
*
|
|
* @author kenton@google.com Kenton Varda
|
|
*/
|
|
final class FieldSet<T extends FieldSet.FieldDescriptorLite<T>> {
|
|
/**
|
|
* Interface for a FieldDescriptor or lite extension descriptor. This prevents FieldSet from
|
|
* depending on {@link Descriptors.FieldDescriptor}.
|
|
*/
|
|
public interface FieldDescriptorLite<T extends FieldDescriptorLite<T>> extends Comparable<T> {
|
|
int getNumber();
|
|
|
|
WireFormat.FieldType getLiteType();
|
|
|
|
WireFormat.JavaType getLiteJavaType();
|
|
|
|
boolean isRepeated();
|
|
|
|
boolean isPacked();
|
|
|
|
Internal.EnumLiteMap<?> getEnumType();
|
|
|
|
// If getLiteJavaType() == MESSAGE, this merges a message object of the
|
|
// type into a builder of the type. Returns {@code to}.
|
|
MessageLite.Builder internalMergeFrom(MessageLite.Builder to, MessageLite from);
|
|
}
|
|
|
|
private static final int DEFAULT_FIELD_MAP_ARRAY_SIZE = 16;
|
|
|
|
private final SmallSortedMap<T, Object> fields;
|
|
private boolean isImmutable;
|
|
private boolean hasLazyField;
|
|
|
|
/** Construct a new FieldSet. */
|
|
private FieldSet() {
|
|
this.fields = SmallSortedMap.newFieldMap(DEFAULT_FIELD_MAP_ARRAY_SIZE);
|
|
}
|
|
|
|
/** Construct an empty FieldSet. This is only used to initialize DEFAULT_INSTANCE. */
|
|
@SuppressWarnings("unused")
|
|
private FieldSet(final boolean dummy) {
|
|
this(SmallSortedMap.<T>newFieldMap(0));
|
|
makeImmutable();
|
|
}
|
|
|
|
private FieldSet(SmallSortedMap<T, Object> fields) {
|
|
this.fields = fields;
|
|
makeImmutable();
|
|
}
|
|
|
|
/** Construct a new FieldSet. */
|
|
public static <T extends FieldSet.FieldDescriptorLite<T>> FieldSet<T> newFieldSet() {
|
|
return new FieldSet<T>();
|
|
}
|
|
|
|
/** Get an immutable empty FieldSet. */
|
|
@SuppressWarnings("unchecked")
|
|
public static <T extends FieldSet.FieldDescriptorLite<T>> FieldSet<T> emptySet() {
|
|
return DEFAULT_INSTANCE;
|
|
}
|
|
|
|
/** Construct a new Builder. */
|
|
public static <T extends FieldDescriptorLite<T>> Builder<T> newBuilder() {
|
|
return new Builder<T>();
|
|
}
|
|
|
|
@SuppressWarnings("rawtypes")
|
|
private static final FieldSet DEFAULT_INSTANCE = new FieldSet(true);
|
|
|
|
/** Returns {@code true} if empty, {@code false} otherwise. */
|
|
boolean isEmpty() {
|
|
return fields.isEmpty();
|
|
}
|
|
|
|
/** Make this FieldSet immutable from this point forward. */
|
|
public void makeImmutable() {
|
|
if (isImmutable) {
|
|
return;
|
|
}
|
|
for (int i = 0; i < fields.getNumArrayEntries(); ++i) {
|
|
Entry<T, Object> entry = fields.getArrayEntryAt(i);
|
|
if (entry.getValue() instanceof GeneratedMessageLite) {
|
|
((GeneratedMessageLite<?, ?>) entry.getValue()).makeImmutable();
|
|
}
|
|
}
|
|
fields.makeImmutable();
|
|
isImmutable = true;
|
|
}
|
|
|
|
/**
|
|
* Returns whether the FieldSet is immutable. This is true if it is the {@link #emptySet} or if
|
|
* {@link #makeImmutable} were called.
|
|
*
|
|
* @return whether the FieldSet is immutable.
|
|
*/
|
|
public boolean isImmutable() {
|
|
return isImmutable;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (this == o) {
|
|
return true;
|
|
}
|
|
|
|
if (!(o instanceof FieldSet)) {
|
|
return false;
|
|
}
|
|
|
|
FieldSet<?> other = (FieldSet<?>) o;
|
|
return fields.equals(other.fields);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return fields.hashCode();
|
|
}
|
|
|
|
/**
|
|
* Clones the FieldSet. The returned FieldSet will be mutable even if the original FieldSet was
|
|
* immutable.
|
|
*
|
|
* @return the newly cloned FieldSet
|
|
*/
|
|
@Override
|
|
public FieldSet<T> clone() {
|
|
// We can't just call fields.clone because List objects in the map
|
|
// should not be shared.
|
|
FieldSet<T> clone = FieldSet.newFieldSet();
|
|
for (int i = 0; i < fields.getNumArrayEntries(); i++) {
|
|
Map.Entry<T, Object> entry = fields.getArrayEntryAt(i);
|
|
clone.setField(entry.getKey(), entry.getValue());
|
|
}
|
|
for (Map.Entry<T, Object> entry : fields.getOverflowEntries()) {
|
|
clone.setField(entry.getKey(), entry.getValue());
|
|
}
|
|
clone.hasLazyField = hasLazyField;
|
|
return clone;
|
|
}
|
|
|
|
|
|
// =================================================================
|
|
|
|
/** See {@link Message.Builder#clear()}. */
|
|
public void clear() {
|
|
fields.clear();
|
|
hasLazyField = false;
|
|
}
|
|
|
|
/** Get a simple map containing all the fields. */
|
|
public Map<T, Object> getAllFields() {
|
|
if (hasLazyField) {
|
|
SmallSortedMap<T, Object> result = cloneAllFieldsMap(fields, /* copyList */ false);
|
|
if (fields.isImmutable()) {
|
|
result.makeImmutable();
|
|
}
|
|
return result;
|
|
}
|
|
return fields.isImmutable() ? fields : Collections.unmodifiableMap(fields);
|
|
}
|
|
|
|
private static <T extends FieldDescriptorLite<T>> SmallSortedMap<T, Object> cloneAllFieldsMap(
|
|
SmallSortedMap<T, Object> fields, boolean copyList) {
|
|
SmallSortedMap<T, Object> result = SmallSortedMap.newFieldMap(DEFAULT_FIELD_MAP_ARRAY_SIZE);
|
|
for (int i = 0; i < fields.getNumArrayEntries(); i++) {
|
|
cloneFieldEntry(result, fields.getArrayEntryAt(i), copyList);
|
|
}
|
|
for (Map.Entry<T, Object> entry : fields.getOverflowEntries()) {
|
|
cloneFieldEntry(result, entry, copyList);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private static <T extends FieldDescriptorLite<T>> void cloneFieldEntry(
|
|
Map<T, Object> map, Map.Entry<T, Object> entry, boolean copyList) {
|
|
T key = entry.getKey();
|
|
Object value = entry.getValue();
|
|
if (value instanceof LazyField) {
|
|
map.put(key, ((LazyField) value).getValue());
|
|
} else if (copyList && value instanceof List) {
|
|
map.put(key, new ArrayList<>((List<?>) value));
|
|
} else {
|
|
map.put(key, value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get an iterator to the field map. This iterator should not be leaked out of the protobuf
|
|
* library as it is not protected from mutation when fields is not immutable.
|
|
*/
|
|
public Iterator<Map.Entry<T, Object>> iterator() {
|
|
if (hasLazyField) {
|
|
return new LazyIterator<T>(fields.entrySet().iterator());
|
|
}
|
|
return fields.entrySet().iterator();
|
|
}
|
|
|
|
/**
|
|
* Get an iterator over the fields in the map in descending (i.e. reverse) order. This iterator
|
|
* should not be leaked out of the protobuf library as it is not protected from mutation when
|
|
* fields is not immutable.
|
|
*/
|
|
Iterator<Map.Entry<T, Object>> descendingIterator() {
|
|
if (hasLazyField) {
|
|
return new LazyIterator<T>(fields.descendingEntrySet().iterator());
|
|
}
|
|
return fields.descendingEntrySet().iterator();
|
|
}
|
|
|
|
/** Useful for implementing {@link Message#hasField(Descriptors.FieldDescriptor)}. */
|
|
public boolean hasField(final T descriptor) {
|
|
if (descriptor.isRepeated()) {
|
|
throw new IllegalArgumentException("hasField() can only be called on non-repeated fields.");
|
|
}
|
|
|
|
return fields.get(descriptor) != null;
|
|
}
|
|
|
|
/**
|
|
* Useful for implementing {@link Message#getField(Descriptors.FieldDescriptor)}. This method
|
|
* returns {@code null} if the field is not set; in this case it is up to the caller to fetch the
|
|
* field's default value.
|
|
*/
|
|
public Object getField(final T descriptor) {
|
|
Object o = fields.get(descriptor);
|
|
if (o instanceof LazyField) {
|
|
return ((LazyField) o).getValue();
|
|
}
|
|
return o;
|
|
}
|
|
|
|
/**
|
|
* Useful for implementing {@link Message.Builder#setField(Descriptors.FieldDescriptor,Object)}.
|
|
*/
|
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
public void setField(final T descriptor, Object value) {
|
|
if (descriptor.isRepeated()) {
|
|
if (!(value instanceof List)) {
|
|
throw new IllegalArgumentException(
|
|
"Wrong object type used with protocol message reflection.");
|
|
}
|
|
|
|
// Wrap the contents in a new list so that the caller cannot change
|
|
// the list's contents after setting it.
|
|
final List newList = new ArrayList<>();
|
|
newList.addAll((List) value);
|
|
for (final Object element : newList) {
|
|
verifyType(descriptor, element);
|
|
}
|
|
value = newList;
|
|
} else {
|
|
verifyType(descriptor, value);
|
|
}
|
|
|
|
if (value instanceof LazyField) {
|
|
hasLazyField = true;
|
|
}
|
|
fields.put(descriptor, value);
|
|
}
|
|
|
|
/** Useful for implementing {@link Message.Builder#clearField(Descriptors.FieldDescriptor)}. */
|
|
public void clearField(final T descriptor) {
|
|
fields.remove(descriptor);
|
|
if (fields.isEmpty()) {
|
|
hasLazyField = false;
|
|
}
|
|
}
|
|
|
|
/** Useful for implementing {@link Message#getRepeatedFieldCount(Descriptors.FieldDescriptor)}. */
|
|
public int getRepeatedFieldCount(final T descriptor) {
|
|
if (!descriptor.isRepeated()) {
|
|
throw new IllegalArgumentException(
|
|
"getRepeatedField() can only be called on repeated fields.");
|
|
}
|
|
|
|
final Object value = getField(descriptor);
|
|
if (value == null) {
|
|
return 0;
|
|
} else {
|
|
return ((List<?>) value).size();
|
|
}
|
|
}
|
|
|
|
/** Useful for implementing {@link Message#getRepeatedField(Descriptors.FieldDescriptor,int)}. */
|
|
public Object getRepeatedField(final T descriptor, final int index) {
|
|
if (!descriptor.isRepeated()) {
|
|
throw new IllegalArgumentException(
|
|
"getRepeatedField() can only be called on repeated fields.");
|
|
}
|
|
|
|
final Object value = getField(descriptor);
|
|
|
|
if (value == null) {
|
|
throw new IndexOutOfBoundsException();
|
|
} else {
|
|
return ((List<?>) value).get(index);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Useful for implementing {@link
|
|
* Message.Builder#setRepeatedField(Descriptors.FieldDescriptor,int,Object)}.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public void setRepeatedField(final T descriptor, final int index, final Object value) {
|
|
if (!descriptor.isRepeated()) {
|
|
throw new IllegalArgumentException(
|
|
"getRepeatedField() can only be called on repeated fields.");
|
|
}
|
|
|
|
final Object list = getField(descriptor);
|
|
if (list == null) {
|
|
throw new IndexOutOfBoundsException();
|
|
}
|
|
|
|
verifyType(descriptor, value);
|
|
((List<Object>) list).set(index, value);
|
|
}
|
|
|
|
/**
|
|
* Useful for implementing {@link
|
|
* Message.Builder#addRepeatedField(Descriptors.FieldDescriptor,Object)}.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public void addRepeatedField(final T descriptor, final Object value) {
|
|
if (!descriptor.isRepeated()) {
|
|
throw new IllegalArgumentException(
|
|
"addRepeatedField() can only be called on repeated fields.");
|
|
}
|
|
|
|
verifyType(descriptor, value);
|
|
|
|
final Object existingValue = getField(descriptor);
|
|
List<Object> list;
|
|
if (existingValue == null) {
|
|
list = new ArrayList<Object>();
|
|
fields.put(descriptor, list);
|
|
} else {
|
|
list = (List<Object>) existingValue;
|
|
}
|
|
|
|
list.add(value);
|
|
}
|
|
|
|
/**
|
|
* Verifies that the given object is of the correct type to be a valid value for the given field.
|
|
* (For repeated fields, this checks if the object is the right type to be one element of the
|
|
* field.)
|
|
*
|
|
* @throws IllegalArgumentException the value is not of the right type
|
|
*/
|
|
private void verifyType(final T descriptor, final Object value) {
|
|
if (!isValidType(descriptor.getLiteType(), value)) {
|
|
throw new IllegalArgumentException(
|
|
String.format(
|
|
"Wrong object type used with protocol message reflection.\n"
|
|
+ "Field number: %d, field java type: %s, value type: %s\n",
|
|
descriptor.getNumber(),
|
|
descriptor.getLiteType().getJavaType(),
|
|
value.getClass().getName()));
|
|
}
|
|
}
|
|
|
|
|
|
private static boolean isValidType(final WireFormat.FieldType type, final Object value) {
|
|
checkNotNull(value);
|
|
switch (type.getJavaType()) {
|
|
case INT:
|
|
return value instanceof Integer;
|
|
case LONG:
|
|
return value instanceof Long;
|
|
case FLOAT:
|
|
return value instanceof Float;
|
|
case DOUBLE:
|
|
return value instanceof Double;
|
|
case BOOLEAN:
|
|
return value instanceof Boolean;
|
|
case STRING:
|
|
return value instanceof String;
|
|
case BYTE_STRING:
|
|
return value instanceof ByteString || value instanceof byte[];
|
|
case ENUM:
|
|
return (value instanceof Integer || value instanceof Internal.EnumLite);
|
|
case MESSAGE:
|
|
return (value instanceof MessageLite) || (value instanceof LazyField);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// =================================================================
|
|
// Parsing and serialization
|
|
|
|
/**
|
|
* See {@link Message#isInitialized()}. Note: Since {@code FieldSet} itself does not have any way
|
|
* of knowing about required fields that aren't actually present in the set, it is up to the
|
|
* caller to check that all required fields are present.
|
|
*/
|
|
public boolean isInitialized() {
|
|
for (int i = 0; i < fields.getNumArrayEntries(); i++) {
|
|
if (!isInitialized(fields.getArrayEntryAt(i))) {
|
|
return false;
|
|
}
|
|
}
|
|
for (final Map.Entry<T, Object> entry : fields.getOverflowEntries()) {
|
|
if (!isInitialized(entry)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static <T extends FieldDescriptorLite<T>> boolean isInitialized(
|
|
final Map.Entry<T, Object> entry) {
|
|
final T descriptor = entry.getKey();
|
|
if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) {
|
|
if (descriptor.isRepeated()) {
|
|
for (final Object element : (List<?>) entry.getValue()) {
|
|
if (!isMessageFieldValueInitialized(element)) {
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
return isMessageFieldValueInitialized(entry.getValue());
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static boolean isMessageFieldValueInitialized(Object value) {
|
|
if (value instanceof MessageLiteOrBuilder) {
|
|
// Message fields cannot have builder values in FieldSet, but can in FieldSet.Builder, and
|
|
// this method is used by FieldSet.Builder.isInitialized.
|
|
return ((MessageLiteOrBuilder) value).isInitialized();
|
|
} else if (value instanceof LazyField) {
|
|
return true;
|
|
} else {
|
|
throw new IllegalArgumentException(
|
|
"Wrong object type used with protocol message reflection.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Given a field type, return the wire type.
|
|
*
|
|
* @return One of the {@code WIRETYPE_} constants defined in {@link WireFormat}.
|
|
*/
|
|
static int getWireFormatForFieldType(final WireFormat.FieldType type, boolean isPacked) {
|
|
if (isPacked) {
|
|
return WireFormat.WIRETYPE_LENGTH_DELIMITED;
|
|
} else {
|
|
return type.getWireType();
|
|
}
|
|
}
|
|
|
|
/** Like {@link Message.Builder#mergeFrom(Message)}, but merges from another {@link FieldSet}. */
|
|
public void mergeFrom(final FieldSet<T> other) {
|
|
for (int i = 0; i < other.fields.getNumArrayEntries(); i++) {
|
|
mergeFromField(other.fields.getArrayEntryAt(i));
|
|
}
|
|
for (final Map.Entry<T, Object> entry : other.fields.getOverflowEntries()) {
|
|
mergeFromField(entry);
|
|
}
|
|
}
|
|
|
|
private static Object cloneIfMutable(Object value) {
|
|
if (value instanceof byte[]) {
|
|
byte[] bytes = (byte[]) value;
|
|
byte[] copy = new byte[bytes.length];
|
|
System.arraycopy(bytes, 0, copy, 0, bytes.length);
|
|
return copy;
|
|
} else {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
private void mergeFromField(final Map.Entry<T, Object> entry) {
|
|
final T descriptor = entry.getKey();
|
|
Object otherValue = entry.getValue();
|
|
if (otherValue instanceof LazyField) {
|
|
otherValue = ((LazyField) otherValue).getValue();
|
|
}
|
|
|
|
if (descriptor.isRepeated()) {
|
|
Object value = getField(descriptor);
|
|
if (value == null) {
|
|
value = new ArrayList<>();
|
|
}
|
|
for (Object element : (List) otherValue) {
|
|
((List) value).add(cloneIfMutable(element));
|
|
}
|
|
fields.put(descriptor, value);
|
|
} else if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) {
|
|
Object value = getField(descriptor);
|
|
if (value == null) {
|
|
fields.put(descriptor, cloneIfMutable(otherValue));
|
|
} else {
|
|
// Merge the messages.
|
|
value =
|
|
descriptor
|
|
.internalMergeFrom(((MessageLite) value).toBuilder(), (MessageLite) otherValue)
|
|
.build();
|
|
fields.put(descriptor, value);
|
|
}
|
|
} else {
|
|
fields.put(descriptor, cloneIfMutable(otherValue));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read a field of any primitive type for immutable messages from a CodedInputStream. Enums,
|
|
* groups, and embedded messages are not handled by this method.
|
|
*
|
|
* @param input the stream from which to read
|
|
* @param type declared type of the field
|
|
* @param checkUtf8 When true, check that the input is valid UTF-8
|
|
* @return an object representing the field's value, of the exact type which would be returned by
|
|
* {@link Message#getField(Descriptors.FieldDescriptor)} for this field
|
|
*/
|
|
public static Object readPrimitiveField(
|
|
CodedInputStream input, final WireFormat.FieldType type, boolean checkUtf8)
|
|
throws IOException {
|
|
if (checkUtf8) {
|
|
return WireFormat.readPrimitiveField(input, type, WireFormat.Utf8Validation.STRICT);
|
|
} else {
|
|
return WireFormat.readPrimitiveField(input, type, WireFormat.Utf8Validation.LOOSE);
|
|
}
|
|
}
|
|
|
|
|
|
/** See {@link Message#writeTo(CodedOutputStream)}. */
|
|
public void writeTo(final CodedOutputStream output) throws IOException {
|
|
for (int i = 0; i < fields.getNumArrayEntries(); i++) {
|
|
final Map.Entry<T, Object> entry = fields.getArrayEntryAt(i);
|
|
writeField(entry.getKey(), entry.getValue(), output);
|
|
}
|
|
for (final Map.Entry<T, Object> entry : fields.getOverflowEntries()) {
|
|
writeField(entry.getKey(), entry.getValue(), output);
|
|
}
|
|
}
|
|
|
|
/** Like {@link #writeTo} but uses MessageSet wire format. */
|
|
public void writeMessageSetTo(final CodedOutputStream output) throws IOException {
|
|
for (int i = 0; i < fields.getNumArrayEntries(); i++) {
|
|
writeMessageSetTo(fields.getArrayEntryAt(i), output);
|
|
}
|
|
for (final Map.Entry<T, Object> entry : fields.getOverflowEntries()) {
|
|
writeMessageSetTo(entry, output);
|
|
}
|
|
}
|
|
|
|
private void writeMessageSetTo(final Map.Entry<T, Object> entry, final CodedOutputStream output)
|
|
throws IOException {
|
|
final T descriptor = entry.getKey();
|
|
if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE
|
|
&& !descriptor.isRepeated()
|
|
&& !descriptor.isPacked()) {
|
|
Object value = entry.getValue();
|
|
if (value instanceof LazyField) {
|
|
value = ((LazyField) value).getValue();
|
|
}
|
|
output.writeMessageSetExtension(entry.getKey().getNumber(), (MessageLite) value);
|
|
} else {
|
|
writeField(descriptor, entry.getValue(), output);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Write a single tag-value pair to the stream.
|
|
*
|
|
* @param output The output stream.
|
|
* @param type The field's type.
|
|
* @param number The field's number.
|
|
* @param value Object representing the field's value. Must be of the exact type which would be
|
|
* returned by {@link Message#getField(Descriptors.FieldDescriptor)} for this field.
|
|
*/
|
|
static void writeElement(
|
|
final CodedOutputStream output,
|
|
final WireFormat.FieldType type,
|
|
final int number,
|
|
final Object value)
|
|
throws IOException {
|
|
// Special case for groups, which need a start and end tag; other fields
|
|
// can just use writeTag() and writeFieldNoTag().
|
|
if (type == WireFormat.FieldType.GROUP) {
|
|
output.writeGroup(number, (MessageLite) value);
|
|
} else {
|
|
output.writeTag(number, getWireFormatForFieldType(type, false));
|
|
writeElementNoTag(output, type, value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Write a field of arbitrary type, without its tag, to the stream.
|
|
*
|
|
* @param output The output stream.
|
|
* @param type The field's type.
|
|
* @param value Object representing the field's value. Must be of the exact type which would be
|
|
* returned by {@link Message#getField(Descriptors.FieldDescriptor)} for this field.
|
|
*/
|
|
static void writeElementNoTag(
|
|
final CodedOutputStream output, final WireFormat.FieldType type, final Object value)
|
|
throws IOException {
|
|
switch (type) {
|
|
case DOUBLE:
|
|
output.writeDoubleNoTag((Double) value);
|
|
break;
|
|
case FLOAT:
|
|
output.writeFloatNoTag((Float) value);
|
|
break;
|
|
case INT64:
|
|
output.writeInt64NoTag((Long) value);
|
|
break;
|
|
case UINT64:
|
|
output.writeUInt64NoTag((Long) value);
|
|
break;
|
|
case INT32:
|
|
output.writeInt32NoTag((Integer) value);
|
|
break;
|
|
case FIXED64:
|
|
output.writeFixed64NoTag((Long) value);
|
|
break;
|
|
case FIXED32:
|
|
output.writeFixed32NoTag((Integer) value);
|
|
break;
|
|
case BOOL:
|
|
output.writeBoolNoTag((Boolean) value);
|
|
break;
|
|
case GROUP:
|
|
output.writeGroupNoTag((MessageLite) value);
|
|
break;
|
|
case MESSAGE:
|
|
output.writeMessageNoTag((MessageLite) value);
|
|
break;
|
|
case STRING:
|
|
if (value instanceof ByteString) {
|
|
output.writeBytesNoTag((ByteString) value);
|
|
} else {
|
|
output.writeStringNoTag((String) value);
|
|
}
|
|
break;
|
|
case BYTES:
|
|
if (value instanceof ByteString) {
|
|
output.writeBytesNoTag((ByteString) value);
|
|
} else {
|
|
output.writeByteArrayNoTag((byte[]) value);
|
|
}
|
|
break;
|
|
case UINT32:
|
|
output.writeUInt32NoTag((Integer) value);
|
|
break;
|
|
case SFIXED32:
|
|
output.writeSFixed32NoTag((Integer) value);
|
|
break;
|
|
case SFIXED64:
|
|
output.writeSFixed64NoTag((Long) value);
|
|
break;
|
|
case SINT32:
|
|
output.writeSInt32NoTag((Integer) value);
|
|
break;
|
|
case SINT64:
|
|
output.writeSInt64NoTag((Long) value);
|
|
break;
|
|
|
|
case ENUM:
|
|
if (value instanceof Internal.EnumLite) {
|
|
output.writeEnumNoTag(((Internal.EnumLite) value).getNumber());
|
|
} else {
|
|
output.writeEnumNoTag(((Integer) value).intValue());
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** Write a single field. */
|
|
public static void writeField(
|
|
final FieldDescriptorLite<?> descriptor, final Object value, final CodedOutputStream output)
|
|
throws IOException {
|
|
WireFormat.FieldType type = descriptor.getLiteType();
|
|
int number = descriptor.getNumber();
|
|
if (descriptor.isRepeated()) {
|
|
final List<?> valueList = (List<?>) value;
|
|
if (descriptor.isPacked()) {
|
|
output.writeTag(number, WireFormat.WIRETYPE_LENGTH_DELIMITED);
|
|
// Compute the total data size so the length can be written.
|
|
int dataSize = 0;
|
|
for (final Object element : valueList) {
|
|
dataSize += computeElementSizeNoTag(type, element);
|
|
}
|
|
output.writeUInt32NoTag(dataSize);
|
|
// Write the data itself, without any tags.
|
|
for (final Object element : valueList) {
|
|
writeElementNoTag(output, type, element);
|
|
}
|
|
} else {
|
|
for (final Object element : valueList) {
|
|
writeElement(output, type, number, element);
|
|
}
|
|
}
|
|
} else {
|
|
if (value instanceof LazyField) {
|
|
writeElement(output, type, number, ((LazyField) value).getValue());
|
|
} else {
|
|
writeElement(output, type, number, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* See {@link Message#getSerializedSize()}. It's up to the caller to cache the resulting size if
|
|
* desired.
|
|
*/
|
|
public int getSerializedSize() {
|
|
int size = 0;
|
|
for (int i = 0; i < fields.getNumArrayEntries(); i++) {
|
|
final Map.Entry<T, Object> entry = fields.getArrayEntryAt(i);
|
|
size += computeFieldSize(entry.getKey(), entry.getValue());
|
|
}
|
|
for (final Map.Entry<T, Object> entry : fields.getOverflowEntries()) {
|
|
size += computeFieldSize(entry.getKey(), entry.getValue());
|
|
}
|
|
return size;
|
|
}
|
|
|
|
/** Like {@link #getSerializedSize} but uses MessageSet wire format. */
|
|
public int getMessageSetSerializedSize() {
|
|
int size = 0;
|
|
for (int i = 0; i < fields.getNumArrayEntries(); i++) {
|
|
size += getMessageSetSerializedSize(fields.getArrayEntryAt(i));
|
|
}
|
|
for (final Map.Entry<T, Object> entry : fields.getOverflowEntries()) {
|
|
size += getMessageSetSerializedSize(entry);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
private int getMessageSetSerializedSize(final Map.Entry<T, Object> entry) {
|
|
final T descriptor = entry.getKey();
|
|
Object value = entry.getValue();
|
|
if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE
|
|
&& !descriptor.isRepeated()
|
|
&& !descriptor.isPacked()) {
|
|
if (value instanceof LazyField) {
|
|
return CodedOutputStream.computeLazyFieldMessageSetExtensionSize(
|
|
entry.getKey().getNumber(), (LazyField) value);
|
|
} else {
|
|
return CodedOutputStream.computeMessageSetExtensionSize(
|
|
entry.getKey().getNumber(), (MessageLite) value);
|
|
}
|
|
} else {
|
|
return computeFieldSize(descriptor, value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compute the number of bytes that would be needed to encode a single tag/value pair of arbitrary
|
|
* type.
|
|
*
|
|
* @param type The field's type.
|
|
* @param number The field's number.
|
|
* @param value Object representing the field's value. Must be of the exact type which would be
|
|
* returned by {@link Message#getField(Descriptors.FieldDescriptor)} for this field.
|
|
*/
|
|
static int computeElementSize(
|
|
final WireFormat.FieldType type, final int number, final Object value) {
|
|
int tagSize = CodedOutputStream.computeTagSize(number);
|
|
if (type == WireFormat.FieldType.GROUP) {
|
|
// Only count the end group tag for proto2 messages as for proto1 the end
|
|
// group tag will be counted as a part of getSerializedSize().
|
|
tagSize *= 2;
|
|
}
|
|
return tagSize + computeElementSizeNoTag(type, value);
|
|
}
|
|
|
|
/**
|
|
* Compute the number of bytes that would be needed to encode a particular value of arbitrary
|
|
* type, excluding tag.
|
|
*
|
|
* @param type The field's type.
|
|
* @param value Object representing the field's value. Must be of the exact type which would be
|
|
* returned by {@link Message#getField(Descriptors.FieldDescriptor)} for this field.
|
|
*/
|
|
static int computeElementSizeNoTag(final WireFormat.FieldType type, final Object value) {
|
|
switch (type) {
|
|
case DOUBLE:
|
|
return CodedOutputStream.computeDoubleSizeNoTag((Double) value);
|
|
case FLOAT:
|
|
return CodedOutputStream.computeFloatSizeNoTag((Float) value);
|
|
case INT64:
|
|
return CodedOutputStream.computeInt64SizeNoTag((Long) value);
|
|
case UINT64:
|
|
return CodedOutputStream.computeUInt64SizeNoTag((Long) value);
|
|
case INT32:
|
|
return CodedOutputStream.computeInt32SizeNoTag((Integer) value);
|
|
case FIXED64:
|
|
return CodedOutputStream.computeFixed64SizeNoTag((Long) value);
|
|
case FIXED32:
|
|
return CodedOutputStream.computeFixed32SizeNoTag((Integer) value);
|
|
case BOOL:
|
|
return CodedOutputStream.computeBoolSizeNoTag((Boolean) value);
|
|
case GROUP:
|
|
return CodedOutputStream.computeGroupSizeNoTag((MessageLite) value);
|
|
case BYTES:
|
|
if (value instanceof ByteString) {
|
|
return CodedOutputStream.computeBytesSizeNoTag((ByteString) value);
|
|
} else {
|
|
return CodedOutputStream.computeByteArraySizeNoTag((byte[]) value);
|
|
}
|
|
case STRING:
|
|
if (value instanceof ByteString) {
|
|
return CodedOutputStream.computeBytesSizeNoTag((ByteString) value);
|
|
} else {
|
|
return CodedOutputStream.computeStringSizeNoTag((String) value);
|
|
}
|
|
case UINT32:
|
|
return CodedOutputStream.computeUInt32SizeNoTag((Integer) value);
|
|
case SFIXED32:
|
|
return CodedOutputStream.computeSFixed32SizeNoTag((Integer) value);
|
|
case SFIXED64:
|
|
return CodedOutputStream.computeSFixed64SizeNoTag((Long) value);
|
|
case SINT32:
|
|
return CodedOutputStream.computeSInt32SizeNoTag((Integer) value);
|
|
case SINT64:
|
|
return CodedOutputStream.computeSInt64SizeNoTag((Long) value);
|
|
|
|
case MESSAGE:
|
|
if (value instanceof LazyField) {
|
|
return CodedOutputStream.computeLazyFieldSizeNoTag((LazyField) value);
|
|
} else {
|
|
return CodedOutputStream.computeMessageSizeNoTag((MessageLite) value);
|
|
}
|
|
|
|
case ENUM:
|
|
if (value instanceof Internal.EnumLite) {
|
|
return CodedOutputStream.computeEnumSizeNoTag(((Internal.EnumLite) value).getNumber());
|
|
} else {
|
|
return CodedOutputStream.computeEnumSizeNoTag((Integer) value);
|
|
}
|
|
}
|
|
|
|
throw new RuntimeException("There is no way to get here, but the compiler thinks otherwise.");
|
|
}
|
|
|
|
/** Compute the number of bytes needed to encode a particular field. */
|
|
public static int computeFieldSize(final FieldDescriptorLite<?> descriptor, final Object value) {
|
|
WireFormat.FieldType type = descriptor.getLiteType();
|
|
int number = descriptor.getNumber();
|
|
if (descriptor.isRepeated()) {
|
|
if (descriptor.isPacked()) {
|
|
int dataSize = 0;
|
|
for (final Object element : (List<?>) value) {
|
|
dataSize += computeElementSizeNoTag(type, element);
|
|
}
|
|
return dataSize
|
|
+ CodedOutputStream.computeTagSize(number)
|
|
+ CodedOutputStream.computeUInt32SizeNoTag(dataSize);
|
|
} else {
|
|
int size = 0;
|
|
for (final Object element : (List<?>) value) {
|
|
size += computeElementSize(type, number, element);
|
|
}
|
|
return size;
|
|
}
|
|
} else {
|
|
return computeElementSize(type, number, value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A FieldSet Builder that accept a {@link MessageLite.Builder} as a field value. This is useful
|
|
* for implementing methods in {@link MessageLite.Builder}.
|
|
*/
|
|
static final class Builder<T extends FieldDescriptorLite<T>> {
|
|
|
|
private SmallSortedMap<T, Object> fields;
|
|
private boolean hasLazyField;
|
|
private boolean isMutable;
|
|
private boolean hasNestedBuilders;
|
|
|
|
private Builder() {
|
|
this(SmallSortedMap.<T>newFieldMap(DEFAULT_FIELD_MAP_ARRAY_SIZE));
|
|
}
|
|
|
|
private Builder(SmallSortedMap<T, Object> fields) {
|
|
this.fields = fields;
|
|
this.isMutable = true;
|
|
}
|
|
|
|
/**
|
|
* Creates the FieldSet
|
|
*
|
|
* @throws UninitializedMessageException if a message field is missing required fields.
|
|
*/
|
|
public FieldSet<T> build() {
|
|
return buildImpl(false);
|
|
}
|
|
|
|
/** Creates the FieldSet but does not validate that all required fields are present. */
|
|
public FieldSet<T> buildPartial() {
|
|
return buildImpl(true);
|
|
}
|
|
|
|
/**
|
|
* Creates the FieldSet.
|
|
*
|
|
* @param partial controls whether to do a build() or buildPartial() when converting submessage
|
|
* builders to messages.
|
|
*/
|
|
private FieldSet<T> buildImpl(boolean partial) {
|
|
if (fields.isEmpty()) {
|
|
return FieldSet.emptySet();
|
|
}
|
|
isMutable = false;
|
|
SmallSortedMap<T, Object> fieldsForBuild = fields;
|
|
if (hasNestedBuilders) {
|
|
// Make a copy of the fields map with all Builders replaced by Message.
|
|
fieldsForBuild = cloneAllFieldsMap(fields, /* copyList */ false);
|
|
replaceBuilders(fieldsForBuild, partial);
|
|
}
|
|
FieldSet<T> fieldSet = new FieldSet<>(fieldsForBuild);
|
|
fieldSet.hasLazyField = hasLazyField;
|
|
return fieldSet;
|
|
}
|
|
|
|
private static <T extends FieldDescriptorLite<T>> void replaceBuilders(
|
|
SmallSortedMap<T, Object> fieldMap, boolean partial) {
|
|
for (int i = 0; i < fieldMap.getNumArrayEntries(); i++) {
|
|
replaceBuilders(fieldMap.getArrayEntryAt(i), partial);
|
|
}
|
|
for (Map.Entry<T, Object> entry : fieldMap.getOverflowEntries()) {
|
|
replaceBuilders(entry, partial);
|
|
}
|
|
}
|
|
|
|
private static <T extends FieldDescriptorLite<T>> void replaceBuilders(
|
|
Map.Entry<T, Object> entry, boolean partial) {
|
|
entry.setValue(replaceBuilders(entry.getKey(), entry.getValue(), partial));
|
|
}
|
|
|
|
private static <T extends FieldDescriptorLite<T>> Object replaceBuilders(
|
|
T descriptor, Object value, boolean partial) {
|
|
if (value == null) {
|
|
return value;
|
|
}
|
|
if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) {
|
|
if (descriptor.isRepeated()) {
|
|
if (!(value instanceof List)) {
|
|
throw new IllegalStateException(
|
|
"Repeated field should contains a List but actually contains type: "
|
|
+ value.getClass());
|
|
}
|
|
@SuppressWarnings("unchecked") // We just check that value is an instance of List above.
|
|
List<Object> list = (List<Object>) value;
|
|
for (int i = 0; i < list.size(); i++) {
|
|
Object oldElement = list.get(i);
|
|
Object newElement = replaceBuilder(oldElement, partial);
|
|
if (newElement != oldElement) {
|
|
// If the list contains a Message.Builder, then make a copy of that list and then
|
|
// modify the Message.Builder into a Message and return the new list. This way, the
|
|
// existing Message.Builder will still be able to modify the inner fields of the
|
|
// original FieldSet.Builder.
|
|
if (list == value) {
|
|
list = new ArrayList<>(list);
|
|
}
|
|
list.set(i, newElement);
|
|
}
|
|
}
|
|
return list;
|
|
} else {
|
|
return replaceBuilder(value, partial);
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
private static Object replaceBuilder(Object value, boolean partial) {
|
|
if (!(value instanceof MessageLite.Builder)) {
|
|
return value;
|
|
}
|
|
MessageLite.Builder builder = (MessageLite.Builder) value;
|
|
if (partial) {
|
|
return builder.buildPartial();
|
|
}
|
|
return builder.build();
|
|
}
|
|
|
|
/** Returns a new Builder using the fields from {@code fieldSet}. */
|
|
public static <T extends FieldDescriptorLite<T>> Builder<T> fromFieldSet(FieldSet<T> fieldSet) {
|
|
Builder<T> builder = new Builder<T>(cloneAllFieldsMap(fieldSet.fields, /* copyList */ true));
|
|
builder.hasLazyField = fieldSet.hasLazyField;
|
|
return builder;
|
|
}
|
|
|
|
// =================================================================
|
|
|
|
/** Get a simple map containing all the fields. */
|
|
public Map<T, Object> getAllFields() {
|
|
if (hasLazyField) {
|
|
SmallSortedMap<T, Object> result = cloneAllFieldsMap(fields, /* copyList */ false);
|
|
if (fields.isImmutable()) {
|
|
result.makeImmutable();
|
|
} else {
|
|
replaceBuilders(result, true);
|
|
}
|
|
return result;
|
|
}
|
|
return fields.isImmutable() ? fields : Collections.unmodifiableMap(fields);
|
|
}
|
|
|
|
/** Useful for implementing {@link Message#hasField(Descriptors.FieldDescriptor)}. */
|
|
public boolean hasField(final T descriptor) {
|
|
if (descriptor.isRepeated()) {
|
|
throw new IllegalArgumentException("hasField() can only be called on non-repeated fields.");
|
|
}
|
|
|
|
return fields.get(descriptor) != null;
|
|
}
|
|
|
|
/**
|
|
* Useful for implementing {@link Message#getField(Descriptors.FieldDescriptor)}. This method
|
|
* returns {@code null} if the field is not set; in this case it is up to the caller to fetch
|
|
* the field's default value.
|
|
*/
|
|
public Object getField(final T descriptor) {
|
|
Object value = getFieldAllowBuilders(descriptor);
|
|
return replaceBuilders(descriptor, value, true);
|
|
}
|
|
|
|
/** Same as {@link #getField(F)}, but allow a {@link MessageLite.Builder} to be returned. */
|
|
Object getFieldAllowBuilders(final T descriptor) {
|
|
Object o = fields.get(descriptor);
|
|
if (o instanceof LazyField) {
|
|
return ((LazyField) o).getValue();
|
|
}
|
|
return o;
|
|
}
|
|
|
|
private void ensureIsMutable() {
|
|
if (!isMutable) {
|
|
fields = cloneAllFieldsMap(fields, /* copyList */ true);
|
|
isMutable = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Useful for implementing {@link Message.Builder#setField(Descriptors.FieldDescriptor,
|
|
* Object)}.
|
|
*/
|
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
public void setField(final T descriptor, Object value) {
|
|
ensureIsMutable();
|
|
if (descriptor.isRepeated()) {
|
|
if (!(value instanceof List)) {
|
|
throw new IllegalArgumentException(
|
|
"Wrong object type used with protocol message reflection.");
|
|
}
|
|
|
|
// Wrap the contents in a new list so that the caller cannot change
|
|
// the list's contents after setting it.
|
|
final List newList = new ArrayList((List) value);
|
|
for (final Object element : newList) {
|
|
verifyType(descriptor, element);
|
|
hasNestedBuilders = hasNestedBuilders || element instanceof MessageLite.Builder;
|
|
}
|
|
value = newList;
|
|
} else {
|
|
verifyType(descriptor, value);
|
|
}
|
|
|
|
if (value instanceof LazyField) {
|
|
hasLazyField = true;
|
|
}
|
|
hasNestedBuilders = hasNestedBuilders || value instanceof MessageLite.Builder;
|
|
|
|
fields.put(descriptor, value);
|
|
}
|
|
|
|
/** Useful for implementing {@link Message.Builder#clearField(Descriptors.FieldDescriptor)}. */
|
|
public void clearField(final T descriptor) {
|
|
ensureIsMutable();
|
|
fields.remove(descriptor);
|
|
if (fields.isEmpty()) {
|
|
hasLazyField = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Useful for implementing {@link Message#getRepeatedFieldCount(Descriptors.FieldDescriptor)}.
|
|
*/
|
|
public int getRepeatedFieldCount(final T descriptor) {
|
|
if (!descriptor.isRepeated()) {
|
|
throw new IllegalArgumentException(
|
|
"getRepeatedFieldCount() can only be called on repeated fields.");
|
|
}
|
|
|
|
final Object value = getFieldAllowBuilders(descriptor);
|
|
if (value == null) {
|
|
return 0;
|
|
} else {
|
|
return ((List<?>) value).size();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Useful for implementing {@link Message#getRepeatedField(Descriptors.FieldDescriptor, int)}.
|
|
*/
|
|
public Object getRepeatedField(final T descriptor, final int index) {
|
|
if (hasNestedBuilders) {
|
|
ensureIsMutable();
|
|
}
|
|
Object value = getRepeatedFieldAllowBuilders(descriptor, index);
|
|
return replaceBuilder(value, true);
|
|
}
|
|
|
|
/**
|
|
* Same as {@link #getRepeatedField(F, int)}, but allow a {@link MessageLite.Builder} to be
|
|
* returned.
|
|
*/
|
|
Object getRepeatedFieldAllowBuilders(final T descriptor, final int index) {
|
|
if (!descriptor.isRepeated()) {
|
|
throw new IllegalArgumentException(
|
|
"getRepeatedField() can only be called on repeated fields.");
|
|
}
|
|
|
|
final Object value = getFieldAllowBuilders(descriptor);
|
|
|
|
if (value == null) {
|
|
throw new IndexOutOfBoundsException();
|
|
} else {
|
|
return ((List<?>) value).get(index);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Useful for implementing {@link Message.Builder#setRepeatedField(Descriptors.FieldDescriptor,
|
|
* int, Object)}.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public void setRepeatedField(final T descriptor, final int index, final Object value) {
|
|
ensureIsMutable();
|
|
if (!descriptor.isRepeated()) {
|
|
throw new IllegalArgumentException(
|
|
"getRepeatedField() can only be called on repeated fields.");
|
|
}
|
|
|
|
hasNestedBuilders = hasNestedBuilders || value instanceof MessageLite.Builder;
|
|
|
|
final Object list = getFieldAllowBuilders(descriptor);
|
|
if (list == null) {
|
|
throw new IndexOutOfBoundsException();
|
|
}
|
|
|
|
verifyType(descriptor, value);
|
|
((List<Object>) list).set(index, value);
|
|
}
|
|
|
|
/**
|
|
* Useful for implementing {@link Message.Builder#addRepeatedField(Descriptors.FieldDescriptor,
|
|
* Object)}.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public void addRepeatedField(final T descriptor, final Object value) {
|
|
ensureIsMutable();
|
|
if (!descriptor.isRepeated()) {
|
|
throw new IllegalArgumentException(
|
|
"addRepeatedField() can only be called on repeated fields.");
|
|
}
|
|
|
|
hasNestedBuilders = hasNestedBuilders || value instanceof MessageLite.Builder;
|
|
|
|
verifyType(descriptor, value);
|
|
|
|
final Object existingValue = getFieldAllowBuilders(descriptor);
|
|
List<Object> list;
|
|
if (existingValue == null) {
|
|
list = new ArrayList<>();
|
|
fields.put(descriptor, list);
|
|
} else {
|
|
list = (List<Object>) existingValue;
|
|
}
|
|
|
|
list.add(value);
|
|
}
|
|
|
|
/**
|
|
* Verifies that the given object is of the correct type to be a valid value for the given
|
|
* field. (For repeated fields, this checks if the object is the right type to be one element of
|
|
* the field.)
|
|
*
|
|
* @throws IllegalArgumentException The value is not of the right type.
|
|
*/
|
|
private void verifyType(final T descriptor, final Object value) {
|
|
if (!FieldSet.isValidType(descriptor.getLiteType(), value)) {
|
|
// Builder can accept Message.Builder values even though FieldSet will reject.
|
|
if (descriptor.getLiteType().getJavaType() == WireFormat.JavaType.MESSAGE
|
|
&& value instanceof MessageLite.Builder) {
|
|
return;
|
|
}
|
|
throw new IllegalArgumentException(
|
|
String.format(
|
|
"Wrong object type used with protocol message reflection.\n"
|
|
+ "Field number: %d, field java type: %s, value type: %s\n",
|
|
descriptor.getNumber(),
|
|
descriptor.getLiteType().getJavaType(),
|
|
value.getClass().getName()));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* See {@link Message#isInitialized()}. Note: Since {@code FieldSet} itself does not have any
|
|
* way of knowing about required fields that aren't actually present in the set, it is up to the
|
|
* caller to check that all required fields are present.
|
|
*/
|
|
public boolean isInitialized() {
|
|
for (int i = 0; i < fields.getNumArrayEntries(); i++) {
|
|
if (!FieldSet.isInitialized(fields.getArrayEntryAt(i))) {
|
|
return false;
|
|
}
|
|
}
|
|
for (final Map.Entry<T, Object> entry : fields.getOverflowEntries()) {
|
|
if (!FieldSet.isInitialized(entry)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Like {@link Message.Builder#mergeFrom(Message)}, but merges from another {@link FieldSet}.
|
|
*/
|
|
public void mergeFrom(final FieldSet<T> other) {
|
|
ensureIsMutable();
|
|
for (int i = 0; i < other.fields.getNumArrayEntries(); i++) {
|
|
mergeFromField(other.fields.getArrayEntryAt(i));
|
|
}
|
|
for (final Map.Entry<T, Object> entry : other.fields.getOverflowEntries()) {
|
|
mergeFromField(entry);
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private void mergeFromField(final Map.Entry<T, Object> entry) {
|
|
final T descriptor = entry.getKey();
|
|
Object otherValue = entry.getValue();
|
|
if (otherValue instanceof LazyField) {
|
|
otherValue = ((LazyField) otherValue).getValue();
|
|
}
|
|
|
|
if (descriptor.isRepeated()) {
|
|
List<Object> value = (List<Object>) getFieldAllowBuilders(descriptor);
|
|
if (value == null) {
|
|
value = new ArrayList<>();
|
|
fields.put(descriptor, value);
|
|
}
|
|
for (Object element : (List<?>) otherValue) {
|
|
value.add(FieldSet.cloneIfMutable(element));
|
|
}
|
|
} else if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) {
|
|
Object value = getFieldAllowBuilders(descriptor);
|
|
if (value == null) {
|
|
fields.put(descriptor, FieldSet.cloneIfMutable(otherValue));
|
|
} else {
|
|
// Merge the messages.
|
|
if (value instanceof MessageLite.Builder) {
|
|
descriptor.internalMergeFrom((MessageLite.Builder) value, (MessageLite) otherValue);
|
|
} else {
|
|
value =
|
|
descriptor
|
|
.internalMergeFrom(((MessageLite) value).toBuilder(), (MessageLite) otherValue)
|
|
.build();
|
|
fields.put(descriptor, value);
|
|
}
|
|
}
|
|
} else {
|
|
fields.put(descriptor, cloneIfMutable(otherValue));
|
|
}
|
|
}
|
|
}
|
|
}
|