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 static com.google.common.base.Preconditions.checkArgument; 34 35 import com.google.common.primitives.Ints; 36 import com.google.protobuf.Descriptors.Descriptor; 37 import com.google.protobuf.Descriptors.FieldDescriptor; 38 import com.google.protobuf.FieldMask; 39 import com.google.protobuf.Internal; 40 import com.google.protobuf.Message; 41 42 import java.util.Arrays; 43 44 /** 45 * Utility helper functions to work with {@link com.google.protobuf.FieldMask}. 46 */ 47 public class FieldMaskUtil { 48 private static final String FIELD_PATH_SEPARATOR = ","; 49 private static final String FIELD_PATH_SEPARATOR_REGEX = ","; 50 private static final String FIELD_SEPARATOR_REGEX = "\\."; 51 FieldMaskUtil()52 private FieldMaskUtil() {} 53 54 /** 55 * Converts a FieldMask to a string. 56 */ toString(FieldMask fieldMask)57 public static String toString(FieldMask fieldMask) { 58 // TODO(xiaofeng): Consider using com.google.common.base.Joiner here instead. 59 StringBuilder result = new StringBuilder(); 60 boolean first = true; 61 for (String value : fieldMask.getPathsList()) { 62 if (value.isEmpty()) { 63 // Ignore empty paths. 64 continue; 65 } 66 if (first) { 67 first = false; 68 } else { 69 result.append(FIELD_PATH_SEPARATOR); 70 } 71 result.append(value); 72 } 73 return result.toString(); 74 } 75 76 /** 77 * Parses from a string to a FieldMask. 78 */ fromString(String value)79 public static FieldMask fromString(String value) { 80 // TODO(xiaofeng): Consider using com.google.common.base.Splitter here instead. 81 return fromStringList( 82 null, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX))); 83 } 84 85 /** 86 * Parses from a string to a FieldMask and validates all field paths. 87 * 88 * @throws IllegalArgumentException if any of the field path is invalid. 89 */ fromString(Class<? extends Message> type, String value)90 public static FieldMask fromString(Class<? extends Message> type, String value) { 91 // TODO(xiaofeng): Consider using com.google.common.base.Splitter here instead. 92 return fromStringList( 93 type, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX))); 94 } 95 96 /** 97 * Constructs a FieldMask for a list of field paths in a certain type. 98 * 99 * @throws IllegalArgumentException if any of the field path is not valid. 100 */ 101 // TODO(xiaofeng): Consider renaming fromStrings() fromStringList( Class<? extends Message> type, Iterable<String> paths)102 public static FieldMask fromStringList( 103 Class<? extends Message> type, Iterable<String> paths) { 104 FieldMask.Builder builder = FieldMask.newBuilder(); 105 for (String path : paths) { 106 if (path.isEmpty()) { 107 // Ignore empty field paths. 108 continue; 109 } 110 if (type != null && !isValid(type, path)) { 111 throw new IllegalArgumentException( 112 path + " is not a valid path for " + type); 113 } 114 builder.addPaths(path); 115 } 116 return builder.build(); 117 } 118 119 /** 120 * Constructs a FieldMask from the passed field numbers. 121 * 122 * @throws IllegalArgumentException if any of the fields are invalid for the message. 123 */ fromFieldNumbers(Class<? extends Message> type, int... fieldNumbers)124 public static FieldMask fromFieldNumbers(Class<? extends Message> type, int... fieldNumbers) { 125 return fromFieldNumbers(type, Ints.asList(fieldNumbers)); 126 } 127 128 /** 129 * Constructs a FieldMask from the passed field numbers. 130 * 131 * @throws IllegalArgumentException if any of the fields are invalid for the message. 132 */ fromFieldNumbers( Class<? extends Message> type, Iterable<Integer> fieldNumbers)133 public static FieldMask fromFieldNumbers( 134 Class<? extends Message> type, Iterable<Integer> fieldNumbers) { 135 Descriptor descriptor = Internal.getDefaultInstance(type).getDescriptorForType(); 136 137 FieldMask.Builder builder = FieldMask.newBuilder(); 138 for (Integer fieldNumber : fieldNumbers) { 139 FieldDescriptor field = descriptor.findFieldByNumber(fieldNumber); 140 checkArgument( 141 field != null, 142 String.format("%s is not a valid field number for %s.", fieldNumber, type)); 143 builder.addPaths(field.getName()); 144 } 145 return builder.build(); 146 } 147 148 /** 149 * Checks whether paths in a given fields mask are valid. 150 */ isValid(Class<? extends Message> type, FieldMask fieldMask)151 public static boolean isValid(Class<? extends Message> type, FieldMask fieldMask) { 152 Descriptor descriptor = 153 Internal.getDefaultInstance(type).getDescriptorForType(); 154 155 return isValid(descriptor, fieldMask); 156 } 157 158 /** 159 * Checks whether paths in a given fields mask are valid. 160 */ isValid(Descriptor descriptor, FieldMask fieldMask)161 public static boolean isValid(Descriptor descriptor, FieldMask fieldMask) { 162 for (String path : fieldMask.getPathsList()) { 163 if (!isValid(descriptor, path)) { 164 return false; 165 } 166 } 167 return true; 168 } 169 170 /** 171 * Checks whether a given field path is valid. 172 */ isValid(Class<? extends Message> type, String path)173 public static boolean isValid(Class<? extends Message> type, String path) { 174 Descriptor descriptor = 175 Internal.getDefaultInstance(type).getDescriptorForType(); 176 177 return isValid(descriptor, path); 178 } 179 180 /** 181 * Checks whether paths in a given fields mask are valid. 182 */ isValid(Descriptor descriptor, String path)183 public static boolean isValid(Descriptor descriptor, String path) { 184 String[] parts = path.split(FIELD_SEPARATOR_REGEX); 185 if (parts.length == 0) { 186 return false; 187 } 188 for (String name : parts) { 189 if (descriptor == null) { 190 return false; 191 } 192 FieldDescriptor field = descriptor.findFieldByName(name); 193 if (field == null) { 194 return false; 195 } 196 if (!field.isRepeated() 197 && field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { 198 descriptor = field.getMessageType(); 199 } else { 200 descriptor = null; 201 } 202 } 203 return true; 204 } 205 206 /** 207 * Converts a FieldMask to its canonical form. In the canonical form of a 208 * FieldMask, all field paths are sorted alphabetically and redundant field 209 * paths are moved. 210 */ normalize(FieldMask mask)211 public static FieldMask normalize(FieldMask mask) { 212 return new FieldMaskTree(mask).toFieldMask(); 213 } 214 215 /** 216 * Creates a union of two or more FieldMasks. 217 */ union( FieldMask firstMask, FieldMask secondMask, FieldMask... otherMasks)218 public static FieldMask union( 219 FieldMask firstMask, FieldMask secondMask, FieldMask... otherMasks) { 220 FieldMaskTree maskTree = new FieldMaskTree(firstMask).mergeFromFieldMask(secondMask); 221 for (FieldMask mask : otherMasks) { 222 maskTree.mergeFromFieldMask(mask); 223 } 224 return maskTree.toFieldMask(); 225 } 226 227 /** 228 * Calculates the intersection of two FieldMasks. 229 */ intersection(FieldMask mask1, FieldMask mask2)230 public static FieldMask intersection(FieldMask mask1, FieldMask mask2) { 231 FieldMaskTree tree = new FieldMaskTree(mask1); 232 FieldMaskTree result = new FieldMaskTree(); 233 for (String path : mask2.getPathsList()) { 234 tree.intersectFieldPath(path, result); 235 } 236 return result.toFieldMask(); 237 } 238 239 /** 240 * Options to customize merging behavior. 241 */ 242 public static final class MergeOptions { 243 private boolean replaceMessageFields = false; 244 private boolean replaceRepeatedFields = false; 245 // TODO(b/28277137): change the default behavior to always replace primitive fields after 246 // fixing all failing TAP tests. 247 private boolean replacePrimitiveFields = false; 248 249 /** 250 * Whether to replace message fields (i.e., discard existing content in 251 * destination message fields) when merging. 252 * Default behavior is to merge the source message field into the 253 * destination message field. 254 */ replaceMessageFields()255 public boolean replaceMessageFields() { 256 return replaceMessageFields; 257 } 258 259 /** 260 * Whether to replace repeated fields (i.e., discard existing content in 261 * destination repeated fields) when merging. 262 * Default behavior is to append elements from source repeated field to the 263 * destination repeated field. 264 */ replaceRepeatedFields()265 public boolean replaceRepeatedFields() { 266 return replaceRepeatedFields; 267 } 268 269 /** 270 * Whether to replace primitive (non-repeated and non-message) fields in 271 * destination message fields with the source primitive fields (i.e., if the 272 * field is set in the source, the value is copied to the 273 * destination; if the field is unset in the source, the field is cleared 274 * from the destination) when merging. 275 * 276 * <p>Default behavior is to always set the value of the source primitive 277 * field to the destination primitive field, and if the source field is 278 * unset, the default value of the source field is copied to the 279 * destination. 280 */ replacePrimitiveFields()281 public boolean replacePrimitiveFields() { 282 return replacePrimitiveFields; 283 } 284 setReplaceMessageFields(boolean value)285 public void setReplaceMessageFields(boolean value) { 286 replaceMessageFields = value; 287 } 288 setReplaceRepeatedFields(boolean value)289 public void setReplaceRepeatedFields(boolean value) { 290 replaceRepeatedFields = value; 291 } 292 setReplacePrimitiveFields(boolean value)293 public void setReplacePrimitiveFields(boolean value) { 294 replacePrimitiveFields = value; 295 } 296 } 297 298 /** 299 * Merges fields specified by a FieldMask from one message to another with the 300 * specified merge options. 301 */ merge(FieldMask mask, Message source, Message.Builder destination, MergeOptions options)302 public static void merge(FieldMask mask, Message source, 303 Message.Builder destination, MergeOptions options) { 304 new FieldMaskTree(mask).merge(source, destination, options); 305 } 306 307 /** 308 * Merges fields specified by a FieldMask from one message to another. 309 */ merge(FieldMask mask, Message source, Message.Builder destination)310 public static void merge(FieldMask mask, Message source, 311 Message.Builder destination) { 312 merge(mask, source, destination, new MergeOptions()); 313 } 314 } 315