Skip to content

Commit 84c5b63

Browse files
Add a FieldMaskUtil#trim overload that accepts TrimOptions and allows retaining unset primitive field state.
Existing FieldMaskUtil#trim method behavior remains the same: unset primitive fields specified in the field mask will be explicitly set to their default values after trimming. PiperOrigin-RevId: 754114689
1 parent 5323651 commit 84c5b63

File tree

2 files changed

+197
-88
lines changed

2 files changed

+197
-88
lines changed

java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java

Lines changed: 73 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,15 @@
2525
import java.util.List;
2626
import 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}. */
3129
public 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

Comments
 (0)