diff options
author | Chris Loer <chris.loer@gmail.com> | 2018-06-27 15:01:54 -0700 |
---|---|---|
committer | Chris Loer <chris.loer@mapbox.com> | 2018-07-03 10:03:05 -0700 |
commit | 11c333672f9823a1951eb3f0a46813eb9b302e98 (patch) | |
tree | e09a2c21089cab22d19865c46f4ec35a3582a699 /platform/android/src | |
parent | cb1328d1d049d5ac7376d4bd07c3b08f2c5f7c7a (diff) | |
download | qtlocation-mapboxgl-11c333672f9823a1951eb3f0a46813eb9b302e98.tar.gz |
[android] Android "collator" implementation
- Uses java.text.Collator for comparison
- Uses java.util.Locale for locale loading
- Uses LanguageTag for BCP 47 parsing
- Falls back to non-locale-aware nunicode/default comparison for case-sensitive/diacritic-insensitive.
- Testing these changes depends on running Android render tests
- "collator" is not yet exposed in the SDK bindings.
Diffstat (limited to 'platform/android/src')
-rwxr-xr-x | platform/android/src/jni.cpp | 3 | ||||
-rw-r--r-- | platform/android/src/text/collator.cpp | 199 | ||||
-rw-r--r-- | platform/android/src/text/collator_jni.hpp | 57 |
3 files changed, 259 insertions, 0 deletions
diff --git a/platform/android/src/jni.cpp b/platform/android/src/jni.cpp index 2f6ed96ab0..beb2c14eb3 100755 --- a/platform/android/src/jni.cpp +++ b/platform/android/src/jni.cpp @@ -46,6 +46,7 @@ #include "style/light.hpp" #include "snapshotter/map_snapshotter.hpp" #include "snapshotter/map_snapshot.hpp" +#include "text/collator_jni.hpp" #include "text/local_glyph_rasterizer_jni.hpp" #include "java/lang.hpp" @@ -188,6 +189,8 @@ void registerNatives(JavaVM *vm) { // text LocalGlyphRasterizer::registerNative(env); + Locale::registerNative(env); + Collator::registerNative(env); } } // namespace android diff --git a/platform/android/src/text/collator.cpp b/platform/android/src/text/collator.cpp new file mode 100644 index 0000000000..37af0a1c5b --- /dev/null +++ b/platform/android/src/text/collator.cpp @@ -0,0 +1,199 @@ +#include <mbgl/style/expression/collator.hpp> +#include <mbgl/text/language_tag.hpp> +#include <mbgl/util/platform.hpp> + +#include <unaccent.hpp> + +#include <jni/jni.hpp> + +#include "../attach_env.hpp" +#include "collator_jni.hpp" + +namespace mbgl { +namespace android { + +void Collator::registerNative(jni::JNIEnv& env) { + javaClass = *jni::Class<Collator>::Find(env).NewGlobalRef(env).release(); +} + +jni::Class<Collator> Collator::javaClass; + +jni::Object<Collator> Collator::getInstance(jni::JNIEnv& env, jni::Object<Locale> locale) { + using Signature = jni::Object<Collator>(jni::Object<Locale>); + auto method = javaClass.GetStaticMethod<Signature>(env, "getInstance"); + return javaClass.Call(env, method, locale); +} + +void Collator::setStrength(jni::JNIEnv& env, jni::Object<Collator> collator, jni::jint strength) { + using Signature = void(jni::jint); + auto static method = javaClass.GetMethod<Signature>(env, "setStrength"); + collator.Call(env, method, strength); +} + +jni::jint Collator::compare(jni::JNIEnv& env, jni::Object<Collator> collator, jni::String lhs, jni::String rhs) { + using Signature = jni::jint(jni::String, jni::String); + auto static method = javaClass.GetMethod<Signature>(env, "compare"); + return collator.Call(env, method, lhs, rhs); +} + +void Locale::registerNative(jni::JNIEnv& env) { + javaClass = *jni::Class<Locale>::Find(env).NewGlobalRef(env).release(); +} + + /* +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::String Locale::getLanguage(jni::JNIEnv& env, jni::Object<Locale> locale) { + using Signature = jni::String(); + auto static method = javaClass.GetMethod<Signature>(env, "getLanguage"); + return locale.Call(env, method); +} + +jni::String Locale::getCountry(jni::JNIEnv& env, jni::Object<Locale> locale) { + using Signature = jni::String(); + auto static method = javaClass.GetMethod<Signature>(env, "getCountry"); + return locale.Call(env, method); +} + +jni::Object<Locale> Locale::getDefault(jni::JNIEnv& env) { + using Signature = jni::Object<Locale>(); + auto method = javaClass.GetStaticMethod<Signature>(env, "getDefault"); + return javaClass.Call(env, method); +} + +jni::Object<Locale> Locale::New(jni::JNIEnv& env, jni::String language) { + static auto constructor = javaClass.GetConstructor<jni::String>(env); + return javaClass.New(env, constructor, language); +} + +jni::Object<Locale> Locale::New(jni::JNIEnv& env, jni::String language, jni::String region) { + static auto constructor = javaClass.GetConstructor<jni::String, jni::String>(env); + return javaClass.New(env, constructor, language, region); +} + +jni::Class<Locale> Locale::javaClass; + +} // namespace android + +namespace style { +namespace expression { + +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 = android::Locale::getDefault(*env).NewGlobalRef(*env); + } else if (!languageTag.region) { + locale = android::Locale::New(*env, + jni::Make<jni::String>(*env, *(languageTag.language))) + .NewGlobalRef(*env); + } else { + locale = android::Locale::New(*env, + jni::Make<jni::String>(*env, *(languageTag.language)), + jni::Make<jni::String>(*env, *(languageTag.region))) + .NewGlobalRef(*env); + } + collator = android::Collator::getInstance(*env, *locale).NewGlobalRef(*env);; + 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::String jlhs = jni::Make<jni::String>(*env, useUnaccent ? + platform::unaccent(lhs) : + lhs); + jni::String jrhs = jni::Make<jni::String>(*env, useUnaccent ? + platform::unaccent(rhs) : + rhs); + + jni::jint result = android::Collator::compare(*env, *collator, jlhs, jrhs);; + + jni::DeleteLocalRef(*env, jlhs); + jni::DeleteLocalRef(*env, jrhs); + + return result; + } + + std::string resolvedLocale() const { + jni::String jLanguage = android::Locale::getLanguage(*env, *locale); + std::string language = jni::Make<std::string>(*env, jLanguage); + jni::DeleteLocalRef(*env, jLanguage); + jni::String jRegion = android::Locale::getCountry(*env, *locale); + std::string region = jni::Make<std::string>(*env, jRegion); + jni::DeleteLocalRef(*env, jRegion); + + 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::UniqueObject<android::Collator> collator; + jni::UniqueObject<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 expression +} // namespace style +} // namespace mbgl diff --git a/platform/android/src/text/collator_jni.hpp b/platform/android/src/text/collator_jni.hpp new file mode 100644 index 0000000000..96c381b1c1 --- /dev/null +++ b/platform/android/src/text/collator_jni.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include <mbgl/util/image.hpp> + +#include <jni/jni.hpp> + +/* + android::Collator and android::Locale are + the JNI wrappers of + java/text/Collator and java/util/Locale + + mbgl::Collator is the portable interface + Both implementations are in collator.cpp + */ + +namespace mbgl { +namespace android { + +class Locale { +public: + static constexpr auto Name() { return "java/util/Locale"; }; + + /* Requires API level 21+ in order to support script/variant + static jni::Object<Locale> forLanguageTag(jni::JNIEnv&, jni::String); + static jni::String toLanguageTag(jni::JNIEnv&, jni::Object<Locale>); + */ + static jni::Object<Locale> getDefault(jni::JNIEnv&); + static jni::String getLanguage(jni::JNIEnv&, jni::Object<Locale>); + static jni::String getCountry(jni::JNIEnv&, jni::Object<Locale>); + + static jni::Object<Locale> New(jni::JNIEnv&, jni::String); + static jni::Object<Locale> New(jni::JNIEnv&, jni::String, jni::String); + + static jni::Class<Locale> javaClass; + + static void registerNative(jni::JNIEnv&); + +}; + +class Collator { +public: + static constexpr auto Name() { return "java/text/Collator"; }; + + static jni::Object<Collator> getInstance(jni::JNIEnv&, jni::Object<Locale>); + + static void setStrength(jni::JNIEnv&, jni::Object<Collator>, jni::jint); + + static jni::jint compare(jni::JNIEnv&, jni::Object<Collator>, jni::String, jni::String); + + static jni::Class<Collator> javaClass; + + static void registerNative(jni::JNIEnv&); + +}; + +} // namespace android +} // namespace mbgl |