/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.runtime.objects.intl;
import static com.github.anba.es6draft.runtime.AbstractOperations.ToObject;
import static com.github.anba.es6draft.runtime.internal.Properties.createProperties;
import static com.github.anba.es6draft.runtime.objects.intl.IntlAbstractOperations.*;
import static java.util.Arrays.asList;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.github.anba.es6draft.runtime.ExecutionContext;
import com.github.anba.es6draft.runtime.Realm;
import com.github.anba.es6draft.runtime.internal.Initializable;
import com.github.anba.es6draft.runtime.internal.Lazy;
import com.github.anba.es6draft.runtime.internal.Properties.Attributes;
import com.github.anba.es6draft.runtime.internal.Properties.Function;
import com.github.anba.es6draft.runtime.internal.Properties.Prototype;
import com.github.anba.es6draft.runtime.internal.Properties.Value;
import com.github.anba.es6draft.runtime.objects.intl.IntlAbstractOperations.ExtensionKey;
import com.github.anba.es6draft.runtime.objects.intl.IntlAbstractOperations.LocaleData;
import com.github.anba.es6draft.runtime.objects.intl.IntlAbstractOperations.LocaleDataInfo;
import com.github.anba.es6draft.runtime.objects.intl.IntlAbstractOperations.OptionsRecord;
import com.github.anba.es6draft.runtime.objects.intl.IntlAbstractOperations.ResolvedLocale;
import com.github.anba.es6draft.runtime.types.Constructor;
import com.github.anba.es6draft.runtime.types.Intrinsics;
import com.github.anba.es6draft.runtime.types.ScriptObject;
import com.github.anba.es6draft.runtime.types.Type;
import com.github.anba.es6draft.runtime.types.builtins.BuiltinConstructor;
import com.ibm.icu.text.Collator;
import com.ibm.icu.util.ULocale;
/**
* <h1>10 Collator Objects</h1>
* <ul>
* <li>10.1 The Intl.Collator Constructor
* <li>10.2 Properties of the Intl.Collator Constructor
* </ul>
*/
public final class CollatorConstructor extends BuiltinConstructor implements Initializable {
/** [[availableLocales]] */
private final Lazy<Set<String>> availableLocales = Lazy
.of(() -> GetAvailableLocales(LanguageData.getAvailableCollatorLocales()));
/**
* [[availableLocales]]
*
* @param cx
* the execution context
* @return the set of available locales supported by {@code Intl.Collator}
*/
public static Set<String> getAvailableLocales(ExecutionContext cx) {
return getAvailableLocalesLazy(cx).get();
}
private static Lazy<Set<String>> getAvailableLocalesLazy(ExecutionContext cx) {
CollatorConstructor collator = (CollatorConstructor) cx.getIntrinsic(Intrinsics.Intl_Collator);
return collator.availableLocales;
}
/** [[relevantExtensionKeys]] */
private static final List<ExtensionKey> relevantExtensionKeys = asList(ExtensionKey.co, ExtensionKey.kn,
ExtensionKey.kf);
/**
* Collation type keys (BCP 47; CLDR, version 28)
*/
private enum CollationType {/* @formatter:off */
big5han("big5han"),
compat("compat"),
dict("dict", "dictionary"),
direct("direct"), // deprecated, not supported in ICU
ducet("ducet"),
emoji("emoji"),
eor("eor"),
gb2312("gb2312", "gb2312han"),
phonebk("phonebk", "phonebook"),
phonetic("phonetic"),
pinyin("pinyin"),
reformed("reformed"),
search("search"),
searchjl("searchjl"),
standard("standard"),
stroke("stroke"),
trad("trad", "traditional"),
unihan("unihan"), // not supported in ICU
zhuyin("zhuyin");
/* @formatter:on */
private final String name;
private final String alias;
private CollationType(String name) {
this.name = name;
this.alias = null;
}
private CollationType(String name, String alias) {
this.name = name;
this.alias = alias;
}
public String getName() {
return name;
}
public static CollationType forName(String name) {
for (CollationType co : values()) {
if (name.equals(co.name) || name.equals(co.alias)) {
return co;
}
}
throw new IllegalArgumentException(name);
}
}
/** [[sortLocaleData]] + [[searchLocaleData]] */
private static final class CollatorLocaleData implements LocaleData {
private final String usage;
public CollatorLocaleData(String usage) {
this.usage = usage;
}
@Override
public LocaleDataInfo info(ULocale locale) {
return new CollatorLocaleDataInfo(usage, locale);
}
}
/** [[sortLocaleData]] + [[searchLocaleData]] */
private static final class CollatorLocaleDataInfo implements LocaleDataInfo {
private final String usage;
private final ULocale locale;
public CollatorLocaleDataInfo(String usage, ULocale locale) {
this.usage = usage;
this.locale = locale;
}
@Override
public String defaultValue(ExtensionKey extensionKey) {
switch (extensionKey) {
case co:
return null; // null must be first value, cf. 10.2.3
case kf:
if ("sort".equals(usage)) {
// special case 'sort' usage for Danish and Maltese to use uppercase first defaults
String language = locale.getLanguage();
if ("da".equals(language) || "mt".equals(language)) {
return "upper";
}
}
return "false";
case kn:
return "false";
default:
throw new IllegalArgumentException(extensionKey.name());
}
}
@Override
public List<String> entries(ExtensionKey extensionKey) {
switch (extensionKey) {
case co:
return getCollationInfo();
case kf:
return getCaseFirstInfo();
case kn:
return getNumericInfo();
default:
throw new IllegalArgumentException(extensionKey.name());
}
}
private List<String> getCollationInfo() {
String[] values = Collator.getKeywordValuesForLocale("collation", locale, false);
ArrayList<String> result = new ArrayList<>(values.length);
result.add(null); // null must be first value, cf. 10.2.3
for (int i = 0, len = values.length; i < len; ++i) {
CollationType type = CollationType.forName(values[i]);
if (type == CollationType.standard || type == CollationType.search) {
// 'standard' and 'search' must not be elements of 'co' array, cf. 10.2.3
// FIXME: spec issue? This gives slightly akward results for `new
// Intl.Collator("de-u-co-phonebk",{usage:"search"}).resolvedOptions()`.
// The resolved locale is "de-u-co-phonebk", that means co=phonebk, but for the
// actual collator co=search will be used...
continue;
}
result.add(type.getName());
}
return result;
}
private List<String> getNumericInfo() {
return asList("false", "true");
}
private List<String> getCaseFirstInfo() {
if ("sort".equals(usage)) {
// special case 'sort' usage for Danish and Maltese to use uppercase first defaults
String language = locale.getLanguage();
if ("da".equals(language) || "mt".equals(language)) {
return asList("upper", "false", "lower");
}
}
return asList("false", "lower", "upper");
}
}
/**
* Constructs a new Collator constructor function.
*
* @param realm
* the realm object
*/
public CollatorConstructor(Realm realm) {
super(realm, "Collator", 0);
}
@Override
public void initialize(Realm realm) {
createProperties(realm, this, Properties.class);
}
@Override
public CollatorConstructor clone() {
return new CollatorConstructor(getRealm());
}
@SafeVarargs
private static <T> Set<T> set(T... elements) {
return new HashSet<>(asList(elements));
}
/**
* 10.1.1 InitializeCollator (collator, locales, options)
*
* @param cx
* the execution context
* @param collator
* the collator object
* @param locales
* the locales array
* @param opts
* the options object
*/
public static void InitializeCollator(ExecutionContext cx, CollatorObject collator, Object locales, Object opts) {
/* steps 1-2 (FIXME: spec bug - unnecessary internal slot) */
/* step 3 */
Set<String> requestedLocals = CanonicalizeLocaleList(cx, locales);
/* steps 4-5 */
ScriptObject options;
if (Type.isUndefined(opts)) {
options = ObjectCreate(cx, Intrinsics.ObjectPrototype);
} else {
options = ToObject(cx, opts);
}
/* step 6 */
String u = GetStringOption(cx, options, "usage", set("sort", "search"), "sort");
/* step 7 */
collator.setUsage(u);
/* steps 8-9 */
CollatorLocaleData localeData = new CollatorLocaleData(u);
/* step 11 */
String matcher = GetStringOption(cx, options, "localeMatcher", set("lookup", "best fit"), "best fit");
/* steps 10, 12 */
OptionsRecord opt = new OptionsRecord(OptionsRecord.MatcherType.forName(matcher));
// FIXME: spec should propably define exact iteration order here
/* step 13 (kn-numeric) */
Boolean numeric = GetBooleanOption(cx, options, "numeric", null);
if (numeric != null) {
opt.set(ExtensionKey.kn, numeric.toString());
}
/* step 13 (kf-caseFirst) */
String caseFirst = GetStringOption(cx, options, "caseFirst", set("upper", "lower", "false"), null);
if (caseFirst != null) {
opt.set(ExtensionKey.kf, caseFirst);
}
/* steps 14-15 */
ResolvedLocale r = ResolveLocale(cx.getRealm(), getAvailableLocalesLazy(cx), requestedLocals, opt,
relevantExtensionKeys, localeData);
/* step 16 */
collator.setLocale(r.getLocale());
/* steps 17-20 (co-collation) */
String collation = r.getValue(ExtensionKey.co);
collator.setCollation(collation != null ? collation : "default");
/* steps 17-20 (kn-numeric) */
collator.setNumeric("true".equals(r.getValue(ExtensionKey.kn)));
/* steps 17-20 (kf-caseFirst) */
collator.setCaseFirst(r.getValue(ExtensionKey.kf));
/* step 21 */
String s = GetStringOption(cx, options, "sensitivity", set("base", "accent", "case", "variant"), null);
/* step 22 */
if (s == null) {
// The specification differentiates between "sort" and "search" usage, but effectively
// you'll end up with "variant" in both cases, so take the short path here.
s = "variant";
}
/* step 23 */
collator.setSensitivity(s);
/* step 24 */
boolean ip = GetBooleanOption(cx, options, "ignorePunctuation", false);
/* step 25 */
collator.setIgnorePunctuation(ip);
/* step 26 */
collator.setBoundCompare(null);
/* step 27 (FIXME: spec bug - unnecessary internal slot) */
/* step 28 (omitted) */
}
/**
* 10.1.1 InitializeCollator (collator, locales, options)
*
* @param realm
* the realm instance
* @param collator
* the collator object
*/
public static void InitializeDefaultCollator(Realm realm, CollatorObject collator) {
/* steps 1-2 (FIXME: spec bug - unnecessary internal slot) */
/* steps 3-5 (not applicable) */
/* step 6 */
String u = "sort";
/* step 7 */
collator.setUsage(u);
/* steps 8-9 */
CollatorLocaleData localeData = new CollatorLocaleData(u);
/* steps 10-13 (not applicable) */
/* steps 14-15 */
ResolvedLocale r = ResolveDefaultLocale(realm, relevantExtensionKeys, localeData);
/* step 16 */
collator.setLocale(r.getLocale());
/* steps 17-20 (co-collation) */
String collation = r.getValue(ExtensionKey.co);
collator.setCollation(collation != null ? collation : "default");
/* steps 17-20 (kn-numeric) */
collator.setNumeric("true".equals(r.getValue(ExtensionKey.kn)));
/* steps 17-20 (kf-caseFirst) */
collator.setCaseFirst(r.getValue(ExtensionKey.kf));
/* steps 21-23 */
collator.setSensitivity("variant");
/* steps 24-25 */
collator.setIgnorePunctuation(false);
/* step 26 */
collator.setBoundCompare(null);
/* step 27 (FIXME: spec bug - unnecessary internal slot) */
/* step 28 (omitted) */
}
/**
* 10.1.2 Intl.Collator([ locales [, options]])
*/
@Override
public ScriptObject call(ExecutionContext callerContext, Object thisValue, Object... args) {
/* steps 1-6 */
return construct(callerContext, this, args);
}
/**
* 10.1.2 Intl.Collator([ locales [, options]])
*/
@Override
public CollatorObject construct(ExecutionContext callerContext, Constructor newTarget, Object... args) {
ExecutionContext calleeContext = calleeContext();
Object locales = argument(args, 0);
Object options = argument(args, 1);
/* step 1 (not applicable) */
/* steps 2-5 */
CollatorObject collator = OrdinaryCreateFromConstructor(calleeContext, newTarget,
Intrinsics.Intl_CollatorPrototype, CollatorObject::new);
/* step 6 */
InitializeCollator(calleeContext, collator, locales, options);
return collator;
}
/**
* 10.2 Properties of the Intl.Collator Constructor
*/
public enum Properties {
;
@Prototype
public static final Intrinsics __proto__ = Intrinsics.FunctionPrototype;
@Value(name = "length", attributes = @Attributes(writable = false, enumerable = false, configurable = true))
public static final int length = 0;
@Value(name = "name", attributes = @Attributes(writable = false, enumerable = false, configurable = true))
public static final String name = "Collator";
/**
* 10.2.1 Intl.Collator.prototype
*/
@Value(name = "prototype",
attributes = @Attributes(writable = false, enumerable = false, configurable = false))
public static final Intrinsics prototype = Intrinsics.Intl_CollatorPrototype;
/**
* 10.2.2 Intl.Collator.supportedLocalesOf (locales [, options ])
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param locales
* the locales array
* @param options
* the options object
* @return the array of supported locales
*/
@Function(name = "supportedLocalesOf", arity = 1)
public static Object supportedLocalesOf(ExecutionContext cx, Object thisValue, Object locales, Object options) {
/* step 1 */
Set<String> requestedLocales = CanonicalizeLocaleList(cx, locales);
/* step 2 */
return SupportedLocales(cx, getAvailableLocales(cx), requestedLocales, options);
}
}
}