Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (C) 2014 Google Inc. | 1 // Copyright (C) 2014 Google Inc. |
| 2 // | 2 // |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); | 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 // you may not use this file except in compliance with the License. | 4 // you may not use this file except in compliance with the License. |
| 5 // You may obtain a copy of the License at | 5 // You may obtain a copy of the License at |
| 6 // | 6 // |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 | 7 // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 // | 8 // |
| 9 // Unless required by applicable law or agreed to in writing, software | 9 // Unless required by applicable law or agreed to in writing, software |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, | 10 // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 // See the License for the specific language governing permissions and | 12 // See the License for the specific language governing permissions and |
| 13 // limitations under the License. | 13 // limitations under the License. |
| 14 | 14 |
| 15 #include "country_rules_aggregator.h" | 15 #include "country_rules_aggregator.h" |
| 16 | 16 |
| 17 #include <libaddressinput/address_field.h> | 17 #include <libaddressinput/address_field.h> |
| 18 #include <libaddressinput/callback.h> | 18 #include <libaddressinput/callback.h> |
| 19 #include <libaddressinput/util/basictypes.h> | 19 #include <libaddressinput/util/basictypes.h> |
| 20 #include <libaddressinput/util/scoped_ptr.h> | 20 #include <libaddressinput/util/scoped_ptr.h> |
| 21 | 21 |
| 22 #include <algorithm> | |
| 22 #include <cassert> | 23 #include <cassert> |
| 23 #include <cstddef> | 24 #include <cstddef> |
| 24 #include <map> | |
| 25 #include <string> | 25 #include <string> |
| 26 #include <utility> | 26 #include <vector> |
| 27 | 27 |
| 28 #include "retriever.h" | 28 #include "retriever.h" |
| 29 #include "rule.h" | 29 #include "rule.h" |
| 30 #include "ruleset.h" | 30 #include "ruleset.h" |
| 31 #include "util/stl_util.h" | 31 #include "util/json.h" |
| 32 | 32 |
| 33 namespace i18n { | 33 namespace i18n { |
| 34 namespace addressinput { | 34 namespace addressinput { |
| 35 | 35 |
| 36 // Information about data requests sent to Retriever. This data is not returned | 36 namespace { |
| 37 // as part of the ruleset, but is useful in constructing the ruleset | 37 |
| 38 // asynchronously. | 38 // Stores the shared data for parsing multiple rules into a ruleset. |
| 39 struct CountryRulesAggregator::RequestData { | 39 class RulesetBuilder { |
| 40 // Does not take ownership of |parent|. | 40 public: |
| 41 RequestData(Ruleset* parent, | 41 explicit RulesetBuilder(scoped_ptr<Json> json) |
| 42 AddressField level, | 42 : json_(json.Pass()), |
| 43 bool is_language_code, | 43 languages_() { |
| 44 const std::string& id) | 44 assert(json_ != NULL); |
| 45 : parent(parent), | |
| 46 level(level), | |
| 47 is_language_code(is_language_code), | |
| 48 id(id) { | |
| 49 assert(parent != NULL || level == COUNTRY); | |
| 50 } | 45 } |
| 51 | 46 |
| 52 ~RequestData() {} | 47 ~RulesetBuilder() {} |
| 53 | 48 |
| 54 // The parent ruleset of the data being downloaded. If NULL, then this is the | 49 // Builds and returns the ruleset for |key| at |field| level. Returns NULL on |
| 55 // root ruleset at the COUNTRY level. The language-specific and sub-region | 50 // failure, e.g. missing sub-region data in JSON. |
| 56 // rules are added to this ruleset. Owned by |CountryRulesRetriever|. | 51 scoped_ptr<Ruleset> Build(const std::string& key, AddressField field) { |
| 57 Ruleset* parent; | 52 scoped_ptr<Rule> rule = ParseRule(key, field); |
| 53 if (rule == NULL) { | |
| 54 return scoped_ptr<Ruleset>(); | |
| 55 } | |
| 58 | 56 |
| 59 // The level of the data being requested. Ranges from COUNTRY to | 57 if (field == COUNTRY) { |
| 60 // DEPENDENT_LOCALITY. If COUNTRY, then the rule should use default rules from | 58 languages_ = rule->GetLanguages(); |
| 61 // Rule::GetDefault(). | 59 std::vector<std::string>::iterator default_language_it = |
| 62 AddressField level; | 60 std::find(languages_.begin(), languages_.end(), rule->GetLanguage()); |
| 61 if (default_language_it != languages_.end()) { | |
| 62 languages_.erase(default_language_it); | |
| 63 } | |
| 64 } | |
| 63 | 65 |
| 64 // If true, then |id| is a language. The data received for this request should | 66 scoped_ptr<Ruleset> ruleset(new Ruleset(field, rule.Pass())); |
| 65 // be placed into a language-specific rule. | |
| 66 bool is_language_code; | |
| 67 | 67 |
| 68 // Can be a region name (e.g. "CA") or a language (e.g. "fr"). Used to add a | 68 for (std::vector<std::string>::const_iterator lang_it = languages_.begin(); |
|
Evan Stade
2014/01/18 19:14:32
please add short comments to each of these loops
please use gerrit instead
2014/01/21 18:43:54
Done.
| |
| 69 // sub-region or a language-specific rule to |parent|. | 69 lang_it != languages_.end(); ++lang_it) { |
| 70 std::string id; | 70 scoped_ptr<Rule> lang_rule = ParseRule(key + "--" + *lang_it, field); |
| 71 if (lang_rule == NULL) { | |
| 72 return scoped_ptr<Ruleset>(); | |
| 73 } | |
| 74 ruleset->AddLanguageCodeRule(*lang_it, lang_rule.Pass()); | |
| 75 } | |
| 76 | |
| 77 if (field == DEPENDENT_LOCALITY) { | |
|
Evan Stade
2014/01/18 19:14:32
why is this necessary? Wouldn't the dependent loca
please use gerrit instead
2014/01/21 18:43:54
True. I've changed this statement to an assert(),
| |
| 78 return ruleset.Pass(); | |
| 79 } | |
| 80 | |
| 81 for (std::vector<std::string>::const_iterator | |
| 82 subkey_it = ruleset->rule().GetSubKeys().begin(); | |
| 83 subkey_it != ruleset->rule().GetSubKeys().end(); ++subkey_it) { | |
| 84 scoped_ptr<Ruleset> sub_ruleset = | |
| 85 Build(key + "/" + *subkey_it, static_cast<AddressField>(field + 1)); | |
| 86 if (sub_ruleset == NULL) { | |
| 87 return scoped_ptr<Ruleset>(); | |
| 88 } | |
| 89 ruleset->AddSubRegionRuleset(*subkey_it, sub_ruleset.Pass()); | |
| 90 } | |
| 91 | |
| 92 return ruleset.Pass(); | |
| 93 } | |
| 94 | |
| 95 private: | |
| 96 // Builds and returns the rule for |key| at |field| level. Returns NULL if | |
| 97 // |key| is not in JSON. | |
| 98 scoped_ptr<Rule> ParseRule(const std::string& key, AddressField field) { | |
| 99 Json* value = NULL; | |
| 100 json_->GetJsonValueForKey(key, &value); | |
|
Evan Stade
2014/01/18 19:14:32
why did you make GetJsonValueForKey return a bool
please use gerrit instead
2014/01/21 18:43:54
Using the bool now.
| |
| 101 if (value == NULL) { | |
| 102 return scoped_ptr<Rule>(); | |
| 103 } | |
| 104 scoped_ptr<Json> value_scoped_ptr(value); | |
| 105 scoped_ptr<Rule> rule(new Rule); | |
| 106 if (field == COUNTRY) { | |
| 107 rule->CopyFrom(Rule::GetDefault()); | |
| 108 } | |
| 109 rule->ParseJsonRule(*value_scoped_ptr); | |
| 110 return rule.Pass(); | |
| 111 } | |
| 112 | |
| 113 // The collection of rules for a country code. | |
| 114 scoped_ptr<Json> json_; | |
| 115 | |
| 116 // The non-default languages that have custom rules. | |
| 117 std::vector<std::string> languages_; | |
|
Evan Stade
2014/01/18 19:14:32
nit: non_default_languages_? other_languages_?
please use gerrit instead
2014/01/21 18:43:54
non_default_languages_.
| |
| 118 | |
| 119 DISALLOW_COPY_AND_ASSIGN(RulesetBuilder); | |
| 71 }; | 120 }; |
| 72 | 121 |
| 122 } // namespace | |
| 123 | |
| 73 CountryRulesAggregator::CountryRulesAggregator(scoped_ptr<Retriever> retriever) | 124 CountryRulesAggregator::CountryRulesAggregator(scoped_ptr<Retriever> retriever) |
| 74 : retriever_(retriever.Pass()), | 125 : retriever_(retriever.Pass()), |
| 75 requests_(), | |
| 76 country_code_(), | 126 country_code_(), |
| 77 rules_ready_(), | 127 key_(), |
| 78 root_(), | 128 rules_ready_() { |
| 79 default_language_(), | |
| 80 languages_() { | |
| 81 assert(retriever_ != NULL); | 129 assert(retriever_ != NULL); |
| 82 } | 130 } |
| 83 | 131 |
| 84 CountryRulesAggregator::~CountryRulesAggregator() {} | 132 CountryRulesAggregator::~CountryRulesAggregator() {} |
| 85 | 133 |
| 86 void CountryRulesAggregator::AggregateRules(const std::string& country_code, | 134 void CountryRulesAggregator::AggregateRules(const std::string& country_code, |
| 87 scoped_ptr<Callback> rules_ready) { | 135 scoped_ptr<Callback> rules_ready) { |
| 88 Reset(); | 136 Reset(); |
| 89 country_code_ = country_code; | 137 country_code_ = country_code; |
| 90 rules_ready_.reset(rules_ready.release()); | 138 rules_ready_.reset(rules_ready.release()); |
| 91 | 139 |
| 92 // Key construction: | 140 // Key construction: |
| 93 // https://code.google.com/p/libaddressinput/wiki/AddressValidationMetadata | 141 // https://code.google.com/p/libaddressinput/wiki/AddressValidationMetadata |
| 94 // Example of a country-level key: "data/CA". | 142 // Example of a country-level key: "data/CA". |
| 95 std::string key = "data/" + country_code_; | 143 key_ = "data/" + country_code_; |
| 96 requests_.insert(std::make_pair( | |
| 97 key, RequestData(NULL, COUNTRY, false, std::string()))); | |
| 98 | |
| 99 retriever_->Retrieve( | 144 retriever_->Retrieve( |
| 100 key, BuildCallback(this, &CountryRulesAggregator::OnDataReady)); | 145 key_, BuildCallback(this, &CountryRulesAggregator::OnDataReady)); |
| 101 } | 146 } |
| 102 | 147 |
| 103 void CountryRulesAggregator::OnDataReady(bool success, | 148 void CountryRulesAggregator::OnDataReady(bool success, |
| 104 const std::string& key, | 149 const std::string& key, |
| 105 const std::string& data) { | 150 const std::string& data) { |
| 106 std::map<std::string, RequestData>::iterator request_it = | 151 if (key != key_) { |
| 107 requests_.find(key); | |
| 108 if (request_it == requests_.end()) { | |
| 109 return; // An abandoned request. | 152 return; // An abandoned request. |
| 110 } | 153 } |
| 111 | 154 |
| 112 if (!success) { | 155 scoped_ptr<Json> json(Json::Build()); |
| 156 if (!success || !json->ParseObject(data)) { | |
| 113 (*rules_ready_)(false, country_code_, scoped_ptr<Ruleset>()); | 157 (*rules_ready_)(false, country_code_, scoped_ptr<Ruleset>()); |
| 114 Reset(); | 158 Reset(); |
| 115 return; | 159 return; |
| 116 } | 160 } |
| 117 | 161 |
| 118 RequestData request = request_it->second; | 162 RulesetBuilder builder(json.Pass()); |
| 119 requests_.erase(request_it); | 163 scoped_ptr<Ruleset> ruleset = builder.Build(key_, COUNTRY); |
| 120 | 164 (*rules_ready_)(ruleset != NULL, country_code_, ruleset.Pass()); |
| 121 // All country-level rules are based on the default rule. | 165 Reset(); |
| 122 scoped_ptr<Rule> rule(new Rule); | |
| 123 if (request.level == COUNTRY) { | |
| 124 rule->CopyFrom(Rule::GetDefault()); | |
| 125 } | |
| 126 | |
| 127 if (!rule->ParseSerializedRule(data)) { | |
| 128 (*rules_ready_)(false, country_code_, scoped_ptr<Ruleset>()); | |
| 129 Reset(); | |
| 130 return; | |
| 131 } | |
| 132 | |
| 133 // Place the rule in the correct location in the ruleset. | |
| 134 Ruleset* ruleset = NULL; | |
| 135 if (request.is_language_code) { | |
| 136 assert(request.parent != NULL); | |
| 137 request.parent->AddLanguageCodeRule(request.id, rule.Pass()); | |
| 138 ruleset = request.parent; | |
| 139 } else if (request.level == COUNTRY) { | |
| 140 // The default language and all supported languages for the country code are | |
| 141 // in the country-level rule without a language code identifier. For | |
| 142 // example: "data/CA". | |
| 143 default_language_ = rule->GetLanguage(); | |
| 144 languages_ = rule->GetLanguages(); | |
| 145 | |
| 146 root_.reset(new Ruleset(request.level, rule.Pass())); | |
| 147 ruleset = root_.get(); | |
| 148 } else { | |
| 149 assert(request.parent != NULL); | |
| 150 ruleset = new Ruleset(request.level, rule.Pass()); | |
| 151 request.parent->AddSubRegionRuleset( | |
| 152 request.id, scoped_ptr<Ruleset>(ruleset)); | |
| 153 } | |
| 154 | |
| 155 if (!request.is_language_code) { | |
| 156 // Retrieve the language-specific rules for this region. | |
| 157 for (std::vector<std::string>::const_iterator | |
| 158 lang_it = languages_.begin(); | |
| 159 lang_it != languages_.end(); | |
| 160 ++lang_it) { | |
| 161 if (*lang_it == default_language_) { | |
| 162 continue; | |
| 163 } | |
| 164 // Example of a language-specific key: "data/CA--fr". | |
| 165 std::string language_code_key = key + "--" + *lang_it; | |
| 166 requests_.insert(std::make_pair( | |
| 167 key, RequestData(ruleset, request.level, true, *lang_it))); | |
| 168 retriever_->Retrieve( | |
| 169 language_code_key, | |
| 170 BuildCallback(this, &CountryRulesAggregator::OnDataReady)); | |
| 171 } | |
| 172 | |
| 173 if (request.level < DEPENDENT_LOCALITY) { | |
| 174 // Retrieve the sub-region rules for this region. | |
| 175 for (std::vector<std::string>::const_iterator | |
| 176 subkey_it = ruleset->rule().GetSubKeys().begin(); | |
| 177 subkey_it != ruleset->rule().GetSubKeys().end(); | |
| 178 ++subkey_it) { | |
| 179 // Example of a sub-region key: "data/CA/AB". | |
| 180 std::string sub_region_key = key + "/" + *subkey_it; | |
| 181 requests_.insert(std::make_pair( | |
| 182 key, | |
| 183 RequestData(ruleset, | |
| 184 static_cast<AddressField>(request.level + 1), | |
| 185 false, | |
| 186 *subkey_it))); | |
| 187 retriever_->Retrieve( | |
| 188 sub_region_key, | |
| 189 BuildCallback(this, &CountryRulesAggregator::OnDataReady)); | |
| 190 } | |
| 191 } | |
| 192 } | |
| 193 | |
| 194 if (requests_.empty()) { | |
| 195 (*rules_ready_)(true, country_code_, root_.Pass()); | |
| 196 Reset(); | |
| 197 } | |
| 198 } | 166 } |
| 199 | 167 |
| 200 void CountryRulesAggregator::Reset() { | 168 void CountryRulesAggregator::Reset() { |
| 201 requests_.clear(); | |
| 202 country_code_.clear(); | 169 country_code_.clear(); |
| 170 key_.clear(); | |
| 203 rules_ready_.reset(); | 171 rules_ready_.reset(); |
| 204 root_.reset(); | |
| 205 default_language_.clear(); | |
| 206 languages_.clear(); | |
| 207 } | 172 } |
| 208 | 173 |
| 209 } // namespace addressinput | 174 } // namespace addressinput |
| 210 } // namespace i18n | 175 } // namespace i18n |
| OLD | NEW |