1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.javascript.host.intl;
16
17 import java.util.IllformedLocaleException;
18 import java.util.List;
19
20 import org.apache.commons.lang3.LocaleUtils;
21 import org.htmlunit.corejs.javascript.Context;
22 import org.htmlunit.corejs.javascript.Function;
23 import org.htmlunit.corejs.javascript.FunctionObject;
24 import org.htmlunit.corejs.javascript.Scriptable;
25 import org.htmlunit.corejs.javascript.ScriptableObject;
26 import org.htmlunit.corejs.javascript.VarScope;
27 import org.htmlunit.javascript.HtmlUnitScriptable;
28 import org.htmlunit.javascript.JavaScriptEngine;
29 import org.htmlunit.javascript.configuration.JsxClass;
30 import org.htmlunit.javascript.configuration.JsxConstructor;
31 import org.htmlunit.javascript.configuration.JsxFunction;
32 import org.htmlunit.javascript.configuration.JsxGetter;
33 import org.htmlunit.javascript.configuration.JsxSymbolConstant;
34
35
36
37
38
39
40
41
42
43 @JsxClass
44 public class Locale extends HtmlUnitScriptable {
45
46
47 @JsxSymbolConstant
48 public static final String TO_STRING_TAG = "Intl.Locale";
49
50 private static final List<String> ALLOWED_HOUR_CYCLES = List.of("h11", "h12", "h23", "h24");
51 private static final List<String> ALLOWED_CASE_FIRSTS = List.of("upper", "lower", "false");
52
53 private java.util.Locale locale_;
54 private String language_;
55 private String script_;
56 private String region_;
57 private String calendar_;
58 private String collation_;
59 private String numberingSystem_;
60 private String caseFirst_;
61 private String hourCycle_;
62 private boolean numeric_;
63
64
65
66
67 public Locale() {
68 super();
69 }
70
71 private Locale(final java.util.Locale locale) {
72 super();
73 locale_ = locale;
74 language_ = locale.getLanguage();
75 if (!locale.getScript().isEmpty()) {
76 script_ = locale.getScript();
77 }
78 if (!locale.getCountry().isEmpty()) {
79 region_ = locale.getCountry();
80 }
81 if (locale.hasExtensions()) {
82 calendar_ = locale.getUnicodeLocaleType("ca");
83 collation_ = locale.getUnicodeLocaleType("co");
84 numberingSystem_ = locale.getUnicodeLocaleType("nu");
85 caseFirst_ = locale.getUnicodeLocaleType("kf");
86 hourCycle_ = locale.getUnicodeLocaleType("hc");
87 numeric_ = Boolean.parseBoolean(locale.getUnicodeLocaleType("kn"));
88 }
89 }
90
91
92
93
94
95
96
97
98
99
100 @JsxConstructor
101 public static Scriptable jsConstructor(final Context cx, final VarScope scope,
102 final Object[] args, final Function ctorObj, final boolean inNewExpr) {
103 if (args.length == 0 || JavaScriptEngine.isUndefined(args[0])) {
104 throw JavaScriptEngine.typeError("Invalid element in locales argument");
105 }
106
107 final String languageTag = JavaScriptEngine.toString(args[0]);
108 if (languageTag.isEmpty()) {
109 throw JavaScriptEngine.rangeError("Invalid language tag: ");
110 }
111
112 java.util.Locale locale;
113 try {
114 locale = new java.util.Locale.Builder()
115 .setLanguageTag(languageTag)
116 .build();
117 }
118 catch (final IllformedLocaleException e) {
119 throw JavaScriptEngine.rangeError("Invalid language tag: " + languageTag);
120 }
121
122
123 if (args.length > 1 && !JavaScriptEngine.isUndefined(args[1])) {
124 locale = overrideExistingWithOptions(locale, ScriptableObject.ensureScriptableObject(args[1]));
125 }
126
127 final Locale l = new Locale(locale);
128 l.setParentScope(getTopLevelScope(scope));
129 l.setPrototype(((FunctionObject) ctorObj).getClassPrototype());
130 return l;
131 }
132
133 private static java.util.Locale overrideExistingWithOptions(
134 final java.util.Locale existing, final ScriptableObject options) {
135 final java.util.Locale.Builder builder = new java.util.Locale.Builder().setLocale(existing);
136
137 setStringOption(builder, options, "language");
138 setStringOption(builder, options, "script");
139 setStringOption(builder, options, "region");
140 setUnicodeKeyword(builder, options, "calendar", "ca", null);
141 setUnicodeKeyword(builder, options, "collation", "co", null);
142 setUnicodeKeyword(builder, options, "numberingSystem", "nu", null);
143 setUnicodeKeyword(builder, options, "caseFirst", "kf", ALLOWED_CASE_FIRSTS);
144 setUnicodeKeyword(builder, options, "hourCycle", "hc", ALLOWED_HOUR_CYCLES);
145
146 final Object numeric = ScriptableObject.getProperty(options, "numeric");
147 if (numeric != Scriptable.NOT_FOUND && !JavaScriptEngine.isUndefined(numeric)) {
148 final boolean isNumeric = numeric instanceof Boolean ? (Boolean) numeric : true;
149 builder.setUnicodeLocaleKeyword("kn", Boolean.toString(isNumeric));
150 }
151
152 return builder.build();
153 }
154
155 private static void setStringOption(final java.util.Locale.Builder builder,
156 final ScriptableObject options, final String optionName) {
157 final Object value = ScriptableObject.getProperty(options, optionName);
158 if (value == Scriptable.NOT_FOUND || JavaScriptEngine.isUndefined(value)) {
159 return;
160 }
161 try {
162 final String s = JavaScriptEngine.toString(value);
163 switch (optionName) {
164 case "language":
165 builder.setLanguage(s);
166 break;
167 case "script":
168 builder.setScript(s);
169 break;
170 case "region":
171 builder.setRegion(s);
172 break;
173 default:
174 break;
175 }
176 }
177 catch (final Exception e) {
178 throw JavaScriptEngine.rangeError("Invalid value for option \"" + optionName + "\"");
179 }
180 }
181
182 private static void setUnicodeKeyword(final java.util.Locale.Builder builder,
183 final ScriptableObject options, final String optionName, final String unicodeKey,
184 final List<String> allowedValues) {
185 final Object value = ScriptableObject.getProperty(options, optionName);
186 if (value == Scriptable.NOT_FOUND || JavaScriptEngine.isUndefined(value)) {
187 return;
188 }
189 final String s;
190 try {
191 s = JavaScriptEngine.toString(value);
192 }
193 catch (final Exception e) {
194 throw JavaScriptEngine.rangeError("Invalid value for option \"" + optionName + "\"");
195 }
196 if (allowedValues != null && !allowedValues.contains(s)) {
197 throw JavaScriptEngine.rangeError("Invalid value for option \"" + optionName + "\"");
198 }
199 builder.setUnicodeLocaleKeyword(unicodeKey, s);
200 }
201
202
203
204
205
206 @JsxGetter
207 public Object getLanguage() {
208 return language_ != null ? language_ : JavaScriptEngine.UNDEFINED;
209 }
210
211
212
213
214
215 @JsxGetter
216 public Object getScript() {
217 return script_ != null ? script_ : JavaScriptEngine.UNDEFINED;
218 }
219
220
221
222
223
224 @JsxGetter
225 public Object getRegion() {
226 return region_ != null ? region_ : JavaScriptEngine.UNDEFINED;
227 }
228
229
230
231
232
233 @JsxGetter
234 public Object getCalendar() {
235 return calendar_ != null ? calendar_ : JavaScriptEngine.UNDEFINED;
236 }
237
238
239
240
241
242 @JsxGetter
243 public Object getCollation() {
244 return collation_ != null ? collation_ : JavaScriptEngine.UNDEFINED;
245 }
246
247
248
249
250
251 @JsxGetter
252 public Object getNumberingSystem() {
253 return numberingSystem_ != null ? numberingSystem_ : JavaScriptEngine.UNDEFINED;
254 }
255
256
257
258
259
260 @JsxGetter
261 public Object getCaseFirst() {
262 return caseFirst_ != null ? caseFirst_ : JavaScriptEngine.UNDEFINED;
263 }
264
265
266
267
268
269 @JsxGetter
270 public Object getHourCycle() {
271 return hourCycle_ != null ? hourCycle_ : JavaScriptEngine.UNDEFINED;
272 }
273
274
275
276
277
278 @JsxGetter
279 public boolean isNumeric() {
280 return numeric_;
281 }
282
283
284
285
286
287 @JsxGetter
288 public Object getBaseName() {
289 final String variant = locale_.getVariant().replace("_", "-");
290 return language_
291 + (script_ != null ? "-" + script_ : "")
292 + (region_ != null ? "-" + region_ : "")
293 + (!variant.isEmpty() ? "-" + variant : "");
294 }
295
296
297
298
299
300 @JsxFunction
301 public Locale maximize() {
302 final String region;
303 if (region_ != null) {
304 region = region_;
305 }
306 else {
307 final java.util.List<java.util.Locale> locales =
308 LocaleUtils.countriesByLanguage(language_);
309 if (!locales.isEmpty()) {
310 region = locales.get(0).getCountry();
311 }
312 else {
313 region = null;
314 }
315 }
316
317 final java.util.Locale locale = new java.util.Locale.Builder()
318 .setLanguage(language_)
319 .setScript(script_)
320 .setRegion(region)
321 .setExtension('u', locale_.getExtension('u'))
322 .build();
323
324 final Locale l = new Locale(locale);
325 l.setParentScope(getTopLevelScope(getParentScope()));
326 l.setPrototype(this.getPrototype());
327 return l;
328 }
329
330
331
332
333
334 @JsxFunction
335 public Locale minimize() {
336 final java.util.Locale locale = new java.util.Locale.Builder()
337 .setLanguage(language_)
338 .setExtension('u', locale_.getExtension('u'))
339 .build();
340
341 final Locale l = new Locale(locale);
342 l.setParentScope(getTopLevelScope(getParentScope()));
343 l.setPrototype(this.getPrototype());
344 return l;
345 }
346
347
348
349
350
351 @JsxFunction(functionName = "toString")
352 public String jsToString() {
353 if (locale_ == null) {
354 return super.toString();
355 }
356 return locale_.toLanguageTag();
357 }
358
359
360
361
362 @Override
363 public Object getDefaultValue(final Class<?> hint) {
364 if (getPrototype() != null && (String.class.equals(hint) || hint == null)) {
365 return jsToString();
366 }
367 return super.getDefaultValue(hint);
368 }
369 }