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 #include <google/protobuf/util/internal/default_value_objectwriter.h>
32
33 #include <google/protobuf/stubs/hash.h>
34
35 #include <google/protobuf/util/internal/constants.h>
36 #include <google/protobuf/util/internal/utility.h>
37 #include <google/protobuf/stubs/map_util.h>
38
39 namespace google {
40 namespace protobuf {
41 namespace util {
42 using util::Status;
43 using util::StatusOr;
44 namespace converter {
45
46 namespace {
47 // Helper function to convert string value to given data type by calling the
48 // passed converter function on the DataPiece created from "value" argument.
49 // If value is empty or if conversion fails, the default_value is returned.
50 template <typename T>
ConvertTo(StringPiece value,StatusOr<T> (DataPiece::* converter_fn)()const,T default_value)51 T ConvertTo(StringPiece value, StatusOr<T> (DataPiece::*converter_fn)() const,
52 T default_value) {
53 if (value.empty()) return default_value;
54 StatusOr<T> result = (DataPiece(value, true).*converter_fn)();
55 return result.ok() ? result.ValueOrDie() : default_value;
56 }
57 } // namespace
58
DefaultValueObjectWriter(TypeResolver * type_resolver,const google::protobuf::Type & type,ObjectWriter * ow)59 DefaultValueObjectWriter::DefaultValueObjectWriter(
60 TypeResolver* type_resolver, const google::protobuf::Type& type,
61 ObjectWriter* ow)
62 : typeinfo_(TypeInfo::NewTypeInfo(type_resolver)),
63 own_typeinfo_(true),
64 type_(type),
65 current_(NULL),
66 root_(NULL),
67 suppress_empty_list_(false),
68 field_scrub_callback_(NULL),
69 ow_(ow) {}
70
~DefaultValueObjectWriter()71 DefaultValueObjectWriter::~DefaultValueObjectWriter() {
72 for (int i = 0; i < string_values_.size(); ++i) {
73 delete string_values_[i];
74 }
75 if (own_typeinfo_) {
76 delete typeinfo_;
77 }
78 }
79
RenderBool(StringPiece name,bool value)80 DefaultValueObjectWriter* DefaultValueObjectWriter::RenderBool(StringPiece name,
81 bool value) {
82 if (current_ == NULL) {
83 ow_->RenderBool(name, value);
84 } else {
85 RenderDataPiece(name, DataPiece(value));
86 }
87 return this;
88 }
89
RenderInt32(StringPiece name,int32 value)90 DefaultValueObjectWriter* DefaultValueObjectWriter::RenderInt32(
91 StringPiece name, int32 value) {
92 if (current_ == NULL) {
93 ow_->RenderInt32(name, value);
94 } else {
95 RenderDataPiece(name, DataPiece(value));
96 }
97 return this;
98 }
99
RenderUint32(StringPiece name,uint32 value)100 DefaultValueObjectWriter* DefaultValueObjectWriter::RenderUint32(
101 StringPiece name, uint32 value) {
102 if (current_ == NULL) {
103 ow_->RenderUint32(name, value);
104 } else {
105 RenderDataPiece(name, DataPiece(value));
106 }
107 return this;
108 }
109
RenderInt64(StringPiece name,int64 value)110 DefaultValueObjectWriter* DefaultValueObjectWriter::RenderInt64(
111 StringPiece name, int64 value) {
112 if (current_ == NULL) {
113 ow_->RenderInt64(name, value);
114 } else {
115 RenderDataPiece(name, DataPiece(value));
116 }
117 return this;
118 }
119
RenderUint64(StringPiece name,uint64 value)120 DefaultValueObjectWriter* DefaultValueObjectWriter::RenderUint64(
121 StringPiece name, uint64 value) {
122 if (current_ == NULL) {
123 ow_->RenderUint64(name, value);
124 } else {
125 RenderDataPiece(name, DataPiece(value));
126 }
127 return this;
128 }
129
RenderDouble(StringPiece name,double value)130 DefaultValueObjectWriter* DefaultValueObjectWriter::RenderDouble(
131 StringPiece name, double value) {
132 if (current_ == NULL) {
133 ow_->RenderDouble(name, value);
134 } else {
135 RenderDataPiece(name, DataPiece(value));
136 }
137 return this;
138 }
139
RenderFloat(StringPiece name,float value)140 DefaultValueObjectWriter* DefaultValueObjectWriter::RenderFloat(
141 StringPiece name, float value) {
142 if (current_ == NULL) {
143 ow_->RenderBool(name, value);
144 } else {
145 RenderDataPiece(name, DataPiece(value));
146 }
147 return this;
148 }
149
RenderString(StringPiece name,StringPiece value)150 DefaultValueObjectWriter* DefaultValueObjectWriter::RenderString(
151 StringPiece name, StringPiece value) {
152 if (current_ == NULL) {
153 ow_->RenderString(name, value);
154 } else {
155 // Since StringPiece is essentially a pointer, takes a copy of "value" to
156 // avoid ownership issues.
157 string_values_.push_back(new string(value.ToString()));
158 RenderDataPiece(name, DataPiece(*string_values_.back(), true));
159 }
160 return this;
161 }
162
RenderBytes(StringPiece name,StringPiece value)163 DefaultValueObjectWriter* DefaultValueObjectWriter::RenderBytes(
164 StringPiece name, StringPiece value) {
165 if (current_ == NULL) {
166 ow_->RenderBytes(name, value);
167 } else {
168 RenderDataPiece(name, DataPiece(value, false, true));
169 }
170 return this;
171 }
172
RenderNull(StringPiece name)173 DefaultValueObjectWriter* DefaultValueObjectWriter::RenderNull(
174 StringPiece name) {
175 if (current_ == NULL) {
176 ow_->RenderNull(name);
177 } else {
178 RenderDataPiece(name, DataPiece::NullData());
179 }
180 return this;
181 }
182
RegisterFieldScrubCallBack(FieldScrubCallBackPtr field_scrub_callback)183 void DefaultValueObjectWriter::RegisterFieldScrubCallBack(
184 FieldScrubCallBackPtr field_scrub_callback) {
185 field_scrub_callback_.reset(field_scrub_callback.release());
186 }
187
Node(const string & name,const google::protobuf::Type * type,NodeKind kind,const DataPiece & data,bool is_placeholder,const vector<string> & path,bool suppress_empty_list,FieldScrubCallBack * field_scrub_callback)188 DefaultValueObjectWriter::Node::Node(
189 const string& name, const google::protobuf::Type* type, NodeKind kind,
190 const DataPiece& data, bool is_placeholder, const vector<string>& path,
191 bool suppress_empty_list, FieldScrubCallBack* field_scrub_callback)
192 : name_(name),
193 type_(type),
194 kind_(kind),
195 is_any_(false),
196 data_(data),
197 is_placeholder_(is_placeholder),
198 path_(path),
199 suppress_empty_list_(suppress_empty_list),
200 field_scrub_callback_(field_scrub_callback) {}
201
FindChild(StringPiece name)202 DefaultValueObjectWriter::Node* DefaultValueObjectWriter::Node::FindChild(
203 StringPiece name) {
204 if (name.empty() || kind_ != OBJECT) {
205 return NULL;
206 }
207 for (int i = 0; i < children_.size(); ++i) {
208 Node* child = children_[i];
209 if (child->name() == name) {
210 return child;
211 }
212 }
213 return NULL;
214 }
215
WriteTo(ObjectWriter * ow)216 void DefaultValueObjectWriter::Node::WriteTo(ObjectWriter* ow) {
217 if (kind_ == PRIMITIVE) {
218 ObjectWriter::RenderDataPieceTo(data_, name_, ow);
219 return;
220 }
221
222 // Render maps. Empty maps are rendered as "{}".
223 if (kind_ == MAP) {
224 ow->StartObject(name_);
225 WriteChildren(ow);
226 ow->EndObject();
227 return;
228 }
229
230 // Write out lists. If we didn't have any list in response, write out empty
231 // list.
232 if (kind_ == LIST) {
233 // Suppress empty lists if requested.
234 if (suppress_empty_list_ && is_placeholder_) return;
235
236 ow->StartList(name_);
237 WriteChildren(ow);
238 ow->EndList();
239 return;
240 }
241
242 // If is_placeholder_ = true, we didn't see this node in the response, so
243 // skip output.
244 if (is_placeholder_) return;
245
246 ow->StartObject(name_);
247 WriteChildren(ow);
248 ow->EndObject();
249 }
250
WriteChildren(ObjectWriter * ow)251 void DefaultValueObjectWriter::Node::WriteChildren(ObjectWriter* ow) {
252 for (int i = 0; i < children_.size(); ++i) {
253 Node* child = children_[i];
254 child->WriteTo(ow);
255 }
256 }
257
GetMapValueType(const google::protobuf::Type & found_type,const TypeInfo * typeinfo)258 const google::protobuf::Type* DefaultValueObjectWriter::Node::GetMapValueType(
259 const google::protobuf::Type& found_type, const TypeInfo* typeinfo) {
260 // If this field is a map, we should use the type of its "Value" as
261 // the type of the child node.
262 for (int i = 0; i < found_type.fields_size(); ++i) {
263 const google::protobuf::Field& sub_field = found_type.fields(i);
264 if (sub_field.number() != 2) {
265 continue;
266 }
267 if (sub_field.kind() != google::protobuf::Field_Kind_TYPE_MESSAGE) {
268 // This map's value type is not a message type. We don't need to
269 // get the field_type in this case.
270 break;
271 }
272 util::StatusOr<const google::protobuf::Type*> sub_type =
273 typeinfo->ResolveTypeUrl(sub_field.type_url());
274 if (!sub_type.ok()) {
275 GOOGLE_LOG(WARNING) << "Cannot resolve type '" << sub_field.type_url() << "'.";
276 } else {
277 return sub_type.ValueOrDie();
278 }
279 break;
280 }
281 return NULL;
282 }
283
PopulateChildren(const TypeInfo * typeinfo)284 void DefaultValueObjectWriter::Node::PopulateChildren(
285 const TypeInfo* typeinfo) {
286 // Ignores well known types that don't require automatically populating their
287 // primitive children. For type "Any", we only populate its children when the
288 // "@type" field is set.
289 // TODO(tsun): remove "kStructValueType" from the list. It's being checked
290 // now because of a bug in the tool-chain that causes the "oneof_index"
291 // of kStructValueType to not be set correctly.
292 if (type_ == NULL || type_->name() == kAnyType ||
293 type_->name() == kStructType || type_->name() == kTimestampType ||
294 type_->name() == kDurationType || type_->name() == kStructValueType) {
295 return;
296 }
297 std::vector<Node*> new_children;
298 hash_map<string, int> orig_children_map;
299
300 // Creates a map of child nodes to speed up lookup.
301 for (int i = 0; i < children_.size(); ++i) {
302 InsertIfNotPresent(&orig_children_map, children_[i]->name_, i);
303 }
304
305 for (int i = 0; i < type_->fields_size(); ++i) {
306 const google::protobuf::Field& field = type_->fields(i);
307
308 // This code is checking if the field to be added to the tree should be
309 // scrubbed or not by calling the field_scrub_callback_ callback function.
310 vector<string> path;
311 if (!path_.empty()) {
312 path.insert(path.begin(), path_.begin(), path_.end());
313 }
314 path.push_back(field.name());
315 if (field_scrub_callback_ != NULL &&
316 field_scrub_callback_->Run(path, &field)) {
317 continue;
318 }
319
320 hash_map<string, int>::iterator found =
321 orig_children_map.find(field.name());
322 // If the child field has already been set, we just add it to the new list
323 // of children.
324 if (found != orig_children_map.end()) {
325 new_children.push_back(children_[found->second]);
326 children_[found->second] = NULL;
327 continue;
328 }
329
330 const google::protobuf::Type* field_type = NULL;
331 bool is_map = false;
332 NodeKind kind = PRIMITIVE;
333
334 if (field.kind() == google::protobuf::Field_Kind_TYPE_MESSAGE) {
335 kind = OBJECT;
336 util::StatusOr<const google::protobuf::Type*> found_result =
337 typeinfo->ResolveTypeUrl(field.type_url());
338 if (!found_result.ok()) {
339 // "field" is of an unknown type.
340 GOOGLE_LOG(WARNING) << "Cannot resolve type '" << field.type_url() << "'.";
341 } else {
342 const google::protobuf::Type* found_type = found_result.ValueOrDie();
343 is_map = IsMap(field, *found_type);
344
345 if (!is_map) {
346 field_type = found_type;
347 } else {
348 // If this field is a map, we should use the type of its "Value" as
349 // the type of the child node.
350 field_type = GetMapValueType(*found_type, typeinfo);
351 kind = MAP;
352 }
353 }
354 }
355
356 if (!is_map &&
357 field.cardinality() ==
358 google::protobuf::Field_Cardinality_CARDINALITY_REPEATED) {
359 kind = LIST;
360 }
361
362 // If oneof_index() != 0, the child field is part of a "oneof", which means
363 // the child field is optional and we shouldn't populate its default value.
364 if (field.oneof_index() != 0) continue;
365
366 // If the child field is of primitive type, sets its data to the default
367 // value of its type.
368 google::protobuf::scoped_ptr<Node> child(new Node(
369 field.json_name(), field_type, kind,
370 kind == PRIMITIVE ? CreateDefaultDataPieceForField(field, typeinfo)
371 : DataPiece::NullData(),
372 true, path, suppress_empty_list_, field_scrub_callback_));
373 new_children.push_back(child.release());
374 }
375 // Adds all leftover nodes in children_ to the beginning of new_child.
376 for (int i = 0; i < children_.size(); ++i) {
377 if (children_[i] == NULL) {
378 continue;
379 }
380 new_children.insert(new_children.begin(), children_[i]);
381 children_[i] = NULL;
382 }
383 children_.swap(new_children);
384 }
385
MaybePopulateChildrenOfAny(Node * node)386 void DefaultValueObjectWriter::MaybePopulateChildrenOfAny(Node* node) {
387 // If this is an "Any" node with "@type" already given and no other children
388 // have been added, populates its children.
389 if (node != NULL && node->is_any() && node->type() != NULL &&
390 node->type()->name() != kAnyType && node->number_of_children() == 1) {
391 node->PopulateChildren(typeinfo_);
392 }
393 }
394
FindEnumDefault(const google::protobuf::Field & field,const TypeInfo * typeinfo)395 DataPiece DefaultValueObjectWriter::FindEnumDefault(
396 const google::protobuf::Field& field, const TypeInfo* typeinfo) {
397 if (!field.default_value().empty())
398 return DataPiece(field.default_value(), true);
399
400 const google::protobuf::Enum* enum_type =
401 typeinfo->GetEnumByTypeUrl(field.type_url());
402 if (!enum_type) {
403 GOOGLE_LOG(WARNING) << "Could not find enum with type '" << field.type_url()
404 << "'";
405 return DataPiece::NullData();
406 }
407 // We treat the first value as the default if none is specified.
408 return enum_type->enumvalue_size() > 0
409 ? DataPiece(enum_type->enumvalue(0).name(), true)
410 : DataPiece::NullData();
411 }
412
CreateDefaultDataPieceForField(const google::protobuf::Field & field,const TypeInfo * typeinfo)413 DataPiece DefaultValueObjectWriter::CreateDefaultDataPieceForField(
414 const google::protobuf::Field& field, const TypeInfo* typeinfo) {
415 switch (field.kind()) {
416 case google::protobuf::Field_Kind_TYPE_DOUBLE: {
417 return DataPiece(ConvertTo<double>(
418 field.default_value(), &DataPiece::ToDouble, static_cast<double>(0)));
419 }
420 case google::protobuf::Field_Kind_TYPE_FLOAT: {
421 return DataPiece(ConvertTo<float>(
422 field.default_value(), &DataPiece::ToFloat, static_cast<float>(0)));
423 }
424 case google::protobuf::Field_Kind_TYPE_INT64:
425 case google::protobuf::Field_Kind_TYPE_SINT64:
426 case google::protobuf::Field_Kind_TYPE_SFIXED64: {
427 return DataPiece(ConvertTo<int64>(
428 field.default_value(), &DataPiece::ToInt64, static_cast<int64>(0)));
429 }
430 case google::protobuf::Field_Kind_TYPE_UINT64:
431 case google::protobuf::Field_Kind_TYPE_FIXED64: {
432 return DataPiece(ConvertTo<uint64>(
433 field.default_value(), &DataPiece::ToUint64, static_cast<uint64>(0)));
434 }
435 case google::protobuf::Field_Kind_TYPE_INT32:
436 case google::protobuf::Field_Kind_TYPE_SINT32:
437 case google::protobuf::Field_Kind_TYPE_SFIXED32: {
438 return DataPiece(ConvertTo<int32>(
439 field.default_value(), &DataPiece::ToInt32, static_cast<int32>(0)));
440 }
441 case google::protobuf::Field_Kind_TYPE_BOOL: {
442 return DataPiece(
443 ConvertTo<bool>(field.default_value(), &DataPiece::ToBool, false));
444 }
445 case google::protobuf::Field_Kind_TYPE_STRING: {
446 return DataPiece(field.default_value(), true);
447 }
448 case google::protobuf::Field_Kind_TYPE_BYTES: {
449 return DataPiece(field.default_value(), false, true);
450 }
451 case google::protobuf::Field_Kind_TYPE_UINT32:
452 case google::protobuf::Field_Kind_TYPE_FIXED32: {
453 return DataPiece(ConvertTo<uint32>(
454 field.default_value(), &DataPiece::ToUint32, static_cast<uint32>(0)));
455 }
456 case google::protobuf::Field_Kind_TYPE_ENUM: {
457 return FindEnumDefault(field, typeinfo);
458 }
459 default: { return DataPiece::NullData(); }
460 }
461 }
462
StartObject(StringPiece name)463 DefaultValueObjectWriter* DefaultValueObjectWriter::StartObject(
464 StringPiece name) {
465 if (current_ == NULL) {
466 vector<string> path;
467 root_.reset(new Node(name.ToString(), &type_, OBJECT, DataPiece::NullData(),
468 false, path, suppress_empty_list_,
469 field_scrub_callback_.get()));
470 root_->PopulateChildren(typeinfo_);
471 current_ = root_.get();
472 return this;
473 }
474 MaybePopulateChildrenOfAny(current_);
475 Node* child = current_->FindChild(name);
476 if (current_->kind() == LIST || current_->kind() == MAP || child == NULL) {
477 // If current_ is a list or a map node, we should create a new child and use
478 // the type of current_ as the type of the new child.
479 google::protobuf::scoped_ptr<Node> node(new Node(
480 name.ToString(), ((current_->kind() == LIST || current_->kind() == MAP)
481 ? current_->type()
482 : NULL),
483 OBJECT, DataPiece::NullData(), false,
484 child == NULL ? current_->path() : child->path(),
485 suppress_empty_list_, field_scrub_callback_.get()));
486 child = node.get();
487 current_->AddChild(node.release());
488 }
489
490 child->set_is_placeholder(false);
491 if (child->kind() == OBJECT && child->number_of_children() == 0) {
492 child->PopulateChildren(typeinfo_);
493 }
494
495 stack_.push(current_);
496 current_ = child;
497 return this;
498 }
499
EndObject()500 DefaultValueObjectWriter* DefaultValueObjectWriter::EndObject() {
501 if (stack_.empty()) {
502 // The root object ends here. Writes out the tree.
503 WriteRoot();
504 return this;
505 }
506 current_ = stack_.top();
507 stack_.pop();
508 return this;
509 }
510
StartList(StringPiece name)511 DefaultValueObjectWriter* DefaultValueObjectWriter::StartList(
512 StringPiece name) {
513 if (current_ == NULL) {
514 vector<string> path;
515 root_.reset(new Node(name.ToString(), &type_, LIST, DataPiece::NullData(),
516 false, path, suppress_empty_list_,
517 field_scrub_callback_.get()));
518 current_ = root_.get();
519 return this;
520 }
521 MaybePopulateChildrenOfAny(current_);
522 Node* child = current_->FindChild(name);
523 if (child == NULL || child->kind() != LIST) {
524 google::protobuf::scoped_ptr<Node> node(
525 new Node(name.ToString(), NULL, LIST, DataPiece::NullData(), false,
526 child == NULL ? current_->path() : child->path(),
527 suppress_empty_list_, field_scrub_callback_.get()));
528 child = node.get();
529 current_->AddChild(node.release());
530 }
531 child->set_is_placeholder(false);
532
533 stack_.push(current_);
534 current_ = child;
535 return this;
536 }
537
WriteRoot()538 void DefaultValueObjectWriter::WriteRoot() {
539 root_->WriteTo(ow_);
540 root_.reset(NULL);
541 current_ = NULL;
542 }
543
EndList()544 DefaultValueObjectWriter* DefaultValueObjectWriter::EndList() {
545 if (stack_.empty()) {
546 WriteRoot();
547 return this;
548 }
549 current_ = stack_.top();
550 stack_.pop();
551 return this;
552 }
553
RenderDataPiece(StringPiece name,const DataPiece & data)554 void DefaultValueObjectWriter::RenderDataPiece(StringPiece name,
555 const DataPiece& data) {
556 MaybePopulateChildrenOfAny(current_);
557 util::StatusOr<string> data_string = data.ToString();
558 if (current_->type() != NULL && current_->type()->name() == kAnyType &&
559 name == "@type" && data_string.ok()) {
560 const string& string_value = data_string.ValueOrDie();
561 // If the type of current_ is "Any" and its "@type" field is being set here,
562 // sets the type of current_ to be the type specified by the "@type".
563 util::StatusOr<const google::protobuf::Type*> found_type =
564 typeinfo_->ResolveTypeUrl(string_value);
565 if (!found_type.ok()) {
566 GOOGLE_LOG(WARNING) << "Failed to resolve type '" << string_value << "'.";
567 } else {
568 current_->set_type(found_type.ValueOrDie());
569 }
570 current_->set_is_any(true);
571 // If the "@type" field is placed after other fields, we should populate
572 // other children of primitive type now. Otherwise, we should wait until the
573 // first value field is rendered before we populate the children, because
574 // the "value" field of a Any message could be omitted.
575 if (current_->number_of_children() > 1 && current_->type() != NULL) {
576 current_->PopulateChildren(typeinfo_);
577 }
578 }
579 Node* child = current_->FindChild(name);
580 if (child == NULL || child->kind() != PRIMITIVE) {
581 // No children are found, creates a new child.
582 google::protobuf::scoped_ptr<Node> node(
583 new Node(name.ToString(), NULL, PRIMITIVE, data, false,
584 child == NULL ? current_->path() : child->path(),
585 suppress_empty_list_, field_scrub_callback_.get()));
586 child = node.get();
587 current_->AddChild(node.release());
588 } else {
589 child->set_data(data);
590 }
591 }
592
593 } // namespace converter
594 } // namespace util
595 } // namespace protobuf
596 } // namespace google
597