OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (C) 2013 Google Inc. | |
2 // | |
3 // Licensed under the Apache License, Version 2.0 (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 | |
6 // | |
7 // http://www.apache.org/licenses/LICENSE-2.0 | |
8 // | |
9 // Unless required by applicable law or agreed to in writing, software | |
10 // distributed under the License is distributed on an "AS IS" BASIS, | |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 // See the License for the specific language governing permissions and | |
13 // limitations under the License. | |
14 // | |
15 // CountryRulesRetriever uses Helper to download Rulesets (a tree of rules) for | |
16 // country codes. Helper builds a Ruleset and passes it to the | |
17 // CountryRulesRetriever::Callback in a scoped pointer. Helper uses a map of | |
18 // lookup keys (e.g. "data/CA/AB--fr") to RequestData objects to keep track of | |
19 // how the Ruleset should be built. | |
20 | |
21 #include "country_rules_retriever.h" | |
22 | |
23 #include <libaddressinput/address_field.h> | |
24 #include <libaddressinput/callback.h> | |
25 #include <libaddressinput/util/basictypes.h> | |
26 #include <libaddressinput/util/scoped_ptr.h> | |
27 | |
28 #include <cassert> | |
29 #include <cstddef> | |
30 #include <string> | |
31 | |
32 #include "retriever.h" | |
33 #include "rule.h" | |
34 #include "ruleset.h" | |
35 | |
36 namespace i18n { | |
37 namespace addressinput { | |
38 | |
39 namespace { | |
40 | |
41 // Stores auxiliary information about data requests sent to Retriever. This data | |
42 // is not returned as part of the ruleset, but is useful in constructing the | |
43 // ruleset asynchronously. | |
44 struct RequestData { | |
45 // Does not take ownership of |parent|. | |
46 RequestData(Ruleset* parent, | |
47 AddressField level, | |
48 bool is_language_code, | |
49 const std::string& id) | |
50 : parent(parent), | |
51 level(level), | |
52 is_language_code(is_language_code), | |
53 id(id) {} | |
54 ~RequestData() {} | |
55 | |
56 // The parent ruleset of the data being downloaded. If NULL, then this is the | |
57 // root ruleset at the COUNTRY level. The language-specific and sub-region | |
58 // rules are added to this ruleset. Owned by |Helper|. | |
59 Ruleset* parent; | |
60 | |
61 // The level of the data being requested. Ranges from COUNTRY to | |
62 // DEPENDENT_LOCALITY. If COUNTRY, then the rule should use default rules from | |
63 // Rule::GetDefault(). | |
64 AddressField level; | |
65 | |
66 // If true, then |id| is a language. The data received for this request should | |
67 // be placed into a language-specific rule. | |
68 bool is_language_code; | |
69 | |
70 // Can be a region name (e.g. "CA") or a language (e.g. "fr"). Used to add a | |
71 // sub-region or a language-specific rule to |parent|. | |
72 std::string id; | |
73 }; | |
74 | |
75 // Retrieves a ruleset for a single country code. | |
76 class Helper { | |
77 public: | |
78 // Uses |retriever| to recursively retrieve the ruleset for |country_code|. | |
79 // Invokes |rules_ready| callback when done. | |
80 Helper(const std::string& country_code, | |
81 scoped_ptr<CountryRulesRetriever::Callback> rules_ready, | |
82 const Retriever& retriever) | |
83 : country_code_(country_code), | |
84 rules_ready_(rules_ready.Pass()), | |
85 retriever_(retriever), | |
86 root_(), | |
87 num_requested_(1), | |
88 num_received_(0), | |
89 success_(true), | |
90 default_language_(), | |
91 languages_(), | |
92 requests_() { | |
93 // Key construction: | |
94 // https://code.google.com/p/libaddressinput/wiki/AddressValidationMetadata | |
95 // Example of a country-level key: "data/CA". | |
96 std::string key = "data/" + country_code_; | |
97 requests_.insert(std::make_pair( | |
98 key, RequestData(NULL, COUNTRY, false, std::string()))); | |
99 retriever_.Retrieve(key, BuildCallback(this, &Helper::OnDataReady)); | |
100 } | |
101 | |
102 private: | |
103 ~Helper() {} | |
104 | |
105 void OnDataReady(bool success, | |
106 const std::string& key, | |
107 const std::string& data) { | |
108 success_ &= success; | |
109 ++num_received_; | |
110 | |
111 std::map<std::string, RequestData>::iterator request_it = | |
112 requests_.find(key); | |
113 assert(request_it != requests_.end()); | |
114 RequestData& request = request_it->second; | |
115 | |
116 // All country-level rules are based on the default rule. | |
117 scoped_ptr<Rule> rule(new Rule); | |
118 if (request.level == COUNTRY) { | |
119 rule->CopyFrom(Rule::GetDefault()); | |
120 } | |
121 | |
122 success &= rule->ParseSerializedRule(data); | |
123 | |
124 // The default language and all supported languages for the country code are | |
125 // in the country-level rule without a language code identifier. For | |
126 // example: "data/CA". | |
127 if (request.level == COUNTRY && !request.is_language_code) { | |
128 default_language_ = rule->GetLanguage(); | |
129 languages_ = rule->GetLanguages(); | |
130 } | |
131 | |
132 Ruleset* ruleset = NULL; | |
133 if (request.level == COUNTRY) { | |
134 root_.reset(new Ruleset(rule.Pass())); | |
135 ruleset = root_.get(); | |
136 } else if (request.is_language_code) { | |
137 assert(request.parent != NULL); | |
138 request.parent->AddLanguageCode(request.id, rule.Pass()); | |
139 // The |ruleset| is intentionally NULL. | |
140 } else { | |
141 assert(request.parent != NULL); | |
142 ruleset = request.parent->AddSubRegion(request.id, rule.Pass()); | |
143 } | |
144 | |
145 if (!request.is_language_code) { | |
146 // Retrieve the language-specific rules for this region. | |
147 for (std::vector<std::string>::const_iterator | |
148 lang_it = languages_.begin(); | |
149 lang_it != languages_.end(); | |
150 ++lang_it) { | |
151 if (*lang_it != default_language_) { | |
152 // Example of a language-specific key: "data/CA--fr". | |
153 std::string language_code_key = key + "--" + *lang_it; | |
154 requests_.insert(std::make_pair( | |
155 key, RequestData(ruleset, request.level, true, *lang_it))); | |
156 ++num_requested_; | |
157 retriever_.Retrieve( | |
158 language_code_key, BuildCallback(this, &Helper::OnDataReady)); | |
159 } | |
160 } | |
161 | |
162 if (request.level < DEPENDENT_LOCALITY) { | |
163 assert(ruleset->GetRule() != NULL); | |
164 // Retrieve the sub-region rules for this region. | |
165 for (std::vector<std::string>::const_iterator | |
166 subkey_it = ruleset->GetRule()->GetSubKeys().begin(); | |
167 subkey_it != ruleset->GetRule()->GetSubKeys().end(); | |
168 ++subkey_it) { | |
169 // Example of a sub-region key: "data/CA/AB". | |
170 std::string sub_region_key = key + "/" + *subkey_it; | |
171 requests_.insert(std::make_pair( | |
172 key, | |
173 RequestData(ruleset, | |
174 static_cast<AddressField>(request.level + 1), | |
175 false, | |
176 *subkey_it))); | |
177 ++num_requested_; | |
178 retriever_.Retrieve( | |
179 sub_region_key, BuildCallback(this, &Helper::OnDataReady)); | |
180 } | |
181 } | |
182 } | |
183 | |
184 if (num_received_ == num_requested_) { | |
185 (*rules_ready_)(success_, country_code_, root_.Pass()); | |
186 delete this; | |
187 } | |
188 } | |
189 | |
190 // The country code for which to retrieve the ruleset. Passed to the callback | |
191 // method to identify the ruleset. Examples: "US", "CA", "CH", etc. | |
192 const std::string country_code_; | |
193 | |
194 // The callback to invoke when the ruleset has been retrieved. | |
195 scoped_ptr<CountryRulesRetriever::Callback> rules_ready_; | |
196 | |
197 // The data retriever that can download serialized rules for one sub-region at | |
198 // a time. | |
199 const Retriever& retriever_; | |
200 | |
201 // The top-level ruleset for the country code. Passed to the callback method | |
202 // as the result of the query. | |
203 scoped_ptr<Ruleset> root_; | |
204 | |
205 // The number of requests sent to Retriever. Used to keep track of pending | |
206 // requests. | |
207 int num_requested_; | |
208 | |
209 // The number of responses received from Retriever. Used to determine when the | |
210 // ruleset construction is finished, by comparing this number to | |
211 // |num_requested_|. | |
212 int num_received_; | |
213 | |
214 // True if all requests to Retriever succeeded. Passed to the callback method | |
215 // as an indication of whether ruleset retrieval succeeded. | |
216 bool success_; | |
217 | |
218 // The default language for the country code. This value is parsed from the | |
219 // country-level rule for the country code and is used to filter out the | |
220 // default language from the list of all supported languages for a country. | |
221 // For example, the list of supported languages for Canada is ["en", "fr"], | |
222 // but the default language is "en". Data requests for "data/CA/AB--fr" will | |
223 // succeed, but "data/CA/AB--en" will not return data. | |
224 std::string default_language_; | |
225 | |
226 // The list of all supported languages for the country code. This value is | |
227 // parsed from the country-level rule for the country and is used to download | |
228 // language-specific rules. | |
229 std::vector<std::string> languages_; | |
230 | |
231 // A map of data keys (e.g., "data/CA/AB--fr") to information that will help | |
232 // to parse the response data and place it in the correct location in the | |
233 // ruleset. For example, the RequestData for "data/CA/AB--fr" will specify | |
234 // that the retrieved rule is the "fr" language-specific rule for "data/CA/AB" | |
235 // ruleset. | |
236 std::map<std::string, RequestData> requests_; | |
237 | |
238 DISALLOW_COPY_AND_ASSIGN(Helper); | |
239 }; | |
240 | |
241 } // namespace | |
242 | |
243 CountryRulesRetriever::CountryRulesRetriever( | |
244 scoped_ptr<const Retriever> retriever) | |
245 : retriever_(retriever.Pass()) { | |
246 assert(retriever_ != NULL); | |
247 } | |
248 | |
249 CountryRulesRetriever::~CountryRulesRetriever() {} | |
250 | |
251 void CountryRulesRetriever::RetrieveRules( | |
252 const std::string& country_code, | |
253 scoped_ptr<Callback> rules_ready) const { | |
254 new Helper(country_code, rules_ready.Pass(), *retriever_); | |
Evan Stade
2014/01/06 22:54:19
this seems dangerous. How do you know Helper won't
please use gerrit instead
2014/01/06 23:44:35
This helper uses the same safety measure as all ot
Evan Stade
2014/01/07 00:03:15
Recall how I wanted to make it a rule that you onl
please use gerrit instead
2014/01/07 01:54:38
Done.
| |
255 } | |
256 | |
257 } // namespace addressinput | |
258 } // namespace i18n | |
OLD | NEW |