| 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 |