/* * ----------------------------------------------------------------------- * Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (SPX.java) is part of project Time4J. * * Time4J is free software: You can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * Time4J is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Time4J. If not, see <http://www.gnu.org/licenses/>. * ----------------------------------------------------------------------- */ package net.time4j.tz.model; import net.time4j.Month; import net.time4j.PlainTime; import net.time4j.Weekday; import net.time4j.base.MathUtils; import net.time4j.tz.ZonalOffset; import net.time4j.tz.ZonalTransition; import java.io.DataInput; import java.io.DataOutput; import java.io.Externalizable; import java.io.IOException; import java.io.InvalidClassException; import java.io.InvalidObjectException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.ObjectStreamException; import java.io.StreamCorruptedException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import static net.time4j.PlainTime.SECOND_OF_DAY; /** * <p><i>Serialization Proxy</i> für die Zeitzonenhistorie. </p> * * @author Meno Hochschild * @since 2.2 * @serial include */ final class SPX implements Externalizable { //~ Statische Felder/Initialisierungen -------------------------------- /** Serialisierungstyp von {@code FixedDayPattern}. */ static final int FIXED_DAY_PATTERN_TYPE = 120; /** Serialisierungstyp von {@code DayOfWeekInMonthPattern}. */ static final int DAY_OF_WEEK_IN_MONTH_PATTERN_TYPE = 121; /** Serialisierungstyp von {@code LastWeekdayPattern}. */ static final int LAST_WEEKDAY_PATTERN_TYPE = 122; /** Serialisierungstyp von {@code RuleBasedTransitionModel}. */ static final int RULE_BASED_TRANSITION_MODEL_TYPE = 125; /** Serialisierungstyp von {@code ArrayTransitionModel}. */ static final int ARRAY_TRANSITION_MODEL_TYPE = 126; /** Serialisierungstyp von {@code CompositeTransitionModel}. */ static final int COMPOSITE_TRANSITION_MODEL_TYPE = 127; private static final long POSIX_TIME_1825 = -4575744000L; // 1825-01-01T00Z private static final long DAYS_IN_18_BITS = 86400L * 365 * 718; private static final long QUARTERS_IN_24_BITS = 15040511099L; private static final int NO_COMPRESSION = 0; private static final long serialVersionUID = 6526945678752534989L; //~ Instanzvariablen -------------------------------------------------- private transient Object obj; private transient int type; //~ Konstruktoren ----------------------------------------------------- /** * <p>Benutzt in der Deserialisierung gemäß dem Kontrakt * von {@code Externalizable}. </p> */ public SPX() { super(); } /** * <p>Benutzt in der Serialisierung (writeReplace). </p> * * @param obj object to be serialized * @param type serialization type corresponding to type of obj */ SPX( Object obj, int type ) { super(); this.obj = obj; this.type = type; } //~ Methoden ---------------------------------------------------------- /** * <p>Implementation method of interface {@link Externalizable}. </p> * * <p>The first byte contains the type of the object to be serialized. * Then the data bytes follow in a bit-compressed representation. </p> * * @serialData data layout see {@code writeReplace()}-method of object * to be serialized * @param out output stream * @throws IOException in case of I/O-problems */ /*[deutsch] * <p>Implementierungsmethode des Interface {@link Externalizable}. </p> * * <p>Das erste Byte enthält den Typ des zu serialisierenden Objekts. * Danach folgen die Daten-Bits in einer bit-komprimierten Darstellung. </p> * * @serialData data layout see {@code writeReplace()}-method of object * to be serialized * @param out output stream * @throws IOException in case of I/O-problems */ @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeByte(this.type); switch (this.type) { case FIXED_DAY_PATTERN_TYPE: writeFixedDayPattern(this.obj, out); break; case DAY_OF_WEEK_IN_MONTH_PATTERN_TYPE: writeDayOfWeekInMonthPattern(this.obj, out); break; case LAST_WEEKDAY_PATTERN_TYPE: writeLastDayOfWeekPattern(this.obj, out); break; case RULE_BASED_TRANSITION_MODEL_TYPE: writeRuleBasedTransitionModel(this.obj, out); break; case ARRAY_TRANSITION_MODEL_TYPE: writeArrayTransitionModel(this.obj, out); break; case COMPOSITE_TRANSITION_MODEL_TYPE: writeCompositeTransitionModel(this.obj, out); break; default: throw new InvalidClassException("Unknown serialized type."); } } /** * <p>Implementation method of interface {@link Externalizable}. </p> * * @param in input stream * @throws IOException in case of I/O-problems * @throws ClassNotFoundException if class-loading fails */ /*[deutsch] * <p>Implementierungsmethode des Interface {@link Externalizable}. </p> * * @param in input stream * @throws IOException in case of I/O-problems * @throws ClassNotFoundException if class-loading fails */ @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { int header = in.readByte(); switch (header) { case FIXED_DAY_PATTERN_TYPE: this.obj = readFixedDayPattern(in); break; case DAY_OF_WEEK_IN_MONTH_PATTERN_TYPE: this.obj = readDayOfWeekInMonthPattern(in); break; case LAST_WEEKDAY_PATTERN_TYPE: this.obj = readLastDayOfWeekPattern(in); break; case RULE_BASED_TRANSITION_MODEL_TYPE: this.obj = readRuleBasedTransitionModel(in); break; case ARRAY_TRANSITION_MODEL_TYPE: this.obj = readArrayTransitionModel(in); break; case COMPOSITE_TRANSITION_MODEL_TYPE: this.obj = readCompositeTransitionModel(in); break; default: throw new StreamCorruptedException("Unknown serialized type."); } } // called by ArrayTransitionModel static void writeTransitions( ZonalTransition[] transitions, int size, DataOutput out ) throws IOException { int n = Math.min(size, transitions.length); out.writeInt(n); if (n > 0) { int stdOffset = transitions[0].getPreviousOffset(); writeOffset(out, stdOffset); for (int i = 0; i < n; i++) { stdOffset = writeTransition(transitions[i], stdOffset, out); } } } private static List<ZonalTransition> readTransitions(ObjectInput in) throws IOException { int n = in.readInt(); if (n == 0) { return Collections.emptyList(); } List<ZonalTransition> transitions = new ArrayList<>(n); int previous = readOffset(in); int rawOffset = previous; long oldTsp = Long.MIN_VALUE; for (int i = 0; i < n; i++) { int first = in.readByte(); boolean newStdOffset = (first < 0); int dstIndex = ((first >>> 5) & 3); int timeIndex = ((first >>> 2) & 7); int tod = toTimeOfDayT(timeIndex); long posix; if (tod == -1) { posix = in.readLong(); } else { int dayIndex = ((first & 3) << 16); dayIndex |= ((in.readByte() & 0xFF) << 8); dayIndex |= (in.readByte() & 0xFF); posix = ((dayIndex * 86400L) + POSIX_TIME_1825 + tod - 7200); posix -= rawOffset; } if (posix <= oldTsp) { throw new StreamCorruptedException( "Wrong order of transitions."); } else { oldTsp = posix; } int dstOffset; switch (dstIndex) { case 1: dstOffset = 0; break; case 2: dstOffset = 3600; break; case 3: dstOffset = 7200; break; default: dstOffset = readOffset(in); } if (newStdOffset) { rawOffset = readOffset(in); } int total = rawOffset + dstOffset; ZonalTransition transition = new ZonalTransition(posix, previous, total, dstOffset); previous = total; transitions.add(transition); } return transitions; } private static void writeRules( List<DaylightSavingRule> rules, ObjectOutput out ) throws IOException { out.writeByte(rules.size()); for (DaylightSavingRule rule : rules) { out.writeByte(rule.getType()); switch (rule.getType()) { case FIXED_DAY_PATTERN_TYPE: writeFixedDayPattern(rule, out); break; case DAY_OF_WEEK_IN_MONTH_PATTERN_TYPE: writeDayOfWeekInMonthPattern(rule, out); break; case LAST_WEEKDAY_PATTERN_TYPE: writeLastDayOfWeekPattern(rule, out); break; default: out.writeObject(rule); } } } private static List<DaylightSavingRule> readRules(ObjectInput in) throws IOException, ClassNotFoundException { int n = in.readByte(); if (n == 0) { return Collections.emptyList(); } List<DaylightSavingRule> rules = new ArrayList<>(n); DaylightSavingRule previous = null; for (int i = 0; i < n; i++) { int type = in.readByte(); DaylightSavingRule rule; switch (type) { case FIXED_DAY_PATTERN_TYPE: rule = readFixedDayPattern(in); break; case DAY_OF_WEEK_IN_MONTH_PATTERN_TYPE: rule = readDayOfWeekInMonthPattern(in); break; case LAST_WEEKDAY_PATTERN_TYPE: rule = readLastDayOfWeekPattern(in); break; default: rule = (DaylightSavingRule) in.readObject(); } if ( (previous != null) && (RuleComparator.INSTANCE.compare(previous, rule) >= 0) ) { throw new InvalidObjectException( "Order of daylight saving rules is not ascending."); } previous = rule; rules.add(rule); } return rules; } private static void writeOffset( DataOutput out, int offset ) throws IOException { if ((offset % 900) == 0) { out.writeByte(offset / 900); } else { out.writeByte(127); out.writeInt(offset); } } private static int readOffset(DataInput in) throws IOException { int savings = in.readByte(); if (savings == 127) { return in.readInt(); } else { return savings * 900; } } private static int readSavings(int offsetInfo) throws IOException { switch (offsetInfo / 3) { case 0: return 0; case 1: return 1800; case 2: return 3600; case 3: return 7200; default: return -1; } } private static void writeFixedDayPattern( Object rule, DataOutput out ) throws IOException { FixedDayPattern pattern = (FixedDayPattern) rule; boolean offsetWritten = writeMonthIndicatorOffset(pattern, out); int second = (pattern.getDayOfMonth() << 3); int tod = pattern.getTimeOfDay().get(SECOND_OF_DAY).intValue(); int timeIndex = toTimeIndexR(tod); second |= timeIndex; out.writeByte(second & 0xFF); if (!offsetWritten) { writeOffset(out, pattern.getSavings()); } if (timeIndex == NO_COMPRESSION) { out.writeInt(tod); } } private static DaylightSavingRule readFixedDayPattern(DataInput in) throws IOException, ClassNotFoundException { int first = (in.readByte() & 0xFF); int month = (first >>> 4); int offsetInfo = first & 0x0F; OffsetIndicator indicator = OffsetIndicator.VALUES[offsetInfo % 3]; int dst = readSavings(offsetInfo); int second = (in.readByte() & 0xFF); int dayOfMonth = (second >>> 3); int tod = toTimeOfDayR(second & 7); if (dst == -1) { dst = readOffset(in); } if (tod == -1) { tod = in.readInt(); } PlainTime timeOfDay = PlainTime.midnightAtStartOfDay().with(SECOND_OF_DAY, tod); return new FixedDayPattern( Month.valueOf(month), dayOfMonth, timeOfDay, indicator, dst); } private static void writeDayOfWeekInMonthPattern( Object rule, DataOutput out ) throws IOException { DayOfWeekInMonthPattern pattern = (DayOfWeekInMonthPattern) rule; boolean offsetWritten = writeMonthIndicatorOffset(pattern, out); int second = (pattern.getDayOfMonth() << 3); second |= pattern.getDayOfWeek(); out.writeByte(second & 0xFF); int third = (pattern.isAfter() ? (1 << 7) : 0); int tod = pattern.getTimeOfDay().get(SECOND_OF_DAY).intValue(); boolean timeWritten = false; if ((tod % 1800) == 0) { third |= (tod / 1800); timeWritten = true; } else { third |= 63; } out.writeByte(third & 0xFF); if (!offsetWritten) { writeOffset(out, pattern.getSavings()); } if (!timeWritten) { out.writeInt(tod); } } private static DaylightSavingRule readDayOfWeekInMonthPattern(DataInput in) throws IOException, ClassNotFoundException { int first = (in.readByte() & 0xFF); Month month = Month.valueOf(first >>> 4); int offsetInfo = first & 0x0F; OffsetIndicator indicator = OffsetIndicator.VALUES[offsetInfo % 3]; int dst = readSavings(offsetInfo); int second = (in.readByte() & 0xFF); int dayOfMonth = (second >>> 3); Weekday dayOfWeek = Weekday.valueOf(second & 7); int third = (in.readByte() & 0xFF); boolean after = ((third >>> 7) == 1); int tod = (third & 63); if (dst == -1) { dst = readOffset(in); } if (tod == 63) { tod = in.readInt(); } else { tod *= 1800; } PlainTime timeOfDay = PlainTime.midnightAtStartOfDay().with(SECOND_OF_DAY, tod); return new DayOfWeekInMonthPattern( month, dayOfMonth, dayOfWeek, timeOfDay, indicator, dst, after); } private static void writeLastDayOfWeekPattern( Object rule, DataOutput out ) throws IOException { LastWeekdayPattern pattern = (LastWeekdayPattern) rule; boolean offsetWritten = writeMonthIndicatorOffset(pattern, out); int second = (pattern.getDayOfWeek() << 5); int tod = pattern.getTimeOfDay().get(SECOND_OF_DAY).intValue(); boolean timeWritten = false; if ((tod % 3600) == 0) { second |= (tod / 3600); timeWritten = true; } else { second |= 31; } out.writeByte(second & 0xFF); if (!offsetWritten) { writeOffset(out, pattern.getSavings()); } if (!timeWritten) { out.writeInt(tod); } } private static DaylightSavingRule readLastDayOfWeekPattern(DataInput in) throws IOException, ClassNotFoundException { int first = (in.readByte() & 0xFF); Month month = Month.valueOf(first >>> 4); int offsetInfo = first & 0x0F; OffsetIndicator indicator = OffsetIndicator.VALUES[offsetInfo % 3]; int dst = readSavings(offsetInfo); int second = (in.readByte() & 0xFF); Weekday dayOfWeek = Weekday.valueOf(second >>> 5); int tod = (second & 31); if (dst == -1) { dst = readOffset(in); } if (tod == 31) { tod = in.readInt(); } else { tod *= 3600; } PlainTime timeOfDay = PlainTime.midnightAtStartOfDay().with(SECOND_OF_DAY, tod); return new LastWeekdayPattern( month, dayOfWeek, timeOfDay, indicator, dst); } private static void writeRuleBasedTransitionModel( Object obj, ObjectOutput out ) throws IOException { RuleBasedTransitionModel model = (RuleBasedTransitionModel) obj; ZonalTransition initial = model.getInitialTransition(); long posixTime = initial.getPosixTime(); if ( (posixTime >= POSIX_TIME_1825) && (posixTime < POSIX_TIME_1825 + QUARTERS_IN_24_BITS) && ((posixTime % 900) == 0) ) { int data = (int) ((posixTime - POSIX_TIME_1825) / 900); out.writeByte((data >>> 16) & 0xFF); out.writeByte((data >>> 8) & 0xFF); out.writeByte(data & 0xFF); } else { out.writeByte(0xFF); out.writeLong(initial.getPosixTime()); } writeOffset(out, initial.getPreviousOffset()); writeOffset(out, initial.getTotalOffset()); writeOffset(out, initial.getDaylightSavingOffset()); writeRules(model.getRules(), out); } private static Object readRuleBasedTransitionModel(ObjectInput in) throws IOException, ClassNotFoundException { long posixTime; int high = in.readByte() & 0xFF; if (high == 0xFF) { posixTime = in.readLong(); } else { int mid = in.readByte() & 0xFF; int low = in.readByte() & 0xFF; posixTime = ((high << 16) + (mid << 8) + low) * 900L; posixTime += POSIX_TIME_1825; } int previous = readOffset(in); int total = readOffset(in); int dst = readOffset(in); ZonalTransition initial = new ZonalTransition(posixTime, previous, total, dst); List<DaylightSavingRule> rules = readRules(in); return new RuleBasedTransitionModel( initial, rules, false); } private static void writeArrayTransitionModel( Object obj, ObjectOutput out ) throws IOException { ArrayTransitionModel model = (ArrayTransitionModel) obj; model.writeTransitions(out); } private static Object readArrayTransitionModel(ObjectInput in) throws IOException, ClassNotFoundException { return new ArrayTransitionModel( readTransitions(in), false, false); } private static void writeCompositeTransitionModel( Object obj, ObjectOutput out ) throws IOException { CompositeTransitionModel model = (CompositeTransitionModel) obj; model.writeTransitions(out); writeRules(model.getRules(), out); } private static Object readCompositeTransitionModel(ObjectInput in) throws IOException, ClassNotFoundException { List<ZonalTransition> transitions = readTransitions(in); return TransitionModel.of( ZonalOffset.ofTotalSeconds(transitions.get(0).getPreviousOffset()), transitions, readRules(in), false, false); } private static int writeTransition( ZonalTransition transition, int stdOffset, DataOutput out ) throws IOException { int rawOffset = transition.getStandardOffset(); boolean newStdOffset = (rawOffset != stdOffset); byte first = 0; if (newStdOffset) { first |= (1 << 7); } int dstIndex; switch (transition.getDaylightSavingOffset()) { case 0: dstIndex = 1; break; case 3600: dstIndex = 2; break; case 7200: dstIndex = 3; break; default: dstIndex = NO_COMPRESSION; } first |= (dstIndex << 5); // local standard time plus two hours: 22:00-3:00 => 0:00-5:00 long modTime = transition.getPosixTime() + stdOffset + 7200; int timeIndex = NO_COMPRESSION; if ( (modTime >= POSIX_TIME_1825) && (modTime < POSIX_TIME_1825 + DAYS_IN_18_BITS) // 2542-07-11 ) { timeIndex = toTimeIndexT(MathUtils.floorModulo(modTime, 86400)); } first |= (timeIndex << 2); if (timeIndex == NO_COMPRESSION) { out.writeByte(first); out.writeLong(transition.getPosixTime()); } else { int dayIndex = (int) ((modTime - POSIX_TIME_1825) / 86400); byte high = (byte) ((dayIndex >>> 16) & 3); first |= high; out.writeByte(first); out.writeByte((dayIndex >>> 8) & 0xFF); out.writeByte(dayIndex & 0xFF); } if (dstIndex == NO_COMPRESSION) { writeOffset(out, transition.getDaylightSavingOffset()); } if (newStdOffset) { writeOffset(out, rawOffset); } return rawOffset; } private static int toTimeIndexT(int tod) { switch (tod) { case 0: return 1; case 60: return 2; case 3600: return 3; case 7200: return 4; case 10800: return 5; case 14400: return 6; case 18000: return 7; default: return NO_COMPRESSION; } } private static int toTimeOfDayT(int timeIndex) { switch (timeIndex) { case 1: return 0; case 2: return 60; case 3: return 3600; case 4: return 7200; case 5: return 10800; case 6: return 14400; case 7: return 18000; default: return -1; } } private static boolean writeMonthIndicatorOffset( GregorianTimezoneRule rule, DataOutput out ) throws IOException { int first = (rule.getMonthValue() << 4); int indicator = rule.getIndicator().ordinal(); int dst = rule.getSavings(); boolean offsetWritten = true; switch (dst) { case 0: first |= indicator; break; case 1800: first |= (3 + indicator); break; case 3600: first |= (6 + indicator); break; case 7200: first |= (9 + indicator); break; default: offsetWritten = false; first |= (12 + indicator); } out.writeByte(first & 0xFF); return offsetWritten; } private static int toTimeIndexR(int tod) { switch (tod) { case 0: return 1; case 3600: return 2; case 7200: return 3; case 10800: return 4; case 22 * 3600: return 5; case 23 * 3600: return 6; case 86400: return 7; default: return NO_COMPRESSION; } } private static int toTimeOfDayR(int timeIndex) { switch (timeIndex) { case 1: return 0; case 2: return 3600; case 3: return 7200; case 4: return 10800; case 5: return 22 * 3600; case 6: return 23 * 3600; case 7: return 86400; default: return -1; } } private Object readResolve() throws ObjectStreamException { return this.obj; } }