• 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 "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