From b264e34ef8edff53a3c2d98f86cf716b9aa18cb8 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 12 May 2015 21:40:57 +0300 Subject: [PATCH] Start to implement time zones --- teavm-classlib/pom.xml | 5 + .../java/org/teavm/classlib/impl/Base46.java | 125 ++++++++++++++++++ .../org/teavm/classlib/impl/CharFlow.java | 29 ++++ .../classlib/impl/tz/TimeZoneGenerator.java | 125 ++++++++++++++++++ .../classlib/impl/tz/TimeZoneResource.java | 32 +++++ .../impl/tz/TimeZoneResourceProvider.java | 39 ++++++ .../org/teavm/classlib/impl/Base46Test.java | 38 ++++++ .../classlib/java/util/TimeZoneTest.java | 16 +++ 8 files changed, 409 insertions(+) create mode 100644 teavm-classlib/src/main/java/org/teavm/classlib/impl/Base46.java create mode 100644 teavm-classlib/src/main/java/org/teavm/classlib/impl/CharFlow.java create mode 100644 teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneGenerator.java create mode 100644 teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneResource.java create mode 100644 teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneResourceProvider.java create mode 100644 teavm-classlib/src/test/java/org/teavm/classlib/impl/Base46Test.java create mode 100644 teavm-tests/src/test/java/org/teavm/classlib/java/util/TimeZoneTest.java diff --git a/teavm-classlib/pom.xml b/teavm-classlib/pom.xml index 2b09f1141..a71b55757 100644 --- a/teavm-classlib/pom.xml +++ b/teavm-classlib/pom.xml @@ -69,6 +69,11 @@ jzlib 1.1.3 + + joda-time + joda-time + 2.7 + diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/Base46.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/Base46.java new file mode 100644 index 000000000..9315e1c54 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/Base46.java @@ -0,0 +1,125 @@ +/* + * Copyright 2015 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; + +/** + *

Base47 encoding is best fit for encoding varible length numbers in JavaScript strings.

+ * + *

47 = (int)(93 / 2), where 94 is the number of ASCII characters representable in JavaScript string + * without escaping. These characters are encoded by one byte in UTF-8 charset. All other character require + * either escaping or two or more bytes in UTF-8.

+ * + *

We divide 93 by 2 for the following trick. Representing integers takes 5 bytes in Base93. However, + * we often need smaller integers that might be represented by one or two bytes. By each Base93 digit we + * can encode both part of the number and a flag indicating whether the number contains one more digit.

+ * + * @author Alexey Andreev + */ +public class Base46 { + public static void encodeUnsigned(StringBuilder sb, int number) { + boolean hasMore; + do { + int digit = number % 46; + number /= 46; + hasMore = number > 0; + digit = digit * 2 + (hasMore ? 1 : 0); + sb.append(encodeDigit(digit)); + } while (hasMore); + } + + public static void encode(StringBuilder sb, int number) { + encodeUnsigned(sb, Math.abs(number) * 2 + (number >= 0 ? 0 : 1)); + } + + public static void encodeUnsigned(StringBuilder sb, long number) { + boolean hasMore; + do { + int digit = (int)(number % 46); + number /= 46; + hasMore = number > 0; + digit = digit * 2 + (hasMore ? 1 : 0); + sb.append(encodeDigit(digit)); + } while (hasMore); + } + + public static void encode(StringBuilder sb, long number) { + encodeUnsigned(sb, Math.abs(number) * 2 + (number >= 0 ? 0 : 1)); + } + + public static int decodeUnsigned(CharFlow seq) { + int number = 0; + int pos = 1; + boolean hasMore; + do { + int digit = decodeDigit(seq.characters[seq.pointer++]); + hasMore = digit % 2 == 1; + number += pos * (digit / 2); + pos *= 46; + } while (hasMore); + return number; + } + + public static int decode(CharFlow seq) { + int number = decodeUnsigned(seq); + int result = number / 2; + if (number % 2 != 0) { + result = -result; + } + return result; + } + + public static long decodeUnsignedLong(CharFlow seq) { + long number = 0; + long pos = 1; + boolean hasMore; + do { + int digit = decodeDigit(seq.characters[seq.pointer++]); + hasMore = digit % 2 == 1; + number += pos * (digit / 2); + pos *= 46; + } while (hasMore); + return number; + } + + public static long decodeLong(CharFlow seq) { + long number = decodeUnsigned(seq); + long result = number / 2; + if (number % 2 != 0) { + result = -result; + } + return result; + } + + public static char encodeDigit(int digit) { + if (digit < 2) { + return (char)(digit + ' '); + } else if (digit < 59) { + return (char)(digit + 1 + ' '); + } else { + return (char)(digit + 2 + ' '); + } + } + + public static int decodeDigit(char c) { + if (c < '"') { + return c - ' '; + } else if (c < '\\') { + return c - ' ' - 1; + } else { + return c - ' ' - 2; + } + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/CharFlow.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/CharFlow.java new file mode 100644 index 000000000..3a7944d8e --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/CharFlow.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015 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; + +/** + * + * @author Alexey Andreev + */ +public class CharFlow { + public final char[] characters; + public int pointer; + + public CharFlow(char[] characters) { + this.characters = characters; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneGenerator.java new file mode 100644 index 000000000..87abbed84 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneGenerator.java @@ -0,0 +1,125 @@ +/* + * Copyright 2015 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.tz; + +import org.joda.time.DateTimeZone; +import org.teavm.classlib.impl.Base46; +import org.teavm.model.MethodReference; +import org.teavm.platform.metadata.MetadataGenerator; +import org.teavm.platform.metadata.MetadataGeneratorContext; +import org.teavm.platform.metadata.ResourceMap; + +/** + * + * @author Alexey Andreev + */ +public class TimeZoneGenerator implements MetadataGenerator { + private static int[] divisors = { 60_000, 300_000, 1800_000, 3600_000 }; + public static int RESOLUTION_MINUTE = 0; + public static int RESOLUTION_5_MINUTES = 1; + public static int RESOLUTION_HALF_HOUR = 2; + public static int RESOLUTION_HOUR = 3; + + + @Override + public ResourceMap> generateMetadata( + MetadataGeneratorContext context, MethodReference method) { + ResourceMap> result = context.createResourceMap(); + for (String id : DateTimeZone.getAvailableIDs()) { + int sepIndex = id.indexOf('/'); + String areaName = id.substring(0, sepIndex); + String locationName = id.substring(sepIndex + 1); + ResourceMap area = result.get(areaName); + if (area == null) { + area = context.createResourceMap(); + result.put(areaName, area); + } + + DateTimeZone tz = DateTimeZone.forID(id); + TimeZoneResource tzRes = context.createResource(TimeZoneResource.class); + tzRes.setAbbreviation(locationName); + tzRes.setData(encodeData(tz)); + area.put(locationName, tzRes); + } + + return result; + } + + public String encodeData(DateTimeZone tz) { + // Find resolution + int resolution = RESOLUTION_HOUR; + long current = 0; + long offset = tz.getOffset(0); + while (true) { + long next = tz.nextTransition(current); + if (next == current) { + break; + } + current = next; + + int nextOffset = tz.getOffset(next); + if (nextOffset == offset) { + continue; + } + + offset = nextOffset; + resolution = getResolution(resolution, current); + resolution = getResolution(resolution, offset); + if (resolution == 0) { + break; + } + } + + StringBuilder sb = new StringBuilder(); + Base46.encode(sb, resolution); + + current = 0; + offset = tz.getOffset(0); + int divisor = divisors[resolution]; + long last = 0; + long lastOffset = offset / divisor; + Base46.encode(sb, lastOffset); + while (true) { + long next = tz.nextTransition(current); + if (next == current) { + break; + } + current = next; + + int nextOffset = tz.getOffset(next); + if (nextOffset == offset) { + continue; + } + + offset = nextOffset; + long newTime = current / divisor; + long newOffset = offset / divisor; + Base46.encodeUnsigned(sb, newTime - last); + Base46.encode(sb, newOffset - lastOffset); + last = newTime; + lastOffset = newOffset; + } + + return sb.toString(); + } + + private int getResolution(int currentResolution, long value) { + while (currentResolution > 0 && value % divisors[currentResolution] != 0) { + --currentResolution; + } + return currentResolution; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneResource.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneResource.java new file mode 100644 index 000000000..baa06ca4c --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneResource.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015 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.tz; + +import org.teavm.platform.metadata.Resource; + +/** + * + * @author Alexey Andreev + */ +public interface TimeZoneResource extends Resource { + String getAbbreviation(); + + void setAbbreviation(String abbreviation); + + String getData(); + + void setData(String data); +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneResourceProvider.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneResourceProvider.java new file mode 100644 index 000000000..079892016 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneResourceProvider.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 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.tz; + +import org.teavm.platform.metadata.MetadataProvider; +import org.teavm.platform.metadata.ResourceMap; + +/** + * + * @author Alexey Andreev + */ +public class TimeZoneResourceProvider { + public static TimeZoneResource getTimeZone(String id) { + int sepIndex = id.indexOf('/'); + String areaName = id.substring(0, sepIndex); + String locationName = id.substring(sepIndex + 1); + ResourceMap area = getResource().get(areaName); + if (area == null) { + return null; + } + return area.get(locationName); + } + + @MetadataProvider(TimeZoneGenerator.class) + private static native ResourceMap> getResource(); +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/impl/Base46Test.java b/teavm-classlib/src/test/java/org/teavm/classlib/impl/Base46Test.java new file mode 100644 index 000000000..ff04563a5 --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/impl/Base46Test.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015 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; + +import static org.junit.Assert.*; +import org.junit.Test; + +/** + * + * @author Alexey Andreev + */ +public class Base46Test { + @Test + public void encode() { + StringBuilder sb = new StringBuilder(); + for (int i = -65536; i <= 65536; ++i) { + sb.setLength(0); + Base46.encode(sb, i); + System.out.println(i + " - " + sb); + CharFlow flow = new CharFlow(sb.toString().toCharArray()); + int num = Base46.decode(flow); + assertEquals(i, num); + } + } +} diff --git a/teavm-tests/src/test/java/org/teavm/classlib/java/util/TimeZoneTest.java b/teavm-tests/src/test/java/org/teavm/classlib/java/util/TimeZoneTest.java new file mode 100644 index 000000000..58b1857a1 --- /dev/null +++ b/teavm-tests/src/test/java/org/teavm/classlib/java/util/TimeZoneTest.java @@ -0,0 +1,16 @@ +package org.teavm.classlib.java.util; + +import static org.junit.Assert.*; +import org.junit.Test; +import org.teavm.classlib.impl.tz.TimeZoneResourceProvider; + +/** + * + * @author Alexey Andreev + */ +public class TimeZoneTest { + @Test + public void resourceProvided() { + assertNotNull(TimeZoneResourceProvider.getTimeZone("Europe/Moscow")); + } +}