Commit edf37bf1 authored by rouslan@chromium.org's avatar rouslan@chromium.org

[rac] Download all rules for a country code in libaddressinput.

This patch enables downloading all rules for a country code. The rules are
organized into a Ruleset tree, where nodes contain region-wide rules,
language-specific rules, and Rulesets for the sub-regions.

For example, the country code of Canada is "CA". The Ruleset for "CA"
contains the general validation rules for Canada in the default language
of the country, which is English, or "en".

One of the child nodes of "CA" is a Rule for "fr" language. This Rule
contains the general validation rules for Canada in the French language.

The rest of the child nodes of "CA" are the Ruleset objects for all of the
Canada's provinces. For example, there's a Ruleset for "BC" for British
Columbia.

Example of a Ruleset for Canada and some of its provinces:

                  CA-->fr
                  |
-------------------------------------
|        |        |        |        |
v        v        v        v        v
AB-->fr  BC-->fr  MB-->fr  NB-->fr  NL-->fr

BUG=327046

Review URL: https://codereview.chromium.org/109323011

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@243754 0039d316-1c4b-4281-b951-d872f2087c98
parent bd951a6f
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
#include <libaddressinput/address_field.h> #include <libaddressinput/address_field.h>
#include <libaddressinput/address_problem.h> #include <libaddressinput/address_problem.h>
#include <libaddressinput/util/basictypes.h>
#include <libaddressinput/util/scoped_ptr.h> #include <libaddressinput/util/scoped_ptr.h>
#include <map> #include <map>
...@@ -30,7 +29,6 @@ namespace addressinput { ...@@ -30,7 +29,6 @@ namespace addressinput {
class Downloader; class Downloader;
class LoadRulesDelegate; class LoadRulesDelegate;
class Localization; class Localization;
class Retriever;
class Storage; class Storage;
struct AddressData; struct AddressData;
...@@ -84,20 +82,28 @@ class AddressValidator { ...@@ -84,20 +82,28 @@ class AddressValidator {
RULES_NOT_READY RULES_NOT_READY
}; };
// Does not take ownership of |load_rules_delegate|, which can be NULL. virtual ~AddressValidator();
AddressValidator(scoped_ptr<const Downloader> downloader,
scoped_ptr<Storage> storage, // Builds an address validator. Takes ownership of |downloader| and |storage|,
LoadRulesDelegate* load_rules_delegate); // which cannot be NULL. Does not take ownership of |load_rules_delegate|,
~AddressValidator(); // which can be NULL. The caller owns the result.
static scoped_ptr<AddressValidator> Build(
scoped_ptr<const Downloader> downloader,
scoped_ptr<Storage> storage,
LoadRulesDelegate* load_rules_delegate);
// Loads the generic validation rules for |country_code| and specific rules // Loads the generic validation rules for |country_code| and specific rules
// for the country's administrative areas, localities, and dependent // for the country's administrative areas, localities, and dependent
// localities. A typical data size is 10KB. The largest is 250KB. If a country // localities. A typical data size is 10KB. The largest is 250KB. If a country
// has language-specific validation rules, then these are also loaded. // has language-specific validation rules, then these are also loaded.
// //
// If the rules were loaded successfully before, then does nothing. Notifies // Example rule:
// |load_rules_delegate| when the loading finishes. // https://i18napis.appspot.com/ssl-address/data/US
void LoadRules(const std::string& country_code); //
// If the rules were loaded successfully before or are still being loaded,
// then does nothing. Notifies |load_rules_delegate| when the loading
// finishes.
virtual void LoadRules(const std::string& country_code) = 0;
// Validates the |address| and populates |problems| with the validation // Validates the |address| and populates |problems| with the validation
// problems, filtered according to the |filter| parameter. // problems, filtered according to the |filter| parameter.
...@@ -108,15 +114,10 @@ class AddressValidator { ...@@ -108,15 +114,10 @@ class AddressValidator {
// //
// If the |problems| parameter is NULL, then checks whether the validation // If the |problems| parameter is NULL, then checks whether the validation
// rules are available, but does not validate the |address|. // rules are available, but does not validate the |address|.
Status ValidateAddress(const AddressData& address, virtual Status ValidateAddress(const AddressData& address,
const AddressProblemFilter& filter, const AddressProblemFilter& filter,
const Localization& localization, const Localization& localization,
AddressProblems* problems) const; AddressProblems* problems) const = 0;
private:
scoped_ptr<Retriever> retriever_;
LoadRulesDelegate* load_rules_delegate_;
DISALLOW_COPY_AND_ASSIGN(AddressValidator);
}; };
} // namespace addressinput } // namespace addressinput
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include <cassert> #include <cassert>
#include <cstddef> #include <cstddef>
#include <string>
namespace i18n { namespace i18n {
namespace addressinput { namespace addressinput {
...@@ -26,39 +27,66 @@ namespace addressinput { ...@@ -26,39 +27,66 @@ namespace addressinput {
// Stores a pointer to a method in an object. Sample usage: // Stores a pointer to a method in an object. Sample usage:
// class MyClass { // class MyClass {
// public: // public:
// typedef Callback<MyKeyType, MyDataType> MyCallback; // typedef Callback<std::string, std::string> MyConstRefCallback;
// typedef ScopdedPtrCallback<std::string, MyDataType> MyScopedPtrCallback;
// //
// void GetDataAsynchronously() { // void GetStringAsynchronously() {
// scoped_ptr<MyCallback> callback(BuildCallback( // scoped_ptr<MyCallback> callback(BuildCallback(
// this, &MyClass::OnDataReady)); // this, &MyClass::OnStringReady));
// bool success = ... // bool success = ...
// MyKeyType key = ... // std::string key = ...
// MyDataType data = ... // std::string data = ...
// (*callback)(success, key, data); // (*callback)(success, key, data);
// } // }
// //
// void GetDataAsynchronously() {
// scoped_ptr<MyScopedPtrCallback> callback(BuildScopedPtrCallback(
// this, &MyClass::OnDataReady));
// bool success = ...
// std::string key = ...
// scoped_ptr<MyDataType> data = ...
// (*callback)(success, key, data.Pass());
// }
//
// void OnStringReady(bool success,
// const std::string& key,
// const std::string& data) {
// ...
// }
//
// void OnDataReady(bool success, // void OnDataReady(bool success,
// const MyKeyType& key, // const std::string& key,
// const MyDataType& data) { // scoped_ptr<MyDataType> data) {
// ... // ...
// } // }
// }; // };
template <typename Param1, typename Param2> template <typename RequestType, typename ResponseType>
class Callback { class Callback {
public: public:
virtual ~Callback() {} virtual ~Callback() {}
virtual void operator()(bool success, virtual void operator()(bool success,
const Param1& param1, const RequestType& request,
const Param2& param2) const = 0; const ResponseType& response) const = 0;
};
template <typename RequestType, typename ResponseType>
class ScopedPtrCallback {
public:
virtual ~ScopedPtrCallback() {}
virtual void operator()(bool success,
const RequestType& request,
scoped_ptr<ResponseType> response) const = 0;
}; };
namespace { namespace {
template <typename BaseType, typename Param1, typename Param2> template <typename BaseType, typename RequestType, typename ResponseType>
class CallbackImpl : public Callback<Param1, Param2> { class CallbackImpl : public Callback<RequestType, ResponseType> {
public: public:
typedef void (BaseType::*Method)(bool, const Param1&, const Param2&); typedef void (BaseType::*Method)(
bool, const RequestType&, const ResponseType&);
CallbackImpl(BaseType* instance, Method method) CallbackImpl(BaseType* instance, Method method)
: instance_(instance), : instance_(instance),
...@@ -69,10 +97,39 @@ class CallbackImpl : public Callback<Param1, Param2> { ...@@ -69,10 +97,39 @@ class CallbackImpl : public Callback<Param1, Param2> {
virtual ~CallbackImpl() {} virtual ~CallbackImpl() {}
// Callback implementation.
virtual void operator()(bool success, virtual void operator()(bool success,
const Param1& param1, const RequestType& request,
const Param2& param2) const { const ResponseType& response) const {
(instance_->*method_)(success, param1, param2); (instance_->*method_)(success, request, response);
}
private:
BaseType* instance_;
Method method_;
};
template <typename BaseType, typename RequestType, typename ResponseType>
class ScopedPtrCallbackImpl :
public ScopedPtrCallback<RequestType, ResponseType> {
public:
typedef void (BaseType::*Method)(
bool, const RequestType&, scoped_ptr<ResponseType>);
ScopedPtrCallbackImpl(BaseType* instance, Method method)
: instance_(instance),
method_(method) {
assert(instance_ != NULL);
assert(method_ != NULL);
}
virtual ~ScopedPtrCallbackImpl() {}
// ScopedPtrCallback implementation.
virtual void operator()(bool success,
const RequestType& request,
scoped_ptr<ResponseType> response) const {
(instance_->*method_)(success, request, response.Pass());
} }
private: private:
...@@ -82,13 +139,25 @@ class CallbackImpl : public Callback<Param1, Param2> { ...@@ -82,13 +139,25 @@ class CallbackImpl : public Callback<Param1, Param2> {
} // namespace } // namespace
// Returns a callback to |instance->method|. // Returns a callback to |instance->method| with constant reference to data.
template <typename BaseType, typename Param1, typename Param2> template <typename BaseType, typename RequestType, typename ResponseType>
scoped_ptr<Callback<Param1, Param2> > BuildCallback( scoped_ptr<Callback<RequestType, ResponseType> > BuildCallback(
BaseType* instance,
void (BaseType::*method)(bool, const RequestType&, const ResponseType&)) {
return scoped_ptr<Callback<RequestType, ResponseType> >(
new CallbackImpl<BaseType, RequestType, ResponseType>(instance, method));
}
// Returns a callback to |instance->method| with scoped pointer to data.
template <typename BaseType, typename RequestType, typename ResponseType>
scoped_ptr<ScopedPtrCallback<RequestType, ResponseType> >
BuildScopedPtrCallback(
BaseType* instance, BaseType* instance,
void (BaseType::*method)(bool, const Param1&, const Param2&)) { void (BaseType::*method)(
return scoped_ptr<Callback<Param1, Param2> >( bool, const RequestType&, scoped_ptr<ResponseType>)) {
new CallbackImpl<BaseType, Param1, Param2>(instance, method)); return scoped_ptr<ScopedPtrCallback<RequestType, ResponseType> >(
new ScopedPtrCallbackImpl<BaseType, RequestType, ResponseType>(
instance, method));
} }
} // namespace addressinput } // namespace addressinput
......
...@@ -35,11 +35,13 @@ ...@@ -35,11 +35,13 @@
'src/address_problem.cc', 'src/address_problem.cc',
'src/address_ui.cc', 'src/address_ui.cc',
'src/address_validator.cc', 'src/address_validator.cc',
'src/country_rules_aggregator.cc',
'src/localization.cc', 'src/localization.cc',
'src/lookup_key_util.cc', 'src/lookup_key_util.cc',
'src/region_data_constants.cc', 'src/region_data_constants.cc',
'src/retriever.cc', 'src/retriever.cc',
'src/rule.cc', 'src/rule.cc',
'src/ruleset.cc',
'src/util/json.cc', 'src/util/json.cc',
'src/util/md5.cc', 'src/util/md5.cc',
'src/util/string_split.cc', 'src/util/string_split.cc',
......
...@@ -31,24 +31,6 @@ namespace addressinput { ...@@ -31,24 +31,6 @@ namespace addressinput {
namespace { namespace {
// Parses the default region data into the static Rule object and returns a
// constant reference to this object. Cannot return a copy of the object,
// because Rule objects are not copyable.
const Rule& InitDefaultRule() {
static Rule rule;
rule.ParseSerializedRule(RegionDataConstants::GetDefaultRegionData());
return rule;
}
// Returns the constant reference to the Rule object from InitDefaultRule(). The
// static object is in InitDefaultRule(), but this function maintains a constant
// static reference to it. The constant static reference avoids re-parsing the
// default region data.
const Rule& GetDefaultRule() {
static const Rule& kDefaultRule(InitDefaultRule());
return kDefaultRule;
}
int GetMessageIdForField(AddressField field, int GetMessageIdForField(AddressField field,
int admin_area_name_message_id, int admin_area_name_message_id,
int postal_code_name_message_id) { int postal_code_name_message_id) {
...@@ -88,7 +70,7 @@ std::vector<AddressUiComponent> BuildComponents( ...@@ -88,7 +70,7 @@ std::vector<AddressUiComponent> BuildComponents(
std::vector<AddressUiComponent> result; std::vector<AddressUiComponent> result;
Rule rule; Rule rule;
rule.CopyFrom(GetDefaultRule()); rule.CopyFrom(Rule::GetDefault());
if (!rule.ParseSerializedRule( if (!rule.ParseSerializedRule(
RegionDataConstants::GetRegionData(region_code))) { RegionDataConstants::GetRegionData(region_code))) {
return result; return result;
......
...@@ -18,33 +18,109 @@ ...@@ -18,33 +18,109 @@
#include <libaddressinput/load_rules_delegate.h> #include <libaddressinput/load_rules_delegate.h>
#include <libaddressinput/localization.h> #include <libaddressinput/localization.h>
#include <libaddressinput/storage.h> #include <libaddressinput/storage.h>
#include <libaddressinput/util/basictypes.h>
#include <libaddressinput/util/scoped_ptr.h>
#include <cassert>
#include <cstddef>
#include <map>
#include <set>
#include <string> #include <string>
#include "country_rules_aggregator.h"
#include "retriever.h" #include "retriever.h"
#include "ruleset.h"
#include "util/stl_util.h"
namespace i18n { namespace i18n {
namespace addressinput { namespace addressinput {
AddressValidator::AddressValidator(scoped_ptr<const Downloader> downloader, namespace {
scoped_ptr<Storage> storage,
LoadRulesDelegate* load_rules_delegate) // Validates AddressData structure.
: retriever_(new Retriever( class AddressValidatorImpl : public AddressValidator {
public:
// Takes ownership of |downloader| and |storage|. Does not take ownership of
// |load_rules_delegate|.
AddressValidatorImpl(scoped_ptr<const Downloader> downloader,
scoped_ptr<Storage> storage,
LoadRulesDelegate* load_rules_delegate)
: aggregator_(scoped_ptr<Retriever>(new Retriever(
VALIDATION_DATA_URL, VALIDATION_DATA_URL,
downloader.Pass(), downloader.Pass(),
scoped_ptr<Storage>(storage.Pass()))), storage.Pass()))),
load_rules_delegate_(load_rules_delegate) {} load_rules_delegate_(load_rules_delegate),
loading_rules_(),
rules_() {}
AddressValidator::~AddressValidator() {} virtual ~AddressValidatorImpl() {
STLDeleteValues(&rules_);
}
// AddressValidator implementation.
virtual void LoadRules(const std::string& country_code) {
if (rules_.find(country_code) == rules_.end() &&
loading_rules_.find(country_code) == loading_rules_.end()) {
loading_rules_.insert(country_code);
aggregator_.AggregateRules(
country_code,
BuildScopedPtrCallback(this, &AddressValidatorImpl::OnRulesLoaded));
}
}
// AddressValidator implementation.
virtual Status ValidateAddress(
const AddressData& address,
const AddressProblemFilter& filter,
const Localization& localization,
AddressProblems* problems) const {
// TODO(rouslan): Validate the address.
return RULES_UNAVAILABLE;
}
private:
// Called when CountryRulesAggregator::AggregateRules loads the |ruleset| for
// the |country_code|.
void OnRulesLoaded(bool success,
const std::string& country_code,
scoped_ptr<Ruleset> ruleset) {
assert(rules_.find(country_code) == rules_.end());
loading_rules_.erase(country_code);
if (success) {
rules_[country_code] = ruleset.release();
}
if (load_rules_delegate_ != NULL) {
load_rules_delegate_->OnAddressValidationRulesLoaded(
country_code, success);
}
}
// Loads the ruleset for a country code.
CountryRulesAggregator aggregator_;
void AddressValidator::LoadRules(const std::string& country_code) {} // An optional delegate to be invoked when a ruleset finishes loading.
LoadRulesDelegate* load_rules_delegate_;
// A set of country codes for which a ruleset is being loaded.
std::set<std::string> loading_rules_;
// A mapping of a country code to the owned ruleset for that country code.
std::map<std::string, Ruleset*> rules_;
DISALLOW_COPY_AND_ASSIGN(AddressValidatorImpl);
};
} // namespace
AddressValidator::~AddressValidator() {}
AddressValidator::Status AddressValidator::ValidateAddress( // static
const AddressData& address, scoped_ptr<AddressValidator> AddressValidator::Build(
const AddressProblemFilter& filter, scoped_ptr<const Downloader> downloader,
const Localization& localization, scoped_ptr<Storage> storage,
AddressProblems* problems) const { LoadRulesDelegate* load_rules_delegate) {
return RULES_UNAVAILABLE; return scoped_ptr<AddressValidator>(new AddressValidatorImpl(
downloader.Pass(), storage.Pass(), load_rules_delegate));
} }
} // namespace addressinput } // namespace addressinput
......
// 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 "country_rules_aggregator.h"
#include <libaddressinput/address_field.h>
#include <libaddressinput/callback.h>
#include <libaddressinput/util/basictypes.h>
#include <libaddressinput/util/scoped_ptr.h>
#include <cassert>
#include <cstddef>
#include <map>
#include <string>
#include <utility>
#include "retriever.h"
#include "rule.h"
#include "ruleset.h"
#include "util/stl_util.h"
namespace i18n {
namespace addressinput {
// Information about data requests sent to Retriever. This data is not returned
// as part of the ruleset, but is useful in constructing the ruleset
// asynchronously.
struct CountryRulesAggregator::RequestData {
// Does not take ownership of |parent|.
RequestData(Ruleset* parent,
AddressField level,
bool is_language_code,
const std::string& id)
: parent(parent),
level(level),
is_language_code(is_language_code),
id(id) {
assert(parent != NULL || level == COUNTRY);
}
~RequestData() {}
// The parent ruleset of the data being downloaded. If NULL, then this is the
// root ruleset at the COUNTRY level. The language-specific and sub-region
// rules are added to this ruleset. Owned by |CountryRulesRetriever|.
Ruleset* parent;
// The level of the data being requested. Ranges from COUNTRY to
// DEPENDENT_LOCALITY. If COUNTRY, then the rule should use default rules from
// Rule::GetDefault().
AddressField level;
// If true, then |id| is a language. The data received for this request should
// be placed into a language-specific rule.
bool is_language_code;
// Can be a region name (e.g. "CA") or a language (e.g. "fr"). Used to add a
// sub-region or a language-specific rule to |parent|.
std::string id;
};
CountryRulesAggregator::CountryRulesAggregator(scoped_ptr<Retriever> retriever)
: retriever_(retriever.Pass()),
requests_(),
country_code_(),
rules_ready_(),
root_(),
default_language_(),
languages_() {
assert(retriever_ != NULL);
}
CountryRulesAggregator::~CountryRulesAggregator() {}
void CountryRulesAggregator::AggregateRules(const std::string& country_code,
scoped_ptr<Callback> rules_ready) {
Reset();
country_code_ = country_code;
rules_ready_.reset(rules_ready.release());
// Key construction:
// https://code.google.com/p/libaddressinput/wiki/AddressValidationMetadata
// Example of a country-level key: "data/CA".
std::string key = "data/" + country_code_;
requests_.insert(std::make_pair(
key, RequestData(NULL, COUNTRY, false, std::string())));
retriever_->Retrieve(
key, BuildCallback(this, &CountryRulesAggregator::OnDataReady));
}
void CountryRulesAggregator::OnDataReady(bool success,
const std::string& key,
const std::string& data) {
std::map<std::string, RequestData>::iterator request_it =
requests_.find(key);
if (request_it == requests_.end()) {
return; // An abandoned request.
}
if (!success) {
(*rules_ready_)(false, country_code_, scoped_ptr<Ruleset>());
Reset();
return;
}
RequestData request = request_it->second;
requests_.erase(request_it);
// All country-level rules are based on the default rule.
scoped_ptr<Rule> rule(new Rule);
if (request.level == COUNTRY) {
rule->CopyFrom(Rule::GetDefault());
}
if (!rule->ParseSerializedRule(data)) {
(*rules_ready_)(false, country_code_, scoped_ptr<Ruleset>());
Reset();
return;
}
// Place the rule in the correct location in the ruleset.
Ruleset* ruleset = NULL;
if (request.is_language_code) {
assert(request.parent != NULL);
request.parent->AddLanguageCodeRule(request.id, rule.Pass());
ruleset = request.parent;
} else if (request.level == COUNTRY) {
// The default language and all supported languages for the country code are
// in the country-level rule without a language code identifier. For
// example: "data/CA".
default_language_ = rule->GetLanguage();
languages_ = rule->GetLanguages();
root_.reset(new Ruleset(rule.Pass()));
ruleset = root_.get();
} else {
assert(request.parent != NULL);
ruleset = new Ruleset(rule.Pass());
request.parent->AddSubRegionRuleset(
request.id, scoped_ptr<Ruleset>(ruleset));
}
if (!request.is_language_code) {
// Retrieve the language-specific rules for this region.
for (std::vector<std::string>::const_iterator
lang_it = languages_.begin();
lang_it != languages_.end();
++lang_it) {
if (*lang_it == default_language_) {
continue;
}
// Example of a language-specific key: "data/CA--fr".
std::string language_code_key = key + "--" + *lang_it;
requests_.insert(std::make_pair(
key, RequestData(ruleset, request.level, true, *lang_it)));
retriever_->Retrieve(
language_code_key,
BuildCallback(this, &CountryRulesAggregator::OnDataReady));
}
if (request.level < DEPENDENT_LOCALITY) {
// Retrieve the sub-region rules for this region.
for (std::vector<std::string>::const_iterator
subkey_it = ruleset->rule().GetSubKeys().begin();
subkey_it != ruleset->rule().GetSubKeys().end();
++subkey_it) {
// Example of a sub-region key: "data/CA/AB".
std::string sub_region_key = key + "/" + *subkey_it;
requests_.insert(std::make_pair(
key,
RequestData(ruleset,
static_cast<AddressField>(request.level + 1),
false,
*subkey_it)));
retriever_->Retrieve(
sub_region_key,
BuildCallback(this, &CountryRulesAggregator::OnDataReady));
}
}
}
if (requests_.empty()) {
(*rules_ready_)(true, country_code_, root_.Pass());
Reset();
}
}
void CountryRulesAggregator::Reset() {
requests_.clear();
country_code_.clear();
rules_ready_.reset();
root_.reset();
default_language_.clear();
languages_.clear();
}
} // namespace addressinput
} // namespace i18n
// 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.
#ifndef I18N_ADDRESSINPUT_COUNTRY_RULES_AGGREGATOR_H_
#define I18N_ADDRESSINPUT_COUNTRY_RULES_AGGREGATOR_H_
#include <libaddressinput/callback.h>
#include <libaddressinput/util/basictypes.h>
#include <libaddressinput/util/scoped_ptr.h>
#include <map>
#include <string>
#include <vector>
namespace i18n {
namespace addressinput {
class Retriever;
class Ruleset;
// Aggregates a ruleset for a country. Sample usage:
// class MyClass {
// public:
// MyClass() : aggregator_(scoped_ptr<const Retriever>(...)) {}
//
// ~MyClass() {}
//
// void GetRuleset(const std::string& country_code) {
// aggregator_.AggregateRules(
// country_code,
// BuildScopedPtrCallback(this, &MyClass::OnRulesetReady));
// }
//
// void OnRulesetReady(bool success,
// const std::string& country_code,
// scoped_ptr<Ruleset> ruleset) {
// ...
// }
//
// private:
// CountryRulesAggregator aggregator_;
//
// DISALLOW_COPY_AND_ASSIGN(MyClass);
// };
class CountryRulesAggregator {
public:
typedef i18n::addressinput::ScopedPtrCallback<std::string, Ruleset> Callback;
explicit CountryRulesAggregator(scoped_ptr<Retriever> retriever);
~CountryRulesAggregator();
// Recursively retrieves all of the rules for |country_code| and its
// administrative areas, localities, and dependent localities. Also retrieves
// the language-specific rules. Abandons previous requests if invoked multiple
// times. Invokes the |rules_ready| callback when finished.
void AggregateRules(const std::string& country_code,
scoped_ptr<Callback> rules_ready);
private:
struct RequestData;
// Callback for Retriever::Retrieve() method.
void OnDataReady(bool success,
const std::string& key,
const std::string& data);
// Abandons all requests and clears all retrieved data.
void Reset();
// The data retriever that can download serialized rules for one sub-region at
// a time.
scoped_ptr<Retriever> retriever_;
// A mapping of data keys (e.g., "data/CA/AB--fr") to information that helps
// to parse the response data and place it the correct location in the
// rulesets.
std::map<std::string, RequestData> requests_;
// The country code for which to retrieve the ruleset. Passed to the callback
// method to identify the ruleset. Examples: "US", "CA", "CH", etc.
std::string country_code_;
// The callback to invoke when the ruleset has been retrieved.
scoped_ptr<Callback> rules_ready_;
// The top-level ruleset for the country code. Passed to the callback method
// as the result of the query.
scoped_ptr<Ruleset> root_;
// The default language for the country code. This value is parsed from the
// country-level rule for the country code and is used to filter out the
// default language from the list of all supported languages for a country.
// For example, the list of supported languages for Canada is ["en", "fr"],
// but the default language is "en". Data requests for "data/CA/AB--fr" will
// succeed, but "data/CA/AB--en" will not return data.
std::string default_language_;
// The list of all supported languages for the country code. This value is
// parsed from the country-level rule for the country and is used to download
// language-specific rules.
std::vector<std::string> languages_;
DISALLOW_COPY_AND_ASSIGN(CountryRulesAggregator);
};
} // namespace addressinput
} // namespace i18n
#endif // I18N_ADDRESSINPUT_COUNTRY_RULES_AGGREGATOR_H_
...@@ -48,8 +48,15 @@ Retriever::~Retriever() { ...@@ -48,8 +48,15 @@ Retriever::~Retriever() {
void Retriever::Retrieve(const std::string& key, void Retriever::Retrieve(const std::string& key,
scoped_ptr<Callback> retrieved) { scoped_ptr<Callback> retrieved) {
assert(requests_.find(key) == requests_.end()); std::map<std::string, Callback*>::iterator request_it =
requests_.insert(std::make_pair(key, retrieved.release())); requests_.find(key);
if (request_it != requests_.end()) {
// Abandon a previous request.
delete request_it->second;
requests_.erase(request_it);
}
requests_[key] = retrieved.release();
storage_->Get(key, storage_->Get(key,
BuildCallback(this, &Retriever::OnDataRetrievedFromStorage)); BuildCallback(this, &Retriever::OnDataRetrievedFromStorage));
} }
...@@ -57,9 +64,13 @@ void Retriever::Retrieve(const std::string& key, ...@@ -57,9 +64,13 @@ void Retriever::Retrieve(const std::string& key,
void Retriever::OnDataRetrievedFromStorage(bool success, void Retriever::OnDataRetrievedFromStorage(bool success,
const std::string& key, const std::string& key,
const std::string& data) { const std::string& data) {
// TODO(rouslan): Add validation for data integrity and freshness. If a
// download fails, then it's OK to use stale data.
if (success) { if (success) {
scoped_ptr<Callback> retrieved = GetCallbackForKey(key); scoped_ptr<Callback> retrieved = GetCallbackForKey(key);
(*retrieved)(success, key, data); if (retrieved != NULL) {
(*retrieved)(success, key, data);
}
} else { } else {
downloader_->Download(lookup_key_util_.GetUrlForKey(key), downloader_->Download(lookup_key_util_.GetUrlForKey(key),
BuildCallback(this, &Retriever::OnDownloaded)); BuildCallback(this, &Retriever::OnDownloaded));
...@@ -74,14 +85,19 @@ void Retriever::OnDownloaded(bool success, ...@@ -74,14 +85,19 @@ void Retriever::OnDownloaded(bool success,
storage_->Put(key, data); storage_->Put(key, data);
} }
scoped_ptr<Callback> retrieved = GetCallbackForKey(key); scoped_ptr<Callback> retrieved = GetCallbackForKey(key);
(*retrieved)(success, key, success ? data : std::string()); if (retrieved != NULL) {
(*retrieved)(success, key, success ? data : std::string());
}
} }
scoped_ptr<Retriever::Callback> Retriever::GetCallbackForKey( scoped_ptr<Retriever::Callback> Retriever::GetCallbackForKey(
const std::string& key) { const std::string& key) {
std::map<std::string, Callback*>::iterator iter = std::map<std::string, Callback*>::iterator iter =
requests_.find(key); requests_.find(key);
assert(iter != requests_.end()); if (iter == requests_.end()) {
// An abandonened request.
return scoped_ptr<Callback>();
}
scoped_ptr<Callback> callback(iter->second); scoped_ptr<Callback> callback(iter->second);
requests_.erase(iter); requests_.erase(iter);
return callback.Pass(); return callback.Pass();
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include "grit.h" #include "grit.h"
#include "messages.h" #include "messages.h"
#include "region_data_constants.h"
#include "util/json.h" #include "util/json.h"
#include "util/string_split.h" #include "util/string_split.h"
...@@ -171,6 +172,18 @@ Rule::Rule() ...@@ -171,6 +172,18 @@ Rule::Rule()
Rule::~Rule() {} Rule::~Rule() {}
// static
const Rule& Rule::GetDefault() {
// Allocated once and leaked on shutdown.
static Rule* default_rule = NULL;
if (default_rule == NULL) {
default_rule = new Rule;
default_rule->ParseSerializedRule(
RegionDataConstants::GetDefaultRegionData());
}
return *default_rule;
}
void Rule::CopyFrom(const Rule& rule) { void Rule::CopyFrom(const Rule& rule) {
format_ = rule.format_; format_ = rule.format_;
required_ = rule.required_; required_ = rule.required_;
......
...@@ -36,6 +36,11 @@ class Rule { ...@@ -36,6 +36,11 @@ class Rule {
Rule(); Rule();
~Rule(); ~Rule();
// Returns the default rule at a country level. If a country does not specify
// address format, for example, then the format from this rule should be used
// instead.
static const Rule& GetDefault();
// Copies all data from |rule|. // Copies all data from |rule|.
void CopyFrom(const Rule& rule); void CopyFrom(const Rule& rule);
......
// 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 "ruleset.h"
#include <libaddressinput/util/scoped_ptr.h>
#include <cassert>
#include <cstddef>
#include <map>
#include <string>
#include <utility>
#include "rule.h"
#include "util/stl_util.h"
namespace i18n {
namespace addressinput {
Ruleset::Ruleset(scoped_ptr<Rule> rule)
: rule_(rule.Pass()),
sub_regions_(),
language_codes_() {
assert(rule_ != NULL);
}
Ruleset::~Ruleset() {
STLDeleteValues(&sub_regions_);
STLDeleteValues(&language_codes_);
}
void Ruleset::AddSubRegionRuleset(const std::string& sub_region,
scoped_ptr<Ruleset> ruleset) {
assert(sub_regions_.find(sub_region) == sub_regions_.end());
sub_regions_[sub_region] = ruleset.release();
}
void Ruleset::AddLanguageCodeRule(const std::string& language_code,
scoped_ptr<Rule> rule) {
assert(language_codes_.find(language_code) == language_codes_.end());
language_codes_[language_code] = rule.release();
}
} // namespace addressinput
} // namespace i18n
// 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.
#ifndef I18N_ADDRESSINPUT_RULESET_H_
#define I18N_ADDRESSINPUT_RULESET_H_
#include <libaddressinput/util/basictypes.h>
#include <libaddressinput/util/scoped_ptr.h>
#include <map>
#include <string>
namespace i18n {
namespace addressinput {
class Rule;
// A recursive data structure that stores a set of rules for a region. Can store
// the rules for a country, its administrative areas, localities, and dependent
// localities, in addition to the language-specific rules.
//
// Example for Canada and some of its provinces:
// CA-->fr
// |
// -------------------------------------
// | | | | |
// v v v v v
// AB-->fr BC-->fr MB-->fr NB-->fr NL-->fr
//
// The rules in Canada are in English by default. Each rule also has a French
// language version.
class Ruleset {
public:
// Builds a ruleset with a region-wide |rule| in the default language of the
// country. The |rule| should not be NULL.
explicit Ruleset(scoped_ptr<Rule> rule);
~Ruleset();
// Returns the region-wide rule in the default language of the country.
const Rule& rule() const { return *rule_.get(); }
// Adds and the |ruleset| for |sub_region|.
void AddSubRegionRuleset(const std::string& sub_region,
scoped_ptr<Ruleset> ruleset);
// Adds a language-specific |rule| for |language_code| for this region.
void AddLanguageCodeRule(const std::string& language_code,
scoped_ptr<Rule> rule);
private:
// The region-wide rule in the default language of the country.
scoped_ptr<const Rule> rule_;
// Owned rulesets for sub-regions.
std::map<std::string, Ruleset*> sub_regions_;
// Owned language-specific rules for the region.
std::map<std::string, const Rule*> language_codes_;
DISALLOW_COPY_AND_ASSIGN(Ruleset);
};
} // namespace addressinput
} // namespace i18n
#endif // I18N_ADDRESSINPUT_RULESET_H_
...@@ -142,10 +142,7 @@ TEST_F(RetrieverTest, RequestsDontStack) { ...@@ -142,10 +142,7 @@ TEST_F(RetrieverTest, RequestsDontStack) {
EXPECT_FALSE(success_); EXPECT_FALSE(success_);
EXPECT_TRUE(key_.empty()); EXPECT_TRUE(key_.empty());
#if !defined(NDEBUG) EXPECT_NO_FATAL_FAILURE(slow_retriever.Retrieve(kKey, BuildCallback()));
// This request should cause an assert.
ASSERT_DEATH(slow_retriever.Retrieve(kKey, BuildCallback()), "");
#endif
} }
} // namespace } // namespace
......
...@@ -70,6 +70,8 @@ ...@@ -70,6 +70,8 @@
'<(libaddressinput_dir)/cpp/src/address_problem.cc', '<(libaddressinput_dir)/cpp/src/address_problem.cc',
'<(libaddressinput_dir)/cpp/src/address_ui.cc', '<(libaddressinput_dir)/cpp/src/address_ui.cc',
'<(libaddressinput_dir)/cpp/src/address_validator.cc', '<(libaddressinput_dir)/cpp/src/address_validator.cc',
'<(libaddressinput_dir)/cpp/src/country_rules_aggregator.cc',
'<(libaddressinput_dir)/cpp/src/country_rules_aggregator.h',
'<(libaddressinput_dir)/cpp/src/grit.h', '<(libaddressinput_dir)/cpp/src/grit.h',
'<(libaddressinput_dir)/cpp/src/localization.cc', '<(libaddressinput_dir)/cpp/src/localization.cc',
'<(libaddressinput_dir)/cpp/src/lookup_key_util.cc', '<(libaddressinput_dir)/cpp/src/lookup_key_util.cc',
...@@ -80,6 +82,8 @@ ...@@ -80,6 +82,8 @@
'<(libaddressinput_dir)/cpp/src/retriever.h', '<(libaddressinput_dir)/cpp/src/retriever.h',
'<(libaddressinput_dir)/cpp/src/rule.cc', '<(libaddressinput_dir)/cpp/src/rule.cc',
'<(libaddressinput_dir)/cpp/src/rule.h', '<(libaddressinput_dir)/cpp/src/rule.h',
'<(libaddressinput_dir)/cpp/src/ruleset.cc',
'<(libaddressinput_dir)/cpp/src/ruleset.h',
'<(libaddressinput_dir)/cpp/src/util/json.h', '<(libaddressinput_dir)/cpp/src/util/json.h',
'<(libaddressinput_dir)/cpp/src/util/md5.cc', '<(libaddressinput_dir)/cpp/src/util/md5.cc',
'<(libaddressinput_dir)/cpp/src/util/md5.h', '<(libaddressinput_dir)/cpp/src/util/md5.h',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment