OLD | NEW |
1 // Protocol Buffers - Google's data interchange format | 1 // Protocol Buffers - Google's data interchange format |
2 // Copyright 2008 Google Inc. All rights reserved. | 2 // Copyright 2008 Google Inc. All rights reserved. |
3 // https://developers.google.com/protocol-buffers/ | 3 // https://developers.google.com/protocol-buffers/ |
4 // | 4 // |
5 // Redistribution and use in source and binary forms, with or without | 5 // Redistribution and use in source and binary forms, with or without |
6 // modification, are permitted provided that the following conditions are | 6 // modification, are permitted provided that the following conditions are |
7 // met: | 7 // met: |
8 // | 8 // |
9 // * Redistributions of source code must retain the above copyright | 9 // * Redistributions of source code must retain the above copyright |
10 // notice, this list of conditions and the following disclaimer. | 10 // notice, this list of conditions and the following disclaimer. |
(...skipping 20 matching lines...) Expand all Loading... |
31 package com.google.protobuf.util; | 31 package com.google.protobuf.util; |
32 | 32 |
33 import com.google.protobuf.Descriptors.Descriptor; | 33 import com.google.protobuf.Descriptors.Descriptor; |
34 import com.google.protobuf.Descriptors.FieldDescriptor; | 34 import com.google.protobuf.Descriptors.FieldDescriptor; |
35 import com.google.protobuf.FieldMask; | 35 import com.google.protobuf.FieldMask; |
36 import com.google.protobuf.Message; | 36 import com.google.protobuf.Message; |
37 | 37 |
38 import java.util.ArrayList; | 38 import java.util.ArrayList; |
39 import java.util.List; | 39 import java.util.List; |
40 import java.util.Map.Entry; | 40 import java.util.Map.Entry; |
| 41 import java.util.SortedMap; |
41 import java.util.TreeMap; | 42 import java.util.TreeMap; |
42 import java.util.logging.Logger; | 43 import java.util.logging.Logger; |
43 | 44 |
44 /** | 45 /** |
45 * A tree representation of a FieldMask. Each leaf node in this tree represent | 46 * A tree representation of a FieldMask. Each leaf node in this tree represent |
46 * a field path in the FieldMask. | 47 * a field path in the FieldMask. |
47 * | 48 * |
48 * <p>For example, FieldMask "foo.bar,foo.baz,bar.baz" as a tree will be: | 49 * <p>For example, FieldMask "foo.bar,foo.baz,bar.baz" as a tree will be: |
49 * <pre> | 50 * <pre> |
50 * [root] -+- foo -+- bar | 51 * [root] -+- foo -+- bar |
51 * | | | 52 * | | |
52 * | +- baz | 53 * | +- baz |
53 * | | 54 * | |
54 * +- bar --- baz | 55 * +- bar --- baz |
55 * </pre> | 56 * </pre> |
56 * | 57 * |
57 * <p>By representing FieldMasks with this tree structure we can easily convert | 58 * <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 * a FieldMask to a canonical form, merge two FieldMasks, calculate the |
59 * intersection to two FieldMasks and traverse all fields specified by the | 60 * intersection to two FieldMasks and traverse all fields specified by the |
60 * FieldMask in a message tree. | 61 * FieldMask in a message tree. |
61 */ | 62 */ |
62 class FieldMaskTree { | 63 final class FieldMaskTree { |
63 private static final Logger logger = Logger.getLogger(FieldMaskTree.class.getN
ame()); | 64 private static final Logger logger = Logger.getLogger(FieldMaskTree.class.getN
ame()); |
64 | 65 |
65 private static final String FIELD_PATH_SEPARATOR_REGEX = "\\."; | 66 private static final String FIELD_PATH_SEPARATOR_REGEX = "\\."; |
66 | 67 |
67 private static class Node { | 68 private static final class Node { |
68 public TreeMap<String, Node> children = new TreeMap<String, Node>(); | 69 final SortedMap<String, Node> children = new TreeMap<String, Node>(); |
69 } | 70 } |
70 | 71 |
71 private final Node root = new Node(); | 72 private final Node root = new Node(); |
72 | 73 |
73 /** Creates an empty FieldMaskTree. */ | 74 /** |
74 public FieldMaskTree() {} | 75 * Creates an empty FieldMaskTree. |
| 76 */ |
| 77 FieldMaskTree() {} |
75 | 78 |
76 /** Creates a FieldMaskTree for a given FieldMask. */ | 79 /** |
77 public FieldMaskTree(FieldMask mask) { | 80 * Creates a FieldMaskTree for a given FieldMask. |
| 81 */ |
| 82 FieldMaskTree(FieldMask mask) { |
78 mergeFromFieldMask(mask); | 83 mergeFromFieldMask(mask); |
79 } | 84 } |
80 | 85 |
81 @Override | 86 @Override |
82 public String toString() { | 87 public String toString() { |
83 return FieldMaskUtil.toString(toFieldMask()); | 88 return FieldMaskUtil.toString(toFieldMask()); |
84 } | 89 } |
85 | 90 |
86 /** | 91 /** |
87 * Adds a field path to the tree. In a FieldMask, every field path matches the | 92 * 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 | 93 * 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 | 94 * "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, | 95 * 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 | 96 * 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. | 97 * 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, | 98 * Likewise, if the field path to add is a sub-path of an existing leaf node, |
94 * nothing will be changed in the tree. | 99 * nothing will be changed in the tree. |
95 */ | 100 */ |
96 public FieldMaskTree addFieldPath(String path) { | 101 FieldMaskTree addFieldPath(String path) { |
97 String[] parts = path.split(FIELD_PATH_SEPARATOR_REGEX); | 102 String[] parts = path.split(FIELD_PATH_SEPARATOR_REGEX); |
98 if (parts.length == 0) { | 103 if (parts.length == 0) { |
99 return this; | 104 return this; |
100 } | 105 } |
101 Node node = root; | 106 Node node = root; |
102 boolean createNewBranch = false; | 107 boolean createNewBranch = false; |
103 // Find the matching node in the tree. | 108 // Find the matching node in the tree. |
104 for (String part : parts) { | 109 for (String part : parts) { |
105 // Check whether the path matches an existing leaf node. | 110 // Check whether the path matches an existing leaf node. |
106 if (!createNewBranch && node != root && node.children.isEmpty()) { | 111 if (!createNewBranch && node != root && node.children.isEmpty()) { |
(...skipping 10 matching lines...) Expand all Loading... |
117 } | 122 } |
118 } | 123 } |
119 // Turn the matching node into a leaf node (i.e., remove sub-paths). | 124 // Turn the matching node into a leaf node (i.e., remove sub-paths). |
120 node.children.clear(); | 125 node.children.clear(); |
121 return this; | 126 return this; |
122 } | 127 } |
123 | 128 |
124 /** | 129 /** |
125 * Merges all field paths in a FieldMask into this tree. | 130 * Merges all field paths in a FieldMask into this tree. |
126 */ | 131 */ |
127 public FieldMaskTree mergeFromFieldMask(FieldMask mask) { | 132 FieldMaskTree mergeFromFieldMask(FieldMask mask) { |
128 for (String path : mask.getPathsList()) { | 133 for (String path : mask.getPathsList()) { |
129 addFieldPath(path); | 134 addFieldPath(path); |
130 } | 135 } |
131 return this; | 136 return this; |
132 } | 137 } |
133 | 138 |
134 /** Converts this tree to a FieldMask. */ | 139 /** |
135 public FieldMask toFieldMask() { | 140 * Converts this tree to a FieldMask. |
| 141 */ |
| 142 FieldMask toFieldMask() { |
136 if (root.children.isEmpty()) { | 143 if (root.children.isEmpty()) { |
137 return FieldMask.getDefaultInstance(); | 144 return FieldMask.getDefaultInstance(); |
138 } | 145 } |
139 List<String> paths = new ArrayList<String>(); | 146 List<String> paths = new ArrayList<String>(); |
140 getFieldPaths(root, "", paths); | 147 getFieldPaths(root, "", paths); |
141 return FieldMask.newBuilder().addAllPaths(paths).build(); | 148 return FieldMask.newBuilder().addAllPaths(paths).build(); |
142 } | 149 } |
143 | 150 |
144 /** Gathers all field paths in a sub-tree. */ | 151 /** |
| 152 * Gathers all field paths in a sub-tree. |
| 153 */ |
145 private void getFieldPaths(Node node, String path, List<String> paths) { | 154 private void getFieldPaths(Node node, String path, List<String> paths) { |
146 if (node.children.isEmpty()) { | 155 if (node.children.isEmpty()) { |
147 paths.add(path); | 156 paths.add(path); |
148 return; | 157 return; |
149 } | 158 } |
150 for (Entry<String, Node> entry : node.children.entrySet()) { | 159 for (Entry<String, Node> entry : node.children.entrySet()) { |
151 String childPath = path.isEmpty() ? entry.getKey() : path + "." + entry.ge
tKey(); | 160 String childPath = path.isEmpty() ? entry.getKey() : path + "." + entry.ge
tKey(); |
152 getFieldPaths(entry.getValue(), childPath, paths); | 161 getFieldPaths(entry.getValue(), childPath, paths); |
153 } | 162 } |
154 } | 163 } |
155 | 164 |
156 /** | 165 /** |
157 * Adds the intersection of this tree with the given {@code path} to | 166 * Adds the intersection of this tree with the given {@code path} to {@code ou
tput}. |
158 * {@code output}. | |
159 */ | 167 */ |
160 public void intersectFieldPath(String path, FieldMaskTree output) { | 168 void intersectFieldPath(String path, FieldMaskTree output) { |
161 if (root.children.isEmpty()) { | 169 if (root.children.isEmpty()) { |
162 return; | 170 return; |
163 } | 171 } |
164 String[] parts = path.split(FIELD_PATH_SEPARATOR_REGEX); | 172 String[] parts = path.split(FIELD_PATH_SEPARATOR_REGEX); |
165 if (parts.length == 0) { | 173 if (parts.length == 0) { |
166 return; | 174 return; |
167 } | 175 } |
168 Node node = root; | 176 Node node = root; |
169 for (String part : parts) { | 177 for (String part : parts) { |
170 if (node != root && node.children.isEmpty()) { | 178 if (node != root && node.children.isEmpty()) { |
(...skipping 10 matching lines...) Expand all Loading... |
181 // We found a matching node for the path. All leaf children of this matching | 189 // We found a matching node for the path. All leaf children of this matching |
182 // node is in the intersection. | 190 // node is in the intersection. |
183 List<String> paths = new ArrayList<String>(); | 191 List<String> paths = new ArrayList<String>(); |
184 getFieldPaths(node, path, paths); | 192 getFieldPaths(node, path, paths); |
185 for (String value : paths) { | 193 for (String value : paths) { |
186 output.addFieldPath(value); | 194 output.addFieldPath(value); |
187 } | 195 } |
188 } | 196 } |
189 | 197 |
190 /** | 198 /** |
191 * Merges all fields specified by this FieldMaskTree from {@code source} to | 199 * Merges all fields specified by this FieldMaskTree from {@code source} to {@
code destination}. |
192 * {@code destination}. | |
193 */ | 200 */ |
194 public void merge( | 201 void merge(Message source, Message.Builder destination, FieldMaskUtil.MergeOpt
ions options) { |
195 Message source, Message.Builder destination, FieldMaskUtil.MergeOptions op
tions) { | |
196 if (source.getDescriptorForType() != destination.getDescriptorForType()) { | 202 if (source.getDescriptorForType() != destination.getDescriptorForType()) { |
197 throw new IllegalArgumentException("Cannot merge messages of different typ
es."); | 203 throw new IllegalArgumentException("Cannot merge messages of different typ
es."); |
198 } | 204 } |
199 if (root.children.isEmpty()) { | 205 if (root.children.isEmpty()) { |
200 return; | 206 return; |
201 } | 207 } |
202 merge(root, "", source, destination, options); | 208 merge(root, "", source, destination, options); |
203 } | 209 } |
204 | 210 |
205 /** Merges all fields specified by a sub-tree from {@code source} to | 211 /** |
206 * {@code destination}. | 212 * Merges all fields specified by a sub-tree from {@code source} to {@code des
tination}. |
207 */ | 213 */ |
208 private void merge( | 214 private void merge( |
209 Node node, | 215 Node node, |
210 String path, | 216 String path, |
211 Message source, | 217 Message source, |
212 Message.Builder destination, | 218 Message.Builder destination, |
213 FieldMaskUtil.MergeOptions options) { | 219 FieldMaskUtil.MergeOptions options) { |
214 assert source.getDescriptorForType() == destination.getDescriptorForType(); | 220 if (source.getDescriptorForType() != destination.getDescriptorForType()) { |
| 221 throw new IllegalArgumentException( |
| 222 String.format( |
| 223 "source (%s) and destination (%s) descriptor must be equal", |
| 224 source.getDescriptorForType(), destination.getDescriptorForType())
); |
| 225 } |
215 | 226 |
216 Descriptor descriptor = source.getDescriptorForType(); | 227 Descriptor descriptor = source.getDescriptorForType(); |
217 for (Entry<String, Node> entry : node.children.entrySet()) { | 228 for (Entry<String, Node> entry : node.children.entrySet()) { |
218 FieldDescriptor field = descriptor.findFieldByName(entry.getKey()); | 229 FieldDescriptor field = descriptor.findFieldByName(entry.getKey()); |
219 if (field == null) { | 230 if (field == null) { |
220 logger.warning( | 231 logger.warning( |
221 "Cannot find field \"" | 232 "Cannot find field \"" |
222 + entry.getKey() | 233 + entry.getKey() |
223 + "\" in message type " | 234 + "\" in message type " |
224 + descriptor.getFullName()); | 235 + descriptor.getFullName()); |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
267 if (source.hasField(field) || !options.replacePrimitiveFields()) { | 278 if (source.hasField(field) || !options.replacePrimitiveFields()) { |
268 destination.setField(field, source.getField(field)); | 279 destination.setField(field, source.getField(field)); |
269 } else { | 280 } else { |
270 destination.clearField(field); | 281 destination.clearField(field); |
271 } | 282 } |
272 } | 283 } |
273 } | 284 } |
274 } | 285 } |
275 } | 286 } |
276 } | 287 } |
OLD | NEW |