diff --git a/teavm-classlib/pom.xml b/teavm-classlib/pom.xml index 57d85caa8..f6fc23a7a 100644 --- a/teavm-classlib/pom.xml +++ b/teavm-classlib/pom.xml @@ -34,7 +34,6 @@ org.teavm teavm-platform ${project.version} - test org.teavm diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java index 43a7c6e78..cb3702e93 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java @@ -16,6 +16,7 @@ package org.teavm.classlib.impl; import org.teavm.classlib.impl.unicode.CLDRHelper; +import org.teavm.classlib.impl.unicode.CLDRReader; import org.teavm.classlib.java.util.LocaleSettingsNativeGenerator; import org.teavm.javascript.ni.Generator; import org.teavm.model.MethodDescriptor; @@ -44,8 +45,8 @@ public class JCLPlugin implements TeaVMPlugin { JavacSupport javacSupport = new JavacSupport(); host.add(javacSupport); + host.registerService(CLDRReader.class, new CLDRReader(host.getProperties(), host.getClassLoader())); Generator localeGen = new LocaleSettingsNativeGenerator(host.getClassLoader(), host.getProperties()); - host.add(new MethodReference("java.util.Locale", "readLanguagesFromCLDR", ValueType.VOID), localeGen); host.add(new MethodReference("java.util.Locale", "readCountriesFromCLDR", ValueType.VOID), localeGen); host.add(new MethodReference("java.util.Calendar", "readWeeksFromCDLR", ValueType.VOID), localeGen); host.add(new MethodReference(CLDRHelper.class.getName(), "readLikelySubtagsFromCLDR", ValueType.VOID), diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRHelper.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRHelper.java index 1d6fef75a..259a76b40 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRHelper.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRHelper.java @@ -17,6 +17,9 @@ package org.teavm.classlib.impl.unicode; import org.teavm.dependency.PluggableDependency; import org.teavm.javascript.ni.GeneratedBy; +import org.teavm.platform.metadata.MetadataProvider; +import org.teavm.platform.metadata.ResourceMap; +import org.teavm.platform.metadata.StringResource; /** * @@ -55,4 +58,7 @@ public class CLDRHelper { @GeneratedBy(CLDRHelperNativeGenerator.class) @PluggableDependency(CLDRHelperNativeGenerator.class) private static native String[] getEras(String localeCode); + + @MetadataProvider(LanguageMetadataGenerator.class) + public static native ResourceMap> getLanguagesMap(); } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRLocale.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRLocale.java new file mode 100644 index 000000000..575df5c71 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRLocale.java @@ -0,0 +1,43 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.unicode; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * + * @author Alexey Andreev + */ +public class CLDRLocale { + final Map languages = new LinkedHashMap<>(); + final Map territories = new LinkedHashMap<>(); + String[] eras; + + public Map getLanguages() { + return Collections.unmodifiableMap(languages); + } + + public Map getTerritories() { + return Collections.unmodifiableMap(territories); + } + + public String[] getEras() { + return Arrays.copyOf(eras, eras.length); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRReader.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRReader.java new file mode 100644 index 000000000..4755e0698 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRReader.java @@ -0,0 +1,204 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.unicode; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +/** + * + * @author Alexey Andreev + */ +public class CLDRReader { + private Map knownLocales = new LinkedHashMap<>(); + private Map minDaysMap = new LinkedHashMap<>(); + private Map firstDayMap = new LinkedHashMap<>(); + private Map likelySubtags = new LinkedHashMap<>(); + private Set availableLocales = new LinkedHashSet<>(); + private Set availableLanguages = new LinkedHashSet<>(); + private Set availableCountries = new LinkedHashSet<>(); + + public CLDRReader(Properties properties, ClassLoader classLoader) { + findAvailableLocales(properties); + readCLDR(classLoader); + } + + private void findAvailableLocales(Properties properties) { + String availableLocalesString = properties.getProperty("java.util.Locale.available", "en_EN").trim(); + for (String locale : Arrays.asList(availableLocalesString.split(" *, *"))) { + int countryIndex = locale.indexOf('_'); + if (countryIndex > 0) { + String language = locale.substring(0, countryIndex); + String country = locale.substring(countryIndex + 1); + availableLocales.add(language + "-" + country); + availableLocales.add(language); + availableLanguages.add(language); + availableCountries.add(country); + } else { + availableLocales.add(locale); + availableLanguages.add(locale); + } + } + } + + private void readCLDR(ClassLoader classLoader) { + try (ZipInputStream input = new ZipInputStream(classLoader.getResourceAsStream( + "org/teavm/classlib/impl/unicode/cldr-json.zip"))) { + while (true) { + ZipEntry entry = input.getNextEntry(); + if (entry == null) { + break; + } + if (!entry.getName().endsWith(".json")) { + continue; + } + if (entry.getName().equals("supplemental/weekData.json")) { + readWeekData(input); + continue; + } else if (entry.getName().equals("supplemental/likelySubtags.json")) { + readLikelySubtags(input); + } + int objectIndex = entry.getName().lastIndexOf('/'); + String objectName = entry.getName().substring(objectIndex + 1); + String localeName = entry.getName().substring(0, objectIndex); + if (localeName.startsWith("/")) { + localeName = localeName.substring(1); + } + if (!localeName.equals("root") && !availableLocales.contains(localeName)) { + continue; + } + CLDRLocale localeInfo = knownLocales.get(localeName); + if (localeInfo == null) { + localeInfo = new CLDRLocale(); + knownLocales.put(localeName, localeInfo); + } + switch (objectName) { + case "languages.json": + readLanguages(localeName, localeInfo, input); + break; + case "territories.json": + readCountries(localeName, localeInfo, input); + break; + case "ca-gregorian.json": + readEras(localeName, localeInfo, input); + break; + } + } + } catch (IOException e) { + throw new RuntimeException("Error reading CLDR file", e); + } + } + + private void readLanguages(String localeCode, CLDRLocale locale, InputStream input) { + JsonObject root = (JsonObject)new JsonParser().parse(new InputStreamReader(input)); + JsonObject languagesJson = root.get("main").getAsJsonObject().get(localeCode).getAsJsonObject() + .get("localeDisplayNames").getAsJsonObject().get("languages").getAsJsonObject(); + for (Map.Entry property : languagesJson.entrySet()) { + String language = property.getKey(); + if (availableLanguages.contains(language)) { + locale.languages.put(language, property.getValue().getAsString()); + } + } + } + + private void readCountries(String localeCode, CLDRLocale locale, InputStream input) { + JsonObject root = (JsonObject)new JsonParser().parse(new InputStreamReader(input)); + JsonObject countriesJson = root.get("main").getAsJsonObject().get(localeCode).getAsJsonObject() + .get("localeDisplayNames").getAsJsonObject().get("territories").getAsJsonObject(); + for (Map.Entry property : countriesJson.entrySet()) { + String country = property.getKey(); + if (availableCountries.contains(country)) { + locale.territories.put(country, property.getValue().getAsString()); + } + } + } + + private void readEras(String localeCode, CLDRLocale locale, InputStream input) { + JsonObject root = (JsonObject)new JsonParser().parse(new InputStreamReader(input)); + JsonObject erasJson = root.get("main").getAsJsonObject().get(localeCode).getAsJsonObject() + .get("dates").getAsJsonObject().get("calendars").getAsJsonObject() + .get("gregorian").getAsJsonObject().get("eras").getAsJsonObject().get("eraNames").getAsJsonObject(); + String am = erasJson.get("0").getAsString(); + String pm = erasJson.get("1").getAsString(); + locale.eras = new String[] { am, pm }; + } + + private void readWeekData(InputStream input) { + JsonObject root = (JsonObject)new JsonParser().parse(new InputStreamReader(input)); + JsonObject weekJson = root.get("supplemental").getAsJsonObject().get("weekData").getAsJsonObject(); + JsonObject minDaysJson = weekJson.get("minDays").getAsJsonObject(); + for (Map.Entry property : minDaysJson.entrySet()) { + minDaysMap.put(property.getKey(), property.getValue().getAsInt()); + } + JsonObject firstDayJson = weekJson.get("firstDay").getAsJsonObject(); + for (Map.Entry property : firstDayJson.entrySet()) { + firstDayMap.put(property.getKey(), getNumericDay(property.getValue().getAsString())); + } + } + + private void readLikelySubtags(InputStream input) { + JsonObject root = (JsonObject)new JsonParser().parse(new InputStreamReader(input)); + JsonObject likelySubtagsJson = root.get("supplemental").getAsJsonObject().get("likelySubtags") + .getAsJsonObject(); + for (Map.Entry property : likelySubtagsJson.entrySet()) { + likelySubtags.put(property.getKey(), property.getValue().getAsString()); + } + } + + private int getNumericDay(String day) { + switch (day) { + case "sun": + return 1; + case "mon": + return 2; + case "tue": + return 3; + case "wed": + return 4; + case "thu": + return 5; + case "fri": + return 6; + case "sat": + return 7; + default: + throw new IllegalArgumentException("Can't recognize day name: " + day); + } + } + + public Map getKnownLocales() { + return Collections.unmodifiableMap(knownLocales); + } + + public Set getAvailableLocales() { + return Collections.unmodifiableSet(availableLocales); + } + + public Set getAvailableLanguages() { + return Collections.unmodifiableSet(availableLanguages); + } + + public Set getAvailableCountries() { + return Collections.unmodifiableSet(availableCountries); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/LanguageMetadataGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/LanguageMetadataGenerator.java new file mode 100644 index 000000000..fd5f20c96 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/LanguageMetadataGenerator.java @@ -0,0 +1,43 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.unicode; + +import java.util.Map; +import org.teavm.model.MethodReference; +import org.teavm.platform.metadata.*; + +/** + * + * @author Alexey Andreev + */ +public class LanguageMetadataGenerator implements MetadataGenerator { + @Override + public Resource generateMetadata(MetadataGeneratorContext context, MethodReference method) { + ResourceMap> languages = context.createResourceMap(); + CLDRReader reader = context.getService(CLDRReader.class); + for (Map.Entry entry : reader.getKnownLocales().entrySet()) { + CLDRLocale locale = entry.getValue(); + ResourceMap languageNames = context.createResourceMap(); + languages.put(entry.getKey(), languageNames); + for (Map.Entry language : locale.getLanguages().entrySet()) { + StringResource languageName = context.createResource(StringResource.class); + languageName.setValue(language.getValue()); + languageNames.put(language.getKey(), languageName); + } + } + return languages; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/LocaleSettingsNativeGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/LocaleSettingsNativeGenerator.java index bce04cef7..14f756e49 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/LocaleSettingsNativeGenerator.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/LocaleSettingsNativeGenerator.java @@ -115,9 +115,6 @@ public class LocaleSettingsNativeGenerator implements Generator { case "territories.json": readCountries(localeName, localeInfo, input); break; - case "ca-gregorian.json": - readEras(localeName, localeInfo, input); - break; } } } catch (IOException e) { @@ -149,16 +146,6 @@ public class LocaleSettingsNativeGenerator implements Generator { } } - private void readEras(String localeCode, LocaleInfo locale, InputStream input) { - JsonObject root = (JsonObject)new JsonParser().parse(new InputStreamReader(input)); - JsonObject erasJson = root.get("main").getAsJsonObject().get(localeCode).getAsJsonObject() - .get("calendars").getAsJsonObject().get("gregorian").getAsJsonObject() - .get("eras").getAsJsonObject().get("eraNames").getAsJsonObject(); - String am = erasJson.get("0").getAsString(); - String pm = erasJson.get("1").getAsString(); - locale.eras = new String[] { am, pm }; - } - private void readWeekData(InputStream input) { JsonObject root = (JsonObject)new JsonParser().parse(new InputStreamReader(input)); JsonObject weekJson = root.get("supplemental").getAsJsonObject().get("weekData").getAsJsonObject(); @@ -206,9 +193,6 @@ public class LocaleSettingsNativeGenerator implements Generator { public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { init(); switch (methodRef.getName()) { - case "readLanguagesFromCLDR": - generateReadLanguagesFromCLDR(writer); - break; case "readCountriesFromCLDR": generateReadCountriesFromCLDR(writer); break; @@ -248,32 +232,6 @@ public class LocaleSettingsNativeGenerator implements Generator { writer.append("];").softNewLine(); } - private void generateReadLanguagesFromCLDR(SourceWriter writer) throws IOException { - generateDefender(writer, "languages"); - writer.appendClass("java.util.Locale").append(".$CLDR.languages = {").indent().softNewLine(); - boolean firstLocale = true; - for (Map.Entry entry : knownLocales.entrySet()) { - if (!firstLocale) { - writer.append(",").softNewLine(); - } - firstLocale = false; - writer.append('"').append(Renderer.escapeString(entry.getKey())).append('"').ws().append(":").ws() - .append('{').indent().softNewLine(); - - boolean first = true; - for (Map.Entry langEntry : entry.getValue().languages.entrySet()) { - if (!first) { - writer.append(',').softNewLine(); - } - first = false; - writer.append('"').append(Renderer.escapeString(langEntry.getKey())).append('"').ws().append(':') - .ws().append('"').append(Renderer.escapeString(langEntry.getValue())).append('"'); - } - writer.outdent().append('}'); - } - writer.outdent().append("};").softNewLine(); - } - private void generateReadCountriesFromCLDR(SourceWriter writer) throws IOException { generateDefender(writer, "territories"); writer.appendClass("java.util.Locale").append(".$CLDR.territories = {").indent().softNewLine(); diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TLocale.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TLocale.java index 07c5365ac..5fc084da3 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TLocale.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TLocale.java @@ -33,10 +33,13 @@ package org.teavm.classlib.java.util; import java.util.Arrays; +import org.teavm.classlib.impl.unicode.CLDRHelper; import org.teavm.classlib.java.io.TSerializable; import org.teavm.classlib.java.lang.TCloneable; import org.teavm.dependency.PluggableDependency; import org.teavm.javascript.ni.GeneratedBy; +import org.teavm.platform.metadata.ResourceMap; +import org.teavm.platform.metadata.StringResource; public final class TLocale implements TCloneable, TSerializable { private static TLocale defaultLocale; @@ -85,9 +88,6 @@ public final class TLocale implements TCloneable, TSerializable { // Redefined by JCLPlugin private static native void readCountriesFromCLDR(); - // Redefined by JCLPlugin - private static native void readLanguagesFromCLDR(); - // Redefined by JCLPlugin private static native void readAvailableLocales(); @@ -189,7 +189,6 @@ public final class TLocale implements TCloneable, TSerializable { } public String getDisplayLanguage(TLocale locale) { - readLanguagesFromCLDR(); String result = getDisplayLanguage(locale.getLanguage() + "-" + locale.getCountry(), languageCode); if (result == null) { result = getDisplayLanguage(locale.getLanguage(), languageCode); @@ -197,9 +196,16 @@ public final class TLocale implements TCloneable, TSerializable { return result != null ? result : languageCode; } - @GeneratedBy(LocaleNativeGenerator.class) - @PluggableDependency(LocaleNativeGenerator.class) - private static native String getDisplayLanguage(String localeName, String country); + private static String getDisplayLanguage(String localeName, String language) { + if (!CLDRHelper.getLanguagesMap().has(localeName)) { + return null; + } + ResourceMap languages = CLDRHelper.getLanguagesMap().get(localeName); + if (!languages.has(language)) { + return null; + } + return languages.get(language).getValue(); + } public final String getDisplayName() { return getDisplayName(getDefault()); diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessor.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessor.java index 5002ec399..22cbff38e 100644 --- a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessor.java +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessor.java @@ -22,7 +22,9 @@ import org.teavm.platform.metadata.Resource; * @author Alexey Andreev */ final class ResourceAccessor { - public static native Object get(Object obj, String propertyName); + public static native Object getProperty(Object obj, String propertyName); + + public static native Resource get(Object obj, String propertyName); public static native void put(Object obj, String propertyName, Object elem); diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorGenerator.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorGenerator.java index 1eaa46a52..62c267ad5 100644 --- a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorGenerator.java +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorGenerator.java @@ -32,6 +32,7 @@ class ResourceAccessorGenerator implements Injector { public void generate(InjectorContext context, MethodReference methodRef) throws IOException { switch (methodRef.getName()) { case "get": + case "getProperty": if (methodRef.getDescriptor().parameterType(1) == ValueType.INTEGER) { context.writeExpr(context.getArgument(0)); context.getWriter().append('['); diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceProgramTransformer.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceProgramTransformer.java index 56e51343b..0b80ebbe6 100644 --- a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceProgramTransformer.java +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceProgramTransformer.java @@ -166,7 +166,7 @@ class ResourceProgramTransformer { instructions.add(nameInsn); InvokeInstruction accessorInvoke = new InvokeInstruction(); accessorInvoke.setType(InvocationType.SPECIAL); - accessorInvoke.setMethod(new MethodReference(ResourceAccessor.class, "get", + accessorInvoke.setMethod(new MethodReference(ResourceAccessor.class, "getProperty", Object.class, String.class, Object.class)); accessorInvoke.getArguments().add(insn.getInstance()); accessorInvoke.getArguments().add(nameVar);