• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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