| Index: third_party/protobuf/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java
|
| diff --git a/third_party/protobuf/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java b/third_party/protobuf/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..dc2f4b841e9bb04c152aebca4e76a9a96e076683
|
| --- /dev/null
|
| +++ b/third_party/protobuf/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java
|
| @@ -0,0 +1,259 @@
|
| +// 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.util;
|
| +
|
| +import com.google.protobuf.Descriptors.Descriptor;
|
| +import com.google.protobuf.Descriptors.FieldDescriptor;
|
| +import com.google.protobuf.FieldMask;
|
| +import com.google.protobuf.Message;
|
| +
|
| +import java.util.ArrayList;
|
| +import java.util.List;
|
| +import java.util.Map.Entry;
|
| +import java.util.TreeMap;
|
| +import java.util.logging.Logger;
|
| +
|
| +/**
|
| + * A tree representation of a FieldMask. Each leaf node in this tree represent
|
| + * a field path in the FieldMask.
|
| + *
|
| + * <p>For example, FieldMask "foo.bar,foo.baz,bar.baz" as a tree will be:
|
| + * <pre>
|
| + * [root] -+- foo -+- bar
|
| + * | |
|
| + * | +- baz
|
| + * |
|
| + * +- bar --- baz
|
| + * </pre>
|
| + *
|
| + * <p>By representing FieldMasks with this tree structure we can easily convert
|
| + * a FieldMask to a canonical form, merge two FieldMasks, calculate the
|
| + * intersection to two FieldMasks and traverse all fields specified by the
|
| + * FieldMask in a message tree.
|
| + */
|
| +class FieldMaskTree {
|
| + private static final Logger logger =
|
| + Logger.getLogger(FieldMaskTree.class.getName());
|
| +
|
| + private static final String FIELD_PATH_SEPARATOR_REGEX = "\\.";
|
| +
|
| + private static class Node {
|
| + public TreeMap<String, Node> children = new TreeMap<String, Node>();
|
| + }
|
| +
|
| + private final Node root = new Node();
|
| +
|
| + /** Creates an empty FieldMaskTree. */
|
| + public FieldMaskTree() {}
|
| +
|
| + /** Creates a FieldMaskTree for a given FieldMask. */
|
| + public FieldMaskTree(FieldMask mask) {
|
| + mergeFromFieldMask(mask);
|
| + }
|
| +
|
| + @Override
|
| + public String toString() {
|
| + return FieldMaskUtil.toString(toFieldMask());
|
| + }
|
| +
|
| + /**
|
| + * Adds a field path to the tree. In a FieldMask, every field path matches the
|
| + * specified field as well as all its sub-fields. For example, a field path
|
| + * "foo.bar" matches field "foo.bar" and also "foo.bar.baz", etc. When adding
|
| + * a field path to the tree, redundant sub-paths will be removed. That is,
|
| + * after adding "foo.bar" to the tree, "foo.bar.baz" will be removed if it
|
| + * exists, which will turn the tree node for "foo.bar" to a leaf node.
|
| + * Likewise, if the field path to add is a sub-path of an existing leaf node,
|
| + * nothing will be changed in the tree.
|
| + */
|
| + public FieldMaskTree addFieldPath(String path) {
|
| + String[] parts = path.split(FIELD_PATH_SEPARATOR_REGEX);
|
| + if (parts.length == 0) {
|
| + return this;
|
| + }
|
| + Node node = root;
|
| + boolean createNewBranch = false;
|
| + // Find the matching node in the tree.
|
| + for (String part : parts) {
|
| + // Check whether the path matches an existing leaf node.
|
| + if (!createNewBranch && node != root && node.children.isEmpty()) {
|
| + // The path to add is a sub-path of an existing leaf node.
|
| + return this;
|
| + }
|
| + if (node.children.containsKey(part)) {
|
| + node = node.children.get(part);
|
| + } else {
|
| + createNewBranch = true;
|
| + Node tmp = new Node();
|
| + node.children.put(part, tmp);
|
| + node = tmp;
|
| + }
|
| + }
|
| + // Turn the matching node into a leaf node (i.e., remove sub-paths).
|
| + node.children.clear();
|
| + return this;
|
| + }
|
| +
|
| + /**
|
| + * Merges all field paths in a FieldMask into this tree.
|
| + */
|
| + public FieldMaskTree mergeFromFieldMask(FieldMask mask) {
|
| + for (String path : mask.getPathsList()) {
|
| + addFieldPath(path);
|
| + }
|
| + return this;
|
| + }
|
| +
|
| + /** Converts this tree to a FieldMask. */
|
| + public FieldMask toFieldMask() {
|
| + if (root.children.isEmpty()) {
|
| + return FieldMask.getDefaultInstance();
|
| + }
|
| + List<String> paths = new ArrayList<String>();
|
| + getFieldPaths(root, "", paths);
|
| + return FieldMask.newBuilder().addAllPaths(paths).build();
|
| + }
|
| +
|
| + /** Gathers all field paths in a sub-tree. */
|
| + private void getFieldPaths(Node node, String path, List<String> paths) {
|
| + if (node.children.isEmpty()) {
|
| + paths.add(path);
|
| + return;
|
| + }
|
| + for (Entry<String, Node> entry : node.children.entrySet()) {
|
| + String childPath = path.isEmpty()
|
| + ? entry.getKey() : path + "." + entry.getKey();
|
| + getFieldPaths(entry.getValue(), childPath, paths);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Adds the intersection of this tree with the given {@code path} to
|
| + * {@code output}.
|
| + */
|
| + public void intersectFieldPath(String path, FieldMaskTree output) {
|
| + if (root.children.isEmpty()) {
|
| + return;
|
| + }
|
| + String[] parts = path.split(FIELD_PATH_SEPARATOR_REGEX);
|
| + if (parts.length == 0) {
|
| + return;
|
| + }
|
| + Node node = root;
|
| + for (String part : parts) {
|
| + if (node != root && node.children.isEmpty()) {
|
| + // The given path is a sub-path of an existing leaf node in the tree.
|
| + output.addFieldPath(path);
|
| + return;
|
| + }
|
| + if (node.children.containsKey(part)) {
|
| + node = node.children.get(part);
|
| + } else {
|
| + return;
|
| + }
|
| + }
|
| + // We found a matching node for the path. All leaf children of this matching
|
| + // node is in the intersection.
|
| + List<String> paths = new ArrayList<String>();
|
| + getFieldPaths(node, path, paths);
|
| + for (String value : paths) {
|
| + output.addFieldPath(value);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Merges all fields specified by this FieldMaskTree from {@code source} to
|
| + * {@code destination}.
|
| + */
|
| + public void merge(Message source, Message.Builder destination,
|
| + FieldMaskUtil.MergeOptions options) {
|
| + if (source.getDescriptorForType() != destination.getDescriptorForType()) {
|
| + throw new IllegalArgumentException(
|
| + "Cannot merge messages of different types.");
|
| + }
|
| + if (root.children.isEmpty()) {
|
| + return;
|
| + }
|
| + merge(root, "", source, destination, options);
|
| + }
|
| +
|
| + /** Merges all fields specified by a sub-tree from {@code source} to
|
| + * {@code destination}.
|
| + */
|
| + private void merge(Node node, String path, Message source,
|
| + Message.Builder destination, FieldMaskUtil.MergeOptions options) {
|
| + assert source.getDescriptorForType() == destination.getDescriptorForType();
|
| +
|
| + Descriptor descriptor = source.getDescriptorForType();
|
| + for (Entry<String, Node> entry : node.children.entrySet()) {
|
| + FieldDescriptor field =
|
| + descriptor.findFieldByName(entry.getKey());
|
| + if (field == null) {
|
| + logger.warning("Cannot find field \"" + entry.getKey()
|
| + + "\" in message type " + descriptor.getFullName());
|
| + continue;
|
| + }
|
| + if (!entry.getValue().children.isEmpty()) {
|
| + if (field.isRepeated()
|
| + || field.getJavaType() != FieldDescriptor.JavaType.MESSAGE) {
|
| + logger.warning("Field \"" + field.getFullName() + "\" is not a "
|
| + + "singluar message field and cannot have sub-fields.");
|
| + continue;
|
| + }
|
| + String childPath = path.isEmpty()
|
| + ? entry.getKey() : path + "." + entry.getKey();
|
| + merge(entry.getValue(), childPath, (Message) source.getField(field),
|
| + destination.getFieldBuilder(field), options);
|
| + continue;
|
| + }
|
| + if (field.isRepeated()) {
|
| + if (options.replaceRepeatedFields()) {
|
| + destination.setField(field, source.getField(field));
|
| + } else {
|
| + for (Object element : (List) source.getField(field)) {
|
| + destination.addRepeatedField(field, element);
|
| + }
|
| + }
|
| + } else {
|
| + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
|
| + if (options.replaceMessageFields()) {
|
| + destination.setField(field, source.getField(field));
|
| + } else {
|
| + destination.getFieldBuilder(field).mergeFrom(
|
| + (Message) source.getField(field));
|
| + }
|
| + } else {
|
| + destination.setField(field, source.getField(field));
|
| + }
|
| + }
|
| + }
|
| + }
|
| +}
|
|
|