IO.java
001 /*
002  * Java GPX Library (jpx-3.1.0).
003  * Copyright (c) 2016-2023 Franz Wilhelmstötter
004  *
005  * Licensed under the Apache License, Version 2.0 (the "License");
006  * you may not use this file except in compliance with the License.
007  * You may obtain a copy of the License at
008  *
009  *      http://www.apache.org/licenses/LICENSE-2.0
010  *
011  * Unless required by applicable law or agreed to in writing, software
012  * distributed under the License is distributed on an "AS IS" BASIS,
013  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014  * See the License for the specific language governing permissions and
015  * limitations under the License.
016  *
017  * Author:
018  *    Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com)
019  */
020 package io.jenetics.jpx;
021 
022 import static java.nio.charset.StandardCharsets.UTF_8;
023 import static io.jenetics.jpx.Lists.copyOf;
024 
025 import java.io.ByteArrayInputStream;
026 import java.io.ByteArrayOutputStream;
027 import java.io.DataInput;
028 import java.io.DataOutput;
029 import java.io.IOException;
030 import java.util.ArrayList;
031 import java.util.Collection;
032 import java.util.List;
033 
034 import javax.xml.parsers.ParserConfigurationException;
035 
036 import org.w3c.dom.Document;
037 import org.xml.sax.SAXException;
038 
039 /**
040  * Helper methods needed for implementing the Java serializations.
041  *
042  @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
043  @version 1.2
044  @since 1.2
045  */
046 final class IO {
047 
048     private IO() {
049     }
050 
051     /**
052      * Object writer interface.
053      *
054      @param <T> the object type
055      */
056     @FunctionalInterface
057     interface Writer<T> {
058         void write(final T value, final DataOutput outthrows IOException;
059     }
060 
061     /**
062      * Object reader interface
063      *
064      @param <T> the object type
065      */
066     @FunctionalInterface
067     interface Reader<T> {
068         T read(final DataInput inthrows IOException;
069     }
070 
071     /**
072      * Write the given, possible {@code null}, {@code value} to the data output
073      * using the given {@code writer}.
074      *
075      @param value the, possible {@code null}, value to write
076      @param writer the object writer
077      @param out the data output
078      @param <T> the object type
079      @throws NullPointerException if the {@code writer} or data output is
080      *         {@code null}
081      @throws IOException if an I/O error occurs
082      */
083     static <T> void writeNullable(
084         final T value,
085         final Writer<? super T> writer,
086         final DataOutput out
087     )
088         throws IOException
089     {
090         out.writeBoolean(value != null);
091         if (value != null) {
092             writer.write(value, out);
093         }
094     }
095 
096     /**
097      * Reads a possible {@code null} value from the given data input.
098      *
099      @param reader the object reader
100      @param in the data input
101      @param <T> the object type
102      @return the read object
103      @throws NullPointerException if one of the given arguments is {@code null}
104      @throws IOException if an I/O error occurs
105      */
106     static <T> T readNullable(
107         final Reader<? extends T> reader,
108         final DataInput in
109     )
110         throws IOException
111     {
112         T value = null;
113         if (in.readBoolean()) {
114             value = reader.read(in);
115         }
116 
117         return value;
118     }
119 
120     /**
121      * Write the given string {@code value} to the given data output.
122      *
123      @param value the string value to write
124      @param out the data output
125      @throws NullPointerException if one of the given arguments is {@code null}
126      @throws IOException if an I/O error occurs
127      */
128     static void writeString(final String value, final DataOutput out)
129         throws IOException
130     {
131         final byte[] bytes = value.getBytes(UTF_8);
132         writeInt(bytes.length, out);
133         out.write(bytes);
134     }
135 
136     /**
137      * Reads a string value from the given data input.
138      *
139      @param in the data input
140      @return the read string value
141      @throws NullPointerException if one of the given arguments is {@code null}
142      @throws IOException if an I/O error occurs
143      */
144     static String readString(final DataInput inthrows IOException {
145         final byte[] bytes = new byte[readInt(in)];
146         in.readFully(bytes);
147         return new String(bytes, UTF_8);
148     }
149 
150     /**
151      * Write the given string, possible {@code null}, {@code value} to the given
152      * data output.
153      *
154      @param value the string value to write
155      @param out the data output
156      @throws NullPointerException if the given data output is {@code null}
157      @throws IOException if an I/O error occurs
158      */
159     static void writeNullableString(final String value, final DataOutput out)
160         throws IOException
161     {
162         writeNullable(value, IO::writeString, out);
163     }
164 
165     /**
166      * Reads a, possible {@code null}, string value from the given data input.
167      *
168      @param in the data input
169      @return the read string value
170      @throws NullPointerException if one of the given arguments is {@code null}
171      @throws IOException if an I/O error occurs
172      */
173     static String readNullableString(final DataInput inthrows IOException {
174         return readNullable(IO::readString, in);
175     }
176 
177     /**
178      * Write the given elements to the data output.
179      *
180      @param elements the elements to write
181      @param writer the element writer
182      @param out the data output
183      @param <T> the element type
184      @throws NullPointerException if one of the given arguments is {@code null}
185      @throws IOException if an I/O error occurs
186      */
187     static <T> void writes(
188         final Collection<? extends T> elements,
189         final Writer<? super T> writer,
190         final DataOutput out
191     )
192         throws IOException
193     {
194         writeInt(elements.size(), out);
195         for (T element : elements) {
196             writer.write(element, out);
197         }
198     }
199 
200     /**
201      * Reads a list of elements from the given data input.
202      *
203      @param reader the element reader
204      @param in the data input
205      @param <T> the element type
206      @return the read element list
207      @throws NullPointerException if one of the given arguments is {@code null}
208      @throws IOException if an I/O error occurs
209      */
210     static <T> List<T> reads(
211         final Reader<? extends T> reader,
212         final DataInput in
213     )
214         throws IOException
215     {
216         final int length = readInt(in);
217         final List<T> elements = new ArrayList<>(length);
218         for (int i = 0; i < length; ++i) {
219             elements.add(reader.read(in));
220         }
221         return copyOf(elements);
222     }
223 
224     /**
225      * Writes an int value to a series of bytes. The values are written using
226      * <a href="http://lucene.apache.org/core/3_5_0/fileformats.html#VInt">variable-length</a>
227      * <a href="https://developers.google.com/protocol-buffers/docs/encoding?csw=1#types">zig-zag</a>
228      * coding. Each {@code int} value is written in 1 to 5 bytes.
229      *
230      @see #readInt(DataInput)
231      *
232      @param value the integer value to write
233      @param out the data output the integer value is written to
234      @throws NullPointerException if the given data output is {@code null}
235      @throws IOException if an I/O error occurs
236      */
237     static void writeInt(final int value, final DataOutput outthrows IOException {
238         // Zig-zag encoding.
239         int n = (value << 1)^(value >> 31);
240         if ((n & ~0x7F!= 0) {
241             out.write((byte)((n | 0x800xFF));
242             n >>>= 7;
243             if (n > 0x7F) {
244                 out.write((byte)((n | 0x800xFF));
245                 n >>>= 7;
246                 if (n > 0x7F) {
247                     out.write((byte)((n | 0x800xFF));
248                     n >>>= 7;
249                     if (n > 0x7F) {
250                         out.write((byte)((n | 0x800xFF));
251                         n >>>= 7;
252                     }
253                 }
254             }
255         }
256         out.write((byte)n);
257     }
258 
259     /**
260      * Reads an int value from the given data input. The integer value must have
261      * been written by the {@link #writeInt(int, DataOutput)} before.
262      *
263      @see #writeInt(int, DataOutput)
264      *
265      @param in the data input where the integer value is read from
266      @return the read integer value
267      @throws NullPointerException if the given data input is {@code null}
268      @throws IOException if an I/O error occurs
269      */
270     static int readInt(final DataInput inthrows IOException {
271         int b = in.readByte() 0xFF;
272         int n = b & 0x7F;
273 
274         if (b > 0x7F) {
275             b = in.readByte() 0xFF;
276             n ^= (b & 0x7F<< 7;
277             if (b > 0x7F) {
278                 b = in.readByte() 0xFF;
279                 n ^= (b & 0x7F<< 14;
280                 if (b > 0x7F) {
281                     b = in.readByte() 0xFF;
282                     n ^= (b & 0x7F<< 21;
283                     if (b > 0x7F) {
284                         b = in.readByte() 0xFF;
285                         n ^= (b & 0x7F<< 28;
286                         if (b > 0x7F) {
287                             throw new IOException("Invalid int encoding.");
288                         }
289                     }
290                 }
291             }
292         }
293 
294         return (n >>> 1)^-(n & 1);
295     }
296 
297     /**
298      * Writes a long value to a series of bytes. The values are written using
299      * <a href="http://lucene.apache.org/core/3_5_0/fileformats.html#VInt">variable-length</a>
300      * <a href="https://developers.google.com/protocol-buffers/docs/encoding?csw=1#types">zig-zag</a>
301      * coding. Each {@code long} value is written in 1 to 10 bytes.
302      *
303      @see #readLong(DataInput)
304      *
305      @param value the long value to write
306      @param out the data output the integer value is written to
307      @throws NullPointerException if the given data output is {@code null}
308      @throws IOException if an I/O error occurs
309      */
310     static void writeLong(final long value, final DataOutput out)
311         throws IOException
312     {
313         // Zig-zag encoding.
314         long n = (value << 1)^(value >> 63);
315         if ((n & ~0x7FL!= 0) {
316             out.write((byte)((n | 0x800xFF));
317             n >>>= 7;
318             if (n > 0x7F) {
319                 out.write((byte)((n | 0x800xFF));
320                 n >>>= 7;
321                 if (n > 0x7F) {
322                     out.write((byte)((n | 0x800xFF));
323                     n >>>= 7;
324                     if (n > 0x7F) {
325                         out.write((byte)((n | 0x800xFF));
326                         n >>>= 7;
327                         if (n > 0x7F) {
328                             out.write((byte)((n | 0x800xFF));
329                             n >>>= 7;
330                             if (n > 0x7F) {
331                                 out.write((byte)((n | 0x800xFF));
332                                 n >>>= 7;
333                                 if (n > 0x7F) {
334                                     out.write((byte)((n | 0x800xFF));
335                                     n >>>= 7;
336                                     if (n > 0x7F) {
337                                         out.write((byte)((n | 0x800xFF));
338                                         n >>>= 7;
339                                         if (n > 0x7F) {
340                                             out.write((byte)((n | 0x800xFF));
341                                             n >>>= 7;
342                                         }
343                                     }
344                                 }
345                             }
346                         }
347                     }
348                 }
349             }
350         }
351         out.write((byte)n);
352     }
353 
354     /**
355      * Reads a long value from the given data input. The integer value must have
356      * been written by the {@link #writeInt(int, DataOutput)} before.
357      *
358      @see #writeLong(long, DataOutput)
359      *
360      @param in the data input where the integer value is read from
361      @return the read long value
362      @throws NullPointerException if the given data input is {@code null}
363      @throws IOException if an I/O error occurs
364      */
365     static long readLong(final DataInput inthrows IOException {
366         int b = in.readByte() 0xFF;
367         int n = b & 0x7F;
368         long l;
369         if (b > 0x7F) {
370             b = in.readByte() 0xFF;
371             n ^= (b & 0x7F<< 7;
372             if (b > 0x7F) {
373                 b = in.readByte() 0xFF;
374                 n ^= (b & 0x7F<< 14;
375                 if (b > 0x7F) {
376                     b = in.readByte() 0xFF;
377                     n ^= (b & 0x7F<< 21;
378                     if (b > 0x7F) {
379                         l = innerLongDecode(n, in);
380                     else {
381                         l = n;
382                     }
383                 else {
384                     l = n;
385                 }
386             else {
387                 l = n;
388             }
389         else {
390             l = n;
391         }
392         return (l >>> 1)^-(l & 1);
393     }
394 
395     private static long innerLongDecode(long l, final DataInput in)
396         throws IOException
397     {
398         int b = in.readByte() 0xFF;
399         l ^= (b & 0x7FL<< 28;
400         if (b > 0x7F) {
401             b = in.readByte() 0xFF;
402             l ^= (b & 0x7FL<< 35;
403             if (b > 0x7F) {
404                 b = in.readByte() 0xFF;
405                 l ^= (b & 0x7FL<< 42;
406                 if (b > 0x7F) {
407                     b = in.readByte() 0xFF;
408                     l ^= (b & 0x7FL<< 49;
409                     if (b > 0x7F) {
410                         b = in.readByte() 0xFF;
411                         l ^= (b & 0x7FL<< 56;
412                         if (b > 0x7F) {
413                             b = in.readByte() 0xFF;
414                             l ^= (b & 0x7FL<< 63;
415                             if (b > 0x7F) {
416                                 throw new IOException("Invalid long encoding.");
417                             }
418                         }
419                     }
420                 }
421             }
422         }
423         return l;
424     }
425 
426     /**
427      * Write the given XML document to the given data output.
428      *
429      @since 1.5
430      *
431      @param value the value to write
432      @param out the data output the integer value is written to
433      @throws NullPointerException if one of the arguments is {@code null}
434      @throws IOException if an I/O error occurs
435      */
436     static void write(final Document value, final DataOutput out)
437         throws IOException
438     {
439         final ByteArrayOutputStream bout = new ByteArrayOutputStream();
440         XML.copy(value, bout);
441 
442         final byte[] data = bout.toByteArray();
443         IO.writeInt(data.length, out);
444         out.write(data);
445     }
446 
447     /**
448      * Reads a long value from the given data input. The integer value must have
449      * been written by the {@link #writeInt(int, DataOutput)} before.
450      *
451      @since 1.5
452      *
453      @param in the data input where the value is read from
454      @return the read XML document
455      @throws NullPointerException if the given data input is {@code null}
456      @throws IOException if an I/O error occurs
457      */
458     static Document readDoc(final DataInput inthrows IOException {
459         final int length = IO.readInt(in);
460         final byte[] data = new byte[length];
461         in.readFully(data);
462 
463         final ByteArrayInputStream bin = new ByteArrayInputStream(data);
464         try {
465             return XMLProvider
466                 .provider()
467                 .documentBuilderFactory()
468                 .newDocumentBuilder()
469                 .parse(bin);
470         catch (ParserConfigurationException|SAXException e) {
471             throw new IOException(e);
472         }
473     }
474 
475 }