1 // Protocol Buffers - Google's data interchange format 2 // Copyright 2008 Google Inc. All rights reserved. 3 // https://developers.google.com/protocol-buffers/ 4 // 5 // Redistribution and use in source and binary forms, with or without 6 // modification, are permitted provided that the following conditions are 7 // met: 8 // 9 // * Redistributions of source code must retain the above copyright 10 // notice, this list of conditions and the following disclaimer. 11 // * Redistributions in binary form must reproduce the above 12 // copyright notice, this list of conditions and the following disclaimer 13 // in the documentation and/or other materials provided with the 14 // distribution. 15 // * Neither the name of Google Inc. nor the names of its 16 // contributors may be used to endorse or promote products derived from 17 // this software without specific prior written permission. 18 // 19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 31 package com.google.protobuf.util; 32 33 import com.google.protobuf.Descriptors.Descriptor; 34 import com.google.protobuf.Descriptors.FieldDescriptor; 35 import com.google.protobuf.FieldMask; 36 import com.google.protobuf.Message; 37 38 import java.util.ArrayList; 39 import java.util.List; 40 import java.util.Map.Entry; 41 import java.util.TreeMap; 42 import java.util.logging.Logger; 43 44 /** 45 * A tree representation of a FieldMask. Each leaf node in this tree represent 46 * a field path in the FieldMask. 47 * 48 * <p>For example, FieldMask "foo.bar,foo.baz,bar.baz" as a tree will be: 49 * <pre> 50 * [root] -+- foo -+- bar 51 * | | 52 * | +- baz 53 * | 54 * +- bar --- baz 55 * </pre> 56 * 57 * <p>By representing FieldMasks with this tree structure we can easily convert 58 * a FieldMask to a canonical form, merge two FieldMasks, calculate the 59 * intersection to two FieldMasks and traverse all fields specified by the 60 * FieldMask in a message tree. 61 */ 62 class FieldMaskTree { 63 private static final Logger logger = Logger.getLogger(FieldMaskTree.class.getName()); 64 65 private static final String FIELD_PATH_SEPARATOR_REGEX = "\\."; 66 67 private static class Node { 68 public TreeMap<String, Node> children = new TreeMap<String, Node>(); 69 } 70 71 private final Node root = new Node(); 72 73 /** Creates an empty FieldMaskTree. */ FieldMaskTree()74 public FieldMaskTree() {} 75 76 /** Creates a FieldMaskTree for a given FieldMask. */ FieldMaskTree(FieldMask mask)77 public FieldMaskTree(FieldMask mask) { 78 mergeFromFieldMask(mask); 79 } 80 81 @Override toString()82 public String toString() { 83 return FieldMaskUtil.toString(toFieldMask()); 84 } 85 86 /** 87 * Adds a field path to the tree. In a FieldMask, every field path matches the 88 * specified field as well as all its sub-fields. For example, a field path 89 * "foo.bar" matches field "foo.bar" and also "foo.bar.baz", etc. When adding 90 * a field path to the tree, redundant sub-paths will be removed. That is, 91 * after adding "foo.bar" to the tree, "foo.bar.baz" will be removed if it 92 * exists, which will turn the tree node for "foo.bar" to a leaf node. 93 * Likewise, if the field path to add is a sub-path of an existing leaf node, 94 * nothing will be changed in the tree. 95 */ addFieldPath(String path)96 public FieldMaskTree addFieldPath(String path) { 97 String[] parts = path.split(FIELD_PATH_SEPARATOR_REGEX); 98 if (parts.length == 0) { 99 return this; 100 } 101 Node node = root; 102 boolean createNewBranch = false; 103 // Find the matching node in the tree. 104 for (String part : parts) { 105 // Check whether the path matches an existing leaf node. 106 if (!createNewBranch && node != root && node.children.isEmpty()) { 107 // The path to add is a sub-path of an existing leaf node. 108 return this; 109 } 110 if (node.children.containsKey(part)) { 111 node = node.children.get(part); 112 } else { 113 createNewBranch = true; 114 Node tmp = new Node(); 115 node.children.put(part, tmp); 116 node = tmp; 117 } 118 } 119 // Turn the matching node into a leaf node (i.e., remove sub-paths). 120 node.children.clear(); 121 return this; 122 } 123 124 /** 125 * Merges all field paths in a FieldMask into this tree. 126 */ mergeFromFieldMask(FieldMask mask)127 public FieldMaskTree mergeFromFieldMask(FieldMask mask) { 128 for (String path : mask.getPathsList()) { 129 addFieldPath(path); 130 } 131 return this; 132 } 133 134 /** Converts this tree to a FieldMask. */ toFieldMask()135 public FieldMask toFieldMask() { 136 if (root.children.isEmpty()) { 137 return FieldMask.getDefaultInstance(); 138 } 139 List<String> paths = new ArrayList<String>(); 140 getFieldPaths(root, "", paths); 141 return FieldMask.newBuilder().addAllPaths(paths).build(); 142 } 143 144 /** Gathers all field paths in a sub-tree. */ getFieldPaths(Node node, String path, List<String> paths)145 private void getFieldPaths(Node node, String path, List<String> paths) { 146 if (node.children.isEmpty()) { 147 paths.add(path); 148 return; 149 } 150 for (Entry<String, Node> entry : node.children.entrySet()) { 151 String childPath = path.isEmpty() ? entry.getKey() : path + "." + entry.getKey(); 152 getFieldPaths(entry.getValue(), childPath, paths); 153 } 154 } 155 156 /** 157 * Adds the intersection of this tree with the given {@code path} to 158 * {@code output}. 159 */ intersectFieldPath(String path, FieldMaskTree output)160 public void intersectFieldPath(String path, FieldMaskTree output) { 161 if (root.children.isEmpty()) { 162 return; 163 } 164 String[] parts = path.split(FIELD_PATH_SEPARATOR_REGEX); 165 if (parts.length == 0) { 166 return; 167 } 168 Node node = root; 169 for (String part : parts) { 170 if (node != root && node.children.isEmpty()) { 171 // The given path is a sub-path of an existing leaf node in the tree. 172 output.addFieldPath(path); 173 return; 174 } 175 if (node.children.containsKey(part)) { 176 node = node.children.get(part); 177 } else { 178 return; 179 } 180 } 181 // We found a matching node for the path. All leaf children of this matching 182 // node is in the intersection. 183 List<String> paths = new ArrayList<String>(); 184 getFieldPaths(node, path, paths); 185 for (String value : paths) { 186 output.addFieldPath(value); 187 } 188 } 189 190 /** 191 * Merges all fields specified by this FieldMaskTree from {@code source} to 192 * {@code destination}. 193 */ merge( Message source, Message.Builder destination, FieldMaskUtil.MergeOptions options)194 public void merge( 195 Message source, Message.Builder destination, FieldMaskUtil.MergeOptions options) { 196 if (source.getDescriptorForType() != destination.getDescriptorForType()) { 197 throw new IllegalArgumentException("Cannot merge messages of different types."); 198 } 199 if (root.children.isEmpty()) { 200 return; 201 } 202 merge(root, "", source, destination, options); 203 } 204 205 /** Merges all fields specified by a sub-tree from {@code source} to 206 * {@code destination}. 207 */ merge( Node node, String path, Message source, Message.Builder destination, FieldMaskUtil.MergeOptions options)208 private void merge( 209 Node node, 210 String path, 211 Message source, 212 Message.Builder destination, 213 FieldMaskUtil.MergeOptions options) { 214 assert source.getDescriptorForType() == destination.getDescriptorForType(); 215 216 Descriptor descriptor = source.getDescriptorForType(); 217 for (Entry<String, Node> entry : node.children.entrySet()) { 218 FieldDescriptor field = descriptor.findFieldByName(entry.getKey()); 219 if (field == null) { 220 logger.warning( 221 "Cannot find field \"" 222 + entry.getKey() 223 + "\" in message type " 224 + descriptor.getFullName()); 225 continue; 226 } 227 if (!entry.getValue().children.isEmpty()) { 228 if (field.isRepeated() || field.getJavaType() != FieldDescriptor.JavaType.MESSAGE) { 229 logger.warning( 230 "Field \"" 231 + field.getFullName() 232 + "\" is not a " 233 + "singluar message field and cannot have sub-fields."); 234 continue; 235 } 236 String childPath = path.isEmpty() ? entry.getKey() : path + "." + entry.getKey(); 237 merge( 238 entry.getValue(), 239 childPath, 240 (Message) source.getField(field), 241 destination.getFieldBuilder(field), 242 options); 243 continue; 244 } 245 if (field.isRepeated()) { 246 if (options.replaceRepeatedFields()) { 247 destination.setField(field, source.getField(field)); 248 } else { 249 for (Object element : (List) source.getField(field)) { 250 destination.addRepeatedField(field, element); 251 } 252 } 253 } else { 254 if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { 255 if (options.replaceMessageFields()) { 256 if (!source.hasField(field)) { 257 destination.clearField(field); 258 } else { 259 destination.setField(field, source.getField(field)); 260 } 261 } else { 262 if (source.hasField(field)) { 263 destination.getFieldBuilder(field).mergeFrom((Message) source.getField(field)); 264 } 265 } 266 } else { 267 if (source.hasField(field) || !options.replacePrimitiveFields()) { 268 destination.setField(field, source.getField(field)); 269 } else { 270 destination.clearField(field); 271 } 272 } 273 } 274 } 275 } 276 } 277