From cee3c150ab648b6cee46fb377598c157824d3a61 Mon Sep 17 00:00:00 2001 From: Markus Scherer Date: Mon, 5 Nov 2018 15:21:08 -0800 Subject: [PATCH] ICU-20255 revert to reflection for methods not yet in Android API level 21..23 --- .../src/com/ibm/icu/impl/JavaTimeZone.java | 23 ++- .../core/src/com/ibm/icu/util/ULocale.java | 174 +++++++++++++++--- 2 files changed, 166 insertions(+), 31 deletions(-) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/JavaTimeZone.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/JavaTimeZone.java index b078c78f43a..e71e31f1555 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/JavaTimeZone.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/JavaTimeZone.java @@ -10,6 +10,8 @@ package com.ibm.icu.impl; import java.io.IOException; import java.io.ObjectInputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Date; import java.util.TreeSet; @@ -33,6 +35,7 @@ public class JavaTimeZone extends TimeZone { private java.util.TimeZone javatz; private transient java.util.Calendar javacal; + private static Method mObservesDaylightTime; static { AVAILABLESET = new TreeSet<>(); @@ -40,6 +43,14 @@ public class JavaTimeZone extends TimeZone { for (int i = 0; i < availableIds.length; i++) { AVAILABLESET.add(availableIds[i]); } + + try { + mObservesDaylightTime = java.util.TimeZone.class.getMethod("observesDaylightTime", (Class[]) null); + } catch (NoSuchMethodException e) { + // Android API level 21..23 + } catch (SecurityException e) { + // not visible + } } /** @@ -187,7 +198,17 @@ public class JavaTimeZone extends TimeZone { */ @Override public boolean observesDaylightTime() { - return javatz.observesDaylightTime(); + if (mObservesDaylightTime != null) { + // Java 7+, Android API level 24+ + // https://developer.android.com/reference/java/util/TimeZone + try { + return (Boolean)mObservesDaylightTime.invoke(javatz, (Object[]) null); + } catch (IllegalAccessException e) { + } catch (IllegalArgumentException e) { + } catch (InvocationTargetException e) { + } + } + return super.observesDaylightTime(); } /* (non-Javadoc) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/ULocale.java b/icu4j/main/classes/core/src/com/ibm/icu/util/ULocale.java index 11bf41dbb97..f6507847722 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/util/ULocale.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/util/ULocale.java @@ -10,6 +10,8 @@ package com.ibm.icu.util; import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.text.ParseException; import java.util.Iterator; import java.util.List; @@ -548,14 +550,20 @@ public final class ULocale implements Serializable, Comparable { static { defaultULocale = forLocale(defaultLocale); - // On JRE 7+, Locale.getDefault() should reflect the - // property value to the Locale's default. So ICU just relies on - // Locale.getDefault(). - - for (Category cat: Category.values()) { - int idx = cat.ordinal(); - defaultCategoryLocales[idx] = JDKLocaleHelper.getDefault(cat); - defaultCategoryULocales[idx] = forLocale(defaultCategoryLocales[idx]); + if (JDKLocaleHelper.hasLocaleCategories()) { + for (Category cat: Category.values()) { + int idx = cat.ordinal(); + defaultCategoryLocales[idx] = JDKLocaleHelper.getDefault(cat); + defaultCategoryULocales[idx] = forLocale(defaultCategoryLocales[idx]); + } + } else { + // Android API level 21..23 does not have separate category locales, + // use the non-category default for all. + for (Category cat: Category.values()) { + int idx = cat.ordinal(); + defaultCategoryLocales[idx] = defaultLocale; + defaultCategoryULocales[idx] = defaultULocale; + } } } @@ -585,7 +593,17 @@ public final class ULocale implements Serializable, Comparable { if (!defaultLocale.equals(currentDefault)) { defaultLocale = currentDefault; defaultULocale = forLocale(currentDefault); - } + + if (!JDKLocaleHelper.hasLocaleCategories()) { + // Detected Java default Locale change. + // We need to update category defaults to match + // Java 7's behavior on Android API level 21..23. + for (Category cat : Category.values()) { + int idx = cat.ordinal(); + defaultCategoryLocales[idx] = currentDefault; + defaultCategoryULocales[idx] = forLocale(currentDefault); + } + } } return defaultULocale; } } @@ -633,13 +651,40 @@ public final class ULocale implements Serializable, Comparable { // cyclic dependency for category default. return ULocale.ROOT; } + if (JDKLocaleHelper.hasLocaleCategories()) { + Locale currentCategoryDefault = JDKLocaleHelper.getDefault(category); + if (!defaultCategoryLocales[idx].equals(currentCategoryDefault)) { + defaultCategoryLocales[idx] = currentCategoryDefault; + defaultCategoryULocales[idx] = forLocale(currentCategoryDefault); + } + } else { + // java.util.Locale.setDefault(Locale) in Java 7 updates + // category locale defaults. On Android API level 21..23 + // ICU4J checks if the default locale has changed and update + // category ULocales here if necessary. + + // Note: When java.util.Locale.setDefault(Locale) is called + // with a Locale same with the previous one, Java 7 still + // updates category locale defaults. On Android API level 21..23 + // there is no good way to detect the event, ICU4J simply + // checks if the default Java Locale has changed since last + // time. + + Locale currentDefault = Locale.getDefault(); + if (!defaultLocale.equals(currentDefault)) { + defaultLocale = currentDefault; + defaultULocale = forLocale(currentDefault); + + for (Category cat : Category.values()) { + int tmpIdx = cat.ordinal(); + defaultCategoryLocales[tmpIdx] = currentDefault; + defaultCategoryULocales[tmpIdx] = forLocale(currentDefault); + } + } - Locale currentCategoryDefault = JDKLocaleHelper.getDefault(category); - if (!defaultCategoryLocales[idx].equals(currentCategoryDefault)) { - defaultCategoryLocales[idx] = currentCategoryDefault; - defaultCategoryULocales[idx] = forLocale(currentCategoryDefault); + // No synchronization with JDK Locale, because category default + // is not supported in Android API level 21..23. } - return defaultCategoryULocales[idx]; } } @@ -3955,10 +4000,65 @@ public final class ULocale implements Serializable, Comparable { * JDK Locale Helper */ private static final class JDKLocaleHelper { + // Java 7 has java.util.Locale.Category. + // Android API level 21..23 do not yet have it; only API level 24 (Nougat) adds it. + // https://developer.android.com/reference/java/util/Locale.Category + private static boolean hasLocaleCategories = false; + + private static Method mGetDefault; + private static Method mSetDefault; + private static Object eDISPLAY; + private static Object eFORMAT; + + static { + do { + try { + Class cCategory = null; + Class[] classes = Locale.class.getDeclaredClasses(); + for (Class c : classes) { + if (c.getName().equals("java.util.Locale$Category")) { + cCategory = c; + break; + } + } + if (cCategory == null) { + break; + } + mGetDefault = Locale.class.getDeclaredMethod("getDefault", cCategory); + mSetDefault = Locale.class.getDeclaredMethod("setDefault", cCategory, Locale.class); + + Method mName = cCategory.getMethod("name", (Class[]) null); + Object[] enumConstants = cCategory.getEnumConstants(); + for (Object e : enumConstants) { + String catVal = (String)mName.invoke(e, (Object[])null); + if (catVal.equals("DISPLAY")) { + eDISPLAY = e; + } else if (catVal.equals("FORMAT")) { + eFORMAT = e; + } + } + if (eDISPLAY == null || eFORMAT == null) { + break; + } + + hasLocaleCategories = true; + } catch (NoSuchMethodException e) { + } catch (IllegalArgumentException e) { + } catch (IllegalAccessException e) { + } catch (InvocationTargetException e) { + } catch (SecurityException e) { + // TODO : report? + } + } while (false); + } private JDKLocaleHelper() { } + public static boolean hasLocaleCategories() { + return hasLocaleCategories; + } + public static ULocale toULocale(Locale loc) { String language = loc.getLanguage(); String script = ""; @@ -4123,39 +4223,53 @@ public final class ULocale implements Serializable, Comparable { } public static Locale getDefault(Category category) { - java.util.Locale.Category cat = null; - if (category != null) { + if (hasLocaleCategories) { + Object cat = null; switch (category) { case DISPLAY: - cat = java.util.Locale.Category.DISPLAY; + cat = eDISPLAY; break; case FORMAT: - cat = java.util.Locale.Category.FORMAT; + cat = eFORMAT; break; } - } - if (cat != null) { - return Locale.getDefault(cat); + if (cat != null) { + try { + return (Locale)mGetDefault.invoke(null, cat); + } catch (InvocationTargetException e) { + // fall through - use the base default + } catch (IllegalArgumentException e) { + // fall through - use the base default + } catch (IllegalAccessException e) { + // fall through - use the base default + } + } } return Locale.getDefault(); } public static void setDefault(Category category, Locale newLocale) { - java.util.Locale.Category cat = null; - if (category != null) { + if (hasLocaleCategories) { + Object cat = null; switch (category) { case DISPLAY: - cat = java.util.Locale.Category.DISPLAY; + cat = eDISPLAY; break; case FORMAT: - cat = java.util.Locale.Category.FORMAT; + cat = eFORMAT; break; } - } - if (cat != null) { - Locale.setDefault(cat, newLocale); - } else { - Locale.setDefault(newLocale); + if (cat != null) { + try { + mSetDefault.invoke(null, cat, newLocale); + } catch (InvocationTargetException e) { + // fall through - no effects + } catch (IllegalArgumentException e) { + // fall through - no effects + } catch (IllegalAccessException e) { + // fall through - no effects + } + } } } } -- 2.40.0