FieldMaskTree.java
Go to the documentation of this file.
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.errorprone.annotations.CanIgnoreReturnValue;
36 import com.google.protobuf.FieldMask;
37 import com.google.protobuf.Message;
38 import java.util.ArrayList;
39 import java.util.List;
40 import java.util.Map.Entry;
41 import java.util.SortedMap;
42 import java.util.TreeMap;
43 import java.util.logging.Logger;
44 
63 final class FieldMaskTree {
64  private static final Logger logger = Logger.getLogger(FieldMaskTree.class.getName());
65 
66  private static final String FIELD_PATH_SEPARATOR_REGEX = "\\.";
67 
68  private static final class Node {
69  final SortedMap<String, Node> children = new TreeMap<String, Node>();
70  }
71 
72  private final Node root = new Node();
73 
77  FieldMaskTree() {}
78 
82  FieldMaskTree(FieldMask mask) {
83  mergeFromFieldMask(mask);
84  }
85 
86  @Override
87  public String toString() {
88  return FieldMaskUtil.toString(toFieldMask());
89  }
90 
99  @CanIgnoreReturnValue
100  FieldMaskTree addFieldPath(String path) {
101  String[] parts = path.split(FIELD_PATH_SEPARATOR_REGEX);
102  if (parts.length == 0) {
103  return this;
104  }
105  Node node = root;
106  boolean createNewBranch = false;
107  // Find the matching node in the tree.
108  for (String part : parts) {
109  // Check whether the path matches an existing leaf node.
110  if (!createNewBranch && node != root && node.children.isEmpty()) {
111  // The path to add is a sub-path of an existing leaf node.
112  return this;
113  }
114  if (node.children.containsKey(part)) {
115  node = node.children.get(part);
116  } else {
117  createNewBranch = true;
118  Node tmp = new Node();
119  node.children.put(part, tmp);
120  node = tmp;
121  }
122  }
123  // Turn the matching node into a leaf node (i.e., remove sub-paths).
124  node.children.clear();
125  return this;
126  }
127 
129  @CanIgnoreReturnValue
130  FieldMaskTree mergeFromFieldMask(FieldMask mask) {
131  for (String path : mask.getPathsList()) {
132  addFieldPath(path);
133  }
134  return this;
135  }
136 
140  FieldMask toFieldMask() {
141  if (root.children.isEmpty()) {
142  return FieldMask.getDefaultInstance();
143  }
144  List<String> paths = new ArrayList<String>();
145  getFieldPaths(root, "", paths);
146  return FieldMask.newBuilder().addAllPaths(paths).build();
147  }
148 
152  private void getFieldPaths(Node node, String path, List<String> paths) {
153  if (node.children.isEmpty()) {
154  paths.add(path);
155  return;
156  }
157  for (Entry<String, Node> entry : node.children.entrySet()) {
158  String childPath = path.isEmpty() ? entry.getKey() : path + "." + entry.getKey();
159  getFieldPaths(entry.getValue(), childPath, paths);
160  }
161  }
162 
166  void intersectFieldPath(String path, FieldMaskTree output) {
167  if (root.children.isEmpty()) {
168  return;
169  }
170  String[] parts = path.split(FIELD_PATH_SEPARATOR_REGEX);
171  if (parts.length == 0) {
172  return;
173  }
174  Node node = root;
175  for (String part : parts) {
176  if (node != root && node.children.isEmpty()) {
177  // The given path is a sub-path of an existing leaf node in the tree.
178  output.addFieldPath(path);
179  return;
180  }
181  if (node.children.containsKey(part)) {
182  node = node.children.get(part);
183  } else {
184  return;
185  }
186  }
187  // We found a matching node for the path. All leaf children of this matching
188  // node is in the intersection.
189  List<String> paths = new ArrayList<String>();
190  getFieldPaths(node, path, paths);
191  for (String value : paths) {
192  output.addFieldPath(value);
193  }
194  }
195 
199  void merge(Message source, Message.Builder destination, FieldMaskUtil.MergeOptions options) {
200  if (source.getDescriptorForType() != destination.getDescriptorForType()) {
201  throw new IllegalArgumentException("Cannot merge messages of different types.");
202  }
203  if (root.children.isEmpty()) {
204  return;
205  }
206  merge(root, "", source, destination, options);
207  }
208 
212  private void merge(
213  Node node,
214  String path,
215  Message source,
216  Message.Builder destination,
217  FieldMaskUtil.MergeOptions options) {
218  if (source.getDescriptorForType() != destination.getDescriptorForType()) {
219  throw new IllegalArgumentException(
220  String.format(
221  "source (%s) and destination (%s) descriptor must be equal",
222  source.getDescriptorForType(), destination.getDescriptorForType()));
223  }
224 
225  Descriptor descriptor = source.getDescriptorForType();
226  for (Entry<String, Node> entry : node.children.entrySet()) {
227  FieldDescriptor field = descriptor.findFieldByName(entry.getKey());
228  if (field == null) {
229  logger.warning(
230  "Cannot find field \""
231  + entry.getKey()
232  + "\" in message type "
233  + descriptor.getFullName());
234  continue;
235  }
236  if (!entry.getValue().children.isEmpty()) {
237  if (field.isRepeated() || field.getJavaType() != FieldDescriptor.JavaType.MESSAGE) {
238  logger.warning(
239  "Field \""
240  + field.getFullName()
241  + "\" is not a "
242  + "singular message field and cannot have sub-fields.");
243  continue;
244  }
245  if (!source.hasField(field) && !destination.hasField(field)) {
246  // If the message field is not present in both source and destination, skip recursing
247  // so we don't create unnecessary empty messages.
248  continue;
249  }
250  String childPath = path.isEmpty() ? entry.getKey() : path + "." + entry.getKey();
251  Message.Builder childBuilder = ((Message) destination.getField(field)).toBuilder();
252  merge(entry.getValue(), childPath, (Message) source.getField(field), childBuilder, options);
253  destination.setField(field, childBuilder.buildPartial());
254  continue;
255  }
256  if (field.isRepeated()) {
257  if (options.replaceRepeatedFields()) {
258  destination.setField(field, source.getField(field));
259  } else {
260  for (Object element : (List) source.getField(field)) {
261  destination.addRepeatedField(field, element);
262  }
263  }
264  } else {
265  if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
266  if (options.replaceMessageFields()) {
267  if (!source.hasField(field)) {
268  destination.clearField(field);
269  } else {
270  destination.setField(field, source.getField(field));
271  }
272  } else {
273  if (source.hasField(field)) {
274  destination.setField(
275  field,
276  ((Message) destination.getField(field))
277  .toBuilder()
278  .mergeFrom((Message) source.getField(field))
279  .build());
280  }
281  }
282  } else {
283  if (source.hasField(field) || !options.replacePrimitiveFields()) {
284  destination.setField(field, source.getField(field));
285  } else {
286  destination.clearField(field);
287  }
288  }
289  }
290  }
291  }
292 }
com.google.protobuf.Descriptors
Definition: Descriptors.java:80
com.google.protobuf.util.FieldMaskTree.Node
Definition: FieldMaskTree.java:68
FieldMask
Definition: field_mask.pb.h:69
options
Message * options
Definition: src/google/protobuf/descriptor.cc:3119
FieldMask
struct FieldMask FieldMask
Definition: php/ext/google/protobuf/protobuf.h:640
com.google.protobuf
Definition: ProtoCaliperBenchmark.java:2
descriptor
Descriptor * descriptor
Definition: php/ext/google/protobuf/protobuf.h:936
Descriptor
Definition: ruby/ext/google/protobuf_c/protobuf.h:113
merge
static void merge(upb_refcounted *r, upb_refcounted *from)
Definition: ruby/ext/google/protobuf_c/upb.c:6188
FieldDescriptor
Definition: ruby/ext/google/protobuf_c/protobuf.h:129
mingw.root
def root(location=None, arch=None, version=None, threading=None, exceptions=None, revision=None, log=EmptyLogger())
Definition: mingw.py:172
path
GLsizei const GLchar ** path
Definition: glcorearb.h:3658
mask
GLint GLuint mask
Definition: glcorearb.h:2789
source
GLsizei GLsizei GLchar * source
Definition: glcorearb.h:3072
field
const FieldDescriptor * field
Definition: parser_unittest.cc:2694
java
com.google.protobuf.Descriptors.Descriptor
Definition: Descriptors.java:629
com.google
com
value
GLsizei const GLfloat * value
Definition: glcorearb.h:3093
output
const upb_json_parsermethod const upb_symtab upb_sink * output
Definition: ruby/ext/google/protobuf_c/upb.h:10503
com.google.protobuf.Message
Definition: Message.java:50
com.google.protobuf.Descriptors.FieldDescriptor
Definition: Descriptors.java:949


libaditof
Author(s):
autogenerated on Wed May 21 2025 02:06:51