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