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 "ondemand_supply_task.h"
16
17#include <libaddressinput/address_field.h>
18#include <libaddressinput/callback.h>
19#include <libaddressinput/supplier.h>
20#include <libaddressinput/util/basictypes.h>
21
22#include <algorithm>
23#include <cassert>
24#include <cstddef>
25#include <map>
26#include <set>
27#include <string>
28#include <utility>
29
30#include "lookup_key.h"
31#include "retriever.h"
32#include "rule.h"
33
34namespace i18n {
35namespace addressinput {
36
37OndemandSupplyTask::OndemandSupplyTask(
38    const LookupKey& lookup_key,
39    std::map<std::string, const Rule*>* rules,
40    const Supplier::Callback& supplied)
41    : hierarchy_(),
42      pending_(),
43      lookup_key_(lookup_key),
44      rule_cache_(rules),
45      supplied_(supplied),
46      retrieved_(BuildCallback(this, &OndemandSupplyTask::Load)),
47      success_(true) {
48  assert(rule_cache_ != NULL);
49  assert(retrieved_ != NULL);
50}
51
52OndemandSupplyTask::~OndemandSupplyTask() {
53}
54
55void OndemandSupplyTask::Queue(const std::string& key) {
56  assert(pending_.find(key) == pending_.end());
57  pending_.insert(key);
58}
59
60void OndemandSupplyTask::Retrieve(const Retriever& retriever) {
61  if (pending_.empty()) {
62    Loaded();
63  } else {
64    // When the final pending rule has been retrieved, the retrieved_ callback,
65    // implemented by Load(), will finish by calling Loaded(), which will finish
66    // by delete'ing this OndemandSupplyTask object. So after the final call to
67    // retriever.Retrieve() no attributes of this object can be accessed (as the
68    // object then no longer will exist, if the final callback has finished by
69    // then), and the condition statement of the loop must therefore not use the
70    // otherwise obvious it != pending_.end() but instead test a local variable
71    // that isn't affected by the object being deleted.
72    bool done = false;
73    for (std::set<std::string>::const_iterator it = pending_.begin(); !done; ) {
74      const std::string& key = *it++;
75      done = it == pending_.end();
76      retriever.Retrieve(key, *retrieved_);
77    }
78  }
79}
80
81void OndemandSupplyTask::Load(bool success,
82                              const std::string& key,
83                              const std::string& data) {
84  size_t depth = std::count(key.begin(), key.end(), '/') - 1;
85  assert(depth < arraysize(LookupKey::kHierarchy));
86
87  // Sanity check: This key should be present in the set of pending requests.
88  size_t status = pending_.erase(key);
89  assert(status == 1);  // There will always be one item erased from the set.
90  (void)status;  // Prevent unused variable if assert() is optimized away.
91
92  if (success) {
93    // The address metadata server will return the empty JSON "{}" when it
94    // successfully performed a lookup, but didn't find any data for that key.
95    if (data != "{}") {
96      Rule* rule = new Rule;
97      if (LookupKey::kHierarchy[depth] == COUNTRY) {
98        // All rules on the COUNTRY level inherit from the default rule.
99        rule->CopyFrom(Rule::GetDefault());
100      }
101      if (rule->ParseSerializedRule(data)) {
102        // Try inserting the Rule object into the rule_cache_ map, or else find
103        // the already existing Rule object with the same ID already in the map.
104        // It is possible that a key was queued even though the corresponding
105        // Rule object is already in the cache, as the data server is free to do
106        // advanced normalization and aliasing so that the ID of the data
107        // returned is different from the key requested. (It would be possible
108        // to cache such aliases, to increase performance in the case where a
109        // certain alias is requested repeatedly, but such a cache would then
110        // have to be kept to some limited size to not grow indefinitely with
111        // every possible permutation of a name recognized by the data server.)
112        std::pair<std::map<std::string, const Rule*>::iterator, bool> result =
113            rule_cache_->insert(std::make_pair(rule->GetId(), rule));
114        if (!result.second) {  // There was already an entry with this ID.
115          delete rule;
116        }
117        // Pointer to object in the map.
118        hierarchy_.rule[depth] = result.first->second;
119      } else {
120        delete rule;
121        success_ = false;
122      }
123    }
124  } else {
125    success_ = false;
126  }
127
128  if (pending_.empty()) {
129    Loaded();
130  }
131}
132
133void OndemandSupplyTask::Loaded() {
134  supplied_(success_, lookup_key_, hierarchy_);
135  delete this;
136}
137
138}  // namespace addressinput
139}  // namespace i18n
140