| 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 14 matching lines...) Expand all Loading... |
| 25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 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. | 29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 30 | 30 |
| 31 package com.google.protobuf.util; | 31 package com.google.protobuf.util; |
| 32 | 32 |
| 33 import static com.google.common.base.Preconditions.checkArgument; | 33 import static com.google.common.base.Preconditions.checkArgument; |
| 34 | 34 |
| 35 import com.google.common.base.CaseFormat; |
| 36 import com.google.common.base.Joiner; |
| 37 import com.google.common.base.Splitter; |
| 35 import com.google.common.primitives.Ints; | 38 import com.google.common.primitives.Ints; |
| 36 import com.google.protobuf.Descriptors.Descriptor; | 39 import com.google.protobuf.Descriptors.Descriptor; |
| 37 import com.google.protobuf.Descriptors.FieldDescriptor; | 40 import com.google.protobuf.Descriptors.FieldDescriptor; |
| 38 import com.google.protobuf.FieldMask; | 41 import com.google.protobuf.FieldMask; |
| 39 import com.google.protobuf.Internal; | 42 import com.google.protobuf.Internal; |
| 40 import com.google.protobuf.Message; | 43 import com.google.protobuf.Message; |
| 41 | 44 |
| 45 import java.util.ArrayList; |
| 42 import java.util.Arrays; | 46 import java.util.Arrays; |
| 47 import java.util.List; |
| 43 | 48 |
| 44 /** | 49 /** |
| 45 * Utility helper functions to work with {@link com.google.protobuf.FieldMask}. | 50 * Utility helper functions to work with {@link com.google.protobuf.FieldMask}. |
| 46 */ | 51 */ |
| 47 public class FieldMaskUtil { | 52 public class FieldMaskUtil { |
| 48 private static final String FIELD_PATH_SEPARATOR = ","; | 53 private static final String FIELD_PATH_SEPARATOR = ","; |
| 49 private static final String FIELD_PATH_SEPARATOR_REGEX = ","; | 54 private static final String FIELD_PATH_SEPARATOR_REGEX = ","; |
| 50 private static final String FIELD_SEPARATOR_REGEX = "\\."; | 55 private static final String FIELD_SEPARATOR_REGEX = "\\."; |
| 51 | 56 |
| 52 private FieldMaskUtil() {} | 57 private FieldMaskUtil() {} |
| 53 | 58 |
| 54 /** | 59 /** |
| 55 * Converts a FieldMask to a string. | 60 * Converts a FieldMask to a string. |
| 56 */ | 61 */ |
| 57 public static String toString(FieldMask fieldMask) { | 62 public static String toString(FieldMask fieldMask) { |
| 58 // TODO(xiaofeng): Consider using com.google.common.base.Joiner here instead
. | 63 // TODO(xiaofeng): Consider using com.google.common.base.Joiner here instead
. |
| 59 StringBuilder result = new StringBuilder(); | 64 StringBuilder result = new StringBuilder(); |
| 60 boolean first = true; | 65 boolean first = true; |
| 61 for (String value : fieldMask.getPathsList()) { | 66 for (String value : fieldMask.getPathsList()) { |
| 62 if (value.isEmpty()) { | 67 if (value.isEmpty()) { |
| 63 // Ignore empty paths. | 68 // Ignore empty paths. |
| 64 continue; | 69 continue; |
| 65 } | 70 } |
| 66 if (first) { | 71 if (first) { |
| 67 first = false; | 72 first = false; |
| 68 } else { | 73 } else { |
| 69 result.append(FIELD_PATH_SEPARATOR); | 74 result.append(FIELD_PATH_SEPARATOR); |
| 70 } | 75 } |
| 71 result.append(value); | 76 result.append(value); |
| 72 } | 77 } |
| 73 return result.toString(); | 78 return result.toString(); |
| 74 } | 79 } |
| 75 | 80 |
| 76 /** | 81 /** |
| 77 * Parses from a string to a FieldMask. | 82 * Parses from a string to a FieldMask. |
| 78 */ | 83 */ |
| 79 public static FieldMask fromString(String value) { | 84 public static FieldMask fromString(String value) { |
| 80 // TODO(xiaofeng): Consider using com.google.common.base.Splitter here inste
ad. | 85 // TODO(xiaofeng): Consider using com.google.common.base.Splitter here inste
ad. |
| 81 return fromStringList( | 86 return fromStringList(null, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_R
EGEX))); |
| 82 null, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX))); | |
| 83 } | 87 } |
| 84 | 88 |
| 85 /** | 89 /** |
| 86 * Parses from a string to a FieldMask and validates all field paths. | 90 * Parses from a string to a FieldMask and validates all field paths. |
| 87 * | 91 * |
| 88 * @throws IllegalArgumentException if any of the field path is invalid. | 92 * @throws IllegalArgumentException if any of the field path is invalid. |
| 89 */ | 93 */ |
| 90 public static FieldMask fromString(Class<? extends Message> type, String value
) { | 94 public static FieldMask fromString(Class<? extends Message> type, String value
) { |
| 91 // TODO(xiaofeng): Consider using com.google.common.base.Splitter here inste
ad. | 95 // TODO(xiaofeng): Consider using com.google.common.base.Splitter here inste
ad. |
| 92 return fromStringList( | 96 return fromStringList(type, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_R
EGEX))); |
| 93 type, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX))); | |
| 94 } | 97 } |
| 95 | 98 |
| 96 /** | 99 /** |
| 97 * Constructs a FieldMask for a list of field paths in a certain type. | 100 * Constructs a FieldMask for a list of field paths in a certain type. |
| 98 * | 101 * |
| 99 * @throws IllegalArgumentException if any of the field path is not valid. | 102 * @throws IllegalArgumentException if any of the field path is not valid. |
| 100 */ | 103 */ |
| 101 // TODO(xiaofeng): Consider renaming fromStrings() | 104 // TODO(xiaofeng): Consider renaming fromStrings() |
| 102 public static FieldMask fromStringList( | 105 public static FieldMask fromStringList(Class<? extends Message> type, Iterable
<String> paths) { |
| 103 Class<? extends Message> type, Iterable<String> paths) { | |
| 104 FieldMask.Builder builder = FieldMask.newBuilder(); | 106 FieldMask.Builder builder = FieldMask.newBuilder(); |
| 105 for (String path : paths) { | 107 for (String path : paths) { |
| 106 if (path.isEmpty()) { | 108 if (path.isEmpty()) { |
| 107 // Ignore empty field paths. | 109 // Ignore empty field paths. |
| 108 continue; | 110 continue; |
| 109 } | 111 } |
| 110 if (type != null && !isValid(type, path)) { | 112 if (type != null && !isValid(type, path)) { |
| 111 throw new IllegalArgumentException( | 113 throw new IllegalArgumentException(path + " is not a valid path for " +
type); |
| 112 path + " is not a valid path for " + type); | |
| 113 } | 114 } |
| 114 builder.addPaths(path); | 115 builder.addPaths(path); |
| 115 } | 116 } |
| 116 return builder.build(); | 117 return builder.build(); |
| 117 } | 118 } |
| 118 | 119 |
| 119 /** | 120 /** |
| 120 * Constructs a FieldMask from the passed field numbers. | 121 * Constructs a FieldMask from the passed field numbers. |
| 121 * | 122 * |
| 122 * @throws IllegalArgumentException if any of the fields are invalid for the m
essage. | 123 * @throws IllegalArgumentException if any of the fields are invalid for the m
essage. |
| (...skipping 16 matching lines...) Expand all Loading... |
| 139 FieldDescriptor field = descriptor.findFieldByNumber(fieldNumber); | 140 FieldDescriptor field = descriptor.findFieldByNumber(fieldNumber); |
| 140 checkArgument( | 141 checkArgument( |
| 141 field != null, | 142 field != null, |
| 142 String.format("%s is not a valid field number for %s.", fieldNumber, t
ype)); | 143 String.format("%s is not a valid field number for %s.", fieldNumber, t
ype)); |
| 143 builder.addPaths(field.getName()); | 144 builder.addPaths(field.getName()); |
| 144 } | 145 } |
| 145 return builder.build(); | 146 return builder.build(); |
| 146 } | 147 } |
| 147 | 148 |
| 148 /** | 149 /** |
| 150 * Converts a field mask to a Proto3 JSON string, that is converting from snak
e case to camel |
| 151 * case and joining all paths into one string with commas. |
| 152 */ |
| 153 public static String toJsonString(FieldMask fieldMask) { |
| 154 List<String> paths = new ArrayList<String>(fieldMask.getPathsCount()); |
| 155 for (String path : fieldMask.getPathsList()) { |
| 156 if (path.isEmpty()) { |
| 157 continue; |
| 158 } |
| 159 paths.add(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, path)); |
| 160 } |
| 161 return Joiner.on(FIELD_PATH_SEPARATOR).join(paths); |
| 162 } |
| 163 |
| 164 /** |
| 165 * Converts a field mask from a Proto3 JSON string, that is splitting the path
s along commas and |
| 166 * converting from camel case to snake case. |
| 167 */ |
| 168 public static FieldMask fromJsonString(String value) { |
| 169 Iterable<String> paths = Splitter.on(FIELD_PATH_SEPARATOR).split(value); |
| 170 FieldMask.Builder builder = FieldMask.newBuilder(); |
| 171 for (String path : paths) { |
| 172 if (path.isEmpty()) { |
| 173 continue; |
| 174 } |
| 175 builder.addPaths(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, pa
th)); |
| 176 } |
| 177 return builder.build(); |
| 178 } |
| 179 |
| 180 /** |
| 149 * Checks whether paths in a given fields mask are valid. | 181 * Checks whether paths in a given fields mask are valid. |
| 150 */ | 182 */ |
| 151 public static boolean isValid(Class<? extends Message> type, FieldMask fieldMa
sk) { | 183 public static boolean isValid(Class<? extends Message> type, FieldMask fieldMa
sk) { |
| 152 Descriptor descriptor = | 184 Descriptor descriptor = Internal.getDefaultInstance(type).getDescriptorForTy
pe(); |
| 153 Internal.getDefaultInstance(type).getDescriptorForType(); | 185 |
| 154 | |
| 155 return isValid(descriptor, fieldMask); | 186 return isValid(descriptor, fieldMask); |
| 156 } | 187 } |
| 157 | 188 |
| 158 /** | 189 /** |
| 159 * Checks whether paths in a given fields mask are valid. | 190 * Checks whether paths in a given fields mask are valid. |
| 160 */ | 191 */ |
| 161 public static boolean isValid(Descriptor descriptor, FieldMask fieldMask) { | 192 public static boolean isValid(Descriptor descriptor, FieldMask fieldMask) { |
| 162 for (String path : fieldMask.getPathsList()) { | 193 for (String path : fieldMask.getPathsList()) { |
| 163 if (!isValid(descriptor, path)) { | 194 if (!isValid(descriptor, path)) { |
| 164 return false; | 195 return false; |
| 165 } | 196 } |
| 166 } | 197 } |
| 167 return true; | 198 return true; |
| 168 } | 199 } |
| 169 | 200 |
| 170 /** | 201 /** |
| 171 * Checks whether a given field path is valid. | 202 * Checks whether a given field path is valid. |
| 172 */ | 203 */ |
| 173 public static boolean isValid(Class<? extends Message> type, String path) { | 204 public static boolean isValid(Class<? extends Message> type, String path) { |
| 174 Descriptor descriptor = | 205 Descriptor descriptor = Internal.getDefaultInstance(type).getDescriptorForTy
pe(); |
| 175 Internal.getDefaultInstance(type).getDescriptorForType(); | 206 |
| 176 | |
| 177 return isValid(descriptor, path); | 207 return isValid(descriptor, path); |
| 178 } | 208 } |
| 179 | 209 |
| 180 /** | 210 /** |
| 181 * Checks whether paths in a given fields mask are valid. | 211 * Checks whether paths in a given fields mask are valid. |
| 182 */ | 212 */ |
| 183 public static boolean isValid(Descriptor descriptor, String path) { | 213 public static boolean isValid(Descriptor descriptor, String path) { |
| 184 String[] parts = path.split(FIELD_SEPARATOR_REGEX); | 214 String[] parts = path.split(FIELD_SEPARATOR_REGEX); |
| 185 if (parts.length == 0) { | 215 if (parts.length == 0) { |
| 186 return false; | 216 return false; |
| 187 } | 217 } |
| 188 for (String name : parts) { | 218 for (String name : parts) { |
| 189 if (descriptor == null) { | 219 if (descriptor == null) { |
| 190 return false; | 220 return false; |
| 191 } | 221 } |
| 192 FieldDescriptor field = descriptor.findFieldByName(name); | 222 FieldDescriptor field = descriptor.findFieldByName(name); |
| 193 if (field == null) { | 223 if (field == null) { |
| 194 return false; | 224 return false; |
| 195 } | 225 } |
| 196 if (!field.isRepeated() | 226 if (!field.isRepeated() && field.getJavaType() == FieldDescriptor.JavaType
.MESSAGE) { |
| 197 && field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { | |
| 198 descriptor = field.getMessageType(); | 227 descriptor = field.getMessageType(); |
| 199 } else { | 228 } else { |
| 200 descriptor = null; | 229 descriptor = null; |
| 201 } | 230 } |
| 202 } | 231 } |
| 203 return true; | 232 return true; |
| 204 } | 233 } |
| 205 | 234 |
| 206 /** | 235 /** |
| 207 * Converts a FieldMask to its canonical form. In the canonical form of a | 236 * Converts a FieldMask to its canonical form. In the canonical form of a |
| 208 * FieldMask, all field paths are sorted alphabetically and redundant field | 237 * FieldMask, all field paths are sorted alphabetically and redundant field |
| 209 * paths are moved. | 238 * paths are moved. |
| 210 */ | 239 */ |
| 211 public static FieldMask normalize(FieldMask mask) { | 240 public static FieldMask normalize(FieldMask mask) { |
| 212 return new FieldMaskTree(mask).toFieldMask(); | 241 return new FieldMaskTree(mask).toFieldMask(); |
| 213 } | 242 } |
| 214 | 243 |
| 215 /** | 244 /** |
| (...skipping 28 matching lines...) Expand all Loading... |
| 244 private boolean replaceRepeatedFields = false; | 273 private boolean replaceRepeatedFields = false; |
| 245 // TODO(b/28277137): change the default behavior to always replace primitive
fields after | 274 // TODO(b/28277137): change the default behavior to always replace primitive
fields after |
| 246 // fixing all failing TAP tests. | 275 // fixing all failing TAP tests. |
| 247 private boolean replacePrimitiveFields = false; | 276 private boolean replacePrimitiveFields = false; |
| 248 | 277 |
| 249 /** | 278 /** |
| 250 * Whether to replace message fields (i.e., discard existing content in | 279 * Whether to replace message fields (i.e., discard existing content in |
| 251 * destination message fields) when merging. | 280 * destination message fields) when merging. |
| 252 * Default behavior is to merge the source message field into the | 281 * Default behavior is to merge the source message field into the |
| 253 * destination message field. | 282 * destination message field. |
| 254 */ | 283 */ |
| 255 public boolean replaceMessageFields() { | 284 public boolean replaceMessageFields() { |
| 256 return replaceMessageFields; | 285 return replaceMessageFields; |
| 257 } | 286 } |
| 258 | 287 |
| 259 /** | 288 /** |
| 260 * Whether to replace repeated fields (i.e., discard existing content in | 289 * Whether to replace repeated fields (i.e., discard existing content in |
| 261 * destination repeated fields) when merging. | 290 * destination repeated fields) when merging. |
| 262 * Default behavior is to append elements from source repeated field to the | 291 * Default behavior is to append elements from source repeated field to the |
| 263 * destination repeated field. | 292 * destination repeated field. |
| 264 */ | 293 */ |
| (...skipping 27 matching lines...) Expand all Loading... |
| 292 | 321 |
| 293 public void setReplacePrimitiveFields(boolean value) { | 322 public void setReplacePrimitiveFields(boolean value) { |
| 294 replacePrimitiveFields = value; | 323 replacePrimitiveFields = value; |
| 295 } | 324 } |
| 296 } | 325 } |
| 297 | 326 |
| 298 /** | 327 /** |
| 299 * Merges fields specified by a FieldMask from one message to another with the | 328 * Merges fields specified by a FieldMask from one message to another with the |
| 300 * specified merge options. | 329 * specified merge options. |
| 301 */ | 330 */ |
| 302 public static void merge(FieldMask mask, Message source, | 331 public static void merge( |
| 303 Message.Builder destination, MergeOptions options) { | 332 FieldMask mask, Message source, Message.Builder destination, MergeOptions
options) { |
| 304 new FieldMaskTree(mask).merge(source, destination, options); | 333 new FieldMaskTree(mask).merge(source, destination, options); |
| 305 } | 334 } |
| 306 | 335 |
| 307 /** | 336 /** |
| 308 * Merges fields specified by a FieldMask from one message to another. | 337 * Merges fields specified by a FieldMask from one message to another. |
| 309 */ | 338 */ |
| 310 public static void merge(FieldMask mask, Message source, | 339 public static void merge(FieldMask mask, Message source, Message.Builder desti
nation) { |
| 311 Message.Builder destination) { | |
| 312 merge(mask, source, destination, new MergeOptions()); | 340 merge(mask, source, destination, new MergeOptions()); |
| 313 } | 341 } |
| 314 } | 342 } |
| OLD | NEW |