001/*
002 * Java Genetic Algorithm Library (jenetics-8.0.0).
003 * Copyright (c) 2007-2024 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 */
020package io.jenetics.internal.util;
021
022import static java.nio.charset.StandardCharsets.UTF_8;
023
024import java.io.DataInput;
025import java.io.DataOutput;
026import java.io.IOException;
027import java.io.ObjectInput;
028import java.io.ObjectOutput;
029import java.util.ArrayList;
030import java.util.Collection;
031import java.util.List;
032
033/**
034 * Helper methods needed for implementing the Java serializations.
035 *
036 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
037 * @version 5.0
038 * @since 5.0
039 */
040public final class SerialIO {
041
042        private SerialIO() {
043        }
044
045        /**
046         * Object writer interface.
047         *
048         * @param <T> the object type
049         */
050        @FunctionalInterface
051        public interface Writer<T> {
052                void write(final T value, final DataOutput out) throws IOException;
053        }
054
055        /**
056         * Object reader interface
057         *
058         * @param <T> the object type
059         */
060        @FunctionalInterface
061        public interface Reader<T> {
062                T read(final DataInput in) throws IOException;
063        }
064
065        /**
066         * Write the given, possible {@code null}, {@code value} to the data output
067         * using the given {@code writer}.
068         *
069         * @param value the, possible {@code null}, value to write
070         * @param writer the object writer
071         * @param out the data output
072         * @param <T> the object type
073         * @throws NullPointerException if the {@code writer} or data output is
074         *         {@code null}
075         * @throws IOException if an I/O error occurs
076         */
077        public static <T> void writeNullable(
078                final T value,
079                final Writer<? super T> writer,
080                final DataOutput out
081        )
082                throws IOException
083        {
084                out.writeBoolean(value != null);
085                if (value != null) {
086                        writer.write(value, out);
087                }
088        }
089
090        /**
091         * Reads a possible {@code null} value from the given data input.
092         *
093         * @param reader the object reader
094         * @param in the data input
095         * @param <T> the object type
096         * @return the read object
097         * @throws NullPointerException if one of the given arguments is {@code null}
098         * @throws IOException if an I/O error occurs
099         */
100        public static <T> T readNullable(
101                final Reader<? extends T> reader,
102                final DataInput in
103        )
104                throws IOException
105        {
106                T value = null;
107                if (in.readBoolean()) {
108                        value = reader.read(in);
109                }
110
111                return value;
112        }
113
114        /**
115         * Write the given string {@code value} to the given data output.
116         *
117         * @param value the string value to write
118         * @param out the data output
119         * @throws NullPointerException if one of the given arguments is {@code null}
120         * @throws IOException if an I/O error occurs
121         */
122        public static void writeString(final String value, final DataOutput out)
123                throws IOException
124        {
125                final byte[] bytes = value.getBytes(UTF_8);
126                writeInt(bytes.length, out);
127                out.write(bytes);
128        }
129
130        /**
131         * Reads a string value from the given data input.
132         *
133         * @param in the data input
134         * @return the read string value
135         * @throws NullPointerException if one of the given arguments is {@code null}
136         * @throws IOException if an I/O error occurs
137         */
138        public static String readString(final DataInput in) throws IOException {
139                final byte[] bytes = new byte[readInt(in)];
140                in.readFully(bytes);
141                return new String(bytes, UTF_8);
142        }
143
144        /**
145         * Write the given string, possible {@code null}, {@code value} to the given
146         * data output.
147         *
148         * @param value the string value to write
149         * @param out the data output
150         * @throws NullPointerException if the given data output is {@code null}
151         * @throws IOException if an I/O error occurs
152         */
153        public static void writeNullableString(final String value, final DataOutput out)
154                throws IOException
155        {
156                writeNullable(value, SerialIO::writeString, out);
157        }
158
159        /**
160         * Reads a, possible {@code null}, string value from the given data input.
161         *
162         * @param in the data input
163         * @return the read string value
164         * @throws NullPointerException if one of the given arguments is {@code null}
165         * @throws IOException if an I/O error occurs
166         */
167        public static String readNullableString(final DataInput in) throws IOException {
168                return readNullable(SerialIO::readString, in);
169        }
170
171        /**
172         * Write the given elements to the data output.
173         *
174         * @param elements the elements to write
175         * @param writer the element writer
176         * @param out the data output
177         * @param <T> the element type
178         * @throws NullPointerException if one of the given arguments is {@code null}
179         * @throws IOException if an I/O error occurs
180         */
181        public static <T> void writes(
182                final Collection<? extends T> elements,
183                final Writer<? super T> writer,
184                final DataOutput out
185        )
186                throws IOException
187        {
188                writeInt(elements.size(), out);
189                for (T element : elements) {
190                        writer.write(element, out);
191                }
192        }
193
194        /**
195         * Reads a list of elements from the given data input.
196         *
197         * @param reader the element reader
198         * @param in the data input
199         * @param <T> the element type
200         * @return the read element list
201         * @throws NullPointerException if one of the given arguments is {@code null}
202         * @throws IOException if an I/O error occurs
203         */
204        public static <T> List<T> reads(
205                final Reader<? extends T> reader,
206                final DataInput in
207        )
208                throws IOException
209        {
210                final int length = readInt(in);
211                final List<T> elements = new ArrayList<>(length);
212                for (int i = 0; i < length; ++i) {
213                        elements.add(reader.read(in));
214                }
215                return elements;
216        }
217
218
219        public static void writeBytes(final byte[] bytes, final DataOutput out)
220                throws IOException
221        {
222                writeInt(bytes.length, out);
223                out.write(bytes);
224        }
225
226        public static byte[] readBytes(final DataInput in) throws IOException {
227                final int length = readInt(in);
228                final byte[] bytes = new byte[length];
229                in.readFully(bytes);
230                return bytes;
231        }
232
233        /**
234         * Writes an int value to a series of bytes. The values are written using
235         * <a href="http://lucene.apache.org/core/3_5_0/fileformats.html#VInt">variable-length</a>
236         * <a href="https://developers.google.com/protocol-buffers/docs/encoding?csw=1#types">zig-zag</a>
237         * coding. Each {@code int} value is written in 1 to 5 bytes.
238         *
239         * @see #readInt(DataInput)
240         *
241         * @param value the integer value to write
242         * @param out the data output the integer value is written to
243         * @throws NullPointerException if the given data output is {@code null}
244         * @throws IOException if an I/O error occurs
245         */
246        public static void writeInt(final int value, final DataOutput out)
247                throws IOException
248        {
249                // Zig-zag encoding.
250                int n = (value << 1)^(value >> 31);
251                if ((n & ~0x7F) != 0) {
252                        out.write((byte)((n | 0x80) & 0xFF));
253                        n >>>= 7;
254                        if (n > 0x7F) {
255                                out.write((byte)((n | 0x80) & 0xFF));
256                                n >>>= 7;
257                                if (n > 0x7F) {
258                                        out.write((byte)((n | 0x80) & 0xFF));
259                                        n >>>= 7;
260                                        if (n > 0x7F) {
261                                                out.write((byte)((n | 0x80) & 0xFF));
262                                                n >>>= 7;
263                                        }
264                                }
265                        }
266                }
267                out.write((byte)n);
268        }
269
270        /**
271         * Reads an int value from the given data input. The integer value must have
272         * been written by the {@link #writeInt(int, DataOutput)} before.
273         *
274         * @see #writeInt(int, DataOutput)
275         *
276         * @param in the data input where the integer value is read from
277         * @return the read integer value
278         * @throws NullPointerException if the given data input is {@code null}
279         * @throws IOException if an I/O error occurs
280         */
281        public static int readInt(final DataInput in) throws IOException {
282                int b = in.readByte() & 0xFF;
283                int n = b & 0x7F;
284
285                if (b > 0x7F) {
286                        b = in.readByte() & 0xFF;
287                        n ^= (b & 0x7F) << 7;
288                        if (b > 0x7F) {
289                                b = in.readByte() & 0xFF;
290                                n ^= (b & 0x7F) << 14;
291                                if (b > 0x7F) {
292                                        b = in.readByte() & 0xFF;
293                                        n ^= (b & 0x7F) << 21;
294                                        if (b > 0x7F) {
295                                                b = in.readByte() & 0xFF;
296                                                n ^= (b & 0x7F) << 28;
297                                                if (b > 0x7F) {
298                                                        throw new IOException("Invalid int encoding.");
299                                                }
300                                        }
301                                }
302                        }
303                }
304
305                return (n >>> 1)^-(n & 1);
306        }
307
308        /**
309         * Writes a long value to a series of bytes. The values are written using
310         * <a href="http://lucene.apache.org/core/3_5_0/fileformats.html#VInt">variable-length</a>
311         * <a href="https://developers.google.com/protocol-buffers/docs/encoding?csw=1#types">zig-zag</a>
312         * coding. Each {@code long} value is written in 1 to 10 bytes.
313         *
314         * @see #readLong(DataInput)
315         *
316         * @param value the long value to write
317         * @param out the data output the integer value is written to
318         * @throws NullPointerException if the given data output is {@code null}
319         * @throws IOException if an I/O error occurs
320         */
321        public static void writeLong(final long value, final DataOutput out)
322                throws IOException
323        {
324                // Zig-zag encoding.
325                long n = (value << 1)^(value >> 63);
326                if ((n & ~0x7FL) != 0) {
327                        out.write((byte)((n | 0x80) & 0xFF));
328                        n >>>= 7;
329                        if (n > 0x7F) {
330                                out.write((byte)((n | 0x80) & 0xFF));
331                                n >>>= 7;
332                                if (n > 0x7F) {
333                                        out.write((byte)((n | 0x80) & 0xFF));
334                                        n >>>= 7;
335                                        if (n > 0x7F) {
336                                                out.write((byte)((n | 0x80) & 0xFF));
337                                                n >>>= 7;
338                                                if (n > 0x7F) {
339                                                        out.write((byte)((n | 0x80) & 0xFF));
340                                                        n >>>= 7;
341                                                        if (n > 0x7F) {
342                                                                out.write((byte)((n | 0x80) & 0xFF));
343                                                                n >>>= 7;
344                                                                if (n > 0x7F) {
345                                                                        out.write((byte)((n | 0x80) & 0xFF));
346                                                                        n >>>= 7;
347                                                                        if (n > 0x7F) {
348                                                                                out.write((byte)((n | 0x80) & 0xFF));
349                                                                                n >>>= 7;
350                                                                                if (n > 0x7F) {
351                                                                                        out.write((byte)((n | 0x80) & 0xFF));
352                                                                                        n >>>= 7;
353                                                                                }
354                                                                        }
355                                                                }
356                                                        }
357                                                }
358                                        }
359                                }
360                        }
361                }
362                out.write((byte)n);
363        }
364
365        /**
366         * Reads a long value from the given data input. The integer value must have
367         * been written by the {@link #writeInt(int, DataOutput)} before.
368         *
369         * @see #writeLong(long, DataOutput)
370         *
371         * @param in the data input where the integer value is read from
372         * @return the read long value
373         * @throws NullPointerException if the given data input is {@code null}
374         * @throws IOException if an I/O error occurs
375         */
376        public static long readLong(final DataInput in) throws IOException {
377                int b = in.readByte() & 0xFF;
378                int n = b & 0x7F;
379                long l;
380                if (b > 0x7F) {
381                        b = in.readByte() & 0xFF;
382                        n ^= (b & 0x7F) << 7;
383                        if (b > 0x7F) {
384                                b = in.readByte() & 0xFF;
385                                n ^= (b & 0x7F) << 14;
386                                if (b > 0x7F) {
387                                        b = in.readByte() & 0xFF;
388                                        n ^= (b & 0x7F) << 21;
389                                        l = b > 0x7F ? innerLongDecode(n, in) : n;
390                                } else {
391                                        l = n;
392                                }
393                        } else {
394                                l = n;
395                        }
396                } else {
397                        l = n;
398                }
399                return (l >>> 1)^-(l & 1);
400        }
401
402        private static long innerLongDecode(long l, final DataInput in)
403                throws IOException
404        {
405                int b = in.readByte() & 0xFF;
406                l ^= (b & 0x7FL) << 28;
407                if (b > 0x7F) {
408                        b = in.readByte() & 0xFF;
409                        l ^= (b & 0x7FL) << 35;
410                        if (b > 0x7F) {
411                                b = in.readByte() & 0xFF;
412                                l ^= (b & 0x7FL) << 42;
413                                if (b > 0x7F) {
414                                        b = in.readByte() & 0xFF;
415                                        l ^= (b & 0x7FL) << 49;
416                                        if (b > 0x7F) {
417                                                b = in.readByte() & 0xFF;
418                                                l ^= (b & 0x7FL) << 56;
419                                                if (b > 0x7F) {
420                                                        b = in.readByte() & 0xFF;
421                                                        l ^= (b & 0x7FL) << 63;
422                                                        if (b > 0x7F) {
423                                                                throw new IOException("Invalid long encoding.");
424                                                        }
425                                                }
426                                        }
427                                }
428                        }
429                }
430                return l;
431        }
432
433        /**
434         * Write the given {@code int[]} array to the given data output.
435         *
436         * @param values the values to write
437         * @param out the data sink
438         * @throws IOException if an I/O error occurs
439         */
440        public static void writeIntArray(final int[] values, final DataOutput out)
441                throws IOException
442        {
443                writeInt(values.length, out);
444                for (int value : values) {
445                        writeInt(value, out);
446                }
447        }
448
449        /**
450         * Write the given {@code char[]} array to the given data output.
451         *
452         * @param values the values to write
453         * @param out the data sink
454         * @throws IOException if an I/O error occurs
455         */
456        public static void writeCharArray(final char[] values, final DataOutput out)
457                throws IOException
458        {
459                writeInt(values.length, out);
460                for (int value : values) {
461                        writeInt(value, out);
462                }
463        }
464
465        /**
466         * Write the given {@code long[]} array to the given data output.
467         *
468         * @param values the values to write
469         * @param out the data sink
470         * @throws IOException if an I/O error occurs
471         */
472        public static void writeLongArray(final long[] values, final DataOutput out)
473                throws IOException
474        {
475                writeInt(values.length, out);
476                for (long value : values) {
477                        writeLong(value, out);
478                }
479        }
480
481        /**
482         * Write the given {@code double[]} array to the given data output.
483         *
484         * @param values the values to write
485         * @param out the data sink
486         * @throws IOException if an I/O error occurs
487         */
488        public static void writeDoubleArray(final double[] values, final DataOutput out)
489                throws IOException
490        {
491                writeInt(values.length, out);
492                for (double value : values) {
493                        out.writeDouble(value);
494                }
495        }
496
497        /**
498         * Read an {@code char[]} array from the data input.
499         *
500         * @param in the data source
501         * @return the read values
502         * @throws IOException if an I/O error occurs
503         */
504        public static char[] readCharArray(final DataInput in) throws IOException {
505                final char[] values = new char[readInt(in)];
506                for (int i = 0; i < values.length; ++i) {
507                        values[i] = (char)readInt(in);
508                }
509                return values;
510        }
511
512        /**
513         * Read an {@code int[]} array from the data input.
514         *
515         * @param in the data source
516         * @return the read values
517         * @throws IOException if an I/O error occurs
518         */
519        public static int[] readIntArray(final DataInput in) throws IOException {
520                final int[] values = new int[readInt(in)];
521                for (int i = 0; i < values.length; ++i) {
522                        values[i] = readInt(in);
523                }
524                return values;
525        }
526
527        /**
528         * Read a {@code long[]} array from the data input.
529         *
530         * @param in the data source
531         * @return the read values
532         * @throws IOException if an I/O error occurs
533         */
534        public static long[] readLongArray(final DataInput in) throws IOException {
535                final long[] values = new long[readInt(in)];
536                for (int i = 0; i < values.length; ++i) {
537                        values[i] = readLong(in);
538                }
539                return values;
540        }
541
542        /**
543         * Read a {@code double[]} array from the data input.
544         *
545         * @param in the data source
546         * @return the read values
547         * @throws IOException if an I/O error occurs
548         */
549        public static double[] readDoubleArray(final DataInput in) throws IOException {
550                final double[] values = new double[readInt(in)];
551                for (int i = 0; i < values.length; ++i) {
552                        values[i] = in.readDouble();
553                }
554                return values;
555        }
556
557        /**
558         * Write the given {@code Object[]} array to the given data output.
559         *
560         * @param values the values to write
561         * @param out the data sink
562         * @throws IOException if an I/O error occurs
563         */
564        public static void writeObjectArray(final Object[] values, final ObjectOutput out)
565                throws IOException
566        {
567                writeInt(values.length, out);
568                for (Object value : values) {
569                        out.writeObject(value);
570                }
571        }
572
573        /**
574         * Read an {@code Object[]} array from the data input.
575         *
576         * @param in the data source
577         * @return the read values
578         * @throws IOException if an I/O error occurs
579         * @throws ClassNotFoundException if the class can't be loaded
580         */
581        public static Object[] readObjectArray(final ObjectInput in)
582                throws IOException, ClassNotFoundException
583        {
584                final Object[] values = new Object[readInt(in)];
585                for (int i = 0; i < values.length; ++i) {
586                        values[i] = in.readObject();
587                }
588                return values;
589        }
590
591}