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 out) throws 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 in) throws 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 in) throws 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 in) throws 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 out) throws IOException {
238 // Zig-zag encoding.
239 int n = (value << 1)^(value >> 31);
240 if ((n & ~0x7F) != 0) {
241 out.write((byte)((n | 0x80) & 0xFF));
242 n >>>= 7;
243 if (n > 0x7F) {
244 out.write((byte)((n | 0x80) & 0xFF));
245 n >>>= 7;
246 if (n > 0x7F) {
247 out.write((byte)((n | 0x80) & 0xFF));
248 n >>>= 7;
249 if (n > 0x7F) {
250 out.write((byte)((n | 0x80) & 0xFF));
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 in) throws 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 | 0x80) & 0xFF));
317 n >>>= 7;
318 if (n > 0x7F) {
319 out.write((byte)((n | 0x80) & 0xFF));
320 n >>>= 7;
321 if (n > 0x7F) {
322 out.write((byte)((n | 0x80) & 0xFF));
323 n >>>= 7;
324 if (n > 0x7F) {
325 out.write((byte)((n | 0x80) & 0xFF));
326 n >>>= 7;
327 if (n > 0x7F) {
328 out.write((byte)((n | 0x80) & 0xFF));
329 n >>>= 7;
330 if (n > 0x7F) {
331 out.write((byte)((n | 0x80) & 0xFF));
332 n >>>= 7;
333 if (n > 0x7F) {
334 out.write((byte)((n | 0x80) & 0xFF));
335 n >>>= 7;
336 if (n > 0x7F) {
337 out.write((byte)((n | 0x80) & 0xFF));
338 n >>>= 7;
339 if (n > 0x7F) {
340 out.write((byte)((n | 0x80) & 0xFF));
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 in) throws 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 in) throws 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 }
|