blob: 6b282e599bf3320d86dbc420126ff053bbac8cf6 [file] [log] [blame]
// Copyright (C) 2014 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ondemand_supply_task.h"
#include <libaddressinput/address_field.h>
#include <libaddressinput/callback.h>
#include <libaddressinput/supplier.h>
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <map>
#include <string>
#include "lookup_key.h"
#include "retriever.h"
#include "rule.h"
#include "util/size.h"
namespace i18n {
namespace addressinput {
OndemandSupplyTask::OndemandSupplyTask(
const LookupKey& lookup_key,
std::map<std::string, const Rule*>* rules,
const Supplier::Callback& supplied)
: hierarchy_(),
pending_(),
lookup_key_(lookup_key),
rule_cache_(rules),
supplied_(supplied),
retrieved_(BuildCallback(this, &OndemandSupplyTask::Load)),
success_(true) {
assert(rule_cache_ != nullptr);
assert(retrieved_ != nullptr);
}
OndemandSupplyTask::~OndemandSupplyTask() = default;
void OndemandSupplyTask::Queue(const std::string& key) {
assert(pending_.find(key) == pending_.end());
pending_.insert(key);
}
void OndemandSupplyTask::Retrieve(const Retriever& retriever) {
if (pending_.empty()) {
Loaded();
} else {
// When the final pending rule has been retrieved, the retrieved_ callback,
// implemented by Load(), will finish by calling Loaded(), which will finish
// by delete'ing this OndemandSupplyTask object. So after the final call to
// retriever.Retrieve() no attributes of this object can be accessed (as the
// object then no longer will exist, if the final callback has finished by
// then), and the condition statement of the loop must therefore not use the
// otherwise obvious it != pending_.end() but instead test a local variable
// that isn't affected by the object being deleted.
bool done = false;
for (auto it = pending_.begin(); !done;) {
const std::string& key = *it++;
done = it == pending_.end();
retriever.Retrieve(key, *retrieved_);
}
}
}
void OndemandSupplyTask::Load(bool success,
const std::string& key,
const std::string& data) {
size_t depth = std::count(key.begin(), key.end(), '/') - 1;
assert(depth < size(LookupKey::kHierarchy));
// Sanity check: This key should be present in the set of pending requests.
size_t status = pending_.erase(key);
assert(status == 1); // There will always be one item erased from the set.
(void)status; // Prevent unused variable if assert() is optimized away.
if (success) {
// The address metadata server will return the empty JSON "{}" when it
// successfully performed a lookup, but didn't find any data for that key.
if (data != "{}") {
auto* rule = new Rule;
if (LookupKey::kHierarchy[depth] == COUNTRY) {
// All rules on the COUNTRY level inherit from the default rule.
rule->CopyFrom(Rule::GetDefault());
}
if (rule->ParseSerializedRule(data)) {
// Try inserting the Rule object into the rule_cache_ map, or else find
// the already existing Rule object with the same ID already in the map.
// It is possible that a key was queued even though the corresponding
// Rule object is already in the cache, as the data server is free to do
// advanced normalization and aliasing so that the ID of the data
// returned is different from the key requested. (It would be possible
// to cache such aliases, to increase performance in the case where a
// certain alias is requested repeatedly, but such a cache would then
// have to be kept to some limited size to not grow indefinitely with
// every possible permutation of a name recognized by the data server.)
auto result = rule_cache_->emplace(rule->GetId(), rule);
if (!result.second) { // There was already an entry with this ID.
delete rule;
}
// Pointer to object in the map.
hierarchy_.rule[depth] = result.first->second;
} else {
delete rule;
success_ = false;
}
}
} else {
success_ = false;
}
if (pending_.empty()) {
Loaded();
}
}
void OndemandSupplyTask::Loaded() {
supplied_(success_, lookup_key_, hierarchy_);
delete this;
}
} // namespace addressinput
} // namespace i18n