diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/CachedDateTimeZone.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/CachedDateTimeZone.java index 01048976a..f7f3d5989 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/CachedDateTimeZone.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/CachedDateTimeZone.java @@ -15,8 +15,6 @@ */ package org.teavm.classlib.impl.tz; -import org.teavm.classlib.impl.Base46; - /** * Improves the performance of requesting time zone offsets and name keys by * caching the results. Time zones that have simple rules or are fixed should @@ -64,7 +62,6 @@ public class CachedDateTimeZone extends StorableDateTimeZone { @Override public void write(StringBuilder sb) { - Base46.encodeUnsigned(sb, CACHED); iZone.write(sb); } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/DateTimeZoneBuilder.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/DateTimeZoneBuilder.java index 8f8362796..f7ba08073 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/DateTimeZoneBuilder.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/DateTimeZoneBuilder.java @@ -21,6 +21,7 @@ import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Iterator; import org.teavm.classlib.impl.Base46; +import org.teavm.classlib.impl.CharFlow; /** * DateTimeZoneBuilder allows complex DateTimeZones to be constructed. Since @@ -287,7 +288,7 @@ public class DateTimeZoneBuilder { /** * Supports setting fields of year and moving between transitions. */ - private static final class OfYear { + static final class OfYear { // Is 'u', 'w', or 's'. final char iMode; @@ -317,14 +318,23 @@ public class DateTimeZoneBuilder { public void write(StringBuilder sb) { sb.append(iMode); - Base46.encodeUnsigned(sb, iDayOfMonth); Base46.encodeUnsigned(sb, iMonthOfYear); - Base46.encode(sb, iDayOfMonth); + Base46.encodeUnsigned(sb, iDayOfMonth); Base46.encode(sb, iDayOfWeek); sb.append(iAdvance ? 'y' : 'n'); StorableDateTimeZone.writeUnsignedTime(sb, iMillisOfDay); } + public static OfYear read(CharFlow flow) { + char mode = flow.characters[flow.pointer++]; + int monthOfYear = Base46.decodeUnsigned(flow); + int dayOfMonth = Base46.decodeUnsigned(flow); + int dayOfWeek = Base46.decode(flow); + boolean advance = flow.characters[flow.pointer++] == 'y'; + int millisOfDay = (int)StorableDateTimeZone.readUnsignedTime(flow); + return new OfYear(mode, monthOfYear, dayOfMonth, dayOfWeek, advance, millisOfDay); + } + /** * @param standardOffset standard offset just before instant */ @@ -342,6 +352,10 @@ public class DateTimeZoneBuilder { calendar.setTimeInMillis(0); calendar.set(Calendar.YEAR, year); calendar.set(Calendar.MONTH, iMonthOfYear - 1); + calendar.set(Calendar.HOUR, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); calendar.add(Calendar.MILLISECOND, iMillisOfDay); setDayOfMonth(calendar); @@ -350,7 +364,7 @@ public class DateTimeZoneBuilder { } // Convert from local time to UTC. - return calendar.getTimeInMillis() - offset; + return calendar.getTimeInMillis() + calendar.get(Calendar.ZONE_OFFSET) - offset; } /** @@ -381,13 +395,13 @@ public class DateTimeZoneBuilder { setDayOfMonthNext(calendar); if (iDayOfWeek == 0) { - if (calendar.getTimeInMillis() <= instant) { + if (calendar.getTimeInMillis() + calendar.get(Calendar.ZONE_OFFSET) <= instant) { calendar.add(Calendar.YEAR, 1); setDayOfMonthNext(calendar); } } else { setDayOfWeek(calendar); - if (calendar.getTimeInMillis() <= instant) { + if (calendar.getTimeInMillis() + calendar.get(Calendar.ZONE_OFFSET) <= instant) { calendar.add(Calendar.YEAR, 1); calendar.set(Calendar.MONTH, iMonthOfYear - 1); setDayOfMonthNext(calendar); @@ -396,7 +410,7 @@ public class DateTimeZoneBuilder { } // Convert from local time to UTC. - return calendar.getTimeInMillis() - offset; + return calendar.getTimeInMillis() + calendar.get(Calendar.ZONE_OFFSET) - offset; } /** @@ -428,13 +442,13 @@ public class DateTimeZoneBuilder { setDayOfMonthPrevious(calendar); if (iDayOfWeek == 0) { - if (calendar.getTimeInMillis() >= instant) { + if (calendar.getTimeInMillis() + calendar.get(Calendar.ZONE_OFFSET) >= instant) { calendar.add(Calendar.YEAR, -1); setDayOfMonthPrevious(calendar); } } else { setDayOfWeek(calendar); - if (calendar.getTimeInMillis() >= instant) { + if (calendar.getTimeInMillis() + calendar.get(Calendar.ZONE_OFFSET) >= instant) { calendar.add(Calendar.YEAR, -1); calendar.set(Calendar.MONTH, iMonthOfYear - 1); setDayOfMonthPrevious(calendar); @@ -443,7 +457,7 @@ public class DateTimeZoneBuilder { } // Convert from local time to UTC. - return calendar.getTimeInMillis() - offset; + return calendar.getTimeInMillis() + calendar.get(Calendar.ZONE_OFFSET) - offset; } /** @@ -455,6 +469,7 @@ public class DateTimeZoneBuilder { calendar.add(Calendar.YEAR, 1); } } + setDayOfMonth(calendar); } /** @@ -466,6 +481,7 @@ public class DateTimeZoneBuilder { calendar.add(Calendar.YEAR, -1); } } + setDayOfMonth(calendar); } private void setDayOfMonth(Calendar calendar) { @@ -499,7 +515,7 @@ public class DateTimeZoneBuilder { /** * Extends OfYear with a nameKey and savings. */ - private static final class Recurrence { + static final class Recurrence { final OfYear iOfYear; final int iSaveMillis; @@ -534,6 +550,12 @@ public class DateTimeZoneBuilder { iOfYear.write(sb); StorableDateTimeZone.writeTime(sb, iSaveMillis); } + + public static Recurrence read(CharFlow flow) { + OfYear ofYear = OfYear.read(flow); + int saveMillis = (int)StorableDateTimeZone.readTime(flow); + return new Recurrence(ofYear, saveMillis); + } } /** @@ -585,7 +607,7 @@ public class DateTimeZoneBuilder { calendar.setTimeInMillis(0); calendar.set(Calendar.YEAR, iFromYear); // First advance instant to start of from year. - testInstant = calendar.getTimeInMillis() - wallOffset; + testInstant = calendar.getTimeInMillis() + calendar.get(Calendar.ZONE_OFFSET) - wallOffset; // Back off one millisecond to account for next recurrence // being exactly at the beginning of the year. testInstant -= 1; @@ -878,13 +900,12 @@ public class DateTimeZoneBuilder { } } - private static final class DSTZone extends StorableDateTimeZone { + static final class DSTZone extends StorableDateTimeZone { final int iStandardOffset; final Recurrence iStartRecurrence; final Recurrence iEndRecurrence; - DSTZone(String id, int standardOffset, - Recurrence startRecurrence, Recurrence endRecurrence) { + DSTZone(String id, int standardOffset, Recurrence startRecurrence, Recurrence endRecurrence) { super(id); iStandardOffset = standardOffset; iStartRecurrence = startRecurrence; @@ -989,7 +1010,7 @@ public class DateTimeZoneBuilder { end = instant; } - return ((start > end) ? start : end) - 1; + return (start > end ? start : end) - 1; } private Recurrence findMatchingRecurrence(long instant) { @@ -1031,9 +1052,16 @@ public class DateTimeZoneBuilder { iStartRecurrence.write(sb); iEndRecurrence.write(sb); } + + public static DSTZone readZone(String id, CharFlow flow) { + int standardOffset = (int)readTime(flow); + Recurrence startRecurrence = Recurrence.read(flow); + Recurrence endRecurrence = Recurrence.read(flow); + return new DSTZone(id, standardOffset, startRecurrence, endRecurrence); + } } - private static final class PrecalculatedZone extends StorableDateTimeZone { + static final class PrecalculatedZone extends StorableDateTimeZone { /** * Factory to create instance from builder. * @@ -1097,7 +1125,7 @@ public class DateTimeZoneBuilder { @Override public void write(StringBuilder sb) { int start = 0; - while (start + 1 < iTransitions.length && iTransitions[start + 1] < 0) { + while (start + 1 < iTransitions.length && iTransitions[start + 1] < 631170000000L) { ++start; } @@ -1111,7 +1139,7 @@ public class DateTimeZoneBuilder { writeTime(sb, transitions[start]); for (int i = start + 1; i < transitions.length; ++i) { - writeTime(sb, transitions[i] - transitions[i - 1] - (365 * 3600 * 1000)); + writeTime(sb, transitions[i] - transitions[i - 1] - (365 * 3600 * 1000 / 2)); } writeTimeArray(sb, Arrays.copyOfRange(iWallOffsets, start, transitions.length)); @@ -1125,6 +1153,31 @@ public class DateTimeZoneBuilder { } } + public static StorableDateTimeZone readZone(String id, CharFlow flow) { + int length = Base46.decodeUnsigned(flow); + long[] transitions = new long[length]; + int[] wallOffsets = new int[length]; + int[] standardOffsets = new int[length]; + + transitions[0] = readTime(flow); + for (int i = 1; i < length; ++i) { + transitions[i] = transitions[i - 1] + readTime(flow) + 365 * 3600 * 1000 / 2; + } + + readTimeArray(flow, wallOffsets); + readTimeArray(flow, standardOffsets); + + DSTZone tailZone; + if (flow.characters[flow.pointer++] == 'y') { + tailZone = DSTZone.readZone(id, flow); + } else { + tailZone = null; + } + + PrecalculatedZone result = new PrecalculatedZone(id, transitions, wallOffsets, standardOffsets, tailZone); + return result.isCachable() ? CachedDateTimeZone.forZone(result) : result; + } + @Override public int getOffset(long instant) { long[] transitions = iTransitions; diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/DateTimeZoneProvider.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/DateTimeZoneProvider.java new file mode 100644 index 000000000..7382a3e3e --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/DateTimeZoneProvider.java @@ -0,0 +1,90 @@ +/* + * 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 java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.teavm.classlib.impl.Base46; +import org.teavm.classlib.impl.CharFlow; +import org.teavm.platform.metadata.MetadataProvider; +import org.teavm.platform.metadata.ResourceMap; + +/** + * + * @author Alexey Andreev + */ +public class DateTimeZoneProvider { + private static Map cache = new HashMap<>(); + + public static DateTimeZone getTimeZone(String id) { + if (!cache.containsKey(id)) { + cache.put(id, createTimeZone(id)); + } + return cache.get(id); + } + + private static DateTimeZone createTimeZone(String id) { + TimeZoneResource res = getTimeZoneResource(id); + if (res == null) { + return null; + } + String data = res.getData(); + CharFlow flow = new CharFlow(data.toCharArray()); + if (Base46.decode(flow) == StorableDateTimeZone.ALIAS) { + String aliasId = data.substring(flow.pointer); + return getTimeZone(aliasId); + } else { + return StorableDateTimeZone.read(id, data); + } + } + + public static String[] getIds() { + List ids = new ArrayList<>(); + for (String areaName : getResource().keys()) { + ResourceMap area = getResource().get(areaName); + for (String id : area.keys()) { + if (!areaName.isEmpty()) { + id = areaName + "/" + id; + } + ids.add(id); + } + } + return ids.toArray(new String[ids.size()]); + } + + private static TimeZoneResource getTimeZoneResource(String id) { + String areaName; + String locationName; + int sepIndex = id.indexOf('/'); + if (sepIndex >= 0) { + areaName = id.substring(0, sepIndex); + locationName = id.substring(sepIndex + 1); + } else { + areaName = ""; + locationName = id; + } + 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/main/java/org/teavm/classlib/impl/tz/FixedDateTimeZone.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/FixedDateTimeZone.java index 754f7328a..21e2a780c 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/FixedDateTimeZone.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/FixedDateTimeZone.java @@ -16,6 +16,7 @@ package org.teavm.classlib.impl.tz; import org.teavm.classlib.impl.Base46; +import org.teavm.classlib.impl.CharFlow; /** * Basic DateTimeZone implementation that has a fixed name key and offsets. @@ -71,4 +72,10 @@ public final class FixedDateTimeZone extends StorableDateTimeZone { writeTime(sb, iWallOffset); writeTime(sb, iStandardOffset); } + + public static FixedDateTimeZone readZone(String id, CharFlow flow) { + int wallOffset = (int)readTime(flow); + int standardOffset = (int)readTime(flow); + return new FixedDateTimeZone(id, wallOffset, standardOffset); + } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/StorableDateTimeZone.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/StorableDateTimeZone.java index bbf9395a1..65a82db5f 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/StorableDateTimeZone.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/StorableDateTimeZone.java @@ -16,16 +16,19 @@ package org.teavm.classlib.impl.tz; import org.teavm.classlib.impl.Base46; +import org.teavm.classlib.impl.CharFlow; +import org.teavm.classlib.impl.tz.DateTimeZoneBuilder.DSTZone; +import org.teavm.classlib.impl.tz.DateTimeZoneBuilder.PrecalculatedZone; /** * * @author Alexey Andreev */ public abstract class StorableDateTimeZone extends DateTimeZone { - public static int PRECALCULATED = 0; - public static int FIXED = 1; - public static int CACHED = 2; - public static int DST = 3; + public static final int PRECALCULATED = 0; + public static final int FIXED = 1; + public static final int DST = 3; + public static final int ALIAS = 4; public StorableDateTimeZone(String id) { super(id); @@ -41,6 +44,15 @@ public abstract class StorableDateTimeZone extends DateTimeZone { } } + public static long readTime(CharFlow flow) { + long value = Base46.decodeLong(flow); + if ((value & 1) == 0) { + return (value >> 1) * 1800_000; + } else { + return (value >> 1) * 60_000; + } + } + public static void writeUnsignedTime(StringBuilder sb, long time) { if (time % 1800_000 == 0) { Base46.encodeUnsigned(sb, (int)((time / 1800_000) << 1)); @@ -49,6 +61,15 @@ public abstract class StorableDateTimeZone extends DateTimeZone { } } + public static long readUnsignedTime(CharFlow flow) { + long value = Base46.decodeUnsignedLong(flow); + if ((value & 1) == 0) { + return (value >>> 1) * 1800_000; + } else { + return (value >>> 1) * 60_000; + } + } + public static void writeTimeArray(StringBuilder sb, int[] timeArray) { int last = 0; for (int i = 1; i < timeArray.length; ++i) { @@ -78,4 +99,37 @@ public abstract class StorableDateTimeZone extends DateTimeZone { } } } + + public static void readTimeArray(CharFlow flow, int[] array) { + int index = 0; + while (index < array.length) { + int count = Base46.decode(flow); + if (count >= 0) { + int t = (int)readTime(flow); + while (count-- > 0) { + array[index++] = t; + } + } else { + count = ~count; + while (count-- > 0) { + array[index++] = (int)readTime(flow); + } + } + } + } + + public static StorableDateTimeZone read(String id, String text) { + CharFlow flow = new CharFlow(text.toCharArray()); + int type = Base46.decode(flow); + switch (type) { + case PRECALCULATED: + return PrecalculatedZone.readZone(id, flow); + case DST: + return DSTZone.readZone(id, flow); + case FIXED: + return FixedDateTimeZone.readZone(id, flow); + default: + throw new IllegalArgumentException("Unknown zone type: " + type); + } + } } 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 index f99a9e27f..4a96e4f18 100644 --- 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 @@ -19,9 +19,11 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.HashMap; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import org.teavm.classlib.impl.Base46; import org.teavm.model.MethodReference; import org.teavm.platform.metadata.MetadataGenerator; import org.teavm.platform.metadata.MetadataGeneratorContext; @@ -66,17 +68,22 @@ public class TimeZoneGenerator implements MetadataGenerator { } } } catch (IOException e) { - throw new RuntimeException("Error generating TimeZones", e); + throw new RuntimeException("Error generating time zones", e); } Map zoneMap = compiler.compile(); + Map zones = new HashMap<>(); for (String id : zoneMap.keySet()) { int sepIndex = id.indexOf('/'); + String areaName; + String locationName; if (sepIndex < 0) { - continue; + areaName = ""; + locationName = id; + } else { + areaName = id.substring(0, sepIndex); + locationName = id.substring(sepIndex + 1); } - String areaName = id.substring(0, sepIndex); - String locationName = id.substring(sepIndex + 1); ResourceMap area = result.get(areaName); if (area == null) { area = context.createResourceMap(); @@ -86,7 +93,14 @@ public class TimeZoneGenerator implements MetadataGenerator { StorableDateTimeZone tz = zoneMap.get(id); TimeZoneResource tzRes = context.createResource(TimeZoneResource.class); StringBuilder data = new StringBuilder(); - tz.write(data); + String knownId = zones.get(tz); + if (knownId == null) { + tz.write(data); + zones.put(tz, id); + } else { + Base46.encode(data, StorableDateTimeZone.ALIAS); + data.append(knownId); + } tzRes.setData(data.toString()); area.put(locationName, tzRes); } 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 deleted file mode 100644 index 079892016..000000000 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/TimeZoneResourceProvider.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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/main/java/org/teavm/classlib/impl/tz/ZoneInfoCompiler.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/ZoneInfoCompiler.java index 33ad39a9a..abb7445c9 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/ZoneInfoCompiler.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/tz/ZoneInfoCompiler.java @@ -182,7 +182,7 @@ public class ZoneInfoCompiler { long trans = transitions.get(i).longValue(); - /*if (trans - 1 != millis) { + if (trans - 1 != millis) { System.out.println("*r* Error in " + tz.getID() + " " + new DateTime(millis, ISOChronology.getInstanceUTC()) + " != " @@ -190,7 +190,7 @@ public class ZoneInfoCompiler { ISOChronology.getInstanceUTC())); return false; - }*/ + } } return true; diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java index 6123109cc..46be09ec6 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java @@ -174,7 +174,7 @@ public class TClass extends TObject { return forName(name); } - @SuppressWarnings({ "unchecked" }) + @SuppressWarnings({ "unchecked", "unused" }) public T newInstance() throws TInstantiationException, TIllegalAccessException { Object instance = Platform.newInstance(platformClass); if (instance == null) { diff --git a/teavm-platform/src/main/java/org/teavm/platform/metadata/ResourceMap.java b/teavm-platform/src/main/java/org/teavm/platform/metadata/ResourceMap.java index ddb334de1..a3bfcc157 100644 --- a/teavm-platform/src/main/java/org/teavm/platform/metadata/ResourceMap.java +++ b/teavm-platform/src/main/java/org/teavm/platform/metadata/ResourceMap.java @@ -26,4 +26,6 @@ public interface ResourceMap extends Resource { T get(String key); void put(String key, T value); + + String[] keys(); } diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceMap.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceMap.java index 2e1dba77c..d7cc6e846 100644 --- a/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceMap.java +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceMap.java @@ -59,4 +59,9 @@ class BuildTimeResourceMap implements ResourceMap, Resour } writer.append('}').tokenBoundary(); } + + @Override + public String[] keys() { + return data.keySet().toArray(new String[data.size()]); + } } 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 04f176913..bd93c1078 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 @@ -29,6 +29,17 @@ final class ResourceAccessor { public static native Resource get(Object obj, String propertyName); + public static native Object keys(Object obj); + + public static String[] keysToStrings(Object keys) { + int sz = size(keys); + String[] result = new String[sz]; + for (int i = 0; i < sz; ++i) { + result[i] = castToString(get(keys, i)); + } + return result; + } + public static native void put(Object obj, String propertyName, Object elem); public static native Resource get(Object obj, int index); 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 18337035c..c00ca67da 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 @@ -1,5 +1,5 @@ /* - * Copyright 2014 Alexey Andreev. + * 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. @@ -16,135 +16,22 @@ package org.teavm.platform.plugin; import java.io.IOException; -import org.teavm.javascript.ast.ConstantExpr; -import org.teavm.javascript.ast.Expr; -import org.teavm.javascript.spi.Injector; -import org.teavm.javascript.spi.InjectorContext; +import org.teavm.codegen.SourceWriter; +import org.teavm.javascript.spi.Generator; +import org.teavm.javascript.spi.GeneratorContext; import org.teavm.model.MethodReference; -import org.teavm.model.ValueType; /** * * @author Alexey Andreev */ -class ResourceAccessorGenerator implements Injector { +class ResourceAccessorGenerator implements Generator { @Override - 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('['); - context.writeExpr(context.getArgument(1)); - context.getWriter().append(']'); - } else { - context.writeExpr(context.getArgument(0)); - writePropertyAccessor(context, context.getArgument(1)); - } - break; - case "put": - context.getWriter().append('('); - if (methodRef.getDescriptor().parameterType(1) == ValueType.INTEGER) { - context.writeExpr(context.getArgument(0)); - context.getWriter().append('['); - context.writeExpr(context.getArgument(1)); - } else { - context.writeExpr(context.getArgument(0)); - writePropertyAccessor(context, context.getArgument(1)); - } - context.getWriter().ws().append('=').ws(); - context.writeExpr(context.getArgument(2)); - context.getWriter().append(')'); - break; - case "add": - context.writeExpr(context.getArgument(0)); - context.getWriter().append(".push("); - context.writeExpr(context.getArgument(1)); - context.getWriter().append(')'); - break; - case "has": - context.writeExpr(context.getArgument(0)); - context.getWriter().append(".hasOwnProperty("); - writeStringExpr(context, context.getArgument(1)); - context.getWriter().append(')'); - break; - case "size": - context.writeExpr(context.getArgument(0)); - context.getWriter().append(".length"); - break; - case "castToInt": - case "castToShort": - case "castToByte": - case "castToBoolean": - case "castToFloat": - case "castToDouble": - case "castFromInt": - case "castFromShort": - case "castFromByte": - case "castFromBoolean": - case "castFromFloat": - case "castFromDouble": - context.writeExpr(context.getArgument(0)); - break; - case "castToString": - context.getWriter().append('('); - context.writeExpr(context.getArgument(0)); - context.getWriter().ws().append("!==").ws().append("null").ws().append("?").ws(); - context.getWriter().append("$rt_str("); - context.writeExpr(context.getArgument(0)); - context.getWriter().append(")").ws().append(':').ws().append("null)"); - break; - case "castFromString": - context.getWriter().append('('); - context.writeExpr(context.getArgument(0)); - context.getWriter().ws().append("!==").ws().append("null").ws().append("?").ws(); - context.getWriter().append("$rt_ustr("); - context.writeExpr(context.getArgument(0)); - context.getWriter().append(")").ws().append(':').ws().append("null)"); - break; - } - } - - private void writePropertyAccessor(InjectorContext context, Expr property) throws IOException { - if (property instanceof ConstantExpr) { - String str = (String)((ConstantExpr)property).getValue(); - if (str.isEmpty()) { - context.getWriter().append("[\"\"]"); - return; - } - if (isValidIndentifier(str)) { - context.getWriter().append(".").append(str); - return; - } - } - context.getWriter().append("[$rt_ustr("); - context.writeExpr(property); - context.getWriter().append(")]"); - } - - private void writeStringExpr(InjectorContext context, Expr expr) throws IOException { - if (expr instanceof ConstantExpr) { - String str = (String)((ConstantExpr)expr).getValue(); - context.getWriter().append('"'); - context.writeEscaped(str); - context.getWriter().append('"'); - return; - } - context.getWriter().append("$rt_ustr("); - context.writeExpr(expr); - context.getWriter().append(")"); - } - - private boolean isValidIndentifier(String str) { - if (!Character.isJavaIdentifierStart(str.charAt(0))) { - return false; - } - for (int i = 1; i < str.length(); ++i) { - if (!Character.isJavaIdentifierPart(str.charAt(i))) { - return false; - } - } - return true; + public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { + writer.append("var result = [];").softNewLine(); + writer.append("for (var key in ").append(context.getParameterName(1)).append(") {").indent().softNewLine(); + writer.append("result.push(key);").softNewLine(); + writer.outdent().append("}").softNewLine(); + writer.append("return result;").softNewLine(); } } diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorInjector.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorInjector.java new file mode 100644 index 000000000..9fa80935d --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorInjector.java @@ -0,0 +1,150 @@ +/* + * 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.platform.plugin; + +import java.io.IOException; +import org.teavm.javascript.ast.ConstantExpr; +import org.teavm.javascript.ast.Expr; +import org.teavm.javascript.spi.Injector; +import org.teavm.javascript.spi.InjectorContext; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; + +/** + * + * @author Alexey Andreev + */ +class ResourceAccessorInjector implements Injector { + @Override + 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('['); + context.writeExpr(context.getArgument(1)); + context.getWriter().append(']'); + } else { + context.writeExpr(context.getArgument(0)); + writePropertyAccessor(context, context.getArgument(1)); + } + break; + case "put": + context.getWriter().append('('); + if (methodRef.getDescriptor().parameterType(1) == ValueType.INTEGER) { + context.writeExpr(context.getArgument(0)); + context.getWriter().append('['); + context.writeExpr(context.getArgument(1)); + } else { + context.writeExpr(context.getArgument(0)); + writePropertyAccessor(context, context.getArgument(1)); + } + context.getWriter().ws().append('=').ws(); + context.writeExpr(context.getArgument(2)); + context.getWriter().append(')'); + break; + case "add": + context.writeExpr(context.getArgument(0)); + context.getWriter().append(".push("); + context.writeExpr(context.getArgument(1)); + context.getWriter().append(')'); + break; + case "has": + context.writeExpr(context.getArgument(0)); + context.getWriter().append(".hasOwnProperty("); + writeStringExpr(context, context.getArgument(1)); + context.getWriter().append(')'); + break; + case "size": + context.writeExpr(context.getArgument(0)); + context.getWriter().append(".length"); + break; + case "castToInt": + case "castToShort": + case "castToByte": + case "castToBoolean": + case "castToFloat": + case "castToDouble": + case "castFromInt": + case "castFromShort": + case "castFromByte": + case "castFromBoolean": + case "castFromFloat": + case "castFromDouble": + context.writeExpr(context.getArgument(0)); + break; + case "castToString": + context.getWriter().append('('); + context.writeExpr(context.getArgument(0)); + context.getWriter().ws().append("!==").ws().append("null").ws().append("?").ws(); + context.getWriter().append("$rt_str("); + context.writeExpr(context.getArgument(0)); + context.getWriter().append(")").ws().append(':').ws().append("null)"); + break; + case "castFromString": + context.getWriter().append('('); + context.writeExpr(context.getArgument(0)); + context.getWriter().ws().append("!==").ws().append("null").ws().append("?").ws(); + context.getWriter().append("$rt_ustr("); + context.writeExpr(context.getArgument(0)); + context.getWriter().append(")").ws().append(':').ws().append("null)"); + break; + } + } + + private void writePropertyAccessor(InjectorContext context, Expr property) throws IOException { + if (property instanceof ConstantExpr) { + String str = (String)((ConstantExpr)property).getValue(); + if (str.isEmpty()) { + context.getWriter().append("[\"\"]"); + return; + } + if (isValidIndentifier(str)) { + context.getWriter().append(".").append(str); + return; + } + } + context.getWriter().append("[$rt_ustr("); + context.writeExpr(property); + context.getWriter().append(")]"); + } + + private void writeStringExpr(InjectorContext context, Expr expr) throws IOException { + if (expr instanceof ConstantExpr) { + String str = (String)((ConstantExpr)expr).getValue(); + context.getWriter().append('"'); + context.writeEscaped(str); + context.getWriter().append('"'); + return; + } + context.getWriter().append("$rt_ustr("); + context.writeExpr(expr); + context.getWriter().append(")"); + } + + private boolean isValidIndentifier(String str) { + if (!Character.isJavaIdentifierStart(str.charAt(0))) { + return false; + } + for (int i = 1; i < str.length(); ++i) { + if (!Character.isJavaIdentifierPart(str.charAt(i))) { + return false; + } + } + return true; + } +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorTransformer.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorTransformer.java index 894ce2cdc..9e4afdd3a 100644 --- a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorTransformer.java +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorTransformer.java @@ -33,10 +33,14 @@ class ResourceAccessorTransformer implements ClassHolderTransformer { @Override public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { if (cls.getName().equals(ResourceAccessor.class.getName())) { - ResourceAccessorGenerator generator = new ResourceAccessorGenerator(); + ResourceAccessorInjector injector = new ResourceAccessorInjector(); for (MethodHolder method : cls.getMethods()) { - if (!method.getName().equals("")) { - vm.add(method.getReference(), generator); + if (method.hasModifier(ElementModifier.NATIVE)) { + if (method.getName().equals("keys")) { + vm.add(method.getReference(), new ResourceAccessorGenerator()); + } else { + vm.add(method.getReference(), injector); + } } } } 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 f075ef597..71c4a92af 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 @@ -64,6 +64,9 @@ class ResourceProgramTransformer { MethodReference method = insn.getMethod(); if (method.getClassName().equals(ResourceArray.class.getName()) || method.getClassName().equals(ResourceMap.class.getName())) { + if (method.getName().equals("keys")) { + return transformKeys(insn); + } InvokeInstruction accessInsn = new InvokeInstruction(); accessInsn.setType(InvocationType.SPECIAL); ValueType[] types = new ValueType[method.getDescriptor().parameterCount() + 2]; @@ -96,6 +99,25 @@ class ResourceProgramTransformer { return null; } + private List transformKeys(InvokeInstruction insn) { + Variable tmp = program.createVariable(); + + InvokeInstruction keysInsn = new InvokeInstruction(); + keysInsn.setType(InvocationType.SPECIAL); + keysInsn.setMethod(new MethodReference(ResourceAccessor.class, "keys", Object.class, Object.class)); + keysInsn.getArguments().add(insn.getInstance()); + keysInsn.setReceiver(tmp); + + InvokeInstruction transformInsn = new InvokeInstruction(); + transformInsn.setType(InvocationType.SPECIAL); + transformInsn.setMethod(new MethodReference(ResourceAccessor.class, "keysToStrings", + Object.class, String[].class)); + transformInsn.getArguments().add(tmp); + transformInsn.setReceiver(insn.getReceiver()); + + return Arrays.asList(keysInsn, transformInsn); + } + private boolean isSubclass(ClassReader cls, String superClass) { if (cls.getName().equals(superClass)) { return true; diff --git a/teavm-tests/src/test/java/org/teavm/classlib/java/nio/charset/UTF8Test.java b/teavm-tests/src/test/java/org/teavm/classlib/java/nio/charset/UTF8Test.java index 82925b742..261acda2f 100644 --- a/teavm-tests/src/test/java/org/teavm/classlib/java/nio/charset/UTF8Test.java +++ b/teavm-tests/src/test/java/org/teavm/classlib/java/nio/charset/UTF8Test.java @@ -1,7 +1,6 @@ package org.teavm.classlib.java.nio.charset; import static org.junit.Assert.*; -import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; @@ -94,7 +93,7 @@ public class UTF8Test { } @Test - public void decodeLongUTF8ByteArray() throws UnsupportedEncodingException { + public void decodeLongUTF8ByteArray() { byte[] bytes = new byte[16384]; for (int i = 0; i < bytes.length;) { bytes[i++] = -16; 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 index 58b1857a1..fab5ec2df 100644 --- 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 @@ -1,8 +1,9 @@ package org.teavm.classlib.java.util; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import org.junit.Test; -import org.teavm.classlib.impl.tz.TimeZoneResourceProvider; +import org.teavm.classlib.impl.tz.DateTimeZone; +import org.teavm.classlib.impl.tz.DateTimeZoneProvider; /** * @@ -11,6 +12,9 @@ import org.teavm.classlib.impl.tz.TimeZoneResourceProvider; public class TimeZoneTest { @Test public void resourceProvided() { - assertNotNull(TimeZoneResourceProvider.getTimeZone("Europe/Moscow")); + DateTimeZone tz = DateTimeZoneProvider.getTimeZone("Europe/Moscow"); + assertEquals(1414274399999L, tz.previousTransition(1431781727159L)); + assertEquals(1301183999999L, tz.previousTransition(1414274399999L)); + assertEquals(1288479599999L, tz.previousTransition(1301183999999L)); } }