diff options
Diffstat (limited to 'platform/android/src/i18n/collator.cpp')
-rw-r--r-- | platform/android/src/i18n/collator.cpp | 194 |
1 files changed, 194 insertions, 0 deletions
diff --git a/platform/android/src/i18n/collator.cpp b/platform/android/src/i18n/collator.cpp new file mode 100644 index 0000000000..b3dd8e21b3 --- /dev/null +++ b/platform/android/src/i18n/collator.cpp @@ -0,0 +1,194 @@ +#include <mbgl/i18n/collator.hpp> +#include <mbgl/text/language_tag.hpp> + +#include <jni/jni.hpp> + +#include "../attach_env.hpp" +#include "collator_jni.hpp" + +namespace mbgl { +namespace android { + +void Collator::registerNative(jni::JNIEnv& env) { + jni::Class<Collator>::Singleton(env); +} + +jni::Local<jni::Object<Collator>> Collator::getInstance(jni::JNIEnv& env, const jni::Object<Locale>& locale) { + static auto& javaClass = jni::Class<Collator>::Singleton(env); + static auto method = javaClass.GetStaticMethod<jni::Object<Collator> (jni::Object<Locale>)>(env, "getInstance"); + return javaClass.Call(env, method, locale); +} + +void Collator::setStrength(jni::JNIEnv& env, const jni::Object<Collator>& collator, jni::jint strength) { + static auto& javaClass = jni::Class<Collator>::Singleton(env); + static auto method = javaClass.GetMethod<void (jni::jint)>(env, "setStrength"); + collator.Call(env, method, strength); +} + +jni::jint Collator::compare(jni::JNIEnv& env, const jni::Object<Collator>& collator, const jni::String& lhs, const jni::String& rhs) { + static auto& javaClass = jni::Class<Collator>::Singleton(env); + static auto method = javaClass.GetMethod<jni::jint (jni::String, jni::String)>(env, "compare"); + return collator.Call(env, method, lhs, rhs); +} + +void StringUtils::registerNative(jni::JNIEnv& env) { + jni::Class<StringUtils>::Singleton(env); +} + +jni::Local<jni::String> StringUtils::unaccent(jni::JNIEnv& env, const jni::String& value) { + static auto& javaClass = jni::Class<StringUtils>::Singleton(env); + static auto method = javaClass.GetStaticMethod<jni::String (jni::String)>(env, "unaccent"); + return javaClass.Call(env, method, value); +} + +void Locale::registerNative(jni::JNIEnv& env) { + jni::Class<Locale>::Singleton(env); +} + +/* +We would prefer to use for/toLanguageTag, but they're only available in API level 21+ + +jni::Object<Locale> Locale::forLanguageTag(jni::JNIEnv& env, jni::String languageTag) { + using Signature = jni::Object<Locale>(jni::String); + auto method = javaClass.GetStaticMethod<Signature>(env, "forLanguageTag"); + return javaClass.Call(env, method, languageTag); +} + +jni::String Locale::toLanguageTag(jni::JNIEnv& env, jni::Object<Locale> locale) { + using Signature = jni::String(); + auto static method = javaClass.GetMethod<Signature>(env, "toLanguageTag"); + return locale.Call(env, method); +} +*/ + +jni::Local<jni::String> Locale::getLanguage(jni::JNIEnv& env, const jni::Object<Locale>& locale) { + static auto& javaClass = jni::Class<Locale>::Singleton(env); + static auto method = javaClass.GetMethod<jni::String ()>(env, "getLanguage"); + return locale.Call(env, method); +} + +jni::Local<jni::String> Locale::getCountry(jni::JNIEnv& env, const jni::Object<Locale>& locale) { + static auto& javaClass = jni::Class<Locale>::Singleton(env); + static auto method = javaClass.GetMethod<jni::String ()>(env, "getCountry"); + return locale.Call(env, method); +} + +jni::Local<jni::Object<Locale>> Locale::getDefault(jni::JNIEnv& env) { + static auto& javaClass = jni::Class<Locale>::Singleton(env); + static auto method = javaClass.GetStaticMethod<jni::Object<Locale> ()>(env, "getDefault"); + return javaClass.Call(env, method); +} + +jni::Local<jni::Object<Locale>> Locale::New(jni::JNIEnv& env, const jni::String& language) { + static auto& javaClass = jni::Class<Locale>::Singleton(env); + static auto constructor = javaClass.GetConstructor<jni::String>(env); + return javaClass.New(env, constructor, language); +} + +jni::Local<jni::Object<Locale>> Locale::New(jni::JNIEnv& env, const jni::String& language, const jni::String& region) { + static auto& javaClass = jni::Class<Locale>::Singleton(env); + static auto constructor = javaClass.GetConstructor<jni::String, jni::String>(env); + return javaClass.New(env, constructor, language, region); +} + +} // namespace android + +namespace platform { + +class Collator::Impl { +public: + Impl(bool caseSensitive_, bool diacriticSensitive_, optional<std::string> locale_) + : caseSensitive(caseSensitive_) + , diacriticSensitive(diacriticSensitive_) + , env(android::AttachEnv()) + { + LanguageTag languageTag = locale_ ? LanguageTag::fromBCP47(*locale_) : LanguageTag(); + if (!languageTag.language) { + locale = jni::NewGlobal(*env, + android::Locale::getDefault(*env)); + } else if (!languageTag.region) { + locale = jni::NewGlobal(*env, + android::Locale::New(*env, jni::Make<jni::String>(*env, *languageTag.language))); + } else { + locale = jni::NewGlobal(*env, + android::Locale::New(*env, jni::Make<jni::String>(*env, *languageTag.language), + jni::Make<jni::String>(*env, *languageTag.region))); + } + collator = jni::NewGlobal(*env, android::Collator::getInstance(*env, locale)); + if (!diacriticSensitive && !caseSensitive) { + android::Collator::setStrength(*env, collator, 0 /*PRIMARY*/); + } else if (diacriticSensitive && !caseSensitive) { + android::Collator::setStrength(*env, collator, 1 /*SECONDARY*/); + } else if (caseSensitive) { + // If we're case-sensitive and diacritic-sensitive, we use a case-sensitive collator + // and a fallback implementation of diacritic-insensitivity. + android::Collator::setStrength(*env, collator, 2 /*TERTIARY*/); + } + } + + bool operator==(const Impl& other) const { + return caseSensitive == other.caseSensitive && + diacriticSensitive == other.diacriticSensitive && + resolvedLocale() == other.resolvedLocale(); + } + + int compare(const std::string& lhs, const std::string& rhs) const { + bool useUnaccent = !diacriticSensitive && caseSensitive; + // java.text.Collator doesn't support a diacritic-insensitive/case-sensitive collation + // order, so we have to compromise here. We use Android's case-sensitive Collator + // against strings that have been "unaccented" using non-locale-aware nunicode logic. + // Because of the difference in locale-awareness, this means turning on case-sensitivity + // can _potentially_ change compare results for strings that don't actually have any case + // differences. + jni::Local<jni::String> jlhs = useUnaccent + ? android::StringUtils::unaccent(*env, jni::Make<jni::String>(*env, lhs)) + : jni::Make<jni::String>(*env, lhs); + jni::Local<jni::String> jrhs = useUnaccent + ? android::StringUtils::unaccent(*env, jni::Make<jni::String>(*env, rhs)) + : jni::Make<jni::String>(*env, rhs); + + jni::jint result = android::Collator::compare(*env, collator, jlhs, jrhs); + + return result; + } + + std::string resolvedLocale() const { + std::string language = jni::Make<std::string>(*env, android::Locale::getLanguage(*env, locale)); + std::string region = jni::Make<std::string>(*env, android::Locale::getCountry(*env, locale)); + + optional<std::string> resultLanguage; + if (!language.empty()) resultLanguage = language; + optional<std::string> resultRegion; + if (!region.empty()) resultRegion = region; + + return LanguageTag(resultLanguage, {}, resultRegion).toBCP47(); + } + +private: + bool caseSensitive; + bool diacriticSensitive; + + android::UniqueEnv env; + jni::Global<jni::Object<android::Collator>> collator; + jni::Global<jni::Object<android::Locale>> locale; +}; + + +Collator::Collator(bool caseSensitive, bool diacriticSensitive, optional<std::string> locale_) + : impl(std::make_shared<Impl>(caseSensitive, diacriticSensitive, std::move(locale_))) +{} + +bool Collator::operator==(const Collator& other) const { + return *impl == *(other.impl); +} + +int Collator::compare(const std::string& lhs, const std::string& rhs) const { + return impl->compare(lhs, rhs); +} + +std::string Collator::resolvedLocale() const { + return impl->resolvedLocale(); +} + +} // namespace platform +} // namespace mbgl |