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