/* * ----------------------------------------------------------------------- * 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.range; import net.time4j.Moment; import net.time4j.Month; import net.time4j.PlainDate; import net.time4j.PlainTime; import net.time4j.PlainTimestamp; import net.time4j.Quarter; import net.time4j.engine.TimeLine; import net.time4j.scale.TimeScale; import java.io.Externalizable; import java.io.IOException; import java.io.InvalidClassException; 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; /** * <p>Serialisierungsform für Intervalle. </p> * * @author Meno Hochschild * @serial include */ final class SPX implements Externalizable { //~ Statische Felder/Initialisierungen ---------------------------- /** Serialisierungstyp von {@code DateInterval}. */ static final int DATE_TYPE = 32; /** Serialisierungstyp von {@code ClockInterval}. */ static final int TIME_TYPE = 33; /** Serialisierungstyp von {@code TimestampInterval}. */ static final int TIMESTAMP_TYPE = 34; /** Serialisierungstyp von {@code MomentInterval}. */ static final int MOMENT_TYPE = 35; /** Serialisierungstyp von {@code CalendarYear}. */ static final int YEAR_TYPE = 36; /** Serialisierungstyp von {@code CalendarQuarter}. */ static final int QUARTER_TYPE = 37; /** Serialisierungstyp von {@code CalendarMonth}. */ static final int MONTH_TYPE = 38; /** Serialisierungstyp von {@code CalendarWeek}. */ static final int WEEK_TYPE = 39; /** Serialisierungstyp von {@code DateWindows}. */ static final int DATE_WINDOW_ID = 40; /** Serialisierungstyp von {@code ClockWindows}. */ static final int CLOCK_WINDOW_ID = 41; /** Serialisierungstyp von {@code TimestampWindows}. */ static final int TIMESTAMP_WINDOW_ID = 42; /** Serialisierungstyp von {@code MomentWindows}. */ static final int MOMENT_WINDOW_ID = 43; /** Serialisierungstyp von {@code GenerictWindows}. */ static final int GENERIC_WINDOW_ID = 44; /** Serialisierungstyp von {@code Boundary}. */ static final int BOUNDARY_TYPE = 57; /** Serialisierungstyp von {@code MachineTime}. */ static final int MACHINE_TIME_TYPE = 7; private static final long serialVersionUID = 1L; //~ 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 (corresponds 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 within the 6 most-significant bits 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 um 2 Bits nach links verschoben 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 { if (this.type == BOUNDARY_TYPE) { Boundary<?> boundary = (Boundary<?>) this.obj; int header = (BOUNDARY_TYPE << 2); if (boundary.equals(Boundary.infinitePast())) { header |= 1; out.writeByte(header); } else if (boundary.equals(Boundary.infiniteFuture())) { header |= 2; out.writeByte(header); } else { out.writeByte(header); out.writeByte(boundary.isOpen() ? 1 : 0); out.writeObject(boundary.getTemporal()); } } else { int header = (this.type << 2); out.writeByte(header); switch (this.type) { case DATE_TYPE: case TIME_TYPE: case TIMESTAMP_TYPE: case MOMENT_TYPE: ChronoInterval<?> interval = (ChronoInterval<?>) this.obj; writeBoundary(interval.getStart(), out); writeBoundary(interval.getEnd(), out); break; case YEAR_TYPE: CalendarYear cy = (CalendarYear) this.obj; out.writeInt(cy.getValue()); break; case QUARTER_TYPE: CalendarQuarter cq = (CalendarQuarter) this.obj; out.writeInt(cq.getYear()); out.writeInt(cq.getQuarter().getValue()); break; case MONTH_TYPE: CalendarMonth cm = (CalendarMonth) this.obj; out.writeInt(cm.getYear()); out.writeInt(cm.getMonth().getValue()); break; case WEEK_TYPE: CalendarWeek cw = (CalendarWeek) this.obj; out.writeInt(cw.getYear()); out.writeInt(cw.getWeek()); break; case DATE_WINDOW_ID: case CLOCK_WINDOW_ID: case TIMESTAMP_WINDOW_ID: case MOMENT_WINDOW_ID: IntervalCollection<?> window = IntervalCollection.class.cast(this.obj); out.writeInt(window.getSize()); for (ChronoInterval<?> part : window.getIntervals()) { writeBoundary(part.getStart(), out); writeBoundary(part.getEnd(), out); } break; case GENERIC_WINDOW_ID: IntervalCollection<?> gwindow = IntervalCollection.class.cast(this.obj); out.writeObject(gwindow.getTimeLine()); out.writeInt(gwindow.getSize()); for (ChronoInterval<?> part : gwindow.getIntervals()) { out.writeObject(part.getStart().getTemporal()); out.writeObject(part.getEnd().getTemporal()); } break; case MACHINE_TIME_TYPE: this.writeMachineTime(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 { byte header = in.readByte(); int typeInfo = (header & 0xFF) >> 2; switch (typeInfo) { case DATE_TYPE: this.obj = readDateInterval(in); break; case TIME_TYPE: this.obj = readClockInterval(in); break; case TIMESTAMP_TYPE: this.obj = readTimestampInterval(in); break; case MOMENT_TYPE: this.obj = readMomentInterval(in); break; case YEAR_TYPE: this.obj = readCalendarYear(in); break; case QUARTER_TYPE: this.obj = readCalendarQuarter(in); break; case MONTH_TYPE: this.obj = readCalendarMonth(in); break; case WEEK_TYPE: this.obj = readCalendarWeek(in); break; case DATE_WINDOW_ID: this.obj = readDateWindows(in); break; case CLOCK_WINDOW_ID: this.obj = readClockWindows(in); break; case TIMESTAMP_WINDOW_ID: this.obj = readTimestampWindows(in); break; case MOMENT_WINDOW_ID: this.obj = readMomentWindows(in); break; case GENERIC_WINDOW_ID: this.obj = readGenericWindows(in); break; case BOUNDARY_TYPE: this.obj = readBoundary(in, header); break; case MACHINE_TIME_TYPE: this.obj = this.readMachineTime(in, header); break; default: throw new StreamCorruptedException("Unknown serialized type."); } } private Object readResolve() throws ObjectStreamException { return this.obj; } private static DateInterval readDateInterval(ObjectInput in) throws IOException, ClassNotFoundException { Object o1 = readBoundary(in); Object o2 = readBoundary(in); Boundary<PlainDate> start = getBoundary(o1, PlainDate.class, false); Boundary<PlainDate> end = getBoundary(o2, PlainDate.class, true); if ((start != null) && (end != null)) { return new DateInterval(start, end); } throw new StreamCorruptedException(); } private static ClockInterval readClockInterval(ObjectInput in) throws IOException, ClassNotFoundException { Object o1 = readBoundary(in); Object o2 = readBoundary(in); Boundary<PlainTime> start = getBoundary(o1, PlainTime.class, false); Boundary<PlainTime> end = getBoundary(o2, PlainTime.class, true); if ((start != null) && (end != null)) { return new ClockInterval(start, end); } throw new StreamCorruptedException(); } private static TimestampInterval readTimestampInterval(ObjectInput in) throws IOException, ClassNotFoundException { Object o1 = readBoundary(in); Object o2 = readBoundary(in); Boundary<PlainTimestamp> start = getBoundary(o1, PlainTimestamp.class, false); Boundary<PlainTimestamp> end = getBoundary(o2, PlainTimestamp.class, true); if ((start != null) && (end != null)) { return new TimestampInterval(start, end); } throw new StreamCorruptedException(); } private static MomentInterval readMomentInterval(ObjectInput in) throws IOException, ClassNotFoundException { Object o1 = readBoundary(in); Object o2 = readBoundary(in); Boundary<Moment> start = getBoundary(o1, Moment.class, false); Boundary<Moment> end = getBoundary(o2, Moment.class, true); if ((start != null) && (end != null)) { return new MomentInterval(start, end); } throw new StreamCorruptedException(); } private static CalendarYear readCalendarYear(ObjectInput in) throws IOException { int year = in.readInt(); return CalendarYear.of(year); } private static CalendarQuarter readCalendarQuarter(ObjectInput in) throws IOException { int year = in.readInt(); int quarter = in.readInt(); return CalendarQuarter.of(year, Quarter.valueOf(quarter)); } private static CalendarMonth readCalendarMonth(ObjectInput in) throws IOException { int year = in.readInt(); int month = in.readInt(); return CalendarMonth.of(year, Month.valueOf(month)); } private static CalendarWeek readCalendarWeek(ObjectInput in) throws IOException { int year = in.readInt(); int week = in.readInt(); return CalendarWeek.of(year, week); } private static IntervalCollection<PlainDate> readDateWindows( ObjectInput in ) throws IOException, ClassNotFoundException { int size = in.readInt(); if (size == 0) { return DateWindows.EMPTY; } List<ChronoInterval<PlainDate>> intervals = new ArrayList<>(size); for (int i = 0; i < size; i++) { intervals.add(readDateInterval(in)); } Collections.sort(intervals, DateInterval.comparator()); return DateWindows.EMPTY.plus(intervals); } private static IntervalCollection<PlainTime> readClockWindows( ObjectInput in ) throws IOException, ClassNotFoundException { int size = in.readInt(); if (size == 0) { return ClockWindows.EMPTY; } List<ChronoInterval<PlainTime>> intervals = new ArrayList<>(size); for (int i = 0; i < size; i++) { intervals.add(readClockInterval(in)); } Collections.sort(intervals, ClockInterval.comparator()); return ClockWindows.EMPTY.plus(intervals); } private static IntervalCollection<PlainTimestamp> readTimestampWindows( ObjectInput in ) throws IOException, ClassNotFoundException { int size = in.readInt(); if (size == 0) { return TimestampWindows.EMPTY; } List<ChronoInterval<PlainTimestamp>> intervals = new ArrayList<>(size); for (int i = 0; i < size; i++) { intervals.add(readTimestampInterval(in)); } Collections.sort(intervals, TimestampInterval.comparator()); return TimestampWindows.EMPTY.plus(intervals); } private static IntervalCollection<Moment> readMomentWindows(ObjectInput in) throws IOException, ClassNotFoundException { int size = in.readInt(); if (size == 0) { return MomentWindows.EMPTY; } List<ChronoInterval<Moment>> intervals = new ArrayList<>(size); for (int i = 0; i < size; i++) { intervals.add(readMomentInterval(in)); } Collections.sort(intervals, MomentInterval.comparator()); return MomentWindows.EMPTY.plus(intervals); } @SuppressWarnings("unchecked") private static IntervalCollection<?> readGenericWindows(ObjectInput in) throws IOException, ClassNotFoundException { TimeLine<?> timeLine = TimeLine.class.cast(in.readObject()); int size = in.readInt(); List<ChronoInterval<?>> intervals = new ArrayList<>(size); for (int i = 0; i < size; i++) { Object s = in.readObject(); Object e = in.readObject(); intervals.add(new SimpleInterval(s, e, timeLine)); } Collections.sort(intervals, new IntervalComparator(false, timeLine)); return new GenericWindows(timeLine, intervals); } // serialization of a single boundary object private static Object readBoundary( ObjectInput in, byte header ) throws IOException, ClassNotFoundException { int past = (header & 0x1); if (past == 1) { return Boundary.infinitePast(); } int future = (header & 0x2); if (future == 2) { return Boundary.infiniteFuture(); } int openClosed = in.readByte(); IntervalEdge edge; switch (openClosed) { case 0: edge = IntervalEdge.CLOSED; break; case 1: edge = IntervalEdge.OPEN; break; default: throw new StreamCorruptedException("Invalid edge state."); } Object t = in.readObject(); return Boundary.of(edge, t); } private static void writeBoundary( Boundary<?> boundary, ObjectOutput out ) throws IOException { if (boundary.equals(Boundary.infinitePast())) { out.writeByte(1); } else if (boundary.equals(Boundary.infiniteFuture())) { out.writeByte(2); } else { out.writeByte(boundary.isOpen() ? 4 : 0); out.writeObject(boundary.getTemporal()); } } private static Object readBoundary(ObjectInput in) throws IOException, ClassNotFoundException { int header = (in.readByte() & 0xFF); if ((header & 0x1) == 1) { return Boundary.infinitePast(); } else if ((header & 0x2) == 2) { return Boundary.infiniteFuture(); } IntervalEdge edge = ( ((header & 0x4) == 4) ? IntervalEdge.OPEN : IntervalEdge.CLOSED); Object t = in.readObject(); return Boundary.of(edge, t); } private static <T> Boundary<T> getBoundary( Object obj, Class<T> type, boolean ending ) { Boundary<T> ret = null; if (obj instanceof Boundary) { Boundary<?> b = cast(obj); if (b.isInfinite()) { Boundary<T> expected; if (ending) { expected = Boundary.infiniteFuture(); } else { expected = Boundary.infinitePast(); } if (expected.equals(obj)) { ret = expected; } } else if (type.isInstance(b.getTemporal())) { ret = cast(b); } } return ret; } private void writeMachineTime(ObjectOutput out) throws IOException { MachineTime<?> mt = MachineTime.class.cast(this.obj); int header = MACHINE_TIME_TYPE; header <<= 2; if (mt.getScale() == TimeScale.UTC) { header |= 1; } if (mt.getFraction() == 0) { out.writeByte(header); out.writeLong(mt.getSeconds()); } else { header |= 2; out.writeByte(header); out.writeLong(mt.getSeconds()); out.writeInt(mt.getFraction()); } } private Object readMachineTime( ObjectInput in, byte header ) throws IOException, ClassNotFoundException { TimeScale scale = ( ((header & 0x1) == 1) ? TimeScale.UTC : TimeScale.POSIX); long secs = in.readLong(); int fraction = (((header & 0x2) == 2) ? in.readInt() : 0); if (scale == TimeScale.UTC) { return MachineTime.ofSIUnits(secs, fraction); } else { return MachineTime.ofPosixUnits(secs, fraction); } } @SuppressWarnings("unchecked") private static <T> T cast(Object obj) { return (T) obj; } }