View Javadoc
1   /*
2    * Copyright (C) 2012 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.geometry.io;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.io.RandomAccessFile;
21  import java.nio.MappedByteBuffer;
22  import java.nio.channels.FileChannel;
23  
24  /**
25   * This class provides methods to access file data at random positions using
26   * memory mapping for faster data access.
27   */
28  @SuppressWarnings("DuplicatedCode")
29  public class MappedFileReaderAndWriter extends AbstractFileReaderAndWriter {
30  
31      /**
32       * Underlying input file.
33       */
34      private final RandomAccessFile randomAccessFile;
35  
36      /**
37       * Buffer where data is stored.
38       */
39      private final MappedByteBuffer buffer;
40  
41      /**
42       * Internal value indicating if read has been produced.
43       */
44      private boolean read;
45  
46      /**
47       * Constructor.
48       *
49       * @param f    file to read from or write to.
50       * @param mode file opening mode (read only or read write).
51       * @throws IOException if an I/O error occurs .
52       */
53      public MappedFileReaderAndWriter(final File f, final FileChannel.MapMode mode) throws IOException {
54          if (mode == FileChannel.MapMode.READ_ONLY) {
55              this.randomAccessFile = new RandomAccessFile(f, "r");
56          } else {
57              this.randomAccessFile = new RandomAccessFile(f, "rw");
58          }
59          this.buffer = randomAccessFile.getChannel().map(mode, 0, randomAccessFile.length());
60          this.buffer.load();
61      }
62  
63      /**
64       * Reads one byte at current file position and advances one position.
65       *
66       * @return Next byte of data or -1 if end of file is reached.
67       * @throws IOException if an I/O error occurs. Not thrown if end-of-file has
68       *                     been reached.
69       */
70      @Override
71      public int read() throws IOException {
72          if (isEndOfStream()) {
73              return -1;
74          }
75          int value = buffer.get();
76          value = ((value << 8) >> 8) & 0xff;
77          read = true;
78          return value;
79      }
80  
81      /**
82       * Reads up to b.length bytes of data from this file into an array of bytes.
83       * This method blocks until at least one byte of input is available.
84       *
85       * @param b The buffer into which the data is read.
86       * @return The total number of bytes read into the buffer, or -1 if there is
87       * no more data because the end of the file has been reached.
88       * @throws IOException If the first byte cannot be read for any reason other
89       *                     than end of file, or if the file has been closed, or if some other I/O
90       *                     error occurs.
91       */
92      @Override
93      public int read(final byte[] b) throws IOException {
94          if (isEndOfStream()) {
95              return -1;
96          }
97          final var length = Math.min(buffer.remaining(), b.length);
98          if (length > 0) {
99              buffer.get(b, 0, length);
100         }
101         read = true;
102         return length;
103     }
104 
105     /**
106      * Reads up to len bytes of data from this file into an array of bytes. This
107      * method blocks until at least one byte of input is available.
108      * This method behaves in exactly the same way as the
109      * InputStream.read(byte[], int, int) method of InputStream.
110      *
111      * @param b   the buffer into which the data is read.
112      * @param off the start offset in array b at which the data is written.
113      * @param len the maximum number of bytes read.
114      * @return the total number of bytes read into the buffer, or -1 if there is
115      * no more data because the end of the file has been reached.
116      * @throws IOException If the first byte cannot be read for any reason other
117      *                     than end of file, or if the random access file has been closed, or if
118      *                     some other I/O error occurs.
119      */
120     @Override
121     public int read(final byte[] b, final int off, final int len) throws IOException {
122         if (isEndOfStream()) {
123             return -1;
124         }
125         final var length = Math.min(buffer.remaining(), len);
126         if (length > 0) {
127             buffer.get(b, off, length);
128         }
129         read = true;
130         return length;
131     }
132 
133     /**
134      * Attempts to skip over n byte of input discarding the skipped bytes.
135      * <p>
136      * This method may skip over some number of bytes, possibly zero. This may
137      * result from any of a number of conditions; reaching end of file before n
138      * bytes have been skipped is only one possibility. The actual number of
139      * bytes skipped is returned. If n is negative, no bytes are skipped.
140      *
141      * @param n the number of bytes to be skipped.
142      * @return the actual number of bytes skipped.
143      */
144     @Override
145     public long skip(final long n) {
146         if (n < 0) {
147             return 0;
148         }
149 
150         final var skipped = Math.min(buffer.remaining(), (int) n);
151         final var newPos = buffer.position() + skipped;
152         buffer.position(newPos);
153         read = true;
154         return skipped;
155     }
156 
157     /**
158      * Writes the specified byte to this file. The write starts at the current
159      * file pointer.
160      *
161      * @param b the byte to be written.
162      * @throws IOException if an I/O error occurs.
163      */
164     @Override
165     public void write(final int b) throws IOException {
166         randomAccessFile.write(b);
167         read = false;
168     }
169 
170     /**
171      * Writes b.length bytes from the specified byte array to this file,
172      * starting at the current file pointer.
173      *
174      * @param b the data.
175      * @throws IOException if an I/O error occurs.
176      */
177     @Override
178     public void write(final byte[] b) throws IOException {
179         randomAccessFile.write(b);
180         read = false;
181     }
182 
183     /**
184      * Writes len bytes from the specified byte array starting at offset off to
185      * this file.
186      *
187      * @param b   the data.
188      * @param off the start offset in the data.
189      * @param len the number of bytes to write.
190      * @throws IOException if an I/O error occurs.
191      */
192     @Override
193     public void write(final byte[] b, final int off, final int len) throws IOException {
194         randomAccessFile.write(b, off, len);
195         read = false;
196     }
197 
198     /**
199      * Returns the current offset in this file.
200      *
201      * @return the offset from the beginning of the file, in bytes, at which the
202      * next read or write occurs.
203      * @throws IOException in an I/O error occurs.
204      */
205     @Override
206     public long getPosition() throws IOException {
207         if (read) {
208             return buffer.position();
209         } else {
210             return randomAccessFile.getFilePointer();
211         }
212     }
213 
214     /**
215      * Determines whether end of file has been reached (next read() will return
216      * -1). or not.
217      *
218      * @return True if end of file has been reached, false otherwise.
219      * @throws IOException if an I/O error occurs..
220      */
221     @Override
222     public boolean isEndOfStream() throws IOException {
223         return getPosition() >= randomAccessFile.length();
224     }
225 
226     /**
227      * Sets the file-pointer offset, measured from the beginning of this file,
228      * at which the next read or write occurs. Setting the offset beyond the end
229      * of the file will raise an exception.
230      *
231      * @param pos the offset position, measured in bytes from the beginning of
232      *            the file, at which to set the file pointer.
233      */
234     @Override
235     public void seek(final long pos) {
236         buffer.position((int) pos);
237         read = true;
238     }
239 
240     /**
241      * Closes this file stream and releases any system resources associated with
242      * the stream. A closed file cannot perform input or output operations and
243      * cannot be reopened.
244      * If this file has an associated channel then the channel is closed as
245      * well.
246      *
247      * @throws IOException if an I/O error occurs.
248      */
249     @Override
250     public void close() throws IOException {
251         buffer.force();
252         randomAccessFile.close();
253     }
254 
255     /**
256      * Reads a boolean from this file. This method reads a single byte from the
257      * file, starting at the current file pointer. A value of 0 represents
258      * false. Any other value represents true. This method blocks until the byte
259      * is read, the end of the stream is detected, or an exception is thrown.
260      *
261      * @return the boolean value read.
262      */
263     @Override
264     public boolean readBoolean() {
265         read = true;
266         return buffer.get() != 0;
267     }
268 
269     /**
270      * Reads a signed eight-bit value from this file. This method reads a byte
271      * from the file, starting from the current file pointer. If the byte read
272      * is b, where 0 &lt;= b &lt;= 255, then the result is: (byte)(b)
273      * This method blocks until the byte is read, the end of the stream is
274      * detected, or an exception is thrown.
275      *
276      * @return the next byte of this file is a signed eight-bit byte.
277      */
278     @Override
279     public byte readByte() {
280         read = true;
281         return buffer.get();
282     }
283 
284     /**
285      * Reads an unsigned eight-bit number from this file. This method reads a
286      * byte from this file, starting at the current file pointer, and returns
287      * that byte.
288      * This method blocks until the byte is read, the end of the stream is
289      * detected, or an exception is thrown.
290      *
291      * @return the next byte of this file, interpreted as an unsigned eight-bit
292      * number.
293      */
294     @Override
295     public short readUnsignedByte() {
296         read = true;
297         return (short) (((buffer.get() << 8) >> 8) & 0xff);
298     }
299 
300     /**
301      * Reads a signed 16-bit number from this file. The method reads two byte
302      * from this file, starting at the current file pointer. If the two bytes
303      * read, in order, are b1 and b2, where each of the two values is between 0
304      * and 255, inclusive, then the result is equal to: (short)(b1 &lt;&lt; 8 |
305      * b2).
306      * This method blocks until the two bytes are read, the end of the stream is
307      * detected, or an exception is thrown.
308      *
309      * @return the next two bytes of this file, interpreted as a signed 16-bit
310      * number.
311      */
312     @Override
313     public short readShort() {
314         read = true;
315         return buffer.getShort();
316     }
317 
318     /**
319      * Reads a signed 16-bit number from this file assuming that file is encoded
320      * using provided endian type. If endian type is big endian type, then
321      * natural binary order is preserved, otherwise byte order is reversed.
322      * This method blocks until the two bytes of the 16-bit number are read, the
323      * end of the stream is detected, or an exception is thrown.
324      *
325      * @param endianType Endian type. Big endian preserves natural binary order,
326      *                   little endian reverses byte order.
327      * @return the next two bytes of this file, interpreted as a signed 16-bit
328      * number encoded in provided endian type.
329      */
330     @Override
331     public short readShort(final EndianType endianType) {
332         read = true;
333         return Util.fromEndianType(endianType, buffer.getShort());
334     }
335 
336     /**
337      * Reads an unsigned 16-bit number from this file. This method reads two
338      * bytes from this file, starting at the current file pointer. If the bytes
339      * read, in order, are b1 and b2, where 0 &lt;= b1, b2 &lt;= 255, then the
340      * result is equal to: (b1 &lt;&lt; 8) | b2
341      * This method blocks until the two bytes are read, the end of the stream is
342      * detected, or an exception is thrown.
343      *
344      * @return the next two bytes of this file, interpreted as an unsigned
345      * 16-bit integer.
346      */
347     @Override
348     public int readUnsignedShort() {
349         read = true;
350         final var value = buffer.getShort();
351 
352         // convert value to byte array
353         final var firstShortByte = 0xff & (value >> 8);
354         final var secondShortByte = 0xff & ((value << 8) >> 8);
355 
356         // return it as integer
357         return 0xffff & ((firstShortByte << 8) | secondShortByte);
358     }
359 
360     /**
361      * Reads an unsigned 16-bit number from this file. This method reads two
362      * bytes from this file, starting at the current file pointer and using
363      * provided endian type. If endian type is big endian, then natural binary
364      * order is preserved, otherwise byte order is reversed.
365      * This method blocks until the two bytes are read, the end of the stream is
366      * detected, or an exception is thrown.
367      *
368      * @param endianType Endian type. Big endian preserves natural binary order,
369      *                   little endian reverses byte order.
370      * @return the next two bytes of this file, interpreted as an unsigned
371      * 16-bit integer.
372      */
373     @Override
374     public int readUnsignedShort(final EndianType endianType) {
375         read = true;
376         final var streamValue = buffer.getShort();
377         final var value = Util.fromEndianType(endianType, streamValue);
378 
379         // convert value to byte array
380         final var secondShortByte = 0xff & ((value << 8) >> 8);
381         final var firstShortByte = 0xff & (value >> 8);
382 
383         // return it as integer
384         return 0xffff & ((firstShortByte << 8) | secondShortByte);
385     }
386 
387     /**
388      * Reads a signed 32-bit integer from this file. This method reads 4 bytes
389      * from the file, starting at the current file pointer. If the bytes read,
390      * in order, are b1, b2, b3, and b4, where 0 &lt;= b1, b3, b4 &lt;= 255, then
391      * the result is equal to: (b1 &lt;&lt; 24) | (b2 &lt;&lt; 16) + (b3 &lt;&lt; 8) +
392      * b4.
393      * This method blocks until the four bytes are read, the end of the stream
394      * is detected, or an exception is thrown.
395      *
396      * @return the next four bytes of this file, interpreted as an int.
397      */
398     @Override
399     public int readInt() {
400         read = true;
401         return buffer.getInt();
402     }
403 
404     /**
405      * Reads a signed 32-bit integer from this file. This method reads 4 bytes
406      * from the file, starting at the current file pointer and using provided
407      * endian type. If endian type is big endian, then natural binary order is
408      * preserved, otherwise byte order is reversed.
409      * This method blocks until the four bytes are read, the end of the stream
410      * is detected, or an exception is thrown.
411      *
412      * @param endianType Endian type. Big endian preserves natural binary order,
413      *                   little endian reverses byte order.
414      * @return the next four bytes of this file, interpreted as an int.
415      */
416     @Override
417     public int readInt(final EndianType endianType) {
418         read = true;
419         return Util.fromEndianType(endianType, buffer.getInt());
420     }
421 
422     /**
423      * Reads an unsigned 32-bit integer from this file. This method reads 4
424      * bytes from the file, starting at the current file pointer. If the bytes
425      * read, in order, are b1, b2, b3, and b4, where 0 &lt;= b1, b3, b4 &lt;= 255,
426      * then the result is equal to: (b1 &lt;&lt; 24) | (b2 &lt;&lt; 16) +
427      * (b3 &lt;&lt; 8) + b4.
428      * This method blocks until the four bytes are read, the end of the stream
429      * is detected, or an exception is thrown.
430      *
431      * @return the next four bytes of this file, interpreted as a long.
432      */
433     @Override
434     public long readUnsignedInt() {
435         read = true;
436         final var value = buffer.getInt();
437 
438         // convert value to byte array
439         final var firstIntByte = 0xff & (value >> 24);
440         final var secondIntByte = 0xff & ((value << 8) >> 24);
441         final var thirdIntByte = 0xff & ((value << 16) >> 24);
442         final var fourthIntByte = 0xff & ((value << 24) >> 24);
443 
444         // return it as integer
445         return ((long) firstIntByte << 24) | (secondIntByte << 16) | (thirdIntByte << 8) | fourthIntByte;
446     }
447 
448     /**
449      * Reads an unsigned 32-bit integer from this file. This method reads 4
450      * bytes from the file, starting at the current file pointer and using
451      * provided endian type. If endian type is big endian, then natural binary
452      * order is preserved, otherwise byte order is reversed.
453      * This method blocks until the four bytes are read, the end of the stream
454      * is detected, or an exception is thrown.
455      *
456      * @param endianType Endian type. Big endian preserves natural binary order,
457      *                   little endian reverses byte order.
458      * @return the next four bytes of this file, interpreted as an int.
459      */
460     @Override
461     public long readUnsignedInt(final EndianType endianType) {
462         read = true;
463         final var streamValue = buffer.getInt();
464         final var value = Util.fromEndianType(endianType, streamValue);
465 
466         // convert value to byte array
467         final var firstIntByte = 0xff & (value >> 24);
468         final var secondIntByte = 0xff & ((value << 8) >> 24);
469         final var thirdIntByte = 0xff & ((value << 16) >> 24);
470         final var fourthIntByte = 0xff & ((value << 24) >> 24);
471 
472         // return it as integer
473         return ((long) firstIntByte << 24) | (secondIntByte << 16) | (thirdIntByte << 8) | fourthIntByte;
474     }
475 
476     /**
477      * Reads a signed 64-bit integer from this file. This method reads eight
478      * bytes from the file, starting at the current file pointer. If the bytes
479      * read, in order, are b1, b2, b3, b4, b5, b6, b7, and b8, where:
480      * 0 &lt;= b1, b2, b3, b4. b5. b6. b7. b8 &lt;= 255,
481      * then the result is equal to:
482      * ((long)b1 &lt;&lt; 56) + ((long)b2 &lt;&lt; 48)
483      * + ((long)b3 &lt;&lt; 40) + ((long)b4 &lt;&lt; 32)
484      * + ((long)b5 &lt;&lt; 24) + ((long)b6 &lt;&lt; 16)
485      * + ((long)b7 &lt;&lt; 8) + b8
486      * This method blocks until the eight bytes are read, the end of the stream
487      * is detected, or an exception is thrown.
488      *
489      * @return the next eight bytes of this file, interpreted as a long
490      */
491     @Override
492     public long readLong() {
493         read = true;
494         return buffer.getLong();
495     }
496 
497     /**
498      * Reads a signed 64-bit integer from this file. This method reads eight
499      * bytes from the file, starting at the current file pointer and using
500      * provided endian type. If endian type is big endian, then natural binary
501      * order is preserved, otherwise byte order is reversed.
502      * This method blocks until the eight bytes are read, the end of the stream
503      * is detected, or an exception is thrown.
504      *
505      * @param endianType Endian type. Big endian preserves natural binary order,
506      *                   little endian reverses byte order.
507      * @return the next eight bytes of this file, interpreted as a long
508      */
509     @Override
510     public long readLong(final EndianType endianType) {
511         read = true;
512         return Util.fromEndianType(endianType, buffer.getLong());
513     }
514 
515     /**
516      * Reads a float from this file. This method reads an int value, starting at
517      * the current file pointer, as if by the readInt method and then converts
518      * that in to a float using the intBitsToFloat method in class Float.
519      * This method blocks until the four bytes are read, the end of the stream
520      * is detected, or an exception is thrown.
521      *
522      * @return the next four bytes of this file, interpreted as a float.
523      */
524     @Override
525     public float readFloat() {
526         read = true;
527         return buffer.getFloat();
528     }
529 
530     /**
531      * Reads a float from this file. This method reads four bytes using
532      * provided endian type. If endian type is big endian, then natural binary
533      * order is preserved, otherwise byte order is reversed.
534      * This method blocks until the four bytes are read, the end of the stream
535      * is detected, or an exception is thrown.
536      *
537      * @param endianType Endian type. Big endian preserves natural binary order,
538      *                   little endian reverses byte order.
539      * @return the next four bytes of this file, interpreted as a float.
540      */
541     @Override
542     public float readFloat(final EndianType endianType) {
543         read = true;
544         return Util.fromEndianType(endianType, buffer.getFloat());
545     }
546 
547     /**
548      * Reads a double from this file. This method reads a long value, starting
549      * at the current file pointer, as if by the readLong method and then
550      * converts that long to a double using the longBitsToDouble method in class
551      * Double.
552      * This method blocks until the eight bytes are read, the end of the stream
553      * is detected, or an exception is thrown.
554      *
555      * @return the next eight bytes of this file, interpreted as a double.
556      */
557     @Override
558     public double readDouble() {
559         read = true;
560         return buffer.getDouble();
561     }
562 
563     /**
564      * Reads a double from this file. This method reads eight bytes using
565      * provided endian type. If endian type is big endian, then natural binary
566      * order is preserved, otherwise byte order is reversed.
567      * This method blocks until the eight bytes are read, the end of the stream
568      * is detected, or an exception is thrown.
569      *
570      * @param endianType Endian type. Big endian preserves natural binary order,
571      *                   little endian reverses byte order.
572      * @return the next eight bytes of this file, interpreted as a double.
573      */
574     @Override
575     public double readDouble(final EndianType endianType) {
576         read = true;
577         return Util.fromEndianType(endianType, buffer.getDouble());
578     }
579 
580     /**
581      * Reads the next line of text from this file. This method successively
582      * reads bytes from the file, starting at the current file pointer, until it
583      * reaches a line terminator of the end of the file. Each byte is converted
584      * into a character by taking the byte's value for the lower eight bits of
585      * the character and setting the high eight bits of the character to zero.
586      * This method does not, therefore, support the full Unicode character set.
587      * A line of text is terminated by a carriage-return character ('\r'), a
588      * newline character('\n'), a carriage-return character immediately followed
589      * by a newline character, or the end of the file. Line-terminating
590      * characters are discarded and are not included as part of the string
591      * returned.
592      * This method blocks until a newline character is read, a carriage return
593      * and the byte following it are read (to see if it is a newline), the end
594      * of the file is reached, or an exception is thrown.
595      *
596      * @return the next line of text from this file, or null if end of file is
597      * encountered before even one byte is read.
598      */
599     @Override
600     public String readLine() {
601         read = true;
602         if (buffer.hasRemaining()) {
603             return readUntilAnyOfTheseCharactersIsFound("\n");
604         } else {
605             return null;
606         }
607     }
608 
609     /**
610      * Sequentially reads characters starting at current file position until one
611      * of the characters in provided pattern is found.
612      * All characters read so far will be returned without including any of the
613      * pattern characters.
614      *
615      * @param pattern Stop characters to stop reading when they are found.
616      * @return String read so far until any of the pattern characters was found
617      * or an empty string if the first character is contained in provided
618      * pattern.
619      * @throws IllegalArgumentException if no pattern characters are provided.
620      */
621     @Override
622     public String readUntilAnyOfTheseCharactersIsFound(final String pattern) {
623         if (pattern.isEmpty()) {
624             throw new IllegalArgumentException();
625         }
626 
627         read = true;
628 
629         final var stringBuffer = new StringBuilder();
630         final var charBuffer = new byte[1];
631         String character;
632         while (buffer.hasRemaining()) {
633             charBuffer[0] = buffer.get();
634             character = new String(charBuffer);
635             if (pattern.contains(character)) {
636                 // character found
637                 break;
638             }
639             // add character to output buffer
640             stringBuffer.append(character);
641         }
642 
643         return stringBuffer.toString();
644     }
645 
646     /**
647      * Writes a boolean to the file as a one-byte value. The value true is
648      * written out as the value (byte)1; the value false is written out as the
649      * value (byte)0. The write starts at the current position of the file
650      * pointer.
651      *
652      * @param v a boolean value to be written.
653      * @throws IOException if an I/O error occurs.
654      */
655     @Override
656     public void writeBoolean(final boolean v) throws IOException {
657         randomAccessFile.writeBoolean(v);
658         read = false;
659     }
660 
661     /**
662      * Writes a byte to the file as a one-byte value. The write starts at the
663      * current position of the file pointer.
664      *
665      * @param v a byte value to be written.
666      * @throws IOException if an I/O error occurs.
667      */
668     @Override
669     public void writeByte(final byte v) throws IOException {
670         randomAccessFile.writeByte(v);
671         read = false;
672     }
673 
674     /**
675      * Writes provided value in the range 0-255 as an unsigned byte. The write
676      * starts at the current position of the file pointer.
677      *
678      * @param v a value to be written as an unsigned byte.
679      * @throws IOException if an I/O error occurs.
680      */
681     @Override
682     public void writeUnsignedByte(final short v) throws IOException {
683         final var data = (byte) (0xff & v);
684         randomAccessFile.writeByte(data);
685         read = false;
686     }
687 
688     /**
689      * Writes a short to the file as two bytes, high byte first. The write
690      * starts at the current position of the file pointer.
691      *
692      * @param v a short to be written.
693      * @throws IOException if an I/O error occurs.
694      */
695     @Override
696     public void writeShort(final short v) throws IOException {
697         randomAccessFile.writeShort(v);
698         read = false;
699     }
700 
701     /**
702      * Writes a short to the file as two bytes using provided endian type.
703      * If endian type is big endian, then natural byte order is preserved (and
704      * high byte is written first), if little endian order is chosen, then byte
705      * order is reversed
706      *
707      * @param v          a short to be written
708      * @param endianType endian type. If it is big endian, natural byte order is
709      *                   preserved, otherwise byte order is reversed.
710      * @throws IOException if an I/O error occurs.
711      */
712     @Override
713     public void writeShort(final short v, final EndianType endianType) throws IOException {
714         final var value = Util.toEndianType(endianType, v);
715         randomAccessFile.writeShort(value);
716         read = false;
717     }
718 
719     /**
720      * Writes an unsigned short to the file as two bytes, high byte first.
721      * Provided integer value is converted to an unsigned short by taking into
722      * account only the two lower bytes. The write starts at the current
723      * position of the file pointer.
724      *
725      * @param v an unsigned short to be written (int is converted to unsigned
726      *          short).
727      * @throws IOException if an I/O error occurs.
728      */
729     @Override
730     public void writeUnsignedShort(final int v) throws IOException {
731         final var firstShortByte = 0xff & (v >> 8);
732         final var secondShortByte = 0xff & ((v << 8) >> 8);
733 
734         final var value = (short) ((firstShortByte << 8) | secondShortByte);
735 
736         randomAccessFile.writeShort(value);
737         read = false;
738     }
739 
740     /**
741      * Writes an unsigned short to the file as two bytes, using provided endian
742      * type.
743      * Provided integer value is converted to an unsigned short by taking into
744      * account only the two lower bytes.
745      * If endian type is big endian, then natural byte order is preserved (and
746      * high byte is written first), if little endian order is chosen, then byte
747      * order is reversed
748      * The write starts at the current position of the file pointer.
749      *
750      * @param v          an unsigned short to be written (int is converted to unsigned
751      *                   short).
752      * @param endianType endian type. If it is big endian, natural byte order is
753      *                   preserved, otherwise byte order is reversed.
754      * @throws IOException if an I/O error occurs.
755      */
756     @Override
757     public void writeUnsignedShort(final int v, final EndianType endianType) throws IOException {
758         final var firstShortByte = 0xff & (v >> 8);
759         final var secondShortByte = 0xff & ((v << 8) >> 8);
760 
761         final var machineValue = (short) ((firstShortByte << 8) | secondShortByte);
762         final var value = Util.toEndianType(endianType, machineValue);
763 
764         randomAccessFile.writeShort(value);
765         read = false;
766     }
767 
768     /**
769      * Writes an int to the file as four bytes, high byte first. The write
770      * starts at the current position of the file pointer.
771      *
772      * @param v an int to be written.
773      * @throws IOException if an I/O error occurs.
774      */
775     @Override
776     public void writeInt(final int v) throws IOException {
777         randomAccessFile.writeInt(v);
778         read = false;
779     }
780 
781     /**
782      * Writes an int to the file as four bytes, using provided endian type.
783      * If endian type is big endian, then natural byte order is preserved (and
784      * high byte is written first), if little endian order is chosen, then byte
785      * order is reversed.
786      * The write starts at the current position of the file pointer.
787      *
788      * @param v          an int to be written.
789      * @param endianType endian type. If it is big endian, natural byte order is
790      *                   preserved, otherwise byte order is reversed.
791      * @throws IOException if an I/O error occurs.
792      */
793     @Override
794     public void writeInt(final int v, final EndianType endianType) throws IOException {
795         final var value = Util.toEndianType(endianType, v);
796         randomAccessFile.writeInt(value);
797         read = false;
798     }
799 
800     /**
801      * Writes an unsigned int to the file as four bytes, high byte first.
802      * Provided integer value is converted to an unsigned int by taking into
803      * account only the four lower bytes. The write starts at the current
804      * position of the file pointer.
805      *
806      * @param v an unsigned int to be written (long is converted to unsigned
807      *          int).
808      * @throws IOException if an I/O error occurs.
809      */
810     @Override
811     public void writeUnsignedInt(final long v) throws IOException {
812         final var firstIntByte = (int) (0xff & (v >> 24));
813         final var secondIntByte = (int) (0xff & ((v << 8) >> 24));
814         final var thirdIntByte = (int) (0xff & ((v << 16) >> 24));
815         final var fourthIntByte = (int) (0xff & ((v << 24) >> 24));
816 
817         final var value = (firstIntByte << 24) | (secondIntByte << 16) | (thirdIntByte << 8) | fourthIntByte;
818 
819         randomAccessFile.writeInt(value);
820         read = false;
821     }
822 
823     /**
824      * Writes an unsigned int to the file as four bytes, using provided endian
825      * type.
826      * Provided integer value is converted to an unsigned int by taking into
827      * account only the four lower bytes.
828      * If endian type is big endian, then natural byte order is preserved (and
829      * high byte is written first), if little endian order is chosen, then byte
830      * order is reversed.
831      * The write starts at the current position of the file pointer.
832      *
833      * @param v          an unsigned int to be written (long is converted to unsigned
834      *                   short).
835      * @param endianType endian type. If it is big endian, natural byte order is
836      *                   preserved, otherwise byte order is reversed.
837      * @throws IOException if an I/O error occurs.
838      */
839     @Override
840     public void writeUnsignedInt(final long v, final EndianType endianType) throws IOException {
841         final var firstIntByte = (int) (0xff & (v >> 24));
842         final var secondIntByte = (int) (0xff & ((v << 8) >> 24));
843         final var thirdIntByte = (int) (0xff & ((v << 16) >> 24));
844         final var fourthIntByte = (int) (0xff & ((v << 24) >> 24));
845 
846         final var machineValue = (firstIntByte << 24) | (secondIntByte << 16) | (thirdIntByte << 8) | fourthIntByte;
847         final var value = Util.toEndianType(endianType, machineValue);
848 
849         randomAccessFile.writeInt(value);
850         read = false;
851     }
852 
853     /**
854      * Writes a long to the file as eight bytes, high byte first. The write
855      * starts at the current position of the file pointer.
856      *
857      * @param v a long to be written.
858      * @throws IOException if an I/O error occurs.
859      */
860     @Override
861     public void writeLong(final long v) throws IOException {
862         randomAccessFile.writeLong(v);
863         read = false;
864     }
865 
866     /**
867      * Writes a long to the file as eight bytes, using provided endian type.
868      * If endian type is big endian, then natural byte order is preserved (and
869      * high byte is written first), if little endian order is chosen, then byte
870      * order is reversed.
871      * The write starts at the current position of the file pointer.
872      *
873      * @param v          a long to be written.
874      * @param endianType endian type. If it is big endian, natural byte order is
875      *                   preserved, otherwise byte order is reversed.
876      * @throws IOException if an I/O error occurs.
877      */
878     @Override
879     public void writeLong(final long v, final EndianType endianType) throws IOException {
880         final var value = Util.toEndianType(endianType, v);
881         randomAccessFile.writeLong(value);
882         read = false;
883     }
884 
885     /**
886      * Converts the float argument to an int using the floatToIntBits method in
887      * class Float, and then write that int value to the file as a four-byte
888      * quantity, high byte first. The write starts at the current position of
889      * the file pointer.
890      *
891      * @param v a float value to be written
892      * @throws IOException if an I/O error occurs.
893      */
894     @Override
895     public void writeFloat(final float v) throws IOException {
896         randomAccessFile.writeFloat(v);
897         read = false;
898     }
899 
900     /**
901      * Converts the float argument to an int using the floatToIntBits method in
902      * class Float, and then write that int value to the file as a four-byte
903      * quantity, using provided endian type.
904      * If endian type is big endian, then natural byte order is preserved (and
905      * high byte is written first), if little endian order is chosen, then byte
906      * order is reversed.
907      * The write starts at the current position of the file pointer.
908      *
909      * @param v          a float value to be written
910      * @param endianType endian type. If it is big endian, natural byte order is
911      *                   preserved, otherwise byte order is reversed.
912      * @throws IOException if an I/O error occurs.
913      */
914     @Override
915     public void writeFloat(final float v, final EndianType endianType) throws IOException {
916         final var value = Util.toEndianType(endianType, v);
917         randomAccessFile.writeFloat(value);
918         read = false;
919     }
920 
921     /**
922      * Converts the double argument to a long using the doubleToLongBits method
923      * in class Double, and then writes that long value to the file as an eight
924      * byte quantity, high byte first. The write starts at the current position
925      * of the file pointer.
926      *
927      * @param v a double value to be written.
928      * @throws IOException if an I/O error occurs.
929      */
930     @Override
931     public void writeDouble(final double v) throws IOException {
932         randomAccessFile.writeDouble(v);
933         read = false;
934     }
935 
936     /**
937      * Converts the double argument to a long using the doubleToLongBits method
938      * in class Double, and then writes that long value to the file as an eight
939      * byte quantity, using provided endian type.
940      * If endian type is big endian, then natural byte order is preserved (and
941      * high byte is written first), if little endian order is chosen, then byte
942      * order is reversed.
943      * The write starts at the current position of the file pointer.
944      *
945      * @param v          a double value to be written.
946      * @param endianType endian type. If it is big endian, natural byte order is
947      *                   preserved, otherwise byte order is reversed.
948      * @throws IOException if an I/O error occurs.
949      */
950     @Override
951     public void writeDouble(final double v, final EndianType endianType) throws IOException {
952         final var value = Util.toEndianType(endianType, v);
953         randomAccessFile.writeDouble(value);
954         read = false;
955     }
956 
957     /**
958      * Writes the string to the file as a sequence of bytes. Each character in
959      * the string is written out, in sequence, by discarding its high eight
960      * bits. The write starts at the current position of the file pointer.
961      *
962      * @param s a string of bytes to be written.
963      * @throws IOException if an I/O error occurs.
964      */
965     @Override
966     public void writeASCII(final String s) throws IOException {
967         randomAccessFile.writeBytes(s);
968         read = false;
969     }
970 }