diff options
Diffstat (limited to 'third_party/protobuf/3.4.0/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java')
-rw-r--r-- | third_party/protobuf/3.4.0/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/third_party/protobuf/3.4.0/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java b/third_party/protobuf/3.4.0/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java new file mode 100644 index 0000000000..b192b53edf --- /dev/null +++ b/third_party/protobuf/3.4.0/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java @@ -0,0 +1,287 @@ +// 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.SortedMap; +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. + */ +final class FieldMaskTree { + private static final Logger logger = Logger.getLogger(FieldMaskTree.class.getName()); + + private static final String FIELD_PATH_SEPARATOR_REGEX = "\\."; + + private static final class Node { + final SortedMap<String, Node> children = new TreeMap<String, Node>(); + } + + private final Node root = new Node(); + + /** + * Creates an empty FieldMaskTree. + */ + FieldMaskTree() {} + + /** + * Creates a FieldMaskTree for a given FieldMask. + */ + 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. + */ + 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. + */ + FieldMaskTree mergeFromFieldMask(FieldMask mask) { + for (String path : mask.getPathsList()) { + addFieldPath(path); + } + return this; + } + + /** + * Converts this tree to a FieldMask. + */ + 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}. + */ + 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}. + */ + 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) { + if (source.getDescriptorForType() != destination.getDescriptorForType()) { + throw new IllegalArgumentException( + String.format( + "source (%s) and destination (%s) descriptor must be equal", + 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()) { + if (!source.hasField(field)) { + destination.clearField(field); + } else { + destination.setField(field, source.getField(field)); + } + } else { + if (source.hasField(field)) { + destination.getFieldBuilder(field).mergeFrom((Message) source.getField(field)); + } + } + } else { + if (source.hasField(field) || !options.replacePrimitiveFields()) { + destination.setField(field, source.getField(field)); + } else { + destination.clearField(field); + } + } + } + } + } +} |