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