summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Loer <chris.loer@gmail.com>2018-06-27 15:01:54 -0700
committerChris Loer <chris.loer@mapbox.com>2018-07-03 10:03:05 -0700
commit11c333672f9823a1951eb3f0a46813eb9b302e98 (patch)
treee09a2c21089cab22d19865c46f4ec35a3582a699
parentcb1328d1d049d5ac7376d4bd07c3b08f2c5f7c7a (diff)
downloadqtlocation-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.
-rw-r--r--platform/android/config.cmake6
-rwxr-xr-xplatform/android/src/jni.cpp3
-rw-r--r--platform/android/src/text/collator.cpp199
-rw-r--r--platform/android/src/text/collator_jni.hpp57
4 files changed, 264 insertions, 1 deletions
diff --git a/platform/android/config.cmake b/platform/android/config.cmake
index bb4fa3bfb6..fa26987e5a 100644
--- a/platform/android/config.cmake
+++ b/platform/android/config.cmake
@@ -37,13 +37,17 @@ macro(mbgl_platform_core)
PRIVATE platform/android/src/timer.cpp
# Misc
- PRIVATE platform/android/src/text/local_glyph_rasterizer_jni.hpp
+ PRIVATE platform/android/src/text/collator.cpp
+ PRIVATE platform/android/src/text/collator_jni.hpp
PRIVATE platform/android/src/text/local_glyph_rasterizer.cpp
+ PRIVATE platform/android/src/text/local_glyph_rasterizer_jni.hpp
PRIVATE platform/android/src/logging_android.cpp
PRIVATE platform/android/src/thread.cpp
PRIVATE platform/default/string_stdlib.cpp
PRIVATE platform/default/bidi.cpp
PRIVATE platform/default/thread_local.cpp
+ PRIVATE platform/default/unaccent.cpp
+ PRIVATE platform/default/unaccent.hpp
PRIVATE platform/default/utf.cpp
# Image handling
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