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 |