OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "components/policy/core/common/schema.h" | 5 #include "components/policy/core/common/schema.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <vector> | 8 #include <vector> |
9 | 9 |
10 #include "base/compiler_specific.h" | 10 #include "base/compiler_specific.h" |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
44 if (kSchemaToValueTypeMap[i].schema_type == type_string) { | 44 if (kSchemaToValueTypeMap[i].schema_type == type_string) { |
45 *type = kSchemaToValueTypeMap[i].value_type; | 45 *type = kSchemaToValueTypeMap[i].value_type; |
46 return true; | 46 return true; |
47 } | 47 } |
48 } | 48 } |
49 return false; | 49 return false; |
50 } | 50 } |
51 | 51 |
52 } // namespace | 52 } // namespace |
53 | 53 |
54 // A SchemaOwner can either Wrap() a SchemaData owned elsewhere (currently used | 54 // Contains the internal data representation of a Schema. This can either wrap |
55 // to wrap the Chrome schema, which is generated at compile time), or it can | 55 // a SchemaData owned elsewhere (currently used to wrap the Chrome schema, which |
56 // own its own SchemaData. In that case, the InternalStorage class holds the | 56 // is generated at compile time), or it can own its own SchemaData. |
57 // data referenced by the SchemaData substructures. | 57 class Schema::InternalStorage |
58 class SchemaOwner::InternalStorage { | 58 : public base::RefCountedThreadSafe<InternalStorage> { |
59 public: | 59 public: |
60 ~InternalStorage() {} | 60 static scoped_refptr<const InternalStorage> Wrap(const SchemaData* data); |
61 | 61 |
62 static scoped_ptr<InternalStorage> ParseSchema( | 62 static scoped_refptr<const InternalStorage> ParseSchema( |
63 const base::DictionaryValue& schema, | 63 const base::DictionaryValue& schema, |
64 std::string* error); | 64 std::string* error); |
65 | 65 |
66 const SchemaData* schema_data() const { return &schema_data_; } | 66 const SchemaData* data() const { return &schema_data_; } |
| 67 |
| 68 const SchemaNode* root_node() const { |
| 69 return schema(0); |
| 70 } |
| 71 |
| 72 const SchemaNode* schema(int index) const { |
| 73 return schema_data_.schema_nodes + index; |
| 74 } |
| 75 |
| 76 const PropertiesNode* properties(int index) const { |
| 77 return schema_data_.properties_nodes + index; |
| 78 } |
| 79 |
| 80 const PropertyNode* property(int index) const { |
| 81 return schema_data_.property_nodes + index; |
| 82 } |
67 | 83 |
68 private: | 84 private: |
69 InternalStorage() {} | 85 friend class base::RefCountedThreadSafe<InternalStorage>; |
| 86 |
| 87 InternalStorage(); |
| 88 ~InternalStorage(); |
70 | 89 |
71 // Parses the JSON schema in |schema| and returns the index of the | 90 // Parses the JSON schema in |schema| and returns the index of the |
72 // corresponding SchemaNode in |schema_nodes_|, which gets populated with any | 91 // corresponding SchemaNode in |schema_nodes_|, which gets populated with any |
73 // necessary intermediate nodes. If |schema| is invalid then -1 is returned | 92 // necessary intermediate nodes. If |schema| is invalid then -1 is returned |
74 // and |error| is set to the error cause. | 93 // and |error| is set to the error cause. |
75 int Parse(const base::DictionaryValue& schema, std::string* error); | 94 int Parse(const base::DictionaryValue& schema, std::string* error); |
76 | 95 |
77 // Helper for Parse(). | 96 // Helper for Parse(). |
78 int ParseDictionary(const base::DictionaryValue& schema, std::string* error); | 97 int ParseDictionary(const base::DictionaryValue& schema, std::string* error); |
79 | 98 |
80 // Helper for Parse(). | 99 // Helper for Parse(). |
81 int ParseList(const base::DictionaryValue& schema, std::string* error); | 100 int ParseList(const base::DictionaryValue& schema, std::string* error); |
82 | 101 |
83 SchemaData schema_data_; | 102 SchemaData schema_data_; |
84 // TODO: compute the sizes of these arrays before filling them up to avoid | 103 // TODO: compute the sizes of these arrays before filling them up to avoid |
85 // having to resize them. | 104 // having to resize them. |
86 ScopedVector<std::string> strings_; | 105 ScopedVector<std::string> strings_; |
87 std::vector<SchemaNode> schema_nodes_; | 106 std::vector<SchemaNode> schema_nodes_; |
88 std::vector<PropertyNode> property_nodes_; | 107 std::vector<PropertyNode> property_nodes_; |
89 std::vector<PropertiesNode> properties_nodes_; | 108 std::vector<PropertiesNode> properties_nodes_; |
90 | 109 |
91 DISALLOW_COPY_AND_ASSIGN(InternalStorage); | 110 DISALLOW_COPY_AND_ASSIGN(InternalStorage); |
92 }; | 111 }; |
93 | 112 |
94 Schema::Iterator::Iterator(const SchemaData* data, const PropertiesNode* node) | 113 Schema::InternalStorage::InternalStorage() {} |
95 : data_(data), | |
96 it_(data->property_nodes + node->begin), | |
97 end_(data->property_nodes + node->end) {} | |
98 | 114 |
99 Schema::Iterator::Iterator(const Iterator& iterator) | 115 Schema::InternalStorage::~InternalStorage() {} |
100 : data_(iterator.data_), | |
101 it_(iterator.it_), | |
102 end_(iterator.end_) {} | |
103 | 116 |
104 Schema::Iterator::~Iterator() {} | 117 // static |
105 | 118 scoped_refptr<const Schema::InternalStorage> Schema::InternalStorage::Wrap( |
106 Schema::Iterator& Schema::Iterator::operator=(const Iterator& iterator) { | 119 const SchemaData* data) { |
107 data_ = iterator.data_; | 120 InternalStorage* storage = new InternalStorage(); |
108 it_ = iterator.it_; | 121 storage->schema_data_.schema_nodes = data->schema_nodes; |
109 end_ = iterator.end_; | 122 storage->schema_data_.property_nodes = data->property_nodes; |
110 return *this; | 123 storage->schema_data_.properties_nodes = data->properties_nodes; |
111 } | 124 return storage; |
112 | |
113 bool Schema::Iterator::IsAtEnd() const { | |
114 return it_ == end_; | |
115 } | |
116 | |
117 void Schema::Iterator::Advance() { | |
118 ++it_; | |
119 } | |
120 | |
121 const char* Schema::Iterator::key() const { | |
122 return it_->key; | |
123 } | |
124 | |
125 Schema Schema::Iterator::schema() const { | |
126 return Schema(data_, data_->schema_nodes + it_->schema); | |
127 } | |
128 | |
129 Schema::Schema() : data_(NULL), node_(NULL) {} | |
130 | |
131 Schema::Schema(const SchemaData* data, const SchemaNode* node) | |
132 : data_(data), node_(node) {} | |
133 | |
134 Schema::Schema(const Schema& schema) | |
135 : data_(schema.data_), node_(schema.node_) {} | |
136 | |
137 Schema& Schema::operator=(const Schema& schema) { | |
138 data_ = schema.data_; | |
139 node_ = schema.node_; | |
140 return *this; | |
141 } | |
142 | |
143 base::Value::Type Schema::type() const { | |
144 CHECK(valid()); | |
145 return node_->type; | |
146 } | |
147 | |
148 Schema::Iterator Schema::GetPropertiesIterator() const { | |
149 CHECK(valid()); | |
150 CHECK_EQ(base::Value::TYPE_DICTIONARY, type()); | |
151 return Iterator(data_, data_->properties_nodes + node_->extra); | |
152 } | |
153 | |
154 namespace { | |
155 | |
156 bool CompareKeys(const PropertyNode& node, const std::string& key) { | |
157 return node.key < key; | |
158 } | |
159 | |
160 } // namespace | |
161 | |
162 Schema Schema::GetKnownProperty(const std::string& key) const { | |
163 CHECK(valid()); | |
164 CHECK_EQ(base::Value::TYPE_DICTIONARY, type()); | |
165 const PropertiesNode* node = data_->properties_nodes + node_->extra; | |
166 const PropertyNode* begin = data_->property_nodes + node->begin; | |
167 const PropertyNode* end = data_->property_nodes + node->end; | |
168 const PropertyNode* it = std::lower_bound(begin, end, key, CompareKeys); | |
169 if (it != end && it->key == key) | |
170 return Schema(data_, data_->schema_nodes + it->schema); | |
171 return Schema(); | |
172 } | |
173 | |
174 Schema Schema::GetAdditionalProperties() const { | |
175 CHECK(valid()); | |
176 CHECK_EQ(base::Value::TYPE_DICTIONARY, type()); | |
177 const PropertiesNode* node = data_->properties_nodes + node_->extra; | |
178 if (node->additional == kInvalid) | |
179 return Schema(); | |
180 return Schema(data_, data_->schema_nodes + node->additional); | |
181 } | |
182 | |
183 Schema Schema::GetProperty(const std::string& key) const { | |
184 Schema schema = GetKnownProperty(key); | |
185 return schema.valid() ? schema : GetAdditionalProperties(); | |
186 } | |
187 | |
188 Schema Schema::GetItems() const { | |
189 CHECK(valid()); | |
190 CHECK_EQ(base::Value::TYPE_LIST, type()); | |
191 if (node_->extra == kInvalid) | |
192 return Schema(); | |
193 return Schema(data_, data_->schema_nodes + node_->extra); | |
194 } | |
195 | |
196 SchemaOwner::SchemaOwner(const SchemaData* data, | |
197 scoped_ptr<InternalStorage> storage) | |
198 : storage_(storage.Pass()), data_(data) {} | |
199 | |
200 SchemaOwner::~SchemaOwner() {} | |
201 | |
202 Schema SchemaOwner::schema() const { | |
203 // data_->schema_nodes[0] is the root node. | |
204 return Schema(data_, data_->schema_nodes); | |
205 } | 125 } |
206 | 126 |
207 // static | 127 // static |
208 scoped_ptr<SchemaOwner> SchemaOwner::Wrap(const SchemaData* data) { | 128 scoped_refptr<const Schema::InternalStorage> |
209 return make_scoped_ptr(new SchemaOwner(data, scoped_ptr<InternalStorage>())); | 129 Schema::InternalStorage::ParseSchema(const base::DictionaryValue& schema, |
210 } | 130 std::string* error) { |
211 | 131 scoped_refptr<InternalStorage> storage = new InternalStorage(); |
212 // static | |
213 scoped_ptr<SchemaOwner> SchemaOwner::Parse(const std::string& content, | |
214 std::string* error) { | |
215 // Validate as a generic JSON schema. | |
216 scoped_ptr<base::DictionaryValue> dict = | |
217 JSONSchemaValidator::IsValidSchema(content, error); | |
218 if (!dict) | |
219 return scoped_ptr<SchemaOwner>(); | |
220 | |
221 // Validate the main type. | |
222 std::string string_value; | |
223 if (!dict->GetString(json_schema_constants::kType, &string_value) || | |
224 string_value != json_schema_constants::kObject) { | |
225 *error = | |
226 "The main schema must have a type attribute with \"object\" value."; | |
227 return scoped_ptr<SchemaOwner>(); | |
228 } | |
229 | |
230 // Checks for invalid attributes at the top-level. | |
231 if (dict->HasKey(json_schema_constants::kAdditionalProperties) || | |
232 dict->HasKey(json_schema_constants::kPatternProperties)) { | |
233 *error = "\"additionalProperties\" and \"patternProperties\" are not " | |
234 "supported at the main schema."; | |
235 return scoped_ptr<SchemaOwner>(); | |
236 } | |
237 | |
238 scoped_ptr<InternalStorage> storage = | |
239 InternalStorage::ParseSchema(*dict, error); | |
240 if (!storage) | |
241 return scoped_ptr<SchemaOwner>(); | |
242 const SchemaData* data = storage->schema_data(); | |
243 return make_scoped_ptr(new SchemaOwner(data, storage.Pass())); | |
244 } | |
245 | |
246 // static | |
247 scoped_ptr<SchemaOwner::InternalStorage> | |
248 SchemaOwner::InternalStorage::ParseSchema(const base::DictionaryValue& schema, | |
249 std::string* error) { | |
250 scoped_ptr<InternalStorage> storage(new InternalStorage); | |
251 if (storage->Parse(schema, error) == kInvalid) | 132 if (storage->Parse(schema, error) == kInvalid) |
252 return scoped_ptr<InternalStorage>(); | 133 return NULL; |
253 SchemaData* data = &storage->schema_data_; | 134 SchemaData* data = &storage->schema_data_; |
254 data->schema_nodes = vector_as_array(&storage->schema_nodes_); | 135 data->schema_nodes = vector_as_array(&storage->schema_nodes_); |
255 data->property_nodes = vector_as_array(&storage->property_nodes_); | 136 data->property_nodes = vector_as_array(&storage->property_nodes_); |
256 data->properties_nodes = vector_as_array(&storage->properties_nodes_); | 137 data->properties_nodes = vector_as_array(&storage->properties_nodes_); |
257 return storage.Pass(); | 138 return storage; |
258 } | 139 } |
259 | 140 |
260 int SchemaOwner::InternalStorage::Parse(const base::DictionaryValue& schema, | 141 int Schema::InternalStorage::Parse(const base::DictionaryValue& schema, |
261 std::string* error) { | 142 std::string* error) { |
262 std::string type_string; | 143 std::string type_string; |
263 if (!schema.GetString(json_schema_constants::kType, &type_string)) { | 144 if (!schema.GetString(json_schema_constants::kType, &type_string)) { |
264 *error = "The schema type must be declared."; | 145 *error = "The schema type must be declared."; |
265 return kInvalid; | 146 return kInvalid; |
266 } | 147 } |
267 | 148 |
268 base::Value::Type type = base::Value::TYPE_NULL; | 149 base::Value::Type type = base::Value::TYPE_NULL; |
269 if (!SchemaTypeToValueType(type_string, &type)) { | 150 if (!SchemaTypeToValueType(type_string, &type)) { |
270 *error = "Type not supported: " + type_string; | 151 *error = "Type not supported: " + type_string; |
271 return kInvalid; | 152 return kInvalid; |
272 } | 153 } |
273 | 154 |
274 if (type == base::Value::TYPE_DICTIONARY) | 155 if (type == base::Value::TYPE_DICTIONARY) |
275 return ParseDictionary(schema, error); | 156 return ParseDictionary(schema, error); |
276 if (type == base::Value::TYPE_LIST) | 157 if (type == base::Value::TYPE_LIST) |
277 return ParseList(schema, error); | 158 return ParseList(schema, error); |
278 | 159 |
279 int index = static_cast<int>(schema_nodes_.size()); | 160 int index = static_cast<int>(schema_nodes_.size()); |
280 schema_nodes_.push_back(SchemaNode()); | 161 schema_nodes_.push_back(SchemaNode()); |
281 SchemaNode& node = schema_nodes_.back(); | 162 SchemaNode& node = schema_nodes_.back(); |
282 node.type = type; | 163 node.type = type; |
283 node.extra = kInvalid; | 164 node.extra = kInvalid; |
284 return index; | 165 return index; |
285 } | 166 } |
286 | 167 |
287 // static | 168 // static |
288 int SchemaOwner::InternalStorage::ParseDictionary( | 169 int Schema::InternalStorage::ParseDictionary( |
289 const base::DictionaryValue& schema, | 170 const base::DictionaryValue& schema, |
290 std::string* error) { | 171 std::string* error) { |
291 // Note: recursive calls to Parse() invalidate iterators and references into | 172 // Note: recursive calls to Parse() invalidate iterators and references into |
292 // the vectors. | 173 // the vectors. |
293 | 174 |
294 // Reserve an index for this dictionary at the front, so that the root node | 175 // Reserve an index for this dictionary at the front, so that the root node |
295 // is at index 0. | 176 // is at index 0. |
296 int schema_index = static_cast<int>(schema_nodes_.size()); | 177 int schema_index = static_cast<int>(schema_nodes_.size()); |
297 schema_nodes_.push_back(SchemaNode()); | 178 schema_nodes_.push_back(SchemaNode()); |
298 | 179 |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
335 properties_nodes_[extra].begin = base_index; | 216 properties_nodes_[extra].begin = base_index; |
336 properties_nodes_[extra].end = index; | 217 properties_nodes_[extra].end = index; |
337 } | 218 } |
338 | 219 |
339 schema_nodes_[schema_index].type = base::Value::TYPE_DICTIONARY; | 220 schema_nodes_[schema_index].type = base::Value::TYPE_DICTIONARY; |
340 schema_nodes_[schema_index].extra = extra; | 221 schema_nodes_[schema_index].extra = extra; |
341 return schema_index; | 222 return schema_index; |
342 } | 223 } |
343 | 224 |
344 // static | 225 // static |
345 int SchemaOwner::InternalStorage::ParseList(const base::DictionaryValue& schema, | 226 int Schema::InternalStorage::ParseList(const base::DictionaryValue& schema, |
346 std::string* error) { | 227 std::string* error) { |
347 const base::DictionaryValue* dict = NULL; | 228 const base::DictionaryValue* dict = NULL; |
348 if (!schema.GetDictionary(json_schema_constants::kItems, &dict)) { | 229 if (!schema.GetDictionary(json_schema_constants::kItems, &dict)) { |
349 *error = "Arrays must declare a single schema for their items."; | 230 *error = "Arrays must declare a single schema for their items."; |
350 return kInvalid; | 231 return kInvalid; |
351 } | 232 } |
352 int extra = Parse(*dict, error); | 233 int extra = Parse(*dict, error); |
353 if (extra == kInvalid) | 234 if (extra == kInvalid) |
354 return kInvalid; | 235 return kInvalid; |
355 int index = static_cast<int>(schema_nodes_.size()); | 236 int index = static_cast<int>(schema_nodes_.size()); |
356 schema_nodes_.push_back(SchemaNode()); | 237 schema_nodes_.push_back(SchemaNode()); |
357 schema_nodes_[index].type = base::Value::TYPE_LIST; | 238 schema_nodes_[index].type = base::Value::TYPE_LIST; |
358 schema_nodes_[index].extra = extra; | 239 schema_nodes_[index].extra = extra; |
359 return index; | 240 return index; |
360 } | 241 } |
361 | 242 |
| 243 Schema::Iterator::Iterator(const scoped_refptr<const InternalStorage>& storage, |
| 244 const PropertiesNode* node) |
| 245 : storage_(storage), |
| 246 it_(storage->property(node->begin)), |
| 247 end_(storage->property(node->end)) {} |
| 248 |
| 249 Schema::Iterator::Iterator(const Iterator& iterator) |
| 250 : storage_(iterator.storage_), |
| 251 it_(iterator.it_), |
| 252 end_(iterator.end_) {} |
| 253 |
| 254 Schema::Iterator::~Iterator() {} |
| 255 |
| 256 Schema::Iterator& Schema::Iterator::operator=(const Iterator& iterator) { |
| 257 storage_ = iterator.storage_; |
| 258 it_ = iterator.it_; |
| 259 end_ = iterator.end_; |
| 260 return *this; |
| 261 } |
| 262 |
| 263 bool Schema::Iterator::IsAtEnd() const { |
| 264 return it_ == end_; |
| 265 } |
| 266 |
| 267 void Schema::Iterator::Advance() { |
| 268 ++it_; |
| 269 } |
| 270 |
| 271 const char* Schema::Iterator::key() const { |
| 272 return it_->key; |
| 273 } |
| 274 |
| 275 Schema Schema::Iterator::schema() const { |
| 276 return Schema(storage_, storage_->schema(it_->schema)); |
| 277 } |
| 278 |
| 279 Schema::Schema() : node_(NULL) {} |
| 280 |
| 281 Schema::Schema(const scoped_refptr<const InternalStorage>& storage, |
| 282 const SchemaNode* node) |
| 283 : storage_(storage), node_(node) {} |
| 284 |
| 285 Schema::Schema(const Schema& schema) |
| 286 : storage_(schema.storage_), node_(schema.node_) {} |
| 287 |
| 288 Schema::~Schema() {} |
| 289 |
| 290 Schema& Schema::operator=(const Schema& schema) { |
| 291 storage_ = schema.storage_; |
| 292 node_ = schema.node_; |
| 293 return *this; |
| 294 } |
| 295 |
| 296 // static |
| 297 Schema Schema::Wrap(const SchemaData* data) { |
| 298 scoped_refptr<const InternalStorage> storage = InternalStorage::Wrap(data); |
| 299 return Schema(storage, storage->root_node()); |
| 300 } |
| 301 |
| 302 // static |
| 303 Schema Schema::Parse(const std::string& content, std::string* error) { |
| 304 // Validate as a generic JSON schema. |
| 305 scoped_ptr<base::DictionaryValue> dict = |
| 306 JSONSchemaValidator::IsValidSchema(content, error); |
| 307 if (!dict) |
| 308 return Schema(); |
| 309 |
| 310 // Validate the main type. |
| 311 std::string string_value; |
| 312 if (!dict->GetString(json_schema_constants::kType, &string_value) || |
| 313 string_value != json_schema_constants::kObject) { |
| 314 *error = |
| 315 "The main schema must have a type attribute with \"object\" value."; |
| 316 return Schema(); |
| 317 } |
| 318 |
| 319 // Checks for invalid attributes at the top-level. |
| 320 if (dict->HasKey(json_schema_constants::kAdditionalProperties) || |
| 321 dict->HasKey(json_schema_constants::kPatternProperties)) { |
| 322 *error = "\"additionalProperties\" and \"patternProperties\" are not " |
| 323 "supported at the main schema."; |
| 324 return Schema(); |
| 325 } |
| 326 |
| 327 scoped_refptr<const InternalStorage> storage = |
| 328 InternalStorage::ParseSchema(*dict, error); |
| 329 if (!storage) |
| 330 return Schema(); |
| 331 return Schema(storage, storage->root_node()); |
| 332 } |
| 333 |
| 334 base::Value::Type Schema::type() const { |
| 335 CHECK(valid()); |
| 336 return node_->type; |
| 337 } |
| 338 |
| 339 Schema::Iterator Schema::GetPropertiesIterator() const { |
| 340 CHECK(valid()); |
| 341 CHECK_EQ(base::Value::TYPE_DICTIONARY, type()); |
| 342 return Iterator(storage_, storage_->properties(node_->extra)); |
| 343 } |
| 344 |
| 345 namespace { |
| 346 |
| 347 bool CompareKeys(const PropertyNode& node, const std::string& key) { |
| 348 return node.key < key; |
| 349 } |
| 350 |
| 351 } // namespace |
| 352 |
| 353 Schema Schema::GetKnownProperty(const std::string& key) const { |
| 354 CHECK(valid()); |
| 355 CHECK_EQ(base::Value::TYPE_DICTIONARY, type()); |
| 356 const PropertiesNode* node = storage_->properties(node_->extra); |
| 357 const PropertyNode* begin = storage_->property(node->begin); |
| 358 const PropertyNode* end = storage_->property(node->end); |
| 359 const PropertyNode* it = std::lower_bound(begin, end, key, CompareKeys); |
| 360 if (it != end && it->key == key) |
| 361 return Schema(storage_, storage_->schema(it->schema)); |
| 362 return Schema(); |
| 363 } |
| 364 |
| 365 Schema Schema::GetAdditionalProperties() const { |
| 366 CHECK(valid()); |
| 367 CHECK_EQ(base::Value::TYPE_DICTIONARY, type()); |
| 368 const PropertiesNode* node = storage_->properties(node_->extra); |
| 369 if (node->additional == kInvalid) |
| 370 return Schema(); |
| 371 return Schema(storage_, storage_->schema(node->additional)); |
| 372 } |
| 373 |
| 374 Schema Schema::GetProperty(const std::string& key) const { |
| 375 Schema schema = GetKnownProperty(key); |
| 376 return schema.valid() ? schema : GetAdditionalProperties(); |
| 377 } |
| 378 |
| 379 Schema Schema::GetItems() const { |
| 380 CHECK(valid()); |
| 381 CHECK_EQ(base::Value::TYPE_LIST, type()); |
| 382 if (node_->extra == kInvalid) |
| 383 return Schema(); |
| 384 return Schema(storage_, storage_->schema(node_->extra)); |
| 385 } |
| 386 |
362 } // namespace policy | 387 } // namespace policy |
OLD | NEW |