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 }