blob: 3a5dc488d24846deabe2865a7db7feda9e489a5b [file] [log] [blame]
// 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.
package com.google.protobuf;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.TreeMap;
import java.util.List;
import java.util.Map;
/**
* 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 {
private Map<FieldDescriptor, Object> fields;
/** Construct a new FieldSet. */
private FieldSet() {
// Use a TreeMap because fields need to be in canonical order when
// serializing.
this.fields = new TreeMap<FieldDescriptor, Object>();
}
/**
* Construct a new FieldSet with the given map. This is only used by
* DEFAULT_INSTANCE, to pass in an immutable empty map.
*/
private FieldSet(Map<FieldDescriptor, Object> fields) {
this.fields = fields;
}
/** Construct a new FieldSet. */
public static FieldSet newFieldSet() {
return new FieldSet();
}
/** Get an immutable empty FieldSet. */
public static FieldSet emptySet() {
return DEFAULT_INSTANCE;
}
private static final FieldSet DEFAULT_INSTANCE =
new FieldSet(Collections.<FieldDescriptor, Object>emptyMap());
/** Make this FieldSet immutable from this point forward. */
@SuppressWarnings("unchecked")
public void makeImmutable() {
for (Map.Entry<FieldDescriptor, Object> entry: fields.entrySet()) {
if (entry.getKey().isRepeated()) {
List value = (List)entry.getValue();
entry.setValue(Collections.unmodifiableList(value));
}
}
fields = Collections.unmodifiableMap(fields);
}
// =================================================================
/** See {@link Message.Builder#clear()}. */
public void clear() {
fields.clear();
}
/** See {@link Message#getAllFields()}. */
public Map<Descriptors.FieldDescriptor, Object> getAllFields() {
return Collections.unmodifiableMap(fields);
}
/**
* Get an interator to the field map. This iterator should not be leaked
* out of the protobuf library as it is not protected from mutation.
*/
public Iterator<Map.Entry<Descriptors.FieldDescriptor, Object>> iterator() {
return fields.entrySet().iterator();
}
/** See {@link Message#hasField(Descriptors.FieldDescriptor)}. */
public boolean hasField(Descriptors.FieldDescriptor field) {
if (field.isRepeated()) {
throw new IllegalArgumentException(
"hasField() can only be called on non-repeated fields.");
}
return fields.containsKey(field);
}
/**
* See {@link Message#getField(Descriptors.FieldDescriptor)}. This method
* returns {@code null} if the field is a singular message type and is not
* set; in this case it is up to the caller to fetch the message's default
* instance.
*/
public Object getField(Descriptors.FieldDescriptor field) {
Object result = fields.get(field);
if (result == null) {
if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
if (field.isRepeated()) {
return Collections.emptyList();
} else {
return null;
}
} else {
return field.getDefaultValue();
}
} else {
return result;
}
}
/** See {@link Message.Builder#setField(Descriptors.FieldDescriptor,Object)}. */
@SuppressWarnings("unchecked")
public void setField(Descriptors.FieldDescriptor field, Object value) {
if (field.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.
List newList = new ArrayList();
newList.addAll((List)value);
for (Object element : newList) {
verifyType(field, element);
}
value = newList;
} else {
verifyType(field, value);
}
fields.put(field, value);
}
/** See {@link Message.Builder#clearField(Descriptors.FieldDescriptor)}. */
public void clearField(Descriptors.FieldDescriptor field) {
fields.remove(field);
}
/** See {@link Message#getRepeatedFieldCount(Descriptors.FieldDescriptor)}. */
public int getRepeatedFieldCount(Descriptors.FieldDescriptor field) {
if (!field.isRepeated()) {
throw new IllegalArgumentException(
"getRepeatedFieldCount() can only be called on repeated fields.");
}
return ((List)getField(field)).size();
}
/** See {@link Message#getRepeatedField(Descriptors.FieldDescriptor,int)}. */
public Object getRepeatedField(Descriptors.FieldDescriptor field, int index) {
if (!field.isRepeated()) {
throw new IllegalArgumentException(
"getRepeatedField() can only be called on repeated fields.");
}
return ((List)getField(field)).get(index);
}
/** See {@link Message.Builder#setRepeatedField(Descriptors.FieldDescriptor,int,Object)}. */
@SuppressWarnings("unchecked")
public void setRepeatedField(Descriptors.FieldDescriptor field, int index,
Object value) {
if (!field.isRepeated()) {
throw new IllegalArgumentException(
"setRepeatedField() can only be called on repeated fields.");
}
verifyType(field, value);
List list = (List)fields.get(field);
if (list == null) {
throw new IndexOutOfBoundsException();
}
list.set(index, value);
}
/** See {@link Message.Builder#addRepeatedField(Descriptors.FieldDescriptor,Object)}. */
@SuppressWarnings("unchecked")
public void addRepeatedField(Descriptors.FieldDescriptor field,
Object value) {
if (!field.isRepeated()) {
throw new IllegalArgumentException(
"setRepeatedField() can only be called on repeated fields.");
}
verifyType(field, value);
List list = (List)fields.get(field);
if (list == null) {
list = new ArrayList();
fields.put(field, list);
}
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(FieldDescriptor field, Object value) {
boolean isValid = false;
switch (field.getJavaType()) {
case INT: isValid = value instanceof Integer ; break;
case LONG: isValid = value instanceof Long ; break;
case FLOAT: isValid = value instanceof Float ; break;
case DOUBLE: isValid = value instanceof Double ; break;
case BOOLEAN: isValid = value instanceof Boolean ; break;
case STRING: isValid = value instanceof String ; break;
case BYTE_STRING: isValid = value instanceof ByteString; break;
case ENUM:
isValid = value instanceof EnumValueDescriptor &&
((EnumValueDescriptor)value).getType() == field.getEnumType();
break;
case MESSAGE:
isValid = value instanceof Message &&
((Message)value).getDescriptorForType() == field.getMessageType();
break;
}
if (!isValid) {
// When chaining calls to setField(), it can be hard to tell from
// the stack trace which exact call failed, since the whole chain is
// considered one line of code. So, let's make sure to include the
// field name and other useful info in the exception.
throw new IllegalArgumentException(
"Wrong object type used with protocol message reflection. " +
"Message type \"" + field.getContainingType().getFullName() +
"\", field \"" +
(field.isExtension() ? field.getFullName() : field.getName()) +
"\", value was type \"" + value.getClass().getName() + "\".");
}
}
// =================================================================
// 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.
*/
@SuppressWarnings("unchecked")
public boolean isInitialized() {
for (Map.Entry<FieldDescriptor, Object> entry : fields.entrySet()) {
FieldDescriptor field = entry.getKey();
if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
if (field.isRepeated()) {
for (Message element : (List<Message>) entry.getValue()) {
if (!element.isInitialized()) {
return false;
}
}
} else {
if (!((Message) entry.getValue()).isInitialized()) {
return false;
}
}
}
}
return true;
}
/**
* Like {@link #isInitialized()}, but also checks for the presence of
* all required fields in the given type.
*/
@SuppressWarnings("unchecked")
public boolean isInitialized(Descriptor type) {
// Check that all required fields are present.
for (FieldDescriptor field : type.getFields()) {
if (field.isRequired()) {
if (!hasField(field)) {
return false;
}
}
}
// Check that embedded messages are initialized.
return isInitialized();
}
/** See {@link Message.Builder#mergeFrom(Message)}. */
@SuppressWarnings("unchecked")
public void mergeFrom(Message other) {
// Note: We don't attempt to verify that other's fields have valid
// types. Doing so would be a losing battle. We'd have to verify
// all sub-messages as well, and we'd have to make copies of all of
// them to insure that they don't change after verification (since
// the Message interface itself cannot enforce immutability of
// implementations).
// TODO(kenton): Provide a function somewhere called makeDeepCopy()
// which allows people to make secure deep copies of messages.
for (Map.Entry<FieldDescriptor, Object> entry :
other.getAllFields().entrySet()) {
FieldDescriptor field = entry.getKey();
if (field.isRepeated()) {
List existingValue = (List)fields.get(field);
if (existingValue == null) {
existingValue = new ArrayList();
fields.put(field, existingValue);
}
existingValue.addAll((List)entry.getValue());
} else if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
Message existingValue = (Message)fields.get(field);
if (existingValue == null) {
setField(field, entry.getValue());
} else {
setField(field,
existingValue.newBuilderForType()
.mergeFrom(existingValue)
.mergeFrom((Message)entry.getValue())
.build());
}
} else {
setField(field, entry.getValue());
}
}
}
/**
* Like {@link #mergeFrom(Message)}, but merges from another {@link FieldSet}.
*/
@SuppressWarnings("unchecked")
public void mergeFrom(FieldSet other) {
for (Map.Entry<FieldDescriptor, Object> entry : other.fields.entrySet()) {
FieldDescriptor field = entry.getKey();
Object value = entry.getValue();
if (field.isRepeated()) {
List existingValue = (List)fields.get(field);
if (existingValue == null) {
existingValue = new ArrayList();
fields.put(field, existingValue);
}
existingValue.addAll((List)value);
} else if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
Message existingValue = (Message)fields.get(field);
if (existingValue == null) {
setField(field, value);
} else {
setField(field,
existingValue.newBuilderForType()
.mergeFrom(existingValue)
.mergeFrom((Message)value)
.build());
}
} else {
setField(field, value);
}
}
}
// TODO(kenton): Move parsing code into AbstractMessage, since it no longer
// uses any special knowledge from FieldSet.
/**
* See {@link Message.Builder#mergeFrom(CodedInputStream)}.
* @param builder The {@code Builder} for the target message.
*/
public static void mergeFrom(CodedInputStream input,
UnknownFieldSet.Builder unknownFields,
ExtensionRegistry extensionRegistry,
Message.Builder builder)
throws java.io.IOException {
while (true) {
int tag = input.readTag();
if (tag == 0) {
break;
}
if (!mergeFieldFrom(input, unknownFields, extensionRegistry,
builder, tag)) {
// end group tag
break;
}
}
}
/**
* Like {@link #mergeFrom(CodedInputStream, UnknownFieldSet.Builder,
* ExtensionRegistry, Message.Builder)}, but parses a single field.
* @param tag The tag, which should have already been read.
* @return {@code true} unless the tag is an end-group tag.
*/
@SuppressWarnings("unchecked")
public static boolean mergeFieldFrom(
CodedInputStream input,
UnknownFieldSet.Builder unknownFields,
ExtensionRegistry extensionRegistry,
Message.Builder builder,
int tag) throws java.io.IOException {
Descriptor type = builder.getDescriptorForType();
if (type.getOptions().getMessageSetWireFormat() &&
tag == WireFormat.MESSAGE_SET_ITEM_TAG) {
mergeMessageSetExtensionFromCodedStream(
input, unknownFields, extensionRegistry, builder);
return true;
}
int wireType = WireFormat.getTagWireType(tag);
int fieldNumber = WireFormat.getTagFieldNumber(tag);
FieldDescriptor field;
Message defaultInstance = null;
if (type.isExtensionNumber(fieldNumber)) {
ExtensionRegistry.ExtensionInfo extension =
extensionRegistry.findExtensionByNumber(type, fieldNumber);
if (extension == null) {
field = null;
} else {
field = extension.descriptor;
defaultInstance = extension.defaultInstance;
}
} else {
field = type.findFieldByNumber(fieldNumber);
}
if (field == null ||
wireType != WireFormat.getWireFormatForFieldType(field.getType())) {
// Unknown field or wrong wire type. Skip.
return unknownFields.mergeFieldFrom(tag, input);
} else {
Object value;
switch (field.getType()) {
case GROUP: {
Message.Builder subBuilder;
if (defaultInstance != null) {
subBuilder = defaultInstance.newBuilderForType();
} else {
subBuilder = builder.newBuilderForField(field);
}
if (!field.isRepeated()) {
subBuilder.mergeFrom((Message) builder.getField(field));
}
input.readGroup(field.getNumber(), subBuilder, extensionRegistry);
value = subBuilder.build();
break;
}
case MESSAGE: {
Message.Builder subBuilder;
if (defaultInstance != null) {
subBuilder = defaultInstance.newBuilderForType();
} else {
subBuilder = builder.newBuilderForField(field);
}
if (!field.isRepeated()) {
subBuilder.mergeFrom((Message) builder.getField(field));
}
input.readMessage(subBuilder, extensionRegistry);
value = subBuilder.build();
break;
}
case ENUM: {
int rawValue = input.readEnum();
value = field.getEnumType().findValueByNumber(rawValue);
// If the number isn't recognized as a valid value for this enum,
// drop it.
if (value == null) {
unknownFields.mergeVarintField(fieldNumber, rawValue);
return true;
}
break;
}
default:
value = input.readPrimitiveField(field.getType());
break;
}
if (field.isRepeated()) {
builder.addRepeatedField(field, value);
} else {
builder.setField(field, value);
}
}
return true;
}
/** Called by {@code #mergeFieldFrom()} to parse a MessageSet extension. */
private static void mergeMessageSetExtensionFromCodedStream(
CodedInputStream input,
UnknownFieldSet.Builder unknownFields,
ExtensionRegistry extensionRegistry,
Message.Builder builder) throws java.io.IOException {
Descriptor type = builder.getDescriptorForType();
// The wire format for MessageSet is:
// message MessageSet {
// repeated group Item = 1 {
// required int32 typeId = 2;
// required bytes message = 3;
// }
// }
// "typeId" is the extension's field number. The extension can only be
// a message type, where "message" contains the encoded bytes of that
// message.
//
// In practice, we will probably never see a MessageSet item in which
// the message appears before the type ID, or where either field does not
// appear exactly once. However, in theory such cases are valid, so we
// should be prepared to accept them.
int typeId = 0;
ByteString rawBytes = null; // If we encounter "message" before "typeId"
Message.Builder subBuilder = null;
FieldDescriptor field = null;
while (true) {
int tag = input.readTag();
if (tag == 0) {
break;
}
if (tag == WireFormat.MESSAGE_SET_TYPE_ID_TAG) {
typeId = input.readUInt32();
// Zero is not a valid type ID.
if (typeId != 0) {
ExtensionRegistry.ExtensionInfo extension =
extensionRegistry.findExtensionByNumber(type, typeId);
if (extension != null) {
field = extension.descriptor;
subBuilder = extension.defaultInstance.newBuilderForType();
Message originalMessage = (Message)builder.getField(field);
if (originalMessage != null) {
subBuilder.mergeFrom(originalMessage);
}
if (rawBytes != null) {
// We already encountered the message. Parse it now.
subBuilder.mergeFrom(
CodedInputStream.newInstance(rawBytes.newInput()));
rawBytes = null;
}
} else {
// Unknown extension number. If we already saw data, put it
// in rawBytes.
if (rawBytes != null) {
unknownFields.mergeField(typeId,
UnknownFieldSet.Field.newBuilder()
.addLengthDelimited(rawBytes)
.build());
rawBytes = null;
}
}
}
} else if (tag == WireFormat.MESSAGE_SET_MESSAGE_TAG) {
if (typeId == 0) {
// We haven't seen a type ID yet, so we have to store the raw bytes
// for now.
rawBytes = input.readBytes();
} else if (subBuilder == null) {
// We don't know how to parse this. Ignore it.
unknownFields.mergeField(typeId,
UnknownFieldSet.Field.newBuilder()
.addLengthDelimited(input.readBytes())
.build());
} else {
// We already know the type, so we can parse directly from the input
// with no copying. Hooray!
input.readMessage(subBuilder, extensionRegistry);
}
} else {
// Unknown tag. Skip it.
if (!input.skipField(tag)) {
break; // end of group
}
}
}
input.checkLastTagWas(WireFormat.MESSAGE_SET_ITEM_END_TAG);
if (subBuilder != null) {
builder.setField(field, subBuilder.build());
}
}
/** See {@link Message#writeTo(CodedOutputStream)}. */
public void writeTo(CodedOutputStream output)
throws java.io.IOException {
for (Map.Entry<FieldDescriptor, Object> entry : fields.entrySet()) {
writeField(entry.getKey(), entry.getValue(), output);
}
}
/** Write a single field. */
public void writeField(FieldDescriptor field, Object value,
CodedOutputStream output) throws java.io.IOException {
if (field.isExtension() &&
field.getContainingType().getOptions().getMessageSetWireFormat()) {
output.writeMessageSetExtension(field.getNumber(), (Message)value);
} else {
if (field.isRepeated()) {
for (Object element : (List)value) {
output.writeField(field.getType(), field.getNumber(), element);
}
} else {
output.writeField(field.getType(), field.getNumber(), 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 (Map.Entry<FieldDescriptor, Object> entry : fields.entrySet()) {
FieldDescriptor field = entry.getKey();
Object value = entry.getValue();
if (field.isExtension() &&
field.getContainingType().getOptions().getMessageSetWireFormat()) {
size += CodedOutputStream.computeMessageSetExtensionSize(
field.getNumber(), (Message)value);
} else {
if (field.isRepeated()) {
for (Object element : (List)value) {
size += CodedOutputStream.computeFieldSize(
field.getType(), field.getNumber(), element);
}
} else {
size += CodedOutputStream.computeFieldSize(
field.getType(), field.getNumber(), value);
}
}
}
return size;
}
}