View Javadoc
1   /*
2    * #%L
3    * nom.tam FITS library
4    * %%
5    * Copyright (C) 1996 - 2024 nom-tam-fits
6    * %%
7    * This is free and unencumbered software released into the public domain.
8    *
9    * Anyone is free to copy, modify, publish, use, compile, sell, or
10   * distribute this software, either in source code form or as a compiled
11   * binary, for any purpose, commercial or non-commercial, and by any
12   * means.
13   *
14   * In jurisdictions that recognize copyright laws, the author or authors
15   * of this software dedicate any and all copyright interest in the
16   * software to the public domain. We make this dedication for the benefit
17   * of the public at large and to the detriment of our heirs and
18   * successors. We intend this dedication to be an overt act of
19   * relinquishment in perpetuity of all present and future rights to this
20   * software under copyright law.
21   *
22   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23   * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
25   * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
26   * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
27   * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
28   * OTHER DEALINGS IN THE SOFTWARE.
29   * #L%
30   */
31  
32  package nom.tam.util;
33  
34  import java.io.EOFException;
35  import java.io.IOException;
36  import java.lang.reflect.Array;
37  
38  import nom.tam.fits.FitsFactory;
39  import nom.tam.util.type.ElementType;
40  
41  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
42  
43  /**
44   * Decodes FITS-formatted binary data into Java arrays (<i>primarily for internal use</i>)
45   *
46   * @since 1.16
47   *
48   * @see   FitsEncoder
49   * @see   FitsInputStream
50   * @see   FitsFile
51   */
52  public class FitsDecoder extends InputDecoder {
53  
54      /**
55       * The FITS byte value for the binary representation of a boolean 'true' value
56       */
57      private static final byte FITS_TRUE = (byte) 'T';
58  
59      /**
60       * Instantiates a new decoder of FITS binary data to Java arrays. To be used by subclass constructors only.
61       */
62      protected FitsDecoder() {
63          super();
64      }
65  
66      /**
67       * Instantiates a new FITS binary data decoder for converting FITS data representations into Java arrays.
68       *
69       * @param i the FITS input.
70       */
71      public FitsDecoder(InputReader i) {
72          super(i);
73      }
74  
75      /**
76       * Gets the <code>boolean</code> equivalent for a FITS byte value representing a logical value. This call does not
77       * support <code>null</code> values, which are allowed by the FITS standard, but the similar
78       * {@link #booleanObjectFor(int)} does. FITS defines 'T' as true, 'F' as false, and 0 as null. However, prior
79       * versions of this library have used the value 1 for true, and 0 for false. Therefore, this implementation will
80       * recognise both 'T' and 1 as <code>true</code>, and will return <code>false</code> for all other byte values.
81       *
82       * @param  c The FITS byte that defines a boolean value
83       *
84       * @return   <code>true</code> if and only if the byte is the ASCII character 'T' or has the value of 1, otherwise
85       *               <code>false</code>.
86       *
87       * @see      #booleanObjectFor(int)
88       */
89      public static final boolean booleanFor(int c) {
90          return c == FITS_TRUE || c == 1;
91      }
92  
93      /**
94       * Gets the <code>boolean</code> equivalent for a FITS byte value representing a logical value. This call supports
95       * <code>null</code> values, which are allowed by the FITS standard. FITS defines 'T' as true, 'F' as false, and 0
96       * as null. Prior versions of this library have used the value 1 for true, and 0 for false. Therefore, this
97       * implementation will recognise both 'T' and 1 as <code>true</code>, but 0 will map to <code>null</code> and
98       * everything else will return <code>false</code>.
99       *
100      * @param  c The FITS byte that defines a boolean value
101      *
102      * @return   <code>true</code> if and only if the byte is the ASCII character 'T' or has the value of 1,
103      *               <code>null</code> it the byte is 0, otherwise <code>false</code>.
104      *
105      * @see      #booleanFor(int)
106      */
107     @SuppressFBWarnings(value = "NP_BOOLEAN_RETURN_NULL", justification = "null values are explicitly allowed by FITS, so we want to support them.")
108     public static final Boolean booleanObjectFor(int c) {
109         if (c == 0) {
110             return null;
111         }
112         return booleanFor(c);
113     }
114 
115     /**
116      * @deprecated              (<i>for internal use</i>) Low-level reading/writing should be handled internally as
117      *                              arrays by this library only.
118      *
119      * @return                  the next boolean value from the input.
120      *
121      * @throws     EOFException if already at the end of file.
122      * @throws     IOException  if there was an IO error reading from the input.
123      */
124     @Deprecated
125     protected boolean readBoolean() throws EOFException, IOException {
126         return booleanFor(readByte());
127     }
128 
129     /**
130      * @deprecated              (<i>for internal use</i>) Low-level reading/writing should be handled internally as
131      *                              arrays by this library only.
132      *
133      * @return                  the next character value from the input.
134      *
135      * @throws     EOFException if already at the end of file.
136      * @throws     IOException  if there was an IO error reading from the input.
137      */
138     @Deprecated
139     protected char readChar() throws EOFException, IOException {
140         int b = FitsFactory.isUseUnicodeChars() ? readUnsignedShort() : read();
141         if (b < 0) {
142             throw new EOFException();
143         }
144         return (char) b;
145     }
146 
147     /**
148      * @deprecated             (<i>for internal use</i>) Low-level reading/writing should be handled internally as
149      *                             arrays by this library only.
150      *
151      * @return                 the next byte the input.
152      *
153      * @throws     IOException if there was an IO error reading from the input.
154      */
155     @Deprecated
156     protected final byte readByte() throws IOException {
157         int i = read();
158         if (i < 0) {
159             throw new EOFException();
160         }
161         return (byte) i;
162     }
163 
164     /**
165      * @deprecated             (<i>for internal use</i>) Low-level reading/writing should be handled internally as
166      *                             arrays by this library only.
167      *
168      * @return                 the next unsigned byte from the input, or -1 if there is no more bytes available.
169      *
170      * @throws     IOException if there was an IO error reading from the input, other than the end-of-file.
171      */
172     @Deprecated
173     protected int readUnsignedByte() throws IOException {
174         return read();
175     }
176 
177     /**
178      * @deprecated              (<i>for internal use</i>) Low-level reading/writing should be handled internally as
179      *                              arrays by this library only.
180      *
181      * @return                  the next 16-bit integer value from the input.
182      *
183      * @throws     EOFException if already at the end of file.
184      * @throws     IOException  if there was an IO error reading from the input.
185      */
186     @Deprecated
187     protected final short readShort() throws EOFException, IOException {
188         int i = readUnsignedShort();
189         if (i < 0) {
190             throw new EOFException();
191         }
192         return (short) i;
193     }
194 
195     /**
196      * @deprecated             (<i>for internal use</i>) Low-level reading/writing should be handled internally as
197      *                             arrays by this library only.
198      *
199      * @return                 the next unsigned 16-bit integer value from the input, or -1 if reached the end of stream
200      *
201      * @throws     IOException if there was an IO error reading from the input.
202      */
203     @Deprecated
204     protected int readUnsignedShort() throws IOException {
205         synchronized (lock) {
206             getInputBuffer().loadOne(Short.BYTES);
207             return getInputBuffer().getUnsignedShort();
208         }
209     }
210 
211     /**
212      * @deprecated              (<i>for internal use</i>) Low-level reading/writing should be handled internally as
213      *                              arrays by this library only.
214      *
215      * @return                  the next 32-bit integer value from the input.
216      *
217      * @throws     EOFException if already at the end of file.
218      * @throws     IOException  if there was an IO error reading from the input.
219      */
220     @Deprecated
221     protected int readInt() throws EOFException, IOException {
222         synchronized (lock) {
223             getInputBuffer().loadOne(Integer.BYTES);
224             return getInputBuffer().getInt();
225         }
226     }
227 
228     /**
229      * @deprecated              (<i>for internal use</i>) Low-level reading/writing should be handled internally as
230      *                              arrays by this library only.
231      *
232      * @return                  the next 64-bit integer value from the input.
233      *
234      * @throws     EOFException if already at the end of file.
235      * @throws     IOException  if there was an IO error reading from the input.
236      */
237     @Deprecated
238     protected long readLong() throws EOFException, IOException {
239         synchronized (lock) {
240             getInputBuffer().loadOne(Long.BYTES);
241             return getInputBuffer().getLong();
242         }
243     }
244 
245     /**
246      * @deprecated              (<i>for internal use</i>) Low-level reading/writing should be handled internally as
247      *                              arrays by this library only.
248      *
249      * @return                  the next single-precision (32-bit) floating point value from the input.
250      *
251      * @throws     EOFException if already at the end of file.
252      * @throws     IOException  if there was an IO error reading from the input.
253      */
254     @Deprecated
255     protected float readFloat() throws EOFException, IOException {
256         synchronized (lock) {
257             getInputBuffer().loadOne(Float.BYTES);
258             return getInputBuffer().getFloat();
259         }
260     }
261 
262     /**
263      * @deprecated              (<i>for internal use</i>) Low-level reading/writing should be handled internally as
264      *                              arrays by this library only.
265      *
266      * @return                  the next double-precision (64-bit) floating point value from the input.
267      *
268      * @throws     EOFException if already at the end of file.
269      * @throws     IOException  if there was an IO error reading from the input.
270      */
271     @Deprecated
272     protected double readDouble() throws EOFException, IOException {
273         synchronized (lock) {
274             getInputBuffer().loadOne(Double.BYTES);
275             return getInputBuffer().getDouble();
276         }
277     }
278 
279     /**
280      * @deprecated              (<i>for internal use</i>) Low-level reading/writing should be handled internally as
281      *                              arrays by this library only.
282      *
283      * @return                  the next line of 1-byte ASCII characters, terminated by a LF or EOF.
284      *
285      * @throws     EOFException if already at the end of file.
286      * @throws     IOException  if there was an IO error reading from the input.
287      */
288     @Deprecated
289     protected String readAsciiLine() throws EOFException, IOException {
290         StringBuffer str = new StringBuffer();
291 
292         synchronized (lock) {
293             for (;;) {
294                 int c = read();
295                 if (c < 0) {
296                     if (str.length() > 0) {
297                         break;
298                     }
299                     throw new EOFException();
300                 }
301                 if (c == '\n') {
302                     break;
303                 }
304                 str.append((char) c);
305             }
306         }
307 
308         return new String(str);
309     }
310 
311     /**
312      * See {@link ArrayDataInput#read(boolean[], int, int)} for the general contract of this method. In FITS,
313      * <code>true</code> values are represented by the ASCII byte for 'T', whereas <code>false</code> is represented by
314      * the ASCII byte for 'F'.
315      *
316      * @param  b            an array of boolean values.
317      * @param  start        the buffer index at which to start reading data
318      * @param  length       the total number of elements to read.
319      *
320      * @return              the number of bytes successfully read.
321      *
322      * @throws EOFException if already at the end of file.
323      * @throws IOException  if there was an IO error before, before requested number of bytes could be read
324      */
325     protected int read(boolean[] b, int start, int length) throws EOFException, IOException {
326         if (length == 0) {
327             return 0;
328         }
329 
330         byte[] ascii = new byte[length];
331         length = read(ascii, 0, length);
332 
333         if (length < 0) {
334             throw new EOFException();
335         }
336 
337         for (int i = 0; i < length; i++) {
338             b[start + i] = booleanFor(ascii[i]);
339         }
340 
341         return length;
342     }
343 
344     /**
345      * See {@link ArrayDataInput#read(Boolean[], int, int)} for the general contract of this method. In FITS,
346      * <code>true</code> values are represented by the ASCII byte for 'T', <code>false</code> is represented by the
347      * ASCII byte for 'F', while <code>null</code> values are represented by the value 0.
348      *
349      * @param  b            an array of boolean values.
350      * @param  start        the buffer index at which to start reading data
351      * @param  length       the total number of elements to read.
352      *
353      * @return              the number of bytes successfully read.
354      *
355      * @throws EOFException if already at the end of file.
356      * @throws IOException  if there was an IO error before, before requested number of bytes could be read
357      */
358     protected int read(Boolean[] b, int start, int length) throws EOFException, IOException {
359         if (length == 0) {
360             return 0;
361         }
362 
363         byte[] ascii = new byte[length];
364         length = read(ascii, 0, length);
365 
366         if (length < 0) {
367             throw new EOFException();
368         }
369 
370         for (int i = 0; i < length; i++) {
371             b[start + i] = booleanObjectFor(ascii[i]);
372         }
373 
374         return length;
375     }
376 
377     /**
378      * See {@link ArrayDataInput#read(char[], int, int)} for the general contract of this method. In FITS characters are
379      * usually represented as 1-byte ASCII, not as the 2-byte Java types. However, previous implementations if this
380      * library have erroneously written 2-byte characters into the FITS. For compatibility both the FITS standard
381      * -1-byte ASCII and the old 2-byte behaviour are supported, and can be selected via
382      * {@link FitsFactory#setUseUnicodeChars(boolean)}.
383      *
384      * @param  c            a character array.
385      * @param  start        the buffer index at which to start reading data
386      * @param  length       the total number of elements to read.
387      *
388      * @return              the number of bytes successfully read.
389      *
390      * @throws EOFException if already at the end of file.
391      * @throws IOException  if there was an IO error before, before requested number of bytes could be read
392      *
393      * @see                 FitsFactory#setUseUnicodeChars(boolean)
394      */
395     protected int read(char[] c, int start, int length) throws EOFException, IOException {
396         if (length == 0) {
397             return 0;
398         }
399 
400         if (ElementType.CHAR.size() == 1) {
401             byte[] ascii = new byte[length];
402             length = read(ascii, 0, length);
403 
404             if (length < 0) {
405                 throw new EOFException();
406             }
407 
408             for (int i = 0; i < length; i++) {
409                 c[start + i] = (char) (ascii[i] & FitsIO.BYTE_MASK);
410             }
411         } else {
412             synchronized (lock) {
413                 getInputBuffer().loadBytes(length, Short.BYTES);
414                 short[] s = new short[length];
415                 length = getInputBuffer().get(s, 0, length);
416                 for (int i = 0; i < length; i++) {
417                     c[start + i] = (char) (s[i] & FitsIO.SHORT_MASK);
418                 }
419             }
420         }
421 
422         return length * ElementType.CHAR.size();
423     }
424 
425     /**
426      * See {@link ArrayDataInput#read(short[], int, int)} for a contract of this method.
427      *
428      * @param  s            an array of 16-bit integer values.
429      * @param  start        the buffer index at which to start reading data
430      * @param  length       the total number of elements to read.
431      *
432      * @return              the number of bytes successfully read.
433      *
434      * @throws EOFException if already at the end of file.
435      * @throws IOException  if there was an IO error before, before requested number of bytes could be read
436      */
437     protected int read(short[] s, int start, int length) throws EOFException, IOException {
438         synchronized (lock) {
439             getInputBuffer().loadBytes(length, Short.BYTES);
440             return getInputBuffer().get(s, start, length) * Short.BYTES;
441         }
442     }
443 
444     /**
445      * See {@link ArrayDataInput#read(int[], int, int)} for a contract of this method.
446      *
447      * @param  j            an array of 32-bit integer values.
448      * @param  start        the buffer index at which to start reading data
449      * @param  length       the total number of elements to read.
450      *
451      * @return              the number of bytes successfully read.
452      *
453      * @throws EOFException if already at the end of file.
454      * @throws IOException  if there was an IO error before, before requested number of bytes could be read
455      */
456     protected int read(int[] j, int start, int length) throws EOFException, IOException {
457         synchronized (lock) {
458             getInputBuffer().loadBytes(length, Integer.BYTES);
459             return getInputBuffer().get(j, start, length) * Integer.BYTES;
460         }
461     }
462 
463     /**
464      * See {@link ArrayDataInput#read(long[], int, int)} for a contract of this method.
465      *
466      * @param  l            an array of 64-bit integer values.
467      * @param  start        the buffer index at which to start reading data
468      * @param  length       the total number of elements to read.
469      *
470      * @return              the number of bytes successfully read.
471      *
472      * @throws EOFException if already at the end of file.
473      * @throws IOException  if there was an IO error before, before requested number of bytes could be read
474      */
475     protected int read(long[] l, int start, int length) throws EOFException, IOException {
476         synchronized (lock) {
477             getInputBuffer().loadBytes(length, Long.BYTES);
478             return getInputBuffer().get(l, start, length) * Long.BYTES;
479         }
480     }
481 
482     /**
483      * See {@link ArrayDataInput#read(float[], int, int)} for a contract of this method.
484      *
485      * @param  f            an array of single-precision (32-bit) floating point values.
486      * @param  start        the buffer index at which to start reading data
487      * @param  length       the total number of elements to read.
488      *
489      * @return              the number of bytes successfully read.
490      *
491      * @throws EOFException if already at the end of file.
492      * @throws IOException  if there was an IO error before, before requested number of bytes could be read
493      */
494     protected int read(float[] f, int start, int length) throws EOFException, IOException {
495         synchronized (lock) {
496             getInputBuffer().loadBytes(length, Float.BYTES);
497             return getInputBuffer().get(f, start, length) * Float.BYTES;
498         }
499     }
500 
501     /**
502      * See {@link ArrayDataInput#read(double[], int, int)} for a contract of this method.
503      *
504      * @param  d            an array of double-precision (64-bit) floating point values.
505      * @param  start        the buffer index at which to start reading data
506      * @param  length       the total number of elements to read.
507      *
508      * @return              the number of bytes successfully read.
509      *
510      * @throws EOFException if already at the end of file.
511      * @throws IOException  if there was an IO error before, before requested number of bytes could be read
512      */
513     protected int read(double[] d, int start, int length) throws EOFException, IOException {
514         synchronized (lock) {
515             getInputBuffer().loadBytes(length, Double.BYTES);
516             return getInputBuffer().get(d, start, length) * Double.BYTES;
517         }
518     }
519 
520     @Override
521     public long readArray(Object o) throws IOException, IllegalArgumentException {
522         if (o == null) {
523             return 0L;
524         }
525         if (!o.getClass().isArray()) {
526             throw new IllegalArgumentException("Not an array: " + o.getClass().getName());
527         }
528 
529         int length = Array.getLength(o);
530         if (length == 0) {
531             return 0L;
532         }
533 
534         // This is a 1-d array. Process it using our special
535         // functions.
536         if (o instanceof byte[]) {
537             readFully((byte[]) o, 0, length);
538             return length;
539         }
540         if (o instanceof boolean[]) {
541             return read((boolean[]) o, 0, length);
542         }
543         if (o instanceof char[]) {
544             return read((char[]) o, 0, length);
545         }
546         if (o instanceof short[]) {
547             return read((short[]) o, 0, length);
548         }
549         if (o instanceof int[]) {
550             return read((int[]) o, 0, length);
551         }
552         if (o instanceof float[]) {
553             return read((float[]) o, 0, length);
554         }
555         if (o instanceof long[]) {
556             return read((long[]) o, 0, length);
557         }
558         if (o instanceof double[]) {
559             return read((double[]) o, 0, length);
560         }
561         if (o instanceof Boolean[]) {
562             return read((Boolean[]) o, 0, length);
563         }
564 
565         Object[] array = (Object[]) o;
566         long count = 0L;
567 
568         // Process multidim arrays recursively.
569         for (int i = 0; i < length; i++) {
570             try {
571                 count += readArray(array[i]);
572             } catch (EOFException e) {
573                 return eofCheck(e, count, -1L);
574             }
575         }
576         return count;
577     }
578 
579 }