• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.res.android;
2 
3 import static org.robolectric.res.android.LocaleDataTables.LIKELY_SCRIPTS;
4 import static org.robolectric.res.android.LocaleDataTables.MAX_PARENT_DEPTH;
5 import static org.robolectric.res.android.LocaleDataTables.REPRESENTATIVE_LOCALES;
6 import static org.robolectric.res.android.LocaleDataTables.SCRIPT_CODES;
7 import static org.robolectric.res.android.LocaleDataTables.SCRIPT_PARENTS;
8 
9 import java.util.Arrays;
10 import java.util.Map;
11 
12 // transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/LocaleData.cpp
13 public class LocaleData {
14 
packLocale(final byte[] language, final byte[] region)15   private static int packLocale(final byte[] language, final byte[] region) {
16     return ((language[0] & 0xff) << 24) | ((language[1] & 0xff) << 16) |
17         ((region[0] & 0xff) << 8) | (region[1] & 0xff);
18   }
19 
dropRegion(int packed_locale)20   private static int dropRegion(int packed_locale) {
21     return packed_locale & 0xFFFF0000;
22   }
23 
hasRegion(int packed_locale)24   private static boolean hasRegion(int packed_locale) {
25     return (packed_locale & 0x0000FFFF) != 0;
26   }
27 
28   static final int SCRIPT_LENGTH = 4;
29   private static final int PACKED_ROOT = 0; // to represent the root locale
30 
findParent(int packed_locale, final String script)31   private static int findParent(int packed_locale, final String script) {
32     if (hasRegion(packed_locale)) {
33       for (Map.Entry<String, Map<Integer, Integer>> entry : SCRIPT_PARENTS.entrySet()) {
34         if (script.equals(entry.getKey())) {
35           Map<Integer, Integer> map = entry.getValue();
36           Integer lookup_result = map.get(packed_locale);
37           if (lookup_result != null) {
38             return lookup_result;
39           }
40           break;
41         }
42       }
43       return dropRegion(packed_locale);
44     }
45     return PACKED_ROOT;
46   }
47 
48   // Find the ancestors of a locale, and fill 'out' with it (assumes out has enough
49   // space). If any of the members of stop_list was seen, write it in the
50   // output but stop afterwards.
51   //
52   // This also outputs the index of the last written ancestor in the stop_list
53   // to stop_list_index, which will be -1 if it is not found in the stop_list.
54   //
55   // Returns the number of ancestors written in the output, which is always
56   // at least one.
57   //
58   // (If 'out' is null, we do everything the same way but we simply don't write
59   // any results in 'out'.)
findAncestors(int[] out, Ref<Long> stop_list_index, int packed_locale, final String script, final int[] stop_list, int stop_set_length)60   static int findAncestors(int[] out, Ref<Long> stop_list_index,
61       int packed_locale, final String script,
62       final int[] stop_list, int stop_set_length) {
63     int ancestor = packed_locale;
64     int count = 0;
65     do {
66       if (out != null) {
67         out[count] = ancestor;
68       }
69       count++;
70       for (int i = 0; i < stop_set_length; i++) {
71         if (stop_list[i] == ancestor) {
72           stop_list_index.set((long) i);
73           return count;
74         }
75       }
76       ancestor = findParent(ancestor, script);
77     } while (ancestor != PACKED_ROOT);
78     stop_list_index.set((long) -1);
79     return count;
80   }
81 
findDistance(int supported, final String script, final int[] request_ancestors, int request_ancestors_count)82   static int findDistance(int supported,
83       final String script,
84       final int[] request_ancestors,
85       int request_ancestors_count) {
86     final Ref<Long> request_ancestors_indexRef = new Ref<>(null);
87     final int supported_ancestor_count = findAncestors(
88         null, request_ancestors_indexRef,
89         supported, script,
90         request_ancestors, request_ancestors_count);
91     // Since both locales share the same root, there will always be a shared
92     // ancestor, so the distance in the parent tree is the sum of the distance
93     // of 'supported' to the lowest common ancestor (number of ancestors
94     // written for 'supported' minus 1) plus the distance of 'request' to the
95     // lowest common ancestor (the index of the ancestor in request_ancestors).
96     return (int) (supported_ancestor_count + request_ancestors_indexRef.get() - 1);
97   }
98 
isRepresentative(int language_and_region, final String script)99   static boolean isRepresentative(int language_and_region, final String script) {
100     final long packed_locale = (
101         (((long) language_and_region) << 32) |
102             (((long) script.charAt(0) & 0xff) << 24) |
103             (((long) script.charAt(1) & 0xff) << 16) |
104             (((long) script.charAt(2) & 0xff) << 8) |
105             ((long) script.charAt(3) & 0xff));
106     return (REPRESENTATIVE_LOCALES.contains(packed_locale));
107   }
108 
109   private static final int US_SPANISH = 0x65735553; // es-US
110   private static final int MEXICAN_SPANISH = 0x65734D58; // es-MX
111   private static final int LATIN_AMERICAN_SPANISH = 0x6573A424; // es-419
112 
113   // The two locales es-US and es-MX are treated as special fallbacks for es-419.
114 // If there is no es-419, they are considered its equivalent.
isSpecialSpanish(int language_and_region)115   private static boolean isSpecialSpanish(int language_and_region) {
116     return (language_and_region == US_SPANISH || language_and_region == MEXICAN_SPANISH);
117   }
118 
localeDataCompareRegions( final byte[] left_region, final byte[] right_region, final byte[] requested_language, final String requested_script, final byte[] requested_region)119   static int localeDataCompareRegions(
120       final byte[] left_region, final byte[] right_region,
121       final byte[] requested_language, final String requested_script,
122       final byte[] requested_region) {
123     if (left_region[0] == right_region[0] && left_region[1] == right_region[1]) {
124       return 0;
125     }
126     int left = packLocale(requested_language, left_region);
127     int right = packLocale(requested_language, right_region);
128     final int request = packLocale(requested_language, requested_region);
129 
130     // If one and only one of the two locales is a special Spanish locale, we
131     // replace it with es-419. We don't do the replacement if the other locale
132     // is already es-419, or both locales are special Spanish locales (when
133     // es-US is being compared to es-MX).
134     final boolean leftIsSpecialSpanish = isSpecialSpanish(left);
135     final boolean rightIsSpecialSpanish = isSpecialSpanish(right);
136     if (leftIsSpecialSpanish && !rightIsSpecialSpanish && right != LATIN_AMERICAN_SPANISH) {
137       left = LATIN_AMERICAN_SPANISH;
138     } else if (rightIsSpecialSpanish && !leftIsSpecialSpanish && left != LATIN_AMERICAN_SPANISH) {
139       right = LATIN_AMERICAN_SPANISH;
140     }
141 
142     int[] request_ancestors = new int[MAX_PARENT_DEPTH + 1];
143     final Ref<Long> left_right_indexRef = new Ref<Long>(null);
144     // Find the parents of the request, but stop as soon as we saw left or right
145     final int left_and_right[] = {left, right};
146     final int ancestor_count = findAncestors(
147         request_ancestors, left_right_indexRef,
148         request, requested_script,
149         left_and_right, sizeof(left_and_right));
150     if (left_right_indexRef.get() == 0) { // We saw left earlier
151       return 1;
152     }
153     if (left_right_indexRef.get() == 1) { // We saw right earlier
154       return -1;
155     }
156     // If we are here, neither left nor right are an ancestor of the
157     // request. This means that all the ancestors have been computed and
158     // the last ancestor is just the language by itself. We will use the
159     // distance in the parent tree for determining the better match.
160     final int left_distance = findDistance(
161         left, requested_script, request_ancestors, ancestor_count);
162     final int right_distance = findDistance(
163         right, requested_script, request_ancestors, ancestor_count);
164     if (left_distance != right_distance) {
165       return (int) right_distance - (int) left_distance; // smaller distance is better
166     }
167     // If we are here, left and right are equidistant from the request. We will
168     // try and see if any of them is a representative locale.
169     final boolean left_is_representative = isRepresentative(left, requested_script);
170     final boolean right_is_representative = isRepresentative(right, requested_script);
171     if (left_is_representative != right_is_representative) {
172       return (left_is_representative ? 1 : 0) - (right_is_representative ? 1 : 0);
173     }
174     // We have no way of figuring out which locale is a better match. For
175     // the sake of stability, we consider the locale with the lower region
176     // code (in dictionary order) better, with two-letter codes before
177     // three-digit codes (since two-letter codes are more specific).
178     return right - left;
179   }
180 
localeDataComputeScript(byte[] out, final byte[] language, final byte[] region)181   static void localeDataComputeScript(byte[] out, final byte[] language, final byte[] region) {
182     if (language[0] == '\0') {
183 //      memset(out, '\0', SCRIPT_LENGTH);
184       Arrays.fill(out, (byte) 0);
185       return;
186     }
187     int lookup_key = packLocale(language, region);
188     Byte lookup_result = LIKELY_SCRIPTS.get(lookup_key);
189     if (lookup_result == null) {
190       // We couldn't find the locale. Let's try without the region
191       if (region[0] != '\0') {
192         lookup_key = dropRegion(lookup_key);
193         lookup_result = LIKELY_SCRIPTS.get(lookup_key);
194         if (lookup_result != null) {
195 //          memcpy(out, SCRIPT_CODES[lookup_result.second], SCRIPT_LENGTH);
196           System.arraycopy(SCRIPT_CODES[lookup_result], 0, out, 0, SCRIPT_LENGTH);
197           return;
198         }
199       }
200       // We don't know anything about the locale
201 //      memset(out, '\0', SCRIPT_LENGTH);
202       Arrays.fill(out, (byte) 0);
203       return;
204     } else {
205       // We found the locale.
206 //      memcpy(out, SCRIPT_CODES[lookup_result.second], SCRIPT_LENGTH);
207       System.arraycopy(SCRIPT_CODES[lookup_result], 0, out, 0, SCRIPT_LENGTH);
208     }
209   }
210 
211   static final int[] ENGLISH_STOP_LIST = {
212       0x656E0000, // en
213       0x656E8400, // en-001
214   };
215 
216   static final byte[] ENGLISH_CHARS = {'e', 'n'};
217 
218   static final String LATIN_CHARS = "Latn";
219 
localeDataIsCloseToUsEnglish(final byte[] region)220   static boolean localeDataIsCloseToUsEnglish(final byte[] region) {
221     final int locale = packLocale(ENGLISH_CHARS, region);
222     final Ref<Long> stop_list_indexRef = new Ref<>(null);
223     findAncestors(null, stop_list_indexRef, locale, LATIN_CHARS, ENGLISH_STOP_LIST, 2);
224     // A locale is like US English if we see "en" before "en-001" in its ancestor list.
225     return stop_list_indexRef.get() == 0; // 'en' is first in ENGLISH_STOP_LIST
226   }
227 
228 
sizeof(int[] array)229   private static int sizeof(int[] array) {
230     return array.length;
231   }
232 
233 }
234