1 // Copyright (C) 2014 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 #include <libaddressinput/preload_supplier.h>
16
17 #include <libaddressinput/address_data.h>
18 #include <libaddressinput/address_field.h>
19 #include <libaddressinput/callback.h>
20 #include <libaddressinput/supplier.h>
21 #include <libaddressinput/util/basictypes.h>
22 #include <libaddressinput/util/scoped_ptr.h>
23
24 #include <algorithm>
25 #include <cassert>
26 #include <cstddef>
27 #include <functional>
28 #include <map>
29 #include <set>
30 #include <stack>
31 #include <string>
32 #include <utility>
33 #include <vector>
34
35 #include "language.h"
36 #include "lookup_key.h"
37 #include "region_data_constants.h"
38 #include "retriever.h"
39 #include "rule.h"
40 #include "util/json.h"
41 #include "util/string_compare.h"
42
43 namespace i18n {
44 namespace addressinput {
45
46 namespace {
47
48 // STL predicate less<> that uses StringCompare to match strings that a human
49 // reader would consider to be "the same". The default implementation just does
50 // case insensitive string comparison, but StringCompare can be overriden with
51 // more sophisticated implementations.
52 class IndexLess : public std::binary_function<std::string, std::string, bool> {
53 public:
operator ()(const first_argument_type & a,const second_argument_type & b) const54 result_type operator()(const first_argument_type& a,
55 const second_argument_type& b) const {
56 return kStringCompare.NaturalLess(a, b);
57 }
58
59 private:
60 static const StringCompare kStringCompare;
61 };
62
63 const StringCompare IndexLess::kStringCompare;
64
65 } // namespace
66
67 class IndexMap : public std::map<std::string, const Rule*, IndexLess> {};
68
69 namespace {
70
71 class Helper {
72 public:
73 // Does not take ownership of its parameters.
Helper(const std::string & region_code,const std::string & key,const PreloadSupplier::Callback & loaded,const Retriever & retriever,std::set<std::string> * pending,IndexMap * rule_index,std::vector<const Rule * > * rule_storage)74 Helper(const std::string& region_code,
75 const std::string& key,
76 const PreloadSupplier::Callback& loaded,
77 const Retriever& retriever,
78 std::set<std::string>* pending,
79 IndexMap* rule_index,
80 std::vector<const Rule*>* rule_storage)
81 : region_code_(region_code),
82 loaded_(loaded),
83 pending_(pending),
84 rule_index_(rule_index),
85 rule_storage_(rule_storage),
86 retrieved_(BuildCallback(this, &Helper::OnRetrieved)) {
87 assert(pending_ != NULL);
88 assert(rule_index_ != NULL);
89 assert(rule_storage_ != NULL);
90 assert(retrieved_ != NULL);
91 pending_->insert(key);
92 retriever.Retrieve(key, *retrieved_);
93 }
94
95 private:
~Helper()96 ~Helper() {}
97
OnRetrieved(bool success,const std::string & key,const std::string & data)98 void OnRetrieved(bool success,
99 const std::string& key,
100 const std::string& data) {
101 int rule_count = 0;
102
103 size_t status = pending_->erase(key);
104 assert(status == 1); // There will always be one item erased from the set.
105 (void)status; // Prevent unused variable if assert() is optimized away.
106
107 Json json;
108 std::vector<const Rule*> sub_rules;
109
110 if (!success) {
111 goto callback;
112 }
113
114 if (!json.ParseObject(data)) {
115 success = false;
116 goto callback;
117 }
118
119 for (std::vector<std::string>::const_iterator
120 it = json.GetKeys().begin(); it != json.GetKeys().end(); ++it) {
121 if (!json.HasDictionaryValueForKey(*it)) {
122 success = false;
123 goto callback;
124 }
125 const Json& value = json.GetDictionaryValueForKey(*it);
126
127 if (!value.HasStringValueForKey("id")) {
128 success = false;
129 goto callback;
130 }
131 const std::string& id = value.GetStringValueForKey("id");
132 assert(*it == id); // Sanity check.
133
134 size_t depth = std::count(id.begin(), id.end(), '/') - 1;
135 assert(depth < arraysize(LookupKey::kHierarchy));
136 AddressField field = LookupKey::kHierarchy[depth];
137
138 Rule* rule = new Rule;
139 if (field == COUNTRY) {
140 // All rules on the COUNTRY level inherit from the default rule.
141 rule->CopyFrom(Rule::GetDefault());
142 }
143 rule->ParseJsonRule(value);
144 assert(id == rule->GetId()); // Sanity check.
145
146 rule_storage_->push_back(rule);
147 if (depth > 0) {
148 sub_rules.push_back(rule);
149 }
150
151 // Add the ID of this Rule object to the rule index.
152 std::pair<IndexMap::iterator, bool> result =
153 rule_index_->insert(std::make_pair(id, rule));
154 assert(result.second);
155 (void)result; // Prevent unused variable if assert() is optimized away.
156
157 ++rule_count;
158 }
159
160 /*
161 * Normally the address metadata server takes care of mapping from natural
162 * language names to metadata IDs (eg. "São Paulo" -> "SP") and from Latin
163 * script names to local script names (eg. "Tokushima" -> "徳島県").
164 *
165 * As the PreloadSupplier doesn't contact the metadata server upon each
166 * Supply() request, it instead has an internal lookup table (rule_index_)
167 * that contains such mappings.
168 *
169 * This lookup table is populated by iterating over all sub rules and for
170 * each of them construct ID strings using human readable names (eg. "São
171 * Paulo") and using Latin script names (eg. "Tokushima").
172 */
173 for (std::vector<const Rule*>::const_iterator
174 it = sub_rules.begin(); it != sub_rules.end(); ++it) {
175 std::stack<const Rule*> hierarchy;
176 hierarchy.push(*it);
177
178 // Push pointers to all parent Rule objects onto the hierarchy stack.
179 for (std::string parent_id((*it)->GetId());;) {
180 // Strip the last part of parent_id. Break if COUNTRY level is reached.
181 std::string::size_type pos = parent_id.rfind('/');
182 if (pos == sizeof "data/ZZ" - 1) {
183 break;
184 }
185 parent_id.resize(pos);
186
187 IndexMap::const_iterator jt = rule_index_->find(parent_id);
188 assert(jt != rule_index_->end());
189 hierarchy.push(jt->second);
190 }
191
192 std::string human_id((*it)->GetId().substr(0, sizeof "data/ZZ" - 1));
193 std::string latin_id(human_id);
194
195 // Append the names from all Rule objects on the hierarchy stack.
196 for (; !hierarchy.empty(); hierarchy.pop()) {
197 const Rule* rule = hierarchy.top();
198
199 human_id.push_back('/');
200 if (!rule->GetName().empty()) {
201 human_id.append(rule->GetName());
202 } else {
203 // If the "name" field is empty, the name is the last part of the ID.
204 const std::string& id = rule->GetId();
205 std::string::size_type pos = id.rfind('/');
206 assert(pos != std::string::npos);
207 human_id.append(id.substr(pos + 1));
208 }
209
210 if (!rule->GetLatinName().empty()) {
211 latin_id.push_back('/');
212 latin_id.append(rule->GetLatinName());
213 }
214 }
215
216 // If the ID has a language tag, copy it.
217 {
218 const std::string& id = (*it)->GetId();
219 std::string::size_type pos = id.rfind("--");
220 if (pos != std::string::npos) {
221 human_id.append(id, pos, id.size() - pos);
222 }
223 }
224
225 rule_index_->insert(std::make_pair(human_id, *it));
226
227 // Add the Latin script ID, if a Latin script name could be found for
228 // every part of the ID.
229 if (std::count(human_id.begin(), human_id.end(), '/') ==
230 std::count(latin_id.begin(), latin_id.end(), '/')) {
231 rule_index_->insert(std::make_pair(latin_id, *it));
232 }
233 }
234
235 callback:
236 loaded_(success, region_code_, rule_count);
237 delete this;
238 }
239
240 const std::string region_code_;
241 const PreloadSupplier::Callback& loaded_;
242 std::set<std::string>* const pending_;
243 IndexMap* const rule_index_;
244 std::vector<const Rule*>* const rule_storage_;
245 const scoped_ptr<const Retriever::Callback> retrieved_;
246
247 DISALLOW_COPY_AND_ASSIGN(Helper);
248 };
249
KeyFromRegionCode(const std::string & region_code)250 std::string KeyFromRegionCode(const std::string& region_code) {
251 AddressData address;
252 address.region_code = region_code;
253 LookupKey lookup_key;
254 lookup_key.FromAddress(address);
255 return lookup_key.ToKeyString(0); // Zero depth = COUNTRY level.
256 }
257
258 } // namespace
259
PreloadSupplier(const std::string & validation_data_url,const Downloader * downloader,Storage * storage)260 PreloadSupplier::PreloadSupplier(const std::string& validation_data_url,
261 const Downloader* downloader,
262 Storage* storage)
263 : retriever_(new Retriever(validation_data_url, downloader, storage)),
264 pending_(),
265 rule_index_(new IndexMap),
266 rule_storage_() {}
267
~PreloadSupplier()268 PreloadSupplier::~PreloadSupplier() {
269 for (std::vector<const Rule*>::const_iterator
270 it = rule_storage_.begin(); it != rule_storage_.end(); ++it) {
271 delete *it;
272 }
273 }
274
Supply(const LookupKey & lookup_key,const Supplier::Callback & supplied)275 void PreloadSupplier::Supply(const LookupKey& lookup_key,
276 const Supplier::Callback& supplied) {
277 Supplier::RuleHierarchy hierarchy;
278 bool success = GetRuleHierarchy(lookup_key, &hierarchy);
279 supplied(success, lookup_key, hierarchy);
280 }
281
GetRule(const LookupKey & lookup_key) const282 const Rule* PreloadSupplier::GetRule(const LookupKey& lookup_key) const {
283 assert(IsLoaded(lookup_key.GetRegionCode()));
284 Supplier::RuleHierarchy hierarchy;
285 if (!GetRuleHierarchy(lookup_key, &hierarchy)) {
286 return NULL;
287 }
288 return hierarchy.rule[lookup_key.GetDepth()];
289 }
290
LoadRules(const std::string & region_code,const Callback & loaded)291 void PreloadSupplier::LoadRules(const std::string& region_code,
292 const Callback& loaded) {
293 const std::string& key = KeyFromRegionCode(region_code);
294
295 if (IsLoadedKey(key)) {
296 loaded(true, region_code, 0);
297 return;
298 }
299
300 if (IsPendingKey(key)) {
301 return;
302 }
303
304 new Helper(
305 region_code,
306 key,
307 loaded,
308 *retriever_,
309 &pending_,
310 rule_index_.get(),
311 &rule_storage_);
312 }
313
IsLoaded(const std::string & region_code) const314 bool PreloadSupplier::IsLoaded(const std::string& region_code) const {
315 return IsLoadedKey(KeyFromRegionCode(region_code));
316 }
317
IsPending(const std::string & region_code) const318 bool PreloadSupplier::IsPending(const std::string& region_code) const {
319 return IsPendingKey(KeyFromRegionCode(region_code));
320 }
321
GetRuleHierarchy(const LookupKey & lookup_key,RuleHierarchy * hierarchy) const322 bool PreloadSupplier::GetRuleHierarchy(const LookupKey& lookup_key,
323 RuleHierarchy* hierarchy) const {
324 assert(hierarchy != NULL);
325
326 if (RegionDataConstants::IsSupported(lookup_key.GetRegionCode())) {
327 size_t max_depth = std::min(
328 lookup_key.GetDepth(),
329 RegionDataConstants::GetMaxLookupKeyDepth(lookup_key.GetRegionCode()));
330
331 for (size_t depth = 0; depth <= max_depth; ++depth) {
332 const std::string& key = lookup_key.ToKeyString(depth);
333 IndexMap::const_iterator it = rule_index_->find(key);
334 if (it == rule_index_->end()) {
335 return depth > 0; // No data on COUNTRY level is failure.
336 }
337 hierarchy->rule[depth] = it->second;
338 }
339 }
340
341 return true;
342 }
343
IsLoadedKey(const std::string & key) const344 bool PreloadSupplier::IsLoadedKey(const std::string& key) const {
345 return rule_index_->find(key) != rule_index_->end();
346 }
347
IsPendingKey(const std::string & key) const348 bool PreloadSupplier::IsPendingKey(const std::string& key) const {
349 return pending_.find(key) != pending_.end();
350 }
351
352 } // namespace addressinput
353 } // namespace i18n
354