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 27 matching lines...) Expand all Loading... |
38 namespace util { | 38 namespace util { |
39 | 39 |
40 using google::protobuf::FieldMask; | 40 using google::protobuf::FieldMask; |
41 | 41 |
42 string FieldMaskUtil::ToString(const FieldMask& mask) { | 42 string FieldMaskUtil::ToString(const FieldMask& mask) { |
43 return Join(mask.paths(), ","); | 43 return Join(mask.paths(), ","); |
44 } | 44 } |
45 | 45 |
46 void FieldMaskUtil::FromString(StringPiece str, FieldMask* out) { | 46 void FieldMaskUtil::FromString(StringPiece str, FieldMask* out) { |
47 out->Clear(); | 47 out->Clear(); |
48 std::vector<string> paths = Split(str, ","); | 48 vector<string> paths = Split(str, ","); |
49 for (int i = 0; i < paths.size(); ++i) { | 49 for (int i = 0; i < paths.size(); ++i) { |
50 if (paths[i].empty()) continue; | 50 if (paths[i].empty()) continue; |
51 out->add_paths(paths[i]); | 51 out->add_paths(paths[i]); |
52 } | 52 } |
53 } | 53 } |
54 | 54 |
55 bool FieldMaskUtil::SnakeCaseToCamelCase(StringPiece input, string* output) { | 55 bool FieldMaskUtil::SnakeCaseToCamelCase(StringPiece input, string* output) { |
56 output->clear(); | 56 output->clear(); |
57 bool after_underscore = false; | 57 bool after_underscore = false; |
58 for (int i = 0; i < input.size(); ++i) { | 58 for (int i = 0; i < input.size(); ++i) { |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
109 if (i > 0) { | 109 if (i > 0) { |
110 out->push_back(','); | 110 out->push_back(','); |
111 } | 111 } |
112 out->append(camelcase_path); | 112 out->append(camelcase_path); |
113 } | 113 } |
114 return true; | 114 return true; |
115 } | 115 } |
116 | 116 |
117 bool FieldMaskUtil::FromJsonString(StringPiece str, FieldMask* out) { | 117 bool FieldMaskUtil::FromJsonString(StringPiece str, FieldMask* out) { |
118 out->Clear(); | 118 out->Clear(); |
119 std::vector<string> paths = Split(str, ","); | 119 vector<string> paths = Split(str, ","); |
120 for (int i = 0; i < paths.size(); ++i) { | 120 for (int i = 0; i < paths.size(); ++i) { |
121 if (paths[i].empty()) continue; | 121 if (paths[i].empty()) continue; |
122 string snakecase_path; | 122 string snakecase_path; |
123 if (!CamelCaseToSnakeCase(paths[i], &snakecase_path)) { | 123 if (!CamelCaseToSnakeCase(paths[i], &snakecase_path)) { |
124 return false; | 124 return false; |
125 } | 125 } |
126 out->add_paths(snakecase_path); | 126 out->add_paths(snakecase_path); |
127 } | 127 } |
128 return true; | 128 return true; |
129 } | 129 } |
130 | 130 |
131 bool FieldMaskUtil::GetFieldDescriptors( | 131 bool FieldMaskUtil::InternalIsValidPath(const Descriptor* descriptor, |
132 const Descriptor* descriptor, StringPiece path, | 132 StringPiece path) { |
133 std::vector<const FieldDescriptor*>* field_descriptors) { | 133 vector<string> parts = Split(path, "."); |
134 if (field_descriptors != NULL) { | |
135 field_descriptors->clear(); | |
136 } | |
137 std::vector<string> parts = Split(path, "."); | |
138 for (int i = 0; i < parts.size(); ++i) { | 134 for (int i = 0; i < parts.size(); ++i) { |
139 const string& field_name = parts[i]; | 135 const string& field_name = parts[i]; |
140 if (descriptor == NULL) { | 136 if (descriptor == NULL) { |
141 return false; | 137 return false; |
142 } | 138 } |
143 const FieldDescriptor* field = descriptor->FindFieldByName(field_name); | 139 const FieldDescriptor* field = descriptor->FindFieldByName(field_name); |
144 if (field == NULL) { | 140 if (field == NULL) { |
145 return false; | 141 return false; |
146 } | 142 } |
147 if (field_descriptors != NULL) { | |
148 field_descriptors->push_back(field); | |
149 } | |
150 if (!field->is_repeated() && | 143 if (!field->is_repeated() && |
151 field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { | 144 field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { |
152 descriptor = field->message_type(); | 145 descriptor = field->message_type(); |
153 } else { | 146 } else { |
154 descriptor = NULL; | 147 descriptor = NULL; |
155 } | 148 } |
156 } | 149 } |
157 return true; | 150 return true; |
158 } | 151 } |
159 | 152 |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
200 void MergeMessage(const Message& source, | 193 void MergeMessage(const Message& source, |
201 const FieldMaskUtil::MergeOptions& options, | 194 const FieldMaskUtil::MergeOptions& options, |
202 Message* destination) { | 195 Message* destination) { |
203 // Do nothing if the tree is empty. | 196 // Do nothing if the tree is empty. |
204 if (root_.children.empty()) { | 197 if (root_.children.empty()) { |
205 return; | 198 return; |
206 } | 199 } |
207 MergeMessage(&root_, source, options, destination); | 200 MergeMessage(&root_, source, options, destination); |
208 } | 201 } |
209 | 202 |
210 // Trims all fields not specified by this tree from the given message. | |
211 void TrimMessage(Message* message) { | |
212 // Do nothing if the tree is empty. | |
213 if (root_.children.empty()) { | |
214 return; | |
215 } | |
216 TrimMessage(&root_, message); | |
217 } | |
218 | |
219 private: | 203 private: |
220 struct Node { | 204 struct Node { |
221 Node() {} | 205 Node() {} |
222 | 206 |
223 ~Node() { ClearChildren(); } | 207 ~Node() { ClearChildren(); } |
224 | 208 |
225 void ClearChildren() { | 209 void ClearChildren() { |
226 for (std::map<string, Node*>::iterator it = children.begin(); | 210 for (map<string, Node*>::iterator it = children.begin(); |
227 it != children.end(); ++it) { | 211 it != children.end(); ++it) { |
228 delete it->second; | 212 delete it->second; |
229 } | 213 } |
230 children.clear(); | 214 children.clear(); |
231 } | 215 } |
232 | 216 |
233 std::map<string, Node*> children; | 217 map<string, Node*> children; |
234 | 218 |
235 private: | 219 private: |
236 GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(Node); | 220 GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(Node); |
237 }; | 221 }; |
238 | 222 |
239 // Merge a sub-tree to mask. This method adds the field paths represented | 223 // Merge a sub-tree to mask. This method adds the field paths represented |
240 // by all leaf nodes descended from "node" to mask. | 224 // by all leaf nodes descended from "node" to mask. |
241 void MergeToFieldMask(const string& prefix, const Node* node, FieldMask* out); | 225 void MergeToFieldMask(const string& prefix, const Node* node, FieldMask* out); |
242 | 226 |
243 // Merge all leaf nodes of a sub-tree to another tree. | 227 // Merge all leaf nodes of a sub-tree to another tree. |
244 void MergeLeafNodesToTree(const string& prefix, const Node* node, | 228 void MergeLeafNodesToTree(const string& prefix, const Node* node, |
245 FieldMaskTree* out); | 229 FieldMaskTree* out); |
246 | 230 |
247 // Merge all fields specified by a sub-tree from one message to another. | 231 // Merge all fields specified by a sub-tree from one message to another. |
248 void MergeMessage(const Node* node, const Message& source, | 232 void MergeMessage(const Node* node, const Message& source, |
249 const FieldMaskUtil::MergeOptions& options, | 233 const FieldMaskUtil::MergeOptions& options, |
250 Message* destination); | 234 Message* destination); |
251 | 235 |
252 // Trims all fields not specified by this sub-tree from the given message. | |
253 void TrimMessage(const Node* node, Message* message); | |
254 | |
255 Node root_; | 236 Node root_; |
256 | 237 |
257 GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(FieldMaskTree); | 238 GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(FieldMaskTree); |
258 }; | 239 }; |
259 | 240 |
260 FieldMaskTree::FieldMaskTree() {} | 241 FieldMaskTree::FieldMaskTree() {} |
261 | 242 |
262 FieldMaskTree::~FieldMaskTree() {} | 243 FieldMaskTree::~FieldMaskTree() {} |
263 | 244 |
264 void FieldMaskTree::MergeFromFieldMask(const FieldMask& mask) { | 245 void FieldMaskTree::MergeFromFieldMask(const FieldMask& mask) { |
265 for (int i = 0; i < mask.paths_size(); ++i) { | 246 for (int i = 0; i < mask.paths_size(); ++i) { |
266 AddPath(mask.paths(i)); | 247 AddPath(mask.paths(i)); |
267 } | 248 } |
268 } | 249 } |
269 | 250 |
270 void FieldMaskTree::MergeToFieldMask(FieldMask* mask) { | 251 void FieldMaskTree::MergeToFieldMask(FieldMask* mask) { |
271 MergeToFieldMask("", &root_, mask); | 252 MergeToFieldMask("", &root_, mask); |
272 } | 253 } |
273 | 254 |
274 void FieldMaskTree::MergeToFieldMask(const string& prefix, const Node* node, | 255 void FieldMaskTree::MergeToFieldMask(const string& prefix, const Node* node, |
275 FieldMask* out) { | 256 FieldMask* out) { |
276 if (node->children.empty()) { | 257 if (node->children.empty()) { |
277 if (prefix.empty()) { | 258 if (prefix.empty()) { |
278 // This is the root node. | 259 // This is the root node. |
279 return; | 260 return; |
280 } | 261 } |
281 out->add_paths(prefix); | 262 out->add_paths(prefix); |
282 return; | 263 return; |
283 } | 264 } |
284 for (std::map<string, Node*>::const_iterator it = node->children.begin(); | 265 for (map<string, Node*>::const_iterator it = node->children.begin(); |
285 it != node->children.end(); ++it) { | 266 it != node->children.end(); ++it) { |
286 string current_path = prefix.empty() ? it->first : prefix + "." + it->first; | 267 string current_path = prefix.empty() ? it->first : prefix + "." + it->first; |
287 MergeToFieldMask(current_path, it->second, out); | 268 MergeToFieldMask(current_path, it->second, out); |
288 } | 269 } |
289 } | 270 } |
290 | 271 |
291 void FieldMaskTree::AddPath(const string& path) { | 272 void FieldMaskTree::AddPath(const string& path) { |
292 std::vector<string> parts = Split(path, "."); | 273 vector<string> parts = Split(path, "."); |
293 if (parts.empty()) { | 274 if (parts.empty()) { |
294 return; | 275 return; |
295 } | 276 } |
296 bool new_branch = false; | 277 bool new_branch = false; |
297 Node* node = &root_; | 278 Node* node = &root_; |
298 for (int i = 0; i < parts.size(); ++i) { | 279 for (int i = 0; i < parts.size(); ++i) { |
299 if (!new_branch && node != &root_ && node->children.empty()) { | 280 if (!new_branch && node != &root_ && node->children.empty()) { |
300 // Path matches an existing leaf node. This means the path is already | 281 // Path matches an existing leaf node. This means the path is already |
301 // coverred by this tree (for example, adding "foo.bar.baz" to a tree | 282 // coverred by this tree (for example, adding "foo.bar.baz" to a tree |
302 // which already contains "foo.bar"). | 283 // which already contains "foo.bar"). |
303 return; | 284 return; |
304 } | 285 } |
305 const string& node_name = parts[i]; | 286 const string& node_name = parts[i]; |
306 Node*& child = node->children[node_name]; | 287 Node*& child = node->children[node_name]; |
307 if (child == NULL) { | 288 if (child == NULL) { |
308 new_branch = true; | 289 new_branch = true; |
309 child = new Node(); | 290 child = new Node(); |
310 } | 291 } |
311 node = child; | 292 node = child; |
312 } | 293 } |
313 if (!node->children.empty()) { | 294 if (!node->children.empty()) { |
314 node->ClearChildren(); | 295 node->ClearChildren(); |
315 } | 296 } |
316 } | 297 } |
317 | 298 |
318 void FieldMaskTree::IntersectPath(const string& path, FieldMaskTree* out) { | 299 void FieldMaskTree::IntersectPath(const string& path, FieldMaskTree* out) { |
319 std::vector<string> parts = Split(path, "."); | 300 vector<string> parts = Split(path, "."); |
320 if (parts.empty()) { | 301 if (parts.empty()) { |
321 return; | 302 return; |
322 } | 303 } |
323 const Node* node = &root_; | 304 const Node* node = &root_; |
324 for (int i = 0; i < parts.size(); ++i) { | 305 for (int i = 0; i < parts.size(); ++i) { |
325 if (node->children.empty()) { | 306 if (node->children.empty()) { |
326 if (node != &root_) { | 307 if (node != &root_) { |
327 out->AddPath(path); | 308 out->AddPath(path); |
328 } | 309 } |
329 return; | 310 return; |
330 } | 311 } |
331 const string& node_name = parts[i]; | 312 const string& node_name = parts[i]; |
332 const Node* result = FindPtrOrNull(node->children, node_name); | 313 const Node* result = FindPtrOrNull(node->children, node_name); |
333 if (result == NULL) { | 314 if (result == NULL) { |
334 // No intersection found. | 315 // No intersection found. |
335 return; | 316 return; |
336 } | 317 } |
337 node = result; | 318 node = result; |
338 } | 319 } |
339 // Now we found a matching node with the given path. Add all leaf nodes | 320 // Now we found a matching node with the given path. Add all leaf nodes |
340 // to out. | 321 // to out. |
341 MergeLeafNodesToTree(path, node, out); | 322 MergeLeafNodesToTree(path, node, out); |
342 } | 323 } |
343 | 324 |
344 void FieldMaskTree::MergeLeafNodesToTree(const string& prefix, const Node* node, | 325 void FieldMaskTree::MergeLeafNodesToTree(const string& prefix, const Node* node, |
345 FieldMaskTree* out) { | 326 FieldMaskTree* out) { |
346 if (node->children.empty()) { | 327 if (node->children.empty()) { |
347 out->AddPath(prefix); | 328 out->AddPath(prefix); |
348 } | 329 } |
349 for (std::map<string, Node*>::const_iterator it = node->children.begin(); | 330 for (map<string, Node*>::const_iterator it = node->children.begin(); |
350 it != node->children.end(); ++it) { | 331 it != node->children.end(); ++it) { |
351 string current_path = prefix.empty() ? it->first : prefix + "." + it->first; | 332 string current_path = prefix.empty() ? it->first : prefix + "." + it->first; |
352 MergeLeafNodesToTree(current_path, it->second, out); | 333 MergeLeafNodesToTree(current_path, it->second, out); |
353 } | 334 } |
354 } | 335 } |
355 | 336 |
356 void FieldMaskTree::MergeMessage(const Node* node, const Message& source, | 337 void FieldMaskTree::MergeMessage(const Node* node, const Message& source, |
357 const FieldMaskUtil::MergeOptions& options, | 338 const FieldMaskUtil::MergeOptions& options, |
358 Message* destination) { | 339 Message* destination) { |
359 GOOGLE_DCHECK(!node->children.empty()); | 340 GOOGLE_DCHECK(!node->children.empty()); |
360 const Reflection* source_reflection = source.GetReflection(); | 341 const Reflection* source_reflection = source.GetReflection(); |
361 const Reflection* destination_reflection = destination->GetReflection(); | 342 const Reflection* destination_reflection = destination->GetReflection(); |
362 const Descriptor* descriptor = source.GetDescriptor(); | 343 const Descriptor* descriptor = source.GetDescriptor(); |
363 for (std::map<string, Node*>::const_iterator it = node->children.begin(); | 344 for (map<string, Node*>::const_iterator it = node->children.begin(); |
364 it != node->children.end(); ++it) { | 345 it != node->children.end(); ++it) { |
365 const string& field_name = it->first; | 346 const string& field_name = it->first; |
366 const Node* child = it->second; | 347 const Node* child = it->second; |
367 const FieldDescriptor* field = descriptor->FindFieldByName(field_name); | 348 const FieldDescriptor* field = descriptor->FindFieldByName(field_name); |
368 if (field == NULL) { | 349 if (field == NULL) { |
369 GOOGLE_LOG(ERROR) << "Cannot find field \"" << field_name << "\" in messag
e " | 350 GOOGLE_LOG(ERROR) << "Cannot find field \"" << field_name << "\" in messag
e " |
370 << descriptor->full_name(); | 351 << descriptor->full_name(); |
371 continue; | 352 continue; |
372 } | 353 } |
373 if (!child->children.empty()) { | 354 if (!child->children.empty()) { |
374 // Sub-paths are only allowed for singular message fields. | 355 // Sub-paths are only allowed for singular message fields. |
375 if (field->is_repeated() || | 356 if (field->is_repeated() || |
376 field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) { | 357 field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) { |
377 GOOGLE_LOG(ERROR) << "Field \"" << field_name << "\" in message " | 358 GOOGLE_LOG(ERROR) << "Field \"" << field_name << "\" in message " |
378 << descriptor->full_name() | 359 << descriptor->full_name() |
379 << " is not a singular message field and cannot " | 360 << " is not a singular message field and cannot " |
380 << "have sub-fields."; | 361 << "have sub-fields."; |
381 continue; | 362 continue; |
382 } | 363 } |
383 MergeMessage(child, source_reflection->GetMessage(source, field), options, | 364 MergeMessage(child, source_reflection->GetMessage(source, field), options, |
384 destination_reflection->MutableMessage(destination, field)); | 365 destination_reflection->MutableMessage(destination, field)); |
385 continue; | 366 continue; |
386 } | 367 } |
387 if (!field->is_repeated()) { | 368 if (!field->is_repeated()) { |
388 switch (field->cpp_type()) { | 369 switch (field->cpp_type()) { |
389 #define COPY_VALUE(TYPE, Name) \ | 370 #define COPY_VALUE(TYPE, Name) \ |
390 case FieldDescriptor::CPPTYPE_##TYPE: { \ | 371 case FieldDescriptor::CPPTYPE_##TYPE: { \ |
391 if (source_reflection->HasField(source, field)) { \ | 372 destination_reflection->Set##Name( \ |
392 destination_reflection->Set##Name( \ | 373 destination, field, source_reflection->Get##Name(source, field)); \ |
393 destination, field, source_reflection->Get##Name(source, field)); \ | 374 break; \ |
394 } else { \ | |
395 destination_reflection->ClearField(destination, field); \ | |
396 } \ | |
397 break; \ | |
398 } | 375 } |
399 COPY_VALUE(BOOL, Bool) | 376 COPY_VALUE(BOOL, Bool) |
400 COPY_VALUE(INT32, Int32) | 377 COPY_VALUE(INT32, Int32) |
401 COPY_VALUE(INT64, Int64) | 378 COPY_VALUE(INT64, Int64) |
402 COPY_VALUE(UINT32, UInt32) | 379 COPY_VALUE(UINT32, UInt32) |
403 COPY_VALUE(UINT64, UInt64) | 380 COPY_VALUE(UINT64, UInt64) |
404 COPY_VALUE(FLOAT, Float) | 381 COPY_VALUE(FLOAT, Float) |
405 COPY_VALUE(DOUBLE, Double) | 382 COPY_VALUE(DOUBLE, Double) |
406 COPY_VALUE(ENUM, Enum) | 383 COPY_VALUE(ENUM, Enum) |
407 COPY_VALUE(STRING, String) | 384 COPY_VALUE(STRING, String) |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
449 ->MergeFrom( | 426 ->MergeFrom( |
450 source_reflection->GetRepeatedMessage(source, field, i)); | 427 source_reflection->GetRepeatedMessage(source, field, i)); |
451 } | 428 } |
452 break; | 429 break; |
453 } | 430 } |
454 } | 431 } |
455 } | 432 } |
456 } | 433 } |
457 } | 434 } |
458 | 435 |
459 void FieldMaskTree::TrimMessage(const Node* node, Message* message) { | |
460 GOOGLE_DCHECK(!node->children.empty()); | |
461 const Reflection* reflection = message->GetReflection(); | |
462 const Descriptor* descriptor = message->GetDescriptor(); | |
463 const int32 field_count = descriptor->field_count(); | |
464 for (int index = 0; index < field_count; ++index) { | |
465 const FieldDescriptor* field = descriptor->field(index); | |
466 map<string, Node*>::const_iterator it = node->children.find(field->name()); | |
467 if (it == node->children.end()) { | |
468 reflection->ClearField(message, field); | |
469 } else { | |
470 if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { | |
471 Node* child = it->second; | |
472 if (!child->children.empty()) { | |
473 TrimMessage(child, reflection->MutableMessage(message, field)); | |
474 } | |
475 } | |
476 } | |
477 } | |
478 } | |
479 | |
480 } // namespace | 436 } // namespace |
481 | 437 |
482 void FieldMaskUtil::ToCanonicalForm(const FieldMask& mask, FieldMask* out) { | 438 void FieldMaskUtil::ToCanonicalForm(const FieldMask& mask, FieldMask* out) { |
483 FieldMaskTree tree; | 439 FieldMaskTree tree; |
484 tree.MergeFromFieldMask(mask); | 440 tree.MergeFromFieldMask(mask); |
485 out->Clear(); | 441 out->Clear(); |
486 tree.MergeToFieldMask(out); | 442 tree.MergeToFieldMask(out); |
487 } | 443 } |
488 | 444 |
489 void FieldMaskUtil::Union(const FieldMask& mask1, const FieldMask& mask2, | 445 void FieldMaskUtil::Union(const FieldMask& mask1, const FieldMask& mask2, |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
526 const MergeOptions& options, | 482 const MergeOptions& options, |
527 Message* destination) { | 483 Message* destination) { |
528 GOOGLE_CHECK(source.GetDescriptor() == destination->GetDescriptor()); | 484 GOOGLE_CHECK(source.GetDescriptor() == destination->GetDescriptor()); |
529 // Build a FieldMaskTree and walk through the tree to merge all specified | 485 // Build a FieldMaskTree and walk through the tree to merge all specified |
530 // fields. | 486 // fields. |
531 FieldMaskTree tree; | 487 FieldMaskTree tree; |
532 tree.MergeFromFieldMask(mask); | 488 tree.MergeFromFieldMask(mask); |
533 tree.MergeMessage(source, options, destination); | 489 tree.MergeMessage(source, options, destination); |
534 } | 490 } |
535 | 491 |
536 void FieldMaskUtil::TrimMessage(const FieldMask& mask, Message* destination) { | |
537 // Build a FieldMaskTree and walk through the tree to merge all specified | |
538 // fields. | |
539 FieldMaskTree tree; | |
540 tree.MergeFromFieldMask(mask); | |
541 tree.TrimMessage(GOOGLE_CHECK_NOTNULL(destination)); | |
542 } | |
543 | |
544 } // namespace util | 492 } // namespace util |
545 } // namespace protobuf | 493 } // namespace protobuf |
546 } // namespace google | 494 } // namespace google |
OLD | NEW |