1 /*
2  * hunt-time: A time library for D programming language.
3  *
4  * Copyright (C) 2015-2018 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.time.zone.Ser;
13 
14 import hunt.stream.DataInput;
15 import hunt.stream.DataOutput;
16 import hunt.stream.Externalizable;
17 import hunt.Exceptions;
18 // import hunt.io.InvalidClassException;
19 import hunt.stream.ObjectInput;
20 import hunt.stream.ObjectOutput;
21 // import hunt.io.StreamCorruptedException;
22 import hunt.time.ZoneOffset;
23 import hunt.time.zone.ZoneRules;
24 import hunt.time.zone.ZoneOffsetTransition;
25 import hunt.time.zone.ZoneOffsetTransitionRule;
26 
27 /**
28  * The shared serialization delegate for this package.
29  *
30  * @implNote
31  * This class is mutable and should be created once per serialization.
32  *
33  * @serial include
34  * @since 1.8
35  */
36 final class Ser : Externalizable {
37 
38     /**
39      * Serialization version.
40      */
41     private enum long serialVersionUID = -8885321777449118786L;
42 
43     /** Type for ZoneRules. */
44     enum byte ZRULES = 1;
45     /** Type for ZoneOffsetTransition. */
46     enum byte ZOT = 2;
47     /** Type for ZoneOffsetTransition. */
48     enum byte ZOTRULE = 3;
49 
50     /** The type being serialized. */
51     private byte type;
52     /** The object being serialized. */
53     private Object object;
54 
55     /**
56      * Constructor for deserialization.
57      */
58     public this() {
59     }
60 
61     /**
62      * Creates an instance for serialization.
63      *
64      * @param type  the type
65      * @param object  the object
66      */
67     this(byte type, Object object) {
68         this.type = type;
69         this.object = object;
70     }
71 
72     //-----------------------------------------------------------------------
73     /**
74      * Implements the {@code Externalizable} interface to write the object.
75      * @serialData
76      * Each serializable class is mapped to a type that is the first byte
77      * _in the stream.  Refer to each class {@code writeReplace}
78      * serialized form for the value of the type and sequence of values for the type.
79      *
80      * !(ul)
81      * !(li)<a href="{@docRoot}/serialized-form.html#hunt.time.zone.ZoneRules">ZoneRules.writeReplace</a>
82      * !(li)<a href="{@docRoot}/serialized-form.html#hunt.time.zone.ZoneOffsetTransition">ZoneOffsetTransition.writeReplace</a>
83      * !(li)<a href="{@docRoot}/serialized-form.html#hunt.time.zone.ZoneOffsetTransitionRule">ZoneOffsetTransitionRule.writeReplace</a>
84      * </ul>
85      *
86      * @param _out  the data stream to write to, not null
87      */
88     override
89     public void writeExternal(ObjectOutput _out) /*throws IOException*/ {
90         writeInternal(type, object, _out);
91     }
92 
93     static void write(Object object, DataOutput _out) /*throws IOException*/ {
94         writeInternal(ZRULES, object, _out);
95     }
96 
97     private static void writeInternal(byte type, Object object, DataOutput _out) /*throws IOException*/ {
98         _out.writeByte(type);
99         switch (type) {
100             case ZRULES:
101                 (cast(ZoneRules) object).writeExternal(_out);
102                 break;
103             case ZOT:
104                 (cast(ZoneOffsetTransition) object).writeExternal(_out);
105                 break;
106             case ZOTRULE:
107                 (cast(ZoneOffsetTransitionRule) object).writeExternal(_out);
108                 break;
109             default:
110                 throw new InvalidClassException("Unknown serialized type");
111         }
112     }
113 
114     //-----------------------------------------------------------------------
115     /**
116      * Implements the {@code Externalizable} interface to read the object.
117      * @serialData
118      * The streamed type and parameters defined by the type's {@code writeReplace}
119      * method are read and passed to the corresponding static factory for the type
120      * to create a new instance.  That instance is returned as the de-serialized
121      * {@code Ser} object.
122      *
123      * !(ul)
124      * !(li)<a href="{@docRoot}/serialized-form.html#hunt.time.zone.ZoneRules">ZoneRules</a>
125      * - {@code ZoneRules.of(standardTransitions, standardOffsets, savingsInstantTransitions, wallOffsets, lastRules);}
126      * !(li)<a href="{@docRoot}/serialized-form.html#hunt.time.zone.ZoneOffsetTransition">ZoneOffsetTransition</a>
127      * - {@code ZoneOffsetTransition of(LocalDateTime.ofEpochSecond(epochSecond), offsetBefore, offsetAfter);}
128      * !(li)<a href="{@docRoot}/serialized-form.html#hunt.time.zone.ZoneOffsetTransitionRule">ZoneOffsetTransitionRule</a>
129      * - {@code ZoneOffsetTransitionRule.of(month, dom, dow, time, timeEndOfDay, timeDefinition, standardOffset, offsetBefore, offsetAfter);}
130      * </ul>
131      * @param _in  the data to read, not null
132      */
133     override
134     public void readExternal(ObjectInput _in) /*throws IOException, ClassNotFoundException */{
135         type = _in.readByte();
136         object = readInternal(type, _in);
137     }
138 
139     static Object read(DataInput _in) /*throws IOException, ClassNotFoundException*/ {
140         byte type = _in.readByte();
141         return readInternal(type, _in);
142     }
143 
144     private static Object readInternal(byte type, DataInput _in) /*throws IOException, ClassNotFoundException*/ {
145         switch (type) {
146             case ZRULES:
147                 return ZoneRules.readExternal(_in);
148             case ZOT:
149                 return ZoneOffsetTransition.readExternal(_in);
150             case ZOTRULE:
151                 return ZoneOffsetTransitionRule.readExternal(_in);
152             default:
153                 throw new Exception("Unknown serialized type");
154         }
155     }
156 
157     /**
158      * Returns the object that will replace this one.
159      *
160      * @return the read object, should never be null
161      */
162     private Object readResolve() {
163          return object;
164     }
165 
166     //-----------------------------------------------------------------------
167     /**
168      * Writes the state to the stream.
169      *
170      * @param offset  the offset, not null
171      * @param _out  the output stream, not null
172      * @throws IOException if an error occurs
173      */
174     static void writeOffset(ZoneOffset offset, DataOutput _out) /*throws IOException*/ {
175         int offsetSecs = offset.getTotalSeconds();
176         int offsetByte = offsetSecs % 900 == 0 ? offsetSecs / 900 : 127;  // compress to -72 to +72
177         _out.writeByte(offsetByte);
178         if (offsetByte == 127) {
179             _out.writeInt(offsetSecs);
180         }
181     }
182 
183     /**
184      * Reads the state from the stream.
185      *
186      * @param _in  the input stream, not null
187      * @return the created object, not null
188      * @throws IOException if an error occurs
189      */
190     static ZoneOffset readOffset(DataInput _in) /*throws IOException*/ {
191         int offsetByte = _in.readByte();
192         return (offsetByte == 127 ? ZoneOffset.ofTotalSeconds(_in.readInt()) : ZoneOffset.ofTotalSeconds(offsetByte * 900));
193     }
194 
195     //-----------------------------------------------------------------------
196     /**
197      * Writes the state to the stream.
198      *
199      * @param epochSec  the epoch seconds, not null
200      * @param _out  the output stream, not null
201      * @throws IOException if an error occurs
202      */
203     static void writeEpochSec(long epochSec, DataOutput _out) /*throws IOException*/ {
204         if (epochSec >= -4575744000L && epochSec < 10413792000L && epochSec % 900 == 0) {  // quarter hours between 1825 and 2300
205             int store = cast(int) ((epochSec + 4575744000L) / 900);
206             _out.writeByte((store >>> 16) & 255);
207             _out.writeByte((store >>> 8) & 255);
208             _out.writeByte(store & 255);
209         } else {
210             _out.writeByte(255);
211             _out.writeLong(epochSec);
212         }
213     }
214 
215     /**
216      * Reads the state from the stream.
217      *
218      * @param _in  the input stream, not null
219      * @return the epoch seconds, not null
220      * @throws IOException if an error occurs
221      */
222     static long readEpochSec(DataInput _in) /*throws IOException*/ {
223         int hiByte = _in.readByte() & 255;
224         if (hiByte == 255) {
225             return _in.readLong();
226         } else {
227             int midByte = _in.readByte() & 255;
228             int loByte = _in.readByte() & 255;
229             long tot = ((hiByte << 16) + (midByte << 8) + loByte);
230             return (tot * 900) - 4575744000L;
231         }
232     }
233 
234 }