2525import java .util .List ;
2626import javax .annotation .Nullable ;
2727
28- /**
29- * Utility helper functions to work with {@link com.google.protobuf.FieldMask}.
30- */
28+ /** Utility helper functions to work with {@link com.google.protobuf.FieldMask}. */
3129public final class FieldMaskUtil {
3230 private static final String FIELD_PATH_SEPARATOR = "," ;
3331 private static final String FIELD_PATH_SEPARATOR_REGEX = "," ;
3432 private static final String FIELD_SEPARATOR_REGEX = "\\ ." ;
3533
3634 private FieldMaskUtil () {}
3735
38- /**
39- * Converts a FieldMask to a string.
40- */
36+ /** Converts a FieldMask to a string. */
4137 public static String toString (FieldMask fieldMask ) {
4238 // TODO: Consider using com.google.common.base.Joiner here instead.
4339 StringBuilder result = new StringBuilder ();
@@ -57,9 +53,7 @@ public static String toString(FieldMask fieldMask) {
5753 return result .toString ();
5854 }
5955
60- /**
61- * Parses from a string to a FieldMask.
62- */
56+ /** Parses from a string to a FieldMask. */
6357 public static FieldMask fromString (String value ) {
6458 // TODO: Consider using com.google.common.base.Splitter here instead.
6559 return fromStringList (Arrays .asList (value .split (FIELD_PATH_SEPARATOR_REGEX )));
@@ -145,8 +139,8 @@ public static FieldMask fromFieldNumbers(
145139 }
146140
147141 /**
148- * Converts a field mask to a Proto3 JSON string, that is converting from snake case to camel
149- * case and joining all paths into one string with commas.
142+ * Converts a field mask to a Proto3 JSON string, that is converting from snake case to camel case
143+ * and joining all paths into one string with commas.
150144 */
151145 public static String toJsonString (FieldMask fieldMask ) {
152146 List <String > paths = new ArrayList <String >(fieldMask .getPathsCount ());
@@ -175,18 +169,14 @@ public static FieldMask fromJsonString(String value) {
175169 return builder .build ();
176170 }
177171
178- /**
179- * Checks whether paths in a given fields mask are valid.
180- */
172+ /** Checks whether paths in a given fields mask are valid. */
181173 public static boolean isValid (Class <? extends Message > type , FieldMask fieldMask ) {
182174 Descriptor descriptor = Internal .getDefaultInstance (type ).getDescriptorForType ();
183175
184176 return isValid (descriptor , fieldMask );
185177 }
186178
187- /**
188- * Checks whether paths in a given fields mask are valid.
189- */
179+ /** Checks whether paths in a given fields mask are valid. */
190180 public static boolean isValid (Descriptor descriptor , FieldMask fieldMask ) {
191181 for (String path : fieldMask .getPathsList ()) {
192182 if (!isValid (descriptor , path )) {
@@ -196,9 +186,7 @@ public static boolean isValid(Descriptor descriptor, FieldMask fieldMask) {
196186 return true ;
197187 }
198188
199- /**
200- * Checks whether a given field path is valid.
201- */
189+ /** Checks whether a given field path is valid. */
202190 public static boolean isValid (Class <? extends Message > type , String path ) {
203191 Descriptor descriptor = Internal .getDefaultInstance (type ).getDescriptorForType ();
204192
@@ -229,17 +217,14 @@ public static boolean isValid(@Nullable Descriptor descriptor, String path) {
229217 }
230218
231219 /**
232- * Converts a FieldMask to its canonical form. In the canonical form of a
233- * FieldMask, all field paths are sorted alphabetically and redundant field
234- * paths are removed.
220+ * Converts a FieldMask to its canonical form. In the canonical form of a FieldMask, all field
221+ * paths are sorted alphabetically and redundant field paths are removed.
235222 */
236223 public static FieldMask normalize (FieldMask mask ) {
237224 return new FieldMaskTree (mask ).toFieldMask ();
238225 }
239226
240- /**
241- * Creates a union of two or more FieldMasks.
242- */
227+ /** Creates a union of two or more FieldMasks. */
243228 public static FieldMask union (
244229 FieldMask firstMask , FieldMask secondMask , FieldMask ... otherMasks ) {
245230 FieldMaskTree maskTree = new FieldMaskTree (firstMask ).mergeFromFieldMask (secondMask );
@@ -265,9 +250,7 @@ public static FieldMask subtract(
265250 return maskTree .toFieldMask ();
266251 }
267252
268- /**
269- * Calculates the intersection of two FieldMasks.
270- */
253+ /** Calculates the intersection of two FieldMasks. */
271254 public static FieldMask intersection (FieldMask mask1 , FieldMask mask2 ) {
272255 FieldMaskTree tree = new FieldMaskTree (mask1 );
273256 FieldMaskTree result = new FieldMaskTree ();
@@ -277,9 +260,7 @@ public static FieldMask intersection(FieldMask mask1, FieldMask mask2) {
277260 return result .toFieldMask ();
278261 }
279262
280- /**
281- * Options to customize merging behavior.
282- */
263+ /** Options to customize merging behavior. */
283264 public static final class MergeOptions {
284265 private boolean replaceMessageFields = false ;
285266 private boolean replaceRepeatedFields = false ;
@@ -288,25 +269,25 @@ public static final class MergeOptions {
288269 private boolean replacePrimitiveFields = false ;
289270
290271 /**
291- * Whether to replace message fields (i.e., discard existing content in
292- * destination message fields).
272+ * Whether to replace message fields (i.e., discard existing content in destination message
273+ * fields).
293274 */
294275 public boolean replaceMessageFields () {
295276 return replaceMessageFields ;
296277 }
297278
298279 /**
299- * Whether to replace repeated fields (i.e., discard existing content in
300- * destination repeated fields).
280+ * Whether to replace repeated fields (i.e., discard existing content in destination repeated
281+ * fields).
301282 */
302283 public boolean replaceRepeatedFields () {
303284 return replaceRepeatedFields ;
304285 }
305286
306287 /**
307- * Whether to replace primitive (non-repeated and non-message) fields in
308- * destination message fields with the source primitive fields (i.e., clear
309- * destination field if source field is not set).
288+ * Whether to replace primitive (non-repeated and non-message) fields in destination message
289+ * fields with the source primitive fields (i.e., clear destination field if source field is not
290+ * set).
310291 */
311292 public boolean replacePrimitiveFields () {
312293 return replacePrimitiveFields ;
@@ -365,20 +346,66 @@ public static void merge(
365346 new FieldMaskTree (mask ).merge (source , destination , options );
366347 }
367348
368- /**
369- * Merges fields specified by a FieldMask from one message to another.
370- */
349+ /** Merges fields specified by a FieldMask from one message to another. */
371350 public static void merge (FieldMask mask , Message source , Message .Builder destination ) {
372351 merge (mask , source , destination , new MergeOptions ());
373352 }
374353
354+ /** Options to customize trimming behavior. */
355+ public static final class TrimOptions {
356+ private boolean retainPrimitiveFieldUnsetState = false ;
357+
358+ /** Whether the unset state of primitive fields should be retained when trimming. */
359+ public boolean retainPrimitiveFieldUnsetState () {
360+ return retainPrimitiveFieldUnsetState ;
361+ }
362+
363+ /**
364+ * Specify whether the unset state of primitive fields should be retained when trimming.
365+ * Defaults to false.
366+ *
367+ * <p>If true, unset primitive fields indicated by the field mask will remain unset.
368+ *
369+ * <p>If false, unset primitive fields indicated by the field mask will be set to their default
370+ * values.
371+ */
372+ @ CanIgnoreReturnValue
373+ public TrimOptions setRetainPrimitiveFieldUnsetState (boolean value ) {
374+ retainPrimitiveFieldUnsetState = value ;
375+ return this ;
376+ }
377+ }
378+
375379 /**
376- * Returns the result of keeping only the masked fields of the given proto.
380+ * Returns the result of keeping only the masked fields of the given proto with the specified trim
381+ * options.
382+ *
383+ * <p>Note that the behavior with the default {@link TrimOptions} is for unset primitive fields
384+ * indicated in the field mask to be explicitly set to their default values. Use {@code new
385+ * TrimOptions().setRetainPrimitiveFieldUnsetState(true)} to retain the unset state of primitive
386+ * fields.
377387 */
378388 @ SuppressWarnings ("unchecked" )
379- public static <P extends Message > P trim (FieldMask mask , P source ) {
380- Message .Builder destination = source .newBuilderForType ();
381- merge (mask , source , destination );
389+ public static <P extends Message > P trim (FieldMask mask , P source , TrimOptions options ) {
390+ Message .Builder destination = source .newBuilderForType ();
391+ merge (
392+ mask ,
393+ source ,
394+ destination ,
395+ new MergeOptions ().setReplacePrimitiveFields (options .retainPrimitiveFieldUnsetState ()));
382396 return (P ) destination .build ();
383397 }
398+
399+ /**
400+ * Returns the result of keeping only the masked fields of the given proto.
401+ *
402+ * <p>This method is equivalent to {@link #trim(FieldMask, Message, TrimOptions)} with default
403+ * {@link TrimOptions}.
404+ *
405+ * <p>Note that unset primitive fields indicated in the field mask will be explicitly set to their
406+ * default values.
407+ */
408+ public static <P extends Message > P trim (FieldMask mask , P source ) {
409+ return trim (mask , source , new TrimOptions ());
410+ }
384411}
0 commit comments