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 synchronized 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 synchronized 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 synchronized 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 synchronized int readUnsignedShort() throws IOException {
205 getInputBuffer().loadOne(Short.BYTES);
206 return getInputBuffer().getUnsignedShort();
207 }
208
209 /**
210 * @deprecated (<i>for internal use</i>) Low-level reading/writing should be handled internally as
211 * arrays by this library only.
212 *
213 * @return the next 32-bit integer value from the input.
214 *
215 * @throws EOFException if already at the end of file.
216 * @throws IOException if there was an IO error reading from the input.
217 */
218 @Deprecated
219 protected synchronized int readInt() throws EOFException, IOException {
220 getInputBuffer().loadOne(Integer.BYTES);
221 return getInputBuffer().getInt();
222 }
223
224 /**
225 * @deprecated (<i>for internal use</i>) Low-level reading/writing should be handled internally as
226 * arrays by this library only.
227 *
228 * @return the next 64-bit integer value from the input.
229 *
230 * @throws EOFException if already at the end of file.
231 * @throws IOException if there was an IO error reading from the input.
232 */
233 @Deprecated
234 protected synchronized long readLong() throws EOFException, IOException {
235 getInputBuffer().loadOne(Long.BYTES);
236 return getInputBuffer().getLong();
237 }
238
239 /**
240 * @deprecated (<i>for internal use</i>) Low-level reading/writing should be handled internally as
241 * arrays by this library only.
242 *
243 * @return the next single-precision (32-bit) floating point value from the input.
244 *
245 * @throws EOFException if already at the end of file.
246 * @throws IOException if there was an IO error reading from the input.
247 */
248 @Deprecated
249 protected synchronized float readFloat() throws EOFException, IOException {
250 getInputBuffer().loadOne(Float.BYTES);
251 return getInputBuffer().getFloat();
252 }
253
254 /**
255 * @deprecated (<i>for internal use</i>) Low-level reading/writing should be handled internally as
256 * arrays by this library only.
257 *
258 * @return the next double-precision (64-bit) floating point value from the input.
259 *
260 * @throws EOFException if already at the end of file.
261 * @throws IOException if there was an IO error reading from the input.
262 */
263 @Deprecated
264 protected synchronized double readDouble() throws EOFException, IOException {
265 getInputBuffer().loadOne(Double.BYTES);
266 return getInputBuffer().getDouble();
267 }
268
269 /**
270 * @deprecated (<i>for internal use</i>) Low-level reading/writing should be handled internally as
271 * arrays by this library only.
272 *
273 * @return the next line of 1-byte ASCII characters, terminated by a LF or EOF.
274 *
275 * @throws EOFException if already at the end of file.
276 * @throws IOException if there was an IO error reading from the input.
277 */
278 @Deprecated
279 protected synchronized String readAsciiLine() throws EOFException, IOException {
280 StringBuffer str = new StringBuffer();
281 for (;;) {
282 int c = read();
283 if (c < 0) {
284 if (str.length() > 0) {
285 break;
286 }
287 throw new EOFException();
288 }
289 if (c == '\n') {
290 break;
291 }
292 str.append((char) c);
293 }
294 return new String(str);
295 }
296
297 /**
298 * See {@link ArrayDataInput#read(boolean[], int, int)} for the general contract of this method. In FITS,
299 * <code>true</code> values are represented by the ASCII byte for 'T', whereas <code>false</code> is represented by
300 * the ASCII byte for 'F'.
301 *
302 * @param b an array of boolean values.
303 * @param start the buffer index at which to start reading data
304 * @param length the total number of elements to read.
305 *
306 * @return the number of bytes successfully read.
307 *
308 * @throws EOFException if already at the end of file.
309 * @throws IOException if there was an IO error before, before requested number of bytes could be read
310 */
311 protected synchronized int read(boolean[] b, int start, int length) throws EOFException, IOException {
312 if (length == 0) {
313 return 0;
314 }
315
316 byte[] ascii = new byte[length];
317 length = read(ascii, 0, length);
318
319 if (length < 0) {
320 throw new EOFException();
321 }
322
323 for (int i = 0; i < length; i++) {
324 b[start + i] = booleanFor(ascii[i]);
325 }
326
327 return length;
328 }
329
330 /**
331 * See {@link ArrayDataInput#read(Boolean[], int, int)} for the general contract of this method. In FITS,
332 * <code>true</code> values are represented by the ASCII byte for 'T', <code>false</code> is represented by the
333 * ASCII byte for 'F', while <code>null</code> values are represented by the value 0.
334 *
335 * @param b an array of boolean values.
336 * @param start the buffer index at which to start reading data
337 * @param length the total number of elements to read.
338 *
339 * @return the number of bytes successfully read.
340 *
341 * @throws EOFException if already at the end of file.
342 * @throws IOException if there was an IO error before, before requested number of bytes could be read
343 */
344 protected synchronized int read(Boolean[] b, int start, int length) throws EOFException, IOException {
345 if (length == 0) {
346 return 0;
347 }
348
349 byte[] ascii = new byte[length];
350 length = read(ascii, 0, length);
351
352 if (length < 0) {
353 throw new EOFException();
354 }
355
356 for (int i = 0; i < length; i++) {
357 b[start + i] = booleanObjectFor(ascii[i]);
358 }
359
360 return length;
361 }
362
363 /**
364 * See {@link ArrayDataInput#read(char[], int, int)} for the general contract of this method. In FITS characters are
365 * usually represented as 1-byte ASCII, not as the 2-byte Java types. However, previous implementations if this
366 * library have erroneously written 2-byte characters into the FITS. For compatibility both the FITS standard
367 * -1-byte ASCII and the old 2-byte behaviour are supported, and can be selected via
368 * {@link FitsFactory#setUseUnicodeChars(boolean)}.
369 *
370 * @param c a character array.
371 * @param start the buffer index at which to start reading data
372 * @param length the total number of elements to read.
373 *
374 * @return the number of bytes successfully read.
375 *
376 * @throws EOFException if already at the end of file.
377 * @throws IOException if there was an IO error before, before requested number of bytes could be read
378 *
379 * @see FitsFactory#setUseUnicodeChars(boolean)
380 */
381 protected synchronized int read(char[] c, int start, int length) throws EOFException, IOException {
382 if (length == 0) {
383 return 0;
384 }
385
386 if (ElementType.CHAR.size() == 1) {
387 byte[] ascii = new byte[length];
388 length = read(ascii, 0, length);
389
390 if (length < 0) {
391 throw new EOFException();
392 }
393
394 for (int i = 0; i < length; i++) {
395 c[start + i] = (char) (ascii[i] & FitsIO.BYTE_MASK);
396 }
397 } else {
398 getInputBuffer().loadBytes(length, Short.BYTES);
399 short[] s = new short[length];
400 length = getInputBuffer().get(s, 0, length);
401 for (int i = 0; i < length; i++) {
402 c[start + i] = (char) (s[i] & FitsIO.SHORT_MASK);
403 }
404 }
405
406 return length * ElementType.CHAR.size();
407 }
408
409 /**
410 * See {@link ArrayDataInput#read(short[], int, int)} for a contract of this method.
411 *
412 * @param s an array of 16-bit integer values.
413 * @param start the buffer index at which to start reading data
414 * @param length the total number of elements to read.
415 *
416 * @return the number of bytes successfully read.
417 *
418 * @throws EOFException if already at the end of file.
419 * @throws IOException if there was an IO error before, before requested number of bytes could be read
420 */
421 protected synchronized int read(short[] s, int start, int length) throws EOFException, IOException {
422 getInputBuffer().loadBytes(length, Short.BYTES);
423 return getInputBuffer().get(s, start, length) * Short.BYTES;
424 }
425
426 /**
427 * See {@link ArrayDataInput#read(int[], int, int)} for a contract of this method.
428 *
429 * @param j an array of 32-bit integer values.
430 * @param start the buffer index at which to start reading data
431 * @param length the total number of elements to read.
432 *
433 * @return the number of bytes successfully read.
434 *
435 * @throws EOFException if already at the end of file.
436 * @throws IOException if there was an IO error before, before requested number of bytes could be read
437 */
438 protected synchronized int read(int[] j, int start, int length) throws EOFException, IOException {
439 getInputBuffer().loadBytes(length, Integer.BYTES);
440 return getInputBuffer().get(j, start, length) * Integer.BYTES;
441 }
442
443 /**
444 * See {@link ArrayDataInput#read(long[], int, int)} for a contract of this method.
445 *
446 * @param l an array of 64-bit integer values.
447 * @param start the buffer index at which to start reading data
448 * @param length the total number of elements to read.
449 *
450 * @return the number of bytes successfully read.
451 *
452 * @throws EOFException if already at the end of file.
453 * @throws IOException if there was an IO error before, before requested number of bytes could be read
454 */
455 protected synchronized int read(long[] l, int start, int length) throws EOFException, IOException {
456 getInputBuffer().loadBytes(length, Long.BYTES);
457 return getInputBuffer().get(l, start, length) * Long.BYTES;
458 }
459
460 /**
461 * See {@link ArrayDataInput#read(float[], int, int)} for a contract of this method.
462 *
463 * @param f an array of single-precision (32-bit) floating point values.
464 * @param start the buffer index at which to start reading data
465 * @param length the total number of elements to read.
466 *
467 * @return the number of bytes successfully read.
468 *
469 * @throws EOFException if already at the end of file.
470 * @throws IOException if there was an IO error before, before requested number of bytes could be read
471 */
472 protected synchronized int read(float[] f, int start, int length) throws EOFException, IOException {
473 getInputBuffer().loadBytes(length, Float.BYTES);
474 return getInputBuffer().get(f, start, length) * Float.BYTES;
475 }
476
477 /**
478 * See {@link ArrayDataInput#read(double[], int, int)} for a contract of this method.
479 *
480 * @param d an array of double-precision (64-bit) floating point values.
481 * @param start the buffer index at which to start reading data
482 * @param length the total number of elements to read.
483 *
484 * @return the number of bytes successfully read.
485 *
486 * @throws EOFException if already at the end of file.
487 * @throws IOException if there was an IO error before, before requested number of bytes could be read
488 */
489 protected synchronized int read(double[] d, int start, int length) throws EOFException, IOException {
490 getInputBuffer().loadBytes(length, Double.BYTES);
491 return getInputBuffer().get(d, start, length) * Double.BYTES;
492 }
493
494 @Override
495 public synchronized long readArray(Object o) throws IOException, IllegalArgumentException {
496 if (o == null) {
497 return 0L;
498 }
499 if (!o.getClass().isArray()) {
500 throw new IllegalArgumentException("Not an array: " + o.getClass().getName());
501 }
502
503 int length = Array.getLength(o);
504 if (length == 0) {
505 return 0L;
506 }
507
508 // This is a 1-d array. Process it using our special
509 // functions.
510 if (o instanceof byte[]) {
511 readFully((byte[]) o, 0, length);
512 return length;
513 }
514 if (o instanceof boolean[]) {
515 return read((boolean[]) o, 0, length);
516 }
517 if (o instanceof char[]) {
518 return read((char[]) o, 0, length);
519 }
520 if (o instanceof short[]) {
521 return read((short[]) o, 0, length);
522 }
523 if (o instanceof int[]) {
524 return read((int[]) o, 0, length);
525 }
526 if (o instanceof float[]) {
527 return read((float[]) o, 0, length);
528 }
529 if (o instanceof long[]) {
530 return read((long[]) o, 0, length);
531 }
532 if (o instanceof double[]) {
533 return read((double[]) o, 0, length);
534 }
535 if (o instanceof Boolean[]) {
536 return read((Boolean[]) o, 0, length);
537 }
538
539 Object[] array = (Object[]) o;
540 long count = 0L;
541
542 // Process multidim arrays recursively.
543 for (int i = 0; i < length; i++) {
544 try {
545 count += readArray(array[i]);
546 } catch (EOFException e) {
547 return eofCheck(e, count, -1L);
548 }
549 }
550 return count;
551 }
552
553 }