blob: 698b9d948dc1e6aa20ad5571d80fdf53a706297f [file] [log] [blame]
/*
* Copyright (c) 2016 Google, Inc.
*
* 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.common.truth.extensions.proto;
import com.google.common.base.Optional;
import com.google.common.collect.Maps;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.ExtensionRegistry;
import com.google.protobuf.Message;
import com.google.protobuf.TypeRegistry;
import com.google.protobuf.UnknownFieldSet;
import java.util.List;
import java.util.Map;
/**
* Tree representation of all set field numbers in a message, merging across repeated elements.
*
* <p>Sub messages are represented by child {@link FieldNumberTree} objects.
*
* @see FieldScopeImpl#partialScope
*/
final class FieldNumberTree {
private static final FieldNumberTree EMPTY = new FieldNumberTree();
/** A {@code FieldNumberTree} with no children. */
static FieldNumberTree empty() {
return EMPTY;
}
// Modified only during [factory] construction, never changed afterwards.
private final Map<SubScopeId, FieldNumberTree> children = Maps.newHashMap();
/** Returns whether this {@code FieldNumberTree} has no children. */
boolean isEmpty() {
return children.isEmpty();
}
/**
* Returns the {@code FieldNumberTree} corresponding to this sub-field.
*
* <p>{@code empty()} if there is none.
*/
FieldNumberTree child(SubScopeId subScopeId) {
FieldNumberTree child = children.get(subScopeId);
return child == null ? EMPTY : child;
}
/** Returns whether this tree has a child for this node. */
boolean hasChild(SubScopeId subScopeId) {
return children.containsKey(subScopeId);
}
static FieldNumberTree fromMessage(
Message message, TypeRegistry typeRegistry, ExtensionRegistry extensionRegistry) {
FieldNumberTree tree = new FieldNumberTree();
// Known fields.
Map<FieldDescriptor, Object> knownFieldValues = message.getAllFields();
for (FieldDescriptor field : knownFieldValues.keySet()) {
SubScopeId subScopeId = SubScopeId.of(field);
FieldNumberTree childTree = new FieldNumberTree();
tree.children.put(subScopeId, childTree);
if (field.equals(AnyUtils.valueFieldDescriptor())) {
// Handle Any protos specially.
Optional<Message> unpackedAny = AnyUtils.unpack(message, typeRegistry, extensionRegistry);
if (unpackedAny.isPresent()) {
tree.children.put(
SubScopeId.ofUnpackedAnyValueType(unpackedAny.get().getDescriptorForType()),
fromMessage(unpackedAny.get(), typeRegistry, extensionRegistry));
}
} else {
Object fieldValue = knownFieldValues.get(field);
if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
if (field.isRepeated()) {
List<?> valueList = (List<?>) fieldValue;
for (Object value : valueList) {
childTree.merge(fromMessage((Message) value, typeRegistry, extensionRegistry));
}
} else {
childTree.merge(fromMessage((Message) fieldValue, typeRegistry, extensionRegistry));
}
}
}
}
// Unknown fields.
tree.merge(fromUnknownFieldSet(message.getUnknownFields()));
return tree;
}
static FieldNumberTree fromMessages(
Iterable<? extends Message> messages,
TypeRegistry typeRegistry,
ExtensionRegistry extensionRegistry) {
FieldNumberTree tree = new FieldNumberTree();
for (Message message : messages) {
if (message != null) {
tree.merge(fromMessage(message, typeRegistry, extensionRegistry));
}
}
return tree;
}
private static FieldNumberTree fromUnknownFieldSet(UnknownFieldSet unknownFieldSet) {
FieldNumberTree tree = new FieldNumberTree();
for (int fieldNumber : unknownFieldSet.asMap().keySet()) {
UnknownFieldSet.Field unknownField = unknownFieldSet.asMap().get(fieldNumber);
for (UnknownFieldDescriptor unknownFieldDescriptor :
UnknownFieldDescriptor.descriptors(fieldNumber, unknownField)) {
SubScopeId subScopeId = SubScopeId.of(unknownFieldDescriptor);
FieldNumberTree childTree = new FieldNumberTree();
tree.children.put(subScopeId, childTree);
if (unknownFieldDescriptor.type() == UnknownFieldDescriptor.Type.GROUP) {
for (Object group : unknownFieldDescriptor.type().getValues(unknownField)) {
childTree.merge(fromUnknownFieldSet((UnknownFieldSet) group));
}
}
}
}
return tree;
}
/** Adds the other tree onto this one. May destroy {@code other} in the process. */
private void merge(FieldNumberTree other) {
for (SubScopeId subScopeId : other.children.keySet()) {
FieldNumberTree value = other.children.get(subScopeId);
if (!this.children.containsKey(subScopeId)) {
this.children.put(subScopeId, value);
} else {
this.children.get(subScopeId).merge(value);
}
}
}
}