1 package nom.tam.fits; 2 3 /*- 4 * #%L 5 * nom.tam FITS library 6 * %% 7 * Copyright (C) 1996 - 2024 nom-tam-fits 8 * %% 9 * This is free and unencumbered software released into the public domain. 10 * 11 * Anyone is free to copy, modify, publish, use, compile, sell, or 12 * distribute this software, either in source code form or as a compiled 13 * binary, for any purpose, commercial or non-commercial, and by any 14 * means. 15 * 16 * In jurisdictions that recognize copyright laws, the author or authors 17 * of this software dedicate any and all copyright interest in the 18 * software to the public domain. We make this dedication for the benefit 19 * of the public at large and to the detriment of our heirs and 20 * successors. We intend this dedication to be an overt act of 21 * relinquishment in perpetuity of all present and future rights to this 22 * software under copyright law. 23 * 24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 28 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 29 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 30 * OTHER DEALINGS IN THE SOFTWARE. 31 * #L% 32 */ 33 34 import java.io.IOException; 35 import java.lang.reflect.Array; 36 import java.math.BigDecimal; 37 import java.math.BigInteger; 38 import java.text.DecimalFormat; 39 import java.text.ParsePosition; 40 import java.util.ArrayList; 41 import java.util.Arrays; 42 import java.util.List; 43 import java.util.StringTokenizer; 44 import java.util.logging.Logger; 45 46 import nom.tam.fits.header.Bitpix; 47 import nom.tam.fits.header.NonStandard; 48 import nom.tam.fits.header.Standard; 49 import nom.tam.util.ArrayDataInput; 50 import nom.tam.util.ArrayDataOutput; 51 import nom.tam.util.ArrayFuncs; 52 import nom.tam.util.AsciiFuncs; 53 import nom.tam.util.ColumnTable; 54 import nom.tam.util.ComplexValue; 55 import nom.tam.util.Cursor; 56 import nom.tam.util.FitsEncoder; 57 import nom.tam.util.FitsIO; 58 import nom.tam.util.Quantizer; 59 import nom.tam.util.RandomAccess; 60 import nom.tam.util.ReadWriteAccess; 61 import nom.tam.util.TableException; 62 import nom.tam.util.type.ElementType; 63 64 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 65 66 /** 67 * Table data for binary table HDUs. It has been thoroughly re-written for 1.18 to improve consistency, increase 68 * performance, make it easier to use, and to enhance. 69 * 70 * @see BinaryTableHDU 71 * @see AsciiTable 72 */ 73 @SuppressWarnings("deprecation") 74 public class BinaryTable extends AbstractTableData implements Cloneable { 75 76 /** For fixed-length columns */ 77 private static final char POINTER_NONE = 0; 78 79 /** FITS 32-bit pointer type for variable-sized columns */ 80 private static final char POINTER_INT = 'P'; 81 82 /** FITS 64-bit pointer type for variable-sized columns */ 83 private static final char POINTER_LONG = 'Q'; 84 85 /** Shape firs singleton / scalar entries */ 86 private static final int[] SINGLETON_SHAPE = new int[0]; 87 88 /** The substring convention marker */ 89 private static final String SUBSTRING_MARKER = ":SSTR"; 90 91 /** 92 * Describes the data type and shape stored in a binary table column. 93 */ 94 public static class ColumnDesc implements Cloneable { 95 96 private boolean warnedFlatten; 97 98 /** byte offset of element from row start */ 99 private int offset; 100 101 /** The number of primitive elements in the column */ 102 private int fitsCount; 103 104 /** The dimensions of the column */ 105 private int[] fitsShape = SINGLETON_SHAPE; 106 107 /** Shape on the Java side. Differs from the FITS TDIM shape for String and complex values. */ 108 private int[] legacyShape = SINGLETON_SHAPE; 109 110 /** Length of string elements */ 111 private int stringLength = -1; 112 113 /** The class array entries on the Java side. */ 114 private Class<?> base; 115 116 /** The FITS element class associated with the column. */ 117 private Class<?> fitsBase; 118 119 /** Heap pointer type actually used for locating variable-length column data on the heap */ 120 private char pointerType; 121 122 /** 123 * String component delimiter for substring arrays, for example as defined by the TFORM keyword that uses the 124 * substring array convention... 125 */ 126 private byte delimiter; 127 128 /** 129 * Is this a complex column. Each entry will be associated with a float[2] or double[2] 130 */ 131 private boolean isComplex; 132 133 /** 134 * Whether this column contains bit arrays. These take up to 8-times less space than logicals, which occupy a 135 * byte per value. 136 */ 137 private boolean isBits; 138 139 /** 140 * User defined column name 141 */ 142 private String name; 143 144 private Quantizer quant; 145 146 /** 147 * Creates a new column descriptor with default settings and 32-bit integer heap pointers. 148 */ 149 protected ColumnDesc() { 150 } 151 152 /** 153 * Creates a new column descriptor with default settings, and the specified type of heap pointers 154 * 155 * @param type The Java type of base elements that this column is designated to contain. For example 156 * <code>int.class</code> if the column will contain integers or arrays of integers. 157 * 158 * @throws FitsException if the base type is not one that can be used in binary table columns. 159 */ 160 private ColumnDesc(Class<?> type) throws FitsException { 161 this(); 162 163 base = type; 164 165 if (base == boolean.class) { 166 fitsBase = byte.class; 167 isBits = true; 168 } else if (base == Boolean.class) { 169 base = boolean.class; 170 fitsBase = byte.class; 171 } else if (base == String.class) { 172 fitsBase = byte.class; 173 } else if (base == ComplexValue.class) { 174 base = double.class; 175 fitsBase = double.class; 176 isComplex = true; 177 } else if (base == ComplexValue.Float.class) { 178 base = float.class; 179 fitsBase = float.class; 180 isComplex = true; 181 } else if (base.isPrimitive()) { 182 fitsBase = type; 183 if (base == char.class && FitsFactory.isUseUnicodeChars()) { 184 LOG.warning("char[] will be written as 16-bit integers (type 'I'), not as a ASCII bytes (type 'A')" 185 + " in the binary table. If that is not what you want, you should set FitsFactory.setUseUnicodeChars(false)."); 186 LOG.warning( 187 "Future releases will disable Unicode support by default as it is not supported by the FITS standard." 188 + " If you do want it still, use FitsFactory.setUseUnicodeChars(true) explicitly to keep the non-standard " 189 + " behavior as is."); 190 } 191 } else { 192 throw new TableException("Columns of type " + base + " are not supported."); 193 } 194 195 } 196 197 /** 198 * Creates a new column descriptor for the specified boxed Java type, and fixed array shape. The type may be any 199 * primitive type, or else <code>String.class</code>, <code>Boolean.class</code> (for FITS logicals), 200 * <code>ComplexValue.class</code> or <code>ComplexValue.Float.class</code> (for complex values with 64-bit and 201 * 32-bit precision, respectively). Whereas {@link Boolean} type columns will be stored as FITS logicals (1 202 * element per byte), <code>boolean</code> types will be stored as packed bits (with up to 8 bits per byte). 203 * 204 * @param base The Java type of base elements that this column is designated to contain. For example 205 * <code>int.class</code> if the column will contain integers or arrays of integers. 206 * @param dim the fixed dimensions of the table entries. For strings the trailing dimension must 207 * specify the fixed length of strings. 208 * 209 * @throws FitsException if the base type is not one that can be used in binary table columns. 210 * 211 * @see #createForScalars(Class) 212 * @see #createForStrings(int) 213 * @see #createForStrings(int, int[]) 214 * @see #createForVariableSize(Class) 215 * 216 * @since 1.18 217 */ 218 public ColumnDesc(Class<?> base, int... dim) throws FitsException { 219 this(base); 220 setBoxedShape(dim); 221 } 222 223 /** 224 * Sets a user-specified name for this column. The specified name will be used as the TTYPEn value for this 225 * column. 226 * 227 * @param value The new name for this column. 228 * 229 * @return itself, to support builder patterns. 230 * 231 * @throws IllegalArgumentException If the name contains characters outside of the ASCII range of 0x20 - 0x7F 232 * allowed by FITS. 233 * 234 * @see #name() 235 * @see #getDescriptor(String) 236 * @see #indexOf(String) 237 * @see #addColumn(ColumnDesc) 238 * 239 * @since 1.20 240 */ 241 public ColumnDesc name(String value) throws IllegalArgumentException { 242 HeaderCard.validateChars(value); 243 this.name = value; 244 return this; 245 } 246 247 /** 248 * Returns the name of this column, as it was stored or would be stored by a TTYPEn value in the FITS header. 249 * 250 * @return the name of this column 251 * 252 * @see #name(String) 253 * 254 * @since 1.20 255 */ 256 public String name() { 257 return this.name; 258 } 259 260 /** 261 * Returns the conversion between decimal and integer data representations for the column data. 262 * 263 * @return the quantizer that converts between floating-point and integer data representations, which may be 264 * <code>null</code>. 265 * 266 * @see #setQuantizer(Quantizer) 267 * 268 * @since 1.20 269 */ 270 public Quantizer getQuantizer() { 271 return quant; 272 } 273 274 /** 275 * Sets the conversion between decimal and integer data representations for the column data. If the table is 276 * read from a FITS input, the column's quantizer is automatically set if the Table HDU's header defines any of 277 * the TSCALn, TZEROn, or TNULLn keywords for the column. Users can override that by specifying another quatizer 278 * to use for the column, or dicard qunatizing by calling this method with a <code>null</code>argument. 279 * 280 * @param q the quantizer that converts between floating-point and integer data representations, or 281 * <code>null</code> to not use any quantization, and instead rely on the generic rounding for 282 * decimal-integer conversions for this column. 283 * 284 * @see #getQuantizer() 285 * 286 * @since 1.20 287 */ 288 public void setQuantizer(Quantizer q) { 289 this.quant = q; 290 } 291 292 /** 293 * Recalculate the FITS element count based on the shape of the data 294 */ 295 private void calcFitsCount() { 296 fitsCount = 1; 297 for (int size : fitsShape) { 298 fitsCount *= size; 299 } 300 } 301 302 /** 303 * Sets the shape of entries in the older Java array format of this library (used exclusively prior to 1.18). 304 * For complex columns, there is an extra <code>[2]</code> dimension, such that single complex values are stored 305 * as an array of <code>[2]</code>, and an array of <i>n</i> complex values are stored as arrays of 306 * <code>[n][2]</code>. Otherwise it's the same as {@link #setBoxedShape(int...)}. 307 * 308 * @param dim The Java dimensions for legacy arrays, such as returned by 309 * {@link BinaryTable#getElement(int, int)} 310 */ 311 private void setLegacyShape(int... dim) { 312 legacyShape = dim; 313 calcFitsShape(); 314 calcFitsCount(); 315 } 316 317 /** 318 * Sets the shape of entries as stored in the FITS header by hte TDIM keyword. 319 * 320 * @param dim The dimensions for the TDIM keyword in Java order (outer dimensions first), which is the reverse 321 * of FITS order (inner-dimensions first). 322 */ 323 private void setFitsShape(int... dim) { 324 fitsShape = dim; 325 calcLegacyShape(); 326 calcFitsCount(); 327 } 328 329 /** 330 * Sets the shape of boxed Java array entries. For complex columns, all single entries, including strings and 331 * complex values, have scalar shape <code>[]</code>, whereas an array of <i>n</i> have shape <code>[n]</code>. 332 * 333 * @param dim The Java dimensions for legacy arrays, such as returned by 334 * {@link BinaryTable#getElement(int, int)} 335 */ 336 private void setBoxedShape(int... dim) { 337 if (isComplex()) { 338 setFitsShape(dim); 339 } else { 340 setLegacyShape(dim); 341 } 342 } 343 344 /** 345 * Returns the maximum length of string elements contained in this colum, or -1 if this is not a string based 346 * column, or if there is no limit set to string size (e.g. in a variable-length column) 347 * 348 * @return the maximum length of string values stored in this column, or -1 if it is not as string column, or if 349 * its a string column containing strings of unconstrained variable length. 350 * 351 * @see #getEntryShape() 352 * 353 * @since 1.18 354 */ 355 public final int getStringLength() { 356 return stringLength; 357 } 358 359 /** 360 * Sets the maximum length of string elements in this column. 361 * 362 * @param len The fixed string length in bytes. 363 */ 364 private void setStringLength(int len) { 365 stringLength = len; 366 367 if (!isVariableSize()) { 368 calcFitsShape(); 369 calcFitsCount(); 370 } 371 } 372 373 /** 374 * Returns the string delimiter that separates packed substrings in variable-length string arrays. 375 * 376 * @return the delimiter byte value (usually between 0x20 and 0x7e) or 0 if no delimiter was set. 377 * 378 * @see #getStringLength() 379 */ 380 public final byte getStringDelimiter() { 381 return delimiter; 382 } 383 384 /** 385 * Creates a new column descriptor for a non-string based scalar column. The type may be any primitive type, or 386 * <code>Boolean.class</code> (for FITS logicals), <code>ComplexValue.class</code> or 387 * <code>ComplexValue.Float.class</code> (for complex values with 64-bit and 32-bit precision, respectively). 388 * Whereas {@link Boolean} type columns will be stored as FITS logicals (1 element per byte), 389 * <code>boolean</code> types will be stored as packed bits (with up to 8 bits per byte). 390 * 391 * @param type The Java type of base elements that this column is designated to contain. 392 * For example <code>int.class</code> if the column will contain integers 393 * or arrays of integers. It must not be <code>String.class</code>. To 394 * create scalar {@link String} columns use {@link #createForStrings(int)} 395 * instead. 396 * 397 * @return the new column descriptor. 398 * 399 * @throws IllegalArgumentException if the type is <code>String.class</code>, for which you should be using 400 * {@link #createForStrings(int)} instead. 401 * @throws FitsException if the base type is not one that can be used in binary table columns. 402 * 403 * @see #createForFixedArrays(Class, int[]) 404 * @see #createForVariableSize(Class) 405 * 406 * @since 1.18 407 */ 408 public static ColumnDesc createForScalars(Class<?> type) throws IllegalArgumentException, FitsException { 409 if (String.class.isAssignableFrom(type)) { 410 throw new IllegalArgumentException("Use the createStrings(int) method for scalar strings."); 411 } 412 return new ColumnDesc(type, SINGLETON_SHAPE); 413 } 414 415 /** 416 * Creates a new column descriptor for fixed-shape non-string arrays. The type may be any primitive type, or 417 * else <code>Boolean.class</code> (for FITS logicals), <code>ComplexValue.class</code> or 418 * <code>ComplexValue.Float.class</code> (for complex values with 64-bit and 32-bit precision, respectively). 419 * Whereas {@link Boolean} type columns will be stored as FITS logicals (1 element per byte), 420 * <code>boolean</code> types will be stored as packed bits (with up to 8 bits per byte). 421 * 422 * @param type The Java type of base elements that this column is designated to contain. 423 * For example <code>int.class</code> if the column will contain integers 424 * or arrays of integers. It must not be <code>String.class</code>. To 425 * create scalar {@link String} columns use {@link #createForStrings(int)} 426 * instead. 427 * @param dim the fixed dimensions of the table entries. For strings the trailing 428 * dimension must specify the fixed length of strings. 429 * 430 * @return the new column descriptor. 431 * 432 * @throws IllegalArgumentException if the type is <code>String.class</code>, for which you should be using 433 * {@link #createForStrings(int, int[])} instead. 434 * @throws FitsException if the base type is not one that can be used in binary table columns. 435 * 436 * @see #createForScalars(Class) 437 * @see #createForStrings(int) 438 * @see #createForStrings(int, int[]) 439 * @see #createForVariableSize(Class) 440 * 441 * @since 1.18 442 */ 443 public static ColumnDesc createForFixedArrays(Class<?> type, int... dim) 444 throws IllegalArgumentException, FitsException { 445 if (String.class.isAssignableFrom(type)) { 446 throw new IllegalArgumentException("Use the createStrings(int) method for scalar strings."); 447 } 448 return new ColumnDesc(type, dim); 449 } 450 451 /** 452 * Creates a new column descriptor for single string entries of fixed maximum length. 453 * 454 * @param len The fixed string length in bytes. 455 * 456 * @return the new column descriptor 457 * 458 * @throws FitsException if the base type is not one that can be used in binary table columns. 459 * 460 * @see #createForScalars(Class) 461 * @see #createForStrings(int, int[]) 462 * 463 * @since 1.18 464 */ 465 public static ColumnDesc createForStrings(int len) throws FitsException { 466 return createForStrings(len, SINGLETON_SHAPE); 467 } 468 469 /** 470 * Creates a new column descriptor for arrays of string entries of fixed maximum length. 471 * 472 * @param len The fixed string length in bytes. 473 * @param outerDims The shape of string arrays 474 * 475 * @return the new column descriptor 476 * 477 * @throws FitsException if the base type is not one that can be used in binary table columns. 478 * 479 * @see #createForVariableStringArrays(int) 480 * @see #createForStrings(int) 481 * 482 * @since 1.18 483 */ 484 public static ColumnDesc createForStrings(int len, int... outerDims) throws FitsException { 485 ColumnDesc c = new ColumnDesc(String.class); 486 c.setLegacyShape(outerDims); 487 c.setStringLength(len); 488 return c; 489 } 490 491 /** 492 * Creates a new column descriptor for variable-length arrays of fixed-length string entries. Each string 493 * component will occupy exactly <code>len</code> bytes. 494 * 495 * @param len The fixed string storage length in bytes. 496 * 497 * @return the new column descriptor 498 * 499 * @throws FitsException if the column could not be created. 500 * 501 * @see #createForDelimitedStringArrays(byte) 502 * @see #createForStrings(int, int[]) 503 * 504 * @since 1.18 505 */ 506 public static ColumnDesc createForVariableStringArrays(int len) throws FitsException { 507 ColumnDesc c = createForVariableSize(String.class); 508 c.setStringLength(len); 509 return c; 510 } 511 512 /** 513 * Creates a new column descriptor for variable-length arrays of delimited string entries. 514 * 515 * @param delim the byte value that delimits strings that are shorter than the storage length. It 516 * should be in the ASCII range of 0x20 through 0x7e. 517 * 518 * @return the new column descriptor 519 * 520 * @throws FitsException if the column could not be created. 521 * 522 * @see #createForDelimitedStringArrays(byte) 523 * @see #createForStrings(int, int[]) 524 * 525 * @since 1.18 526 */ 527 public static ColumnDesc createForDelimitedStringArrays(byte delim) throws FitsException { 528 ColumnDesc c = createForVariableStringArrays(-1); 529 c.setStringDelimiter(delim); 530 return c; 531 } 532 533 /** 534 * Creates a new column descriptor for variable length 1D arrays or strings. The type may be any primitive type, 535 * or else <code>String.class</code>, <code>Boolean.class</code> (for FITS logicals), 536 * <code>ComplexValue.class</code> or <code>ComplexValue.Float.class</code> (for complex values with 64-bit and 537 * 32-bit precision, respectively). Whereas {@link Boolean} type columns will be stored as FITS logicals (1 538 * element per byte), <code>boolean</code> types will be stored as packed bits (with up to 8 elements per byte). 539 * 540 * @param type The Java type of base elements that this column is designated to contain. For example 541 * <code>int.class</code> if the column will contain integers or arrays of integers. 542 * 543 * @return the new column descriptor 544 * 545 * @throws FitsException if the base type is not one that can be used in binary table columns. 546 * 547 * @see #createForScalars(Class) 548 * @see #createForStrings(int) 549 * @see #createForStrings(int, int[]) 550 * @see #ColumnDesc(Class, int[]) 551 * 552 * @since 1.18 553 */ 554 public static ColumnDesc createForVariableSize(Class<?> type) throws FitsException { 555 ColumnDesc c = new ColumnDesc(type); 556 c.setVariableSize(false); 557 return c; 558 } 559 560 /** 561 * Recalculate the legacy Java entry shape from the FITS shape (as stored by TDIM). Strings drop the last 562 * dimension from the FITS shape (which becomes the string length), while complex values add a dimension of 563 * <code>[2]</code> to the FITS shape, reflecting the shape of their real-valued components. 564 */ 565 private void calcLegacyShape() { 566 if (isString()) { 567 legacyShape = Arrays.copyOf(fitsShape, fitsShape.length - 1); 568 stringLength = fitsShape[fitsShape.length - 1]; 569 } else if (isComplex()) { 570 legacyShape = Arrays.copyOf(fitsShape, fitsShape.length + 1); 571 legacyShape[fitsShape.length] = 2; 572 } else { 573 legacyShape = fitsShape; 574 } 575 } 576 577 /** 578 * Recalculate the FITS storage shape (as reported by TDIM) from the legacy Java array shape 579 */ 580 private void calcFitsShape() { 581 if (isString()) { 582 fitsShape = Arrays.copyOf(legacyShape, legacyShape.length + 1); 583 fitsShape[legacyShape.length] = stringLength; 584 } else if (isComplex()) { 585 fitsShape = Arrays.copyOf(legacyShape, legacyShape.length - 1); 586 } else { 587 fitsShape = legacyShape; 588 } 589 } 590 591 /** 592 * Returns the size of table entries in their trailing dimension. 593 * 594 * @return the number of elemental components in the trailing dimension of table entries. 595 * 596 * @see #getLeadingShape() 597 */ 598 private int getLastFitsDim() { 599 return fitsShape[fitsShape.length - 1]; 600 } 601 602 @Override 603 public ColumnDesc clone() { 604 try { 605 ColumnDesc copy = (ColumnDesc) super.clone(); 606 fitsShape = fitsShape.clone(); 607 legacyShape = legacyShape.clone(); 608 609 // Model should not be changed... 610 return copy; 611 } catch (CloneNotSupportedException e) { 612 return null; 613 } 614 } 615 616 /** 617 * Specifies that this columns contains single (not array) boxed entrie, such as single primitives, strings, or 618 * complex values. 619 */ 620 private void setSingleton() { 621 setBoxedShape(SINGLETON_SHAPE); 622 } 623 624 /** 625 * Checks if this column contains single (scalar / non-array) elements only, including single strings or single 626 * complex values. 627 * 628 * @return <code>true</code> if the column contains individual elements of its type, or else <code>false</code> 629 * if it contains arrays. 630 * 631 * @since 1.18 632 */ 633 public final boolean isSingleton() { 634 if (isVariableSize()) { 635 return isString() ? (stringLength < 0 && delimiter == 0) : false; 636 } 637 638 if (isComplex()) { 639 return fitsShape.length == 0; 640 } 641 642 return legacyShape.length == 0; 643 } 644 645 /** 646 * Checks if this column contains logical values. FITS logicals can each hve <code>true</code>, 647 * <code>false</code> or <code>null</code> (undefined) values. It is the support for these undefined values that 648 * set it apart from typical booleans. Also, logicals are stored as one byte per element. So if using only 649 * <code>true</code>, <code>false</code> values without <code>null</code> bits will offer more compact storage 650 * (by up to a factor of 8). You can convert existing logical columns to bits via 651 * {@link BinaryTable#convertToBits(int)}. 652 * 653 * @return <code>true</code> if this column contains logical values. 654 * 655 * @see #isBits() 656 * @see BinaryTable#convertToBits(int) 657 * 658 * @since 1.18 659 */ 660 public final boolean isLogical() { 661 return base == Boolean.class || (base == boolean.class && !isBits); 662 } 663 664 /** 665 * Checks if this column contains only true boolean values (bits). Unlike logicals, bits can have only 666 * <code>true</code>, <code>false</code> values with no support for <code>null</code> , but offer more compact 667 * storage (by up to a factor of 8) than logicals. You can convert existing logical columns to bits via 668 * {@link BinaryTable#convertToBits(int)}. 669 * 670 * @return <code>true</code> if this column contains <code>true</code> / <code>false</code> bits only. 671 * 672 * @see #isLogical() 673 * @see BinaryTable#convertToBits(int) 674 * 675 * @since 1.18 676 */ 677 public final boolean isBits() { 678 return base == boolean.class && isBits; 679 } 680 681 /** 682 * Checks if this column stores ASCII strings. 683 * 684 * @return <code>true</code> if this column contains only strings. 685 * 686 * @see #isVariableSize() 687 */ 688 public final boolean isString() { 689 return base == String.class; 690 } 691 692 /** 693 * Checks if this column contains complex values. You can convert suitable columns of <code>float</code> or 694 * <code>double</code> elements to complex using {@link BinaryTable#setComplexColumn(int)}, as long as the last 695 * dimension is 2, ir if the variable-length columns contain even-number of values exclusively. 696 * 697 * @return <code>true</code> if this column contains complex values. 698 */ 699 public final boolean isComplex() { 700 return isComplex; 701 } 702 703 /** 704 * Checks if this column contains numerical values, such as any primitive number type (e.g. 705 * <code>nt.class</code> or <code>double.class</code>) or else a {@link ComplexValue} type. type. 706 * 707 * @return <code>true</code> if this column contains numerical data, including complex-valued data. String, 708 * bits, and FITS logicals are not numerical (but all other column types are). 709 * 710 * @since 1.20 711 */ 712 public final boolean isNumeric() { 713 return !isLogical() && !isBits() && !isString(); 714 } 715 716 /** 717 * Returns the Java array element type that is used in Java to represent data in this column. When accessing 718 * columns or their elements in the old way, through arrays, this is the type that arrays from the Java side 719 * will expect or provide. For example, when storing {@link String} values (regular or variable-sized), this 720 * will return <code>String.class</code>. Arrays returned by {@link BinaryTable#getColumn(int)}, 721 * {@link BinaryTable#getRow(int)}, and {@link BinaryTable#getElement(int, int)} will return arrays of this 722 * type, and the equivalent methods for setting data will expect arrays of this type as their argument. 723 * 724 * @return the Java class, arrays of which, packaged data for this column on the Java side. 725 * 726 * @deprecated Ambiguous, use {@link #getLegacyBase()} instead. It can be confusing since it is not clear if it 727 * refers to array element types used in FITS storage or on the java side when using the older 728 * array access, or if it refers to the class of entries in the main table, which may be heap 729 * pointers. It is also distinct from {@link #getElementClass()}, which returns the boxed type 730 * used by {@link BinaryTable#get(int, int)} or {@link BinaryTable#set(int, int, Object)}. 731 */ 732 @Deprecated 733 public Class<?> getBase() { 734 return getLegacyBase(); 735 } 736 737 /** 738 * Returns the primitive type that is used to store the data for this column in the FITS representation. This is 739 * the class for the actual data type, whether regularly shaped (multidimensional) arrays or variable length 740 * arrays (on the heap). For example, when storing {@link String} values (regular or variable-sized), this will 741 * return <code>byte.class</code>. 742 * 743 * @return the primitive class, in used for storing data in the FITS representation. 744 * 745 * @see #getLegacyBase() 746 * 747 * @since 1.18 748 */ 749 final Class<?> getFitsBase() { 750 return fitsBase; 751 } 752 753 /** 754 * <p> 755 * Returns the Java array element type that is used in Java to represent data in this column for the legacy 756 * table access methods. When accessing columns or their elements in the old way, through arrays, this is the 757 * type that arrays from the Java side will expect or provide. For example, when storing complex values (regular 758 * or variable-sized), this will return <code>float.class</code> or <code>double.class</code>. Arrays returned 759 * by {@link BinaryTable#getColumn(int)}, {@link BinaryTable#getRow(int)}, and 760 * {@link BinaryTable#getElement(int, int)} will return arrays of this type. 761 * </p> 762 * <p> 763 * This is different from {@link #getElementClass()}, which in turn returns the boxed type of objects returned 764 * by {@link BinaryTable#get(int, int)}. 765 * 766 * @return the Java class, arrays of which, packaged data for this column on the Java side. 767 * 768 * @see #getElementClass() 769 * 770 * @since 1.18 771 */ 772 public Class<?> getLegacyBase() { 773 return base; 774 } 775 776 /** 777 * (<i>for internal use</i>) Returns the primitive data class which is used for storing entries in the main 778 * (regular) table. For variable-sized columns, this will be the heap pointer class, not the FITS data class. 779 * 780 * @return the class in which main table entries are stored. 781 * 782 * @see #isVariableSize() 783 */ 784 private Class<?> getTableBase() { 785 return isVariableSize() ? pointerClass() : getFitsBase(); 786 } 787 788 /** 789 * Returns the dimensions of elements in this column. As of 1.18, this method returns a copy ot the array used 790 * internally, which is safe to modify. 791 * 792 * @return an array with the element dimensions. 793 * 794 * @deprecated (<i>for internal use</i>) Use {@link #getEntryShape()} instead. Not useful to users since it 795 * returns the dimensions of the primitive storage types, which is not always the dimension of 796 * table entries on the Java side. 797 */ 798 @Deprecated 799 public int[] getDimens() { 800 return fitsShape.clone(); 801 } 802 803 /** 804 * (<i>for internal use</i>) The dimension of elements in the FITS representation. 805 * 806 * @return the dimension of elements in the FITS representation. For example an array of string will be 2 807 * (number of string, number of bytes per string). 808 * 809 * @see #getEntryDimension() 810 */ 811 private int fitsDimension() { 812 return fitsShape.length; 813 } 814 815 /** 816 * Returns the boxed Java type of elements stored in a column. 817 * 818 * @return The java type of elements in the columns. For columns containing strings, FITS logicals, or complex 819 * values it will be <code>String.class</code>, <code>Boolean.class</code> or 820 * <code>ComplexValue.class</code> respectively. For all other column types the primitive class of 821 * the elements contained (e.g. <code>char.class</code>, <code>float.class</code>) is returned. 822 * 823 * @since 1.18 824 * 825 * @see ColumnDesc#getElementCount() 826 * @see ColumnDesc#getEntryShape() 827 * @see ColumnDesc#getLegacyBase() 828 */ 829 public final Class<?> getElementClass() { 830 if (isLogical()) { 831 return Boolean.class; 832 } 833 if (isComplex()) { 834 return ComplexValue.class; 835 } 836 return base; 837 } 838 839 /** 840 * Returns the dimensionality of the 'boxed' elements as returned by {@link BinaryTable#get(int, int)} or 841 * expected by {@link BinaryTable#set(int, int, Object)}. That is it returns the dimnesion of 'boxed' elements, 842 * such as strings or complex values, rather than the dimension of characters or real components stored in the 843 * FITS for these. 844 * 845 * @return the number of array dimensions in the 'boxed' Java type for this column. Variable-sized columns will 846 * always return 1. 847 * 848 * @see #getEntryShape() 849 * @see #getElementCount() 850 * 851 * @since 1.18 852 */ 853 public final int getEntryDimension() { 854 if (isVariableSize()) { 855 return 1; 856 } 857 return isString() ? legacyShape.length : fitsShape.length; 858 } 859 860 /** 861 * Returns the array shape of the 'boxed' elements as returned by {@link BinaryTable#get(int, int)} or expected 862 * by {@link BinaryTable#set(int, int, Object)}. That is it returns the array shape of 'boxed' elements, such as 863 * strings or complex values, rather than the shape of characters or real components stored in the FITS for 864 * these. 865 * 866 * @return the array sized along each of the dimensions in the 'boxed' Java type for this column, or 867 * <code>null</code> if the data is stored as variable-sized one-dimensional arrays of the boxed 868 * element type. (Note, that accordingly variable-length string columns containing single strings 869 * will thus return <code>{1}</code>, not <code>null</code>). 870 * 871 * @see #getEntryShape() 872 * @see #getElementCount() 873 * @see #isVariableSize() 874 * 875 * @since 1.18 876 */ 877 public final int[] getEntryShape() { 878 if (isVariableSize()) { 879 return null; 880 } 881 882 if (isComplex) { 883 return fitsShape.clone(); 884 } 885 886 return legacyShape.clone(); 887 } 888 889 /** 890 * Returns the number of primitive elements (sych as bytes) that constitute a Java element (such as a String) in 891 * this table. 892 * 893 * @return The number of primitives per Java element in the column, that is 1 for columns of primitive types, 2 894 * for complex-valued columns, or the number of bytes (characters) in a String element. 895 * Variable-length strings will return -1. 896 * 897 * @since 1.18 898 * 899 * @see #getElementCount() 900 * @see #getLegacyBase() 901 */ 902 public final int getElementWidth() { 903 if (isComplex()) { 904 return 2; 905 } 906 if (isString()) { 907 return getStringLength(); 908 } 909 return 1; 910 } 911 912 /** 913 * Returns the number of 'boxed' elements as returned by {@link BinaryTable#get(int, int)} or expected by 914 * {@link BinaryTable#set(int, int, Object)}. That is it returns the number of strings or complex values per 915 * table entry, rather than the number of of characters or real components stored in the FITS for these. 916 * 917 * @return the number of array elements in the 'boxed' Java type for this column, or -1 if the column contains 918 * elements of varying size. 919 * 920 * @see #getEntryShape() 921 * @see #getEntryDimension() 922 * @see #isVariableSize() 923 * 924 * @since 1.18 925 */ 926 public final int getElementCount() { 927 if (isVariableSize()) { 928 return isString() ? 1 : -1; 929 } 930 931 if (isString()) { 932 return fitsCount / getStringLength(); 933 } 934 935 return fitsCount; 936 } 937 938 /** 939 * Returns the number of primitive base elements for a given FITS element count. 940 * 941 * @param fitsLen the FITS element count, sucj a a number of integers, complex-values, or bits 942 * 943 * @return the number of Java primitives that will be used to represent the number of FITS values for 944 * this type of column. 945 * 946 * @see #getFitsBase() 947 */ 948 private int getFitsBaseCount(int fitsLen) { 949 if (isBits) { 950 return (fitsLen + Byte.SIZE - 1) / Byte.SIZE; 951 } 952 if (isComplex) { 953 return fitsLen << 1; 954 } 955 return fitsLen; 956 } 957 958 /** 959 * Returns the number of regular primitive table elements in this column. For example, variable-length columns 960 * will always return 2, and complex-valued columns will return twice the number of complex values stored in 961 * each table entry. 962 * 963 * @return the number of primitive table elements 964 * 965 * @since 1.18 966 */ 967 public final int getTableBaseCount() { 968 if (isVariableSize()) { 969 return 2; 970 } 971 return getFitsBaseCount(fitsCount); 972 } 973 974 /** 975 * Checks if this column contains entries of different size. Data for variable length coulmns is stored on the 976 * heap as one-dimemnsional arrays. As such information about the 'shape' of data is lost when they are stored 977 * that way. 978 * 979 * @return <code>true</code> if the column contains elements of variable size, or else <code>false</code> if all 980 * entries have the same size and shape. 981 */ 982 public final boolean isVariableSize() { 983 return pointerType != POINTER_NONE; 984 } 985 986 /** 987 * @deprecated (<i>for internal use</i>) This method should be private in the future. 988 * 989 * @return new instance of the array with space for the specified number of rows. 990 * 991 * @param nRow the number of rows to allocate the array for 992 */ 993 @Deprecated 994 public Object newInstance(int nRow) { 995 return ArrayFuncs.newInstance(getTableBase(), getTableBaseCount() * nRow); 996 } 997 998 /** 999 * @deprecated (<i>for internal use</i>) It may be reduced to private visibility in the future. Returns the 1000 * number of bytes that each element occupies in its FITS serialized form in the stored row 1001 * data. 1002 * 1003 * @return the number of bytes an element occupies in the FITS binary table data representation 1004 */ 1005 @Deprecated 1006 public int rowLen() { 1007 return getTableBaseCount() * ElementType.forClass(getTableBase()).size(); 1008 } 1009 1010 /** 1011 * Checks if this column used 64-bit heap pointers. 1012 * 1013 * @return <code>true</code> if the column uses 64-bit heap pointers, otherwise <code>false</code> 1014 * 1015 * @see #createForVariableSize(Class) 1016 * 1017 * @since 1.18 1018 */ 1019 public boolean hasLongPointers() { 1020 return pointerType == POINTER_LONG; 1021 } 1022 1023 /** 1024 * Returns the <code>TFORM</code><i>n</i> character code for the heap pointers in this column or 0 if this is 1025 * not a variable-sized column. 1026 * 1027 * @return <code>int.class</code> or <code>long.class</code> 1028 */ 1029 private char pointerType() { 1030 return pointerType; 1031 } 1032 1033 /** 1034 * Returns the primitive class used for sotring heap pointers for this column 1035 * 1036 * @return <code>int.class</code> or <code>long.class</code> 1037 */ 1038 private Class<?> pointerClass() { 1039 return pointerType == POINTER_LONG ? long.class : int.class; 1040 } 1041 1042 /** 1043 * Sets whether this column will contain variable-length data, rather than fixed-shape data. 1044 * 1045 * @param useLongPointers <code>true</code> to use 64-bit heap pointers for variable-length arrays or else 1046 * <code>false</code> to use 32-bit pointers. 1047 */ 1048 private void setVariableSize(boolean useLongPointers) { 1049 pointerType = useLongPointers ? POINTER_LONG : POINTER_INT; 1050 fitsCount = 2; 1051 fitsShape = new int[] {2}; 1052 legacyShape = fitsShape; 1053 stringLength = -1; 1054 } 1055 1056 /** 1057 * Sets a custom substring delimiter byte for variable length string arrays, between ASCII 0x20 and 0x7e. We 1058 * will however tolerate values outside of that range, but log an appropriate warning to alert users of the 1059 * violation of the standard. User's can either 'fix' it, or suppress the warning if they want to stick to their 1060 * guns. 1061 * 1062 * @param delim the delimiter byte value, between ASCII 0x20 and 0x7e (inclusive). 1063 * 1064 * @since 1.18 1065 */ 1066 private void setStringDelimiter(byte delim) { 1067 if (delim < FitsUtil.MIN_ASCII_VALUE || delim > FitsUtil.MAX_ASCII_VALUE) { 1068 LOG.warning("WARNING! Substring terminator byte " + (delim & FitsIO.BYTE_MASK) 1069 + " outside of the conventional range of " + FitsUtil.MIN_ASCII_VALUE + " through " 1070 + FitsUtil.MAX_ASCII_VALUE + " (inclusive)"); 1071 } 1072 delimiter = delim; 1073 } 1074 1075 /** 1076 * Checks if <code>null</code> array elements are permissible for this column. It is for strings (which map to 1077 * empty strings), and for logical columns, where they signify undefined values. 1078 * 1079 * @return <code>true</code> if <code>null</code> entries are considered valid for this column. 1080 */ 1081 private boolean isNullAllowed() { 1082 return isLogical() || isString(); 1083 } 1084 1085 /** 1086 * Parses the substring array convention from a TFORM value, to set string length (if desired) and a delimiter 1087 * in variable-length string arrays. 1088 * 1089 * @param tform the TFORM header value for this column 1090 * @param pos the parse position immediately after the 'A' 1091 * @param setLength Whether to use the substring definition to specify the max string component length, for 1092 * example because it is not defined otherwise by TDIM. 1093 */ 1094 private void parseSubstringConvention(String tform, ParsePosition pos, boolean setLength) { 1095 1096 if (setLength) { 1097 // Default string length... 1098 setStringLength(isVariableSize() ? -1 : fitsCount); 1099 } 1100 1101 // Parse substring array convention... 1102 if (pos.getIndex() >= tform.length()) { 1103 return; 1104 } 1105 1106 // Try 'rAw' format... 1107 try { 1108 int len = AsciiFuncs.parseInteger(tform, pos); 1109 if (setLength) { 1110 setStringLength(len); 1111 } 1112 return; 1113 } catch (Exception e) { 1114 // Keep going... 1115 } 1116 1117 // Find if and where is the ":SSTR" marker in the format 1118 int iSub = tform.indexOf(SUBSTRING_MARKER, pos.getIndex()); 1119 if (iSub < 0) { 1120 // No substring definition... 1121 return; 1122 } 1123 1124 pos.setIndex(iSub + SUBSTRING_MARKER.length()); 1125 1126 // Set the substring width.... 1127 try { 1128 int len = AsciiFuncs.parseInteger(tform, pos); 1129 if (setLength) { 1130 setStringLength(len); 1131 } 1132 } catch (Exception e) { 1133 LOG.warning("WARNING! Could not parse substring length from TFORM: [" + tform + "]"); 1134 } 1135 1136 // Parse substring array convention... 1137 if (pos.getIndex() >= tform.length()) { 1138 return; 1139 } 1140 1141 if (AsciiFuncs.extractChar(tform, pos) != '/') { 1142 return; 1143 } 1144 1145 try { 1146 setStringDelimiter((byte) AsciiFuncs.parseInteger(tform, pos)); 1147 } catch (NumberFormatException e) { 1148 // Warn if the delimiter is outside of the range supported by the convention. 1149 LOG.warning("WARNING! Could not parse substring terminator from TFORM: [" + tform + "]"); 1150 } 1151 } 1152 1153 private void appendSubstringConvention(StringBuffer tform) { 1154 if (getStringLength() > 0) { 1155 tform.append(SUBSTRING_MARKER); 1156 tform.append(getStringLength()); 1157 1158 if (delimiter != 0) { 1159 tform.append('/'); 1160 tform.append(new DecimalFormat("000").format(delimiter & FitsIO.BYTE_MASK)); 1161 } 1162 } 1163 } 1164 1165 /** 1166 * Returns the TFORM header value to use for this column. 1167 * 1168 * @return The TFORM value that describes this column 1169 * 1170 * @throws FitsException If the column itself is invalid. 1171 */ 1172 String getTFORM() throws FitsException { 1173 1174 StringBuffer tform = new StringBuffer(); 1175 1176 tform.append(isVariableSize() ? "1" + pointerType() : fitsCount); 1177 1178 if (base == int.class) { 1179 tform.append('J'); 1180 } else if (base == short.class) { 1181 tform.append('I'); 1182 } else if (base == byte.class) { 1183 tform.append('B'); 1184 } else if (base == char.class) { 1185 if (FitsFactory.isUseUnicodeChars()) { 1186 tform.append('I'); 1187 } else { 1188 tform.append('A'); 1189 } 1190 } else if (base == float.class) { 1191 tform.append(isComplex() ? 'C' : 'E'); 1192 } else if (base == double.class) { 1193 tform.append(isComplex() ? 'M' : 'D'); 1194 } else if (base == long.class) { 1195 tform.append('K'); 1196 } else if (isLogical()) { 1197 tform.append('L'); 1198 } else if (isBits()) { 1199 tform.append('X'); 1200 } else if (isString()) { 1201 tform.append('A'); 1202 if (isVariableSize()) { 1203 appendSubstringConvention(tform); 1204 } 1205 } else { 1206 throw new FitsException("Invalid column data class:" + base); 1207 } 1208 1209 return tform.toString(); 1210 } 1211 1212 /** 1213 * Returns the TDIM header value that descrives the shape of entries in this column 1214 * 1215 * @return the TDIM header value to use, or <code>null</code> if this column is not suited for a TDIM entry for 1216 * example because it is variable-sized, or because its entries are not multidimensional. . 1217 */ 1218 String getTDIM() { 1219 if (isVariableSize()) { 1220 return null; 1221 } 1222 1223 if (fitsShape.length < 2) { 1224 return null; 1225 } 1226 1227 StringBuffer tdim = new StringBuffer(); 1228 char prefix = '('; 1229 for (int i = fitsShape.length - 1; i >= 0; i--) { 1230 tdim.append(prefix); 1231 tdim.append(fitsShape[i]); 1232 prefix = ','; 1233 } 1234 tdim.append(')'); 1235 return tdim.toString(); 1236 } 1237 1238 private boolean setFitsType(char type) throws FitsException { 1239 switch (type) { 1240 case 'A': 1241 fitsBase = byte.class; 1242 base = String.class; 1243 break; 1244 1245 case 'X': 1246 fitsBase = byte.class; 1247 base = boolean.class; 1248 break; 1249 1250 case 'L': 1251 fitsBase = byte.class; 1252 base = boolean.class; 1253 break; 1254 1255 case 'B': 1256 fitsBase = byte.class; 1257 base = byte.class; 1258 break; 1259 1260 case 'I': 1261 fitsBase = short.class; 1262 base = short.class; 1263 break; 1264 1265 case 'J': 1266 fitsBase = int.class; 1267 base = int.class; 1268 break; 1269 1270 case 'K': 1271 fitsBase = long.class; 1272 base = long.class; 1273 break; 1274 1275 case 'E': 1276 case 'C': 1277 fitsBase = float.class; 1278 base = float.class; 1279 break; 1280 1281 case 'D': 1282 case 'M': 1283 fitsBase = double.class; 1284 base = double.class; 1285 break; 1286 1287 default: 1288 return false; 1289 } 1290 1291 return true; 1292 } 1293 } 1294 1295 /** 1296 * The enclosing binary table's properties 1297 * 1298 * @deprecated (<i>for internal use</i>) no longer used, and will be removed in the future. 1299 */ 1300 @Deprecated 1301 protected static class SaveState { 1302 /** 1303 * Create a new saved state 1304 * 1305 * @param columns the column descriptions to save 1306 * @param heap the heap to save 1307 * 1308 * @deprecated (<i>for internal use</i>) no longer in use. Will remove in the future. 1309 */ 1310 @Deprecated 1311 public SaveState(List<ColumnDesc> columns, FitsHeap heap) { 1312 } 1313 } 1314 1315 /** 1316 * Our own Logger instance, for nothing various non-critical issues. 1317 */ 1318 private static final Logger LOG = Logger.getLogger(BinaryTable.class.getName()); 1319 1320 /** 1321 * This is the area in which variable length column data lives. 1322 */ 1323 private FitsHeap heap; 1324 1325 /** 1326 * The heap start from the head of the HDU 1327 */ 1328 private long heapAddress; 1329 1330 /** 1331 * (bytes) Empty space to leave after the populated heap area for future additions. 1332 */ 1333 private int heapReserve; 1334 1335 /** 1336 * The original heap size (from the header) 1337 */ 1338 private int heapFileSize; 1339 1340 /** 1341 * A list describing each of the columns in the table 1342 */ 1343 private List<ColumnDesc> columns; 1344 1345 /** 1346 * The number of rows in the table. 1347 */ 1348 private int nRow; 1349 1350 /** 1351 * The length in bytes of each row. 1352 */ 1353 private int rowLen; 1354 1355 /** 1356 * Where the data is actually stored. 1357 */ 1358 private ColumnTable<?> table; 1359 1360 private FitsEncoder encoder; 1361 1362 /** 1363 * Creates an empty binary table, which can be populated with columns / rows as desired. 1364 */ 1365 public BinaryTable() { 1366 table = new ColumnTable<>(); 1367 columns = new ArrayList<>(); 1368 heap = new FitsHeap(0); 1369 nRow = 0; 1370 rowLen = 0; 1371 } 1372 1373 /** 1374 * Creates a binary table from an existing column table. <b>WARNING!</b>, as of 1.18 we no longer use the column 1375 * data extra state to carry information about an enclosing class, because it is horribly bad practice. You should 1376 * not use this constructor to create imperfect copies of binary tables. Rather, use {@link #copy()} if you want to 1377 * create a new binary table, which properly inherits <b>ALL</b> of the properties of an original one. As for this 1378 * constructor, you should assume that it will not use anything beyond what's available in any generic vanilla 1379 * column table. 1380 * 1381 * @param tab the column table to create the binary table from. It must be a regular column table 1382 * that contains regular data of scalar or fixed 1D arrays only (not heap pointers). 1383 * No information beyond what a generic vanilla column table provides will be used. 1384 * Column tables don't store imensions for their elements, and don't have 1385 * variable-sized entries. Thus, if the table was the used in another binary table to 1386 * store flattened multidimensional data, we'll detect that data as 1D arrays. Andm if 1387 * the table was used to store heap pointers for variable length arrays, we'll detect 1388 * these as regular <code>int[2]</code> or <code>long[2]</code> values. 1389 * 1390 * @deprecated DO NOT USE -- it will be removed in the future. 1391 * 1392 * @throws FitsException if the table could not be copied and threw a {@link nom.tam.util.TableException}, which 1393 * is preserved as the cause. 1394 * 1395 * @see #copy() 1396 */ 1397 @Deprecated 1398 public BinaryTable(ColumnTable<?> tab) throws FitsException { 1399 this(); 1400 1401 table = new ColumnTable<>(); 1402 nRow = tab.getNRows(); 1403 columns = new ArrayList<>(); 1404 1405 for (int i = 0; i < tab.getNCols(); i++) { 1406 int n = tab.getElementSize(i); 1407 ColumnDesc c = new ColumnDesc(tab.getElementClass(i), n > 1 ? new int[] {n} : SINGLETON_SHAPE); 1408 addFlattenedColumn(tab.getColumn(i), nRow, c, true); 1409 } 1410 1411 } 1412 1413 /** 1414 * Creates a binary table from a given FITS header description. The table columns are initialized but no data will 1415 * be available, at least initially. Data may be loaded later (e.g. deferred read mode), provided the table is 1416 * associated to an input (usually only if this constructor is called from a {@link Fits} object reading an input). 1417 * When the table has an input configured via a {@link Fits} object, the table entries may be accessed in-situ in 1418 * the file while in deferred read mode, but operations affecting significant portions of the table (e.g. retrieving 1419 * all data via {@link #getData()} or accessing entire columns) may load the data in memory. You can also call 1420 * {@link #detach()} any time to force loading the data into memory, so that alterations after that will not be 1421 * reflected in the original file, at least not unitl {@link #rewrite()} is called explicitly. 1422 * 1423 * @param header A FITS header describing what the binary table should look like. 1424 * 1425 * @throws FitsException if the specified header is not usable for a binary table 1426 * 1427 * @deprecated (<i>for internal use</i>) This constructor should only be called from a {@link Fits} 1428 * object reading an input; visibility may be reduced to the package level in the 1429 * future. 1430 * 1431 * @see #isDeferred() 1432 */ 1433 @Deprecated 1434 public BinaryTable(Header header) throws FitsException { 1435 String ext = header.getStringValue(Standard.XTENSION, Standard.XTENSION_IMAGE); 1436 1437 if (!ext.equalsIgnoreCase(Standard.XTENSION_BINTABLE) && !ext.equalsIgnoreCase(NonStandard.XTENSION_A3DTABLE)) { 1438 throw new FitsException( 1439 "Not a binary table header (XTENSION = " + header.getStringValue(Standard.XTENSION) + ")"); 1440 } 1441 1442 synchronized (this) { 1443 nRow = header.getIntValue(Standard.NAXIS2); 1444 } 1445 1446 long tableSize = nRow * header.getLongValue(Standard.NAXIS1); 1447 long paramSizeL = header.getLongValue(Standard.PCOUNT); 1448 long heapOffsetL = header.getLongValue(Standard.THEAP, tableSize); 1449 1450 // Subtract out the size of the regular table from 1451 // the heap offset. 1452 long heapSizeL = (tableSize + paramSizeL) - heapOffsetL; 1453 1454 if (heapSizeL < 0) { 1455 throw new FitsException("Inconsistent THEAP and PCOUNT"); 1456 } 1457 if (heapSizeL > Integer.MAX_VALUE) { 1458 throw new FitsException("Heap size > 2 GB"); 1459 } 1460 if (heapSizeL == 0L) { 1461 // There is no heap. Forget the offset 1462 heapAddress = 0; 1463 } 1464 1465 heapAddress = (int) heapOffsetL; 1466 heapFileSize = (int) heapSizeL; 1467 1468 int nCol = header.getIntValue(Standard.TFIELDS); 1469 1470 synchronized (this) { 1471 rowLen = 0; 1472 1473 columns = new ArrayList<>(); 1474 for (int col = 0; col < nCol; col++) { 1475 rowLen += processCol(header, col, rowLen); 1476 } 1477 1478 HeaderCard card = header.getCard(Standard.NAXIS1); 1479 card.setValue(rowLen); 1480 } 1481 } 1482 1483 /** 1484 * Creates a binary table from existing table data int row-major format. That is the first array index is the row 1485 * index while the second array index is the column index. 1486 * 1487 * @param rowColTable Row / column array. Scalars elements are wrapped in arrays of 1, s.t. a single 1488 * <code>int</code> elements is stored as <code>int[1]</code> at its 1489 * <code>[row][col]</code> index. 1490 * 1491 * @throws FitsException if the argument is not a suitable representation of data in rows. 1492 * 1493 * @deprecated The constructor is ambiguous, use {@link #fromRowMajor(Object[][])} instead. You can 1494 * have a column-major array that has no scalar primitives which would also be an 1495 * <code>Object[][]</code> and could be passed erroneously. 1496 */ 1497 @Deprecated 1498 public BinaryTable(Object[][] rowColTable) throws FitsException { 1499 this(); 1500 for (Object[] row : rowColTable) { 1501 addRow(row); 1502 } 1503 } 1504 1505 /** 1506 * Creates a binary table from existing table data in row-major format. That is the first array index is the row 1507 * index while the second array index is the column index; 1508 * 1509 * @param table Row / column array. Scalars elements are wrapped in arrays of 1, s.t. a single 1510 * <code>int</code> elements is stored as <code>int[1]</code> at its 1511 * <code>[row][col]</code> index. 1512 * 1513 * @return a new binary table with the data. The tables data may be partially independent from the 1514 * argument. Modifications to the table data, or that to the argument have undefined 1515 * effect on the other object. If it is important to decouple them, you can use a 1516 * {@link ArrayFuncs#deepClone(Object)} of your original data as an argument. 1517 * 1518 * @throws FitsException if the argument is not a suitable representation of FITS data in rows. 1519 * 1520 * @see #fromColumnMajor(Object[]) 1521 * 1522 * @since 1.18 1523 */ 1524 public static BinaryTable fromRowMajor(Object[][] table) throws FitsException { 1525 BinaryTable tab = new BinaryTable(); 1526 for (Object[] row : table) { 1527 tab.addRow(row); 1528 } 1529 return tab; 1530 } 1531 1532 /** 1533 * Create a binary table from existing data in column-major format order. 1534 * 1535 * @param columns array of columns. The data for scalar entries is a primive array. For all else, the 1536 * entry is an <code>Object[]</code> array of sorts. 1537 * 1538 * @throws FitsException if the data for the columns could not be used as coulumns 1539 * 1540 * @deprecated The constructor is ambiguous, use {@link #fromColumnMajor(Object[])} instead. One could 1541 * call this method with any row-major <code>Object[][]</code> table by mistake. 1542 * 1543 * @see #defragment() 1544 */ 1545 @Deprecated 1546 public BinaryTable(Object[] columns) throws FitsException { 1547 this(); 1548 1549 for (Object element : columns) { 1550 addColumn(element); 1551 } 1552 } 1553 1554 /** 1555 * Creates a binary table from existing data in column-major format order. 1556 * 1557 * @param columns array of columns. The data for scalar entries is a primive array. For all else, the entry 1558 * is an <code>Object[]</code> array of sorts. 1559 * 1560 * @return a new binary table with the data. The tables data may be partially independent from the 1561 * argument. Modifications to the table data, or that to the argument have undefined 1562 * effect on the other object. If it is important to decouple them, you can use a 1563 * {@link ArrayFuncs#deepClone(Object)} of your original data as an argument. 1564 * 1565 * @throws FitsException if the argument is not a suitable representation of FITS data in rows. 1566 * 1567 * @see #fromColumnMajor(Object[]) 1568 * 1569 * @since 1.18 1570 */ 1571 public static BinaryTable fromColumnMajor(Object[] columns) throws FitsException { 1572 BinaryTable t = new BinaryTable(); 1573 for (Object element : columns) { 1574 t.addColumn(element); 1575 } 1576 return t; 1577 } 1578 1579 @Override 1580 protected BinaryTable clone() { 1581 try { 1582 return (BinaryTable) super.clone(); 1583 } catch (CloneNotSupportedException e) { 1584 return null; 1585 } 1586 } 1587 1588 /** 1589 * Returns an independent copy of the binary table. 1590 * 1591 * @return a new binary that tnat contains an exact copy of us, but is completely independent. 1592 * 1593 * @throws FitsException if the table could not be copied 1594 * 1595 * @since 1.18 1596 */ 1597 public synchronized BinaryTable copy() throws FitsException { 1598 BinaryTable copy = clone(); 1599 1600 synchronized (copy) { 1601 if (table != null) { 1602 copy.table = table.copy(); 1603 } 1604 if (heap != null) { 1605 copy.heap = heap.copy(); 1606 } 1607 1608 copy.columns = new ArrayList<>(); 1609 for (ColumnDesc c : columns) { 1610 c = c.clone(); 1611 copy.columns.add(c); 1612 } 1613 } 1614 1615 return copy; 1616 } 1617 1618 /** 1619 * (<i>for internal use</i>) Discards all variable-length arrays from this table, that is all data stored on the 1620 * heap, and resets all heap descritors to (0,0). 1621 * 1622 * @since 1.19.1 1623 */ 1624 protected synchronized void discardVLAs() { 1625 for (int col = 0; col < columns.size(); col++) { 1626 ColumnDesc c = columns.get(col); 1627 1628 if (c.isVariableSize()) { 1629 for (int row = 0; row < nRow; row++) { 1630 table.setElement(row, col, c.hasLongPointers() ? new long[2] : new int[2]); 1631 } 1632 } 1633 } 1634 1635 heap = new FitsHeap(0); 1636 } 1637 1638 /** 1639 * Returns the number of bytes per regular table row 1640 * 1641 * @return the number of bytes in a regular table row. 1642 */ 1643 final synchronized int getRowBytes() { 1644 return rowLen; 1645 } 1646 1647 /** 1648 * @deprecated (<i>for internal use</i>) It may become a private method in the future. 1649 * 1650 * @param table the table to create the column data. 1651 * 1652 * @throws FitsException if the data could not be created. 1653 */ 1654 @Deprecated 1655 public static void createColumnDataFor(BinaryTable table) throws FitsException { 1656 synchronized (table) { 1657 table.createTable(table.nRow); 1658 } 1659 } 1660 1661 /** 1662 * @deprecated (<i>for internal use</i>) It may be reduced to private visibility in the future. Parse the 1663 * TDIMS value. If the TDIMS value cannot be deciphered a one-d array with the size given in 1664 * arrsiz is returned. 1665 * 1666 * @param tdims The value of the TDIMSn card. 1667 * 1668 * @return An int array of the desired dimensions. Note that the order of the tdims is the inverse of the 1669 * order in the TDIMS key. 1670 */ 1671 @Deprecated 1672 public static int[] parseTDims(String tdims) { 1673 if (tdims == null) { 1674 return null; 1675 } 1676 1677 // The TDIMs value should be of the form: "(i,j...)" 1678 int start = tdims.indexOf('('); 1679 1680 if (start < 0) { 1681 return null; 1682 } 1683 1684 int end = tdims.indexOf(')', start); 1685 if (end < 0) { 1686 end = tdims.length(); 1687 } 1688 1689 StringTokenizer st = new StringTokenizer(tdims.substring(start + 1, end), ","); 1690 int dim = st.countTokens(); 1691 1692 if (dim > 0) { 1693 int[] dims = new int[dim]; 1694 for (int i = dim; --i >= 0;) { 1695 dims[i] = Integer.parseInt(st.nextToken().trim()); 1696 } 1697 return dims; 1698 } 1699 1700 return null; 1701 } 1702 1703 /** 1704 * <p> 1705 * Adds a column of complex values stored as the specified decimal type of components in the FITS. While you can 1706 * also use {@link #addColumn(Object)} to add complex values, that method will always add them as 64-bit 1707 * double-precision values. So, this method is provided to allow users more control over how they want their complex 1708 * data be stored. 1709 * </p> 1710 * <p> 1711 * The new column will be named as "Column <i>n</i>" (where <i>n</i> is the 1-based index of the column) by default, 1712 * which can be changed by {@link ColumnDesc#name(String)} after. 1713 * </p> 1714 * 1715 * @param o A {@link ComplexValue} or an array (possibly multi-dimensional) thereof. 1716 * @param decimalType <code>float.class</code> or <code>double.class</code> (all other values default to 1717 * <code>double.class</code>). 1718 * 1719 * @return the number of column in the table including the new column. 1720 * 1721 * @throws FitsException if the object contains values other than {@link ComplexValue} types or if the array is not 1722 * suitable for storing in the FITS, e.g. because it is multi-dimensional but varying in 1723 * shape / size. 1724 * 1725 * @since 1.18 1726 * 1727 * @see #addColumn(Object) 1728 */ 1729 public int addComplexColumn(Object o, Class<?> decimalType) throws FitsException { 1730 int col = columns.size(); 1731 int eSize = addColumn(ArrayFuncs.complexToDecimals(o, decimalType)); 1732 ColumnDesc c = columns.get(col); 1733 c.isComplex = true; 1734 c.setLegacyShape(c.fitsShape); 1735 return eSize; 1736 } 1737 1738 /** 1739 * <p> 1740 * Adds a column of string values (one per row), optimized for storage size. Unlike {@link #addColumn(Object)}, 1741 * which always store strings in fixed format, this method will automatically use variable-length columns for 1742 * storing the strings if their lengths vary sufficiently to make that form of storage more efficient, or if the 1743 * array contains nulls (which may be defined later). 1744 * </p> 1745 * <p> 1746 * The new column will be named as "Column <i>n</i>" (where <i>n</i> is the 1-based index of the column) by default, 1747 * which can be changed by {@link ColumnDesc#name(String)} after. 1748 * </p> 1749 * 1750 * @param o A 1D string array, with 1 string element per table row. The array may contain 1751 * <code>null</code> entries, in which case variable-length columns will be used, since 1752 * these may be defined later... 1753 * 1754 * @return the number of column in the table including the new column. 1755 * 1756 * @throws FitsException if the object contains values other than {@link ComplexValue} types or if the array is not 1757 * suitable for storing in the FITS, e.g. because it is multi-dimensional but varying in 1758 * shape / size. 1759 * 1760 * @since 1.18 1761 * 1762 * @see #addColumn(Object) 1763 */ 1764 public int addStringColumn(String[] o) throws FitsException { 1765 checkRowCount(o); 1766 1767 ColumnDesc c = new ColumnDesc(String.class); 1768 1769 // Check if we should be using variable-length strings 1770 // (provided its a scalar string column with sufficiently varied strings sizes to make it worth.. 1771 int min = FitsUtil.minStringLength(o); 1772 int max = FitsUtil.maxStringLength(o); 1773 1774 if (max - min > 2 * ElementType.forClass(c.pointerClass()).size()) { 1775 c = ColumnDesc.createForVariableSize(String.class); 1776 return addVariableSizeColumn(o, c); 1777 } 1778 1779 c = ColumnDesc.createForStrings(max); 1780 return addFlattenedColumn(o, o.length, c, false); 1781 } 1782 1783 /** 1784 * <p> 1785 * Adds a column of bits. This uses much less space than if adding boolean values as logicals (the default behaviot 1786 * of {@link #addColumn(Object)}, since logicals take up 1 byte per element, whereas bits are really single bits. 1787 * </p> 1788 * <p> 1789 * The new column will be named as "Column <i>n</i>" (where <i>n</i> is the 1-based index of the column) by default, 1790 * which can be changed by {@link ColumnDesc#name(String)} after. 1791 * </p> 1792 * 1793 * @param o An any-dimensional array of <code>boolean</code> values. 1794 * 1795 * @return the number of column in the table including the new column. 1796 * 1797 * @throws IllegalArgumentException if the argument is not an array of <code>boolean</code> values. 1798 * @throws FitsException if the object is not an array of <code>boolean</code> values. 1799 * 1800 * @since 1.18 1801 * 1802 * @see #addColumn(Object) 1803 */ 1804 public int addBitsColumn(Object o) throws FitsException { 1805 if (ArrayFuncs.getBaseClass(o) != boolean.class) { 1806 throw new IllegalArgumentException("Not an array of booleans: " + o.getClass()); 1807 } 1808 return addColumn(o, false); 1809 } 1810 1811 /** 1812 * <p> 1813 * Adds a new empty column to the table to the specification. This is useful when the user may want ot have more 1814 * control on how columns are configured before calling {@link #addRow(Object[])} to start populating. The new 1815 * column will be named as "Column <i>n</i>" (where <i>n</i> is the 1-based index of the column) by default, unless 1816 * already named otherwise. 1817 * </p> 1818 * <p> 1819 * The new column will be named as "Column <i>n</i>" (where <i>n</i> is the 1-based index of the column) by default, 1820 * which can be changed by {@link ColumnDesc#name(String)} after. 1821 * </p> 1822 * 1823 * @param descriptor the column descriptor 1824 * 1825 * @return the number of table columns after the addition 1826 * 1827 * @throws IllegalStateException if the table already contains data rows that prevent the addition of empty 1828 * comlumns. 1829 * 1830 * @see #addRow(Object[]) 1831 * @see ColumnDesc#name(String) 1832 */ 1833 public synchronized int addColumn(ColumnDesc descriptor) throws IllegalStateException { 1834 if (nRow != 0) { 1835 throw new IllegalStateException("Cannot add empty columns to table already containing data rows"); 1836 } 1837 1838 descriptor.offset = rowLen; 1839 rowLen += descriptor.rowLen(); 1840 1841 if (descriptor.name() == null) { 1842 // Set default column name; 1843 descriptor.name(TableHDU.getDefaultColumnName(columns.size())); 1844 } 1845 columns.add(descriptor); 1846 return columns.size(); 1847 } 1848 1849 /** 1850 * <p> 1851 * Adds a new column with the specified data array, with some default mappings. This method will always use 1852 * double-precision representation for {@link ComplexValue}-based data, and will represent <code>boolean</code> 1853 * based array data as one-byte-per element FITS logical values (for back compatibility). It will also store strings 1854 * as fixed sized (sized for the longest string element contained). 1855 * </p> 1856 * <p> 1857 * The new column will be named as "Column <i>n</i>" (where <i>n</i> is the 1-based index of the column) by default, 1858 * which can be changed by {@link ColumnDesc#name(String)} after. 1859 * </p> 1860 * <p> 1861 * If you want other complex-valued representations use {@link #addComplexColumn(Object, Class)} instead, and if you 1862 * want to pack <code>boolean</code>-based data more efficiently (using up to 8 times less space), use 1863 * {@link #addBitsColumn(Object)} instead, or else convert the column to bits afterwards using 1864 * {@link #convertToBits(int)}. And, if you want to allow storing strings more effiently in variable-length columns, 1865 * you should use {@link #addStringColumn(String[])} instead. 1866 * </p> 1867 * <p> 1868 * As of 1.18, the argument can be a boxed primitive for a coulmn containing a single scalar-valued entry (row). 1869 * </p> 1870 * 1871 * @see #addVariableSizeColumn(Object) 1872 * @see #addComplexColumn(Object, Class) 1873 * @see #addBitsColumn(Object) 1874 * @see #convertToBits(int) 1875 * @see #addStringColumn(String[]) 1876 * @see ColumnDesc#name(String) 1877 */ 1878 @Override 1879 public int addColumn(Object o) throws FitsException { 1880 return addColumn(o, true); 1881 } 1882 1883 private synchronized int checkRowCount(Object o) throws FitsException { 1884 if (!o.getClass().isArray()) { 1885 throw new TableException("Not an array: " + o.getClass().getName()); 1886 } 1887 1888 int rows = Array.getLength(o); 1889 1890 if (columns.size() != 0 && rows != nRow) { 1891 throw new TableException("Mismatched number of rows: " + rows + ", expected " + nRow); 1892 } 1893 1894 return rows; 1895 } 1896 1897 /** 1898 * Like {@link #addColumn(Object)}, but allows specifying whether we use back compatible mode. This mainly just 1899 * affects how <code>boolean</code> arrays are stored (as logical bytes in compatibility mode, or as packed bits 1900 * otherwise). 1901 * 1902 * @param Whether to add the column in a back compatibility mode with versions prior to 1.18. If <code>true</code> 1903 * <code>boolean</code> arrays will stored as logical bytes, otherwise as packed bits. 1904 */ 1905 private int addColumn(Object o, boolean compat) throws FitsException { 1906 o = ArrayFuncs.objectToArray(o, compat); 1907 1908 int rows = checkRowCount(o); 1909 1910 ColumnDesc c = new ColumnDesc(ArrayFuncs.getBaseClass(o)); 1911 1912 if (ArrayFuncs.getBaseClass(o) == ComplexValue.class) { 1913 o = ArrayFuncs.complexToDecimals(o, double.class); 1914 c.isComplex = true; 1915 } 1916 1917 try { 1918 int[] dim = ArrayFuncs.checkRegularArray(o, c.isNullAllowed()); 1919 1920 if (c.isString()) { 1921 c.setStringLength(FitsUtil.maxStringLength(o)); 1922 } 1923 1924 if (c.isComplex) { 1925 // Drop the railing 2 dimension, keep only outer dims... 1926 dim = Arrays.copyOf(dim, dim.length - 1); 1927 o = ArrayFuncs.flatten(o); 1928 } 1929 1930 if (dim.length <= 1) { 1931 c.setSingleton(); 1932 } else { 1933 int[] shape = new int[dim.length - 1]; 1934 System.arraycopy(dim, 1, shape, 0, shape.length); 1935 c.setLegacyShape(shape); 1936 o = ArrayFuncs.flatten(o); 1937 } 1938 } catch (IllegalArgumentException e) { 1939 c.setVariableSize(false); 1940 return addVariableSizeColumn(o, c); 1941 } 1942 // getBaseClass() prevents heterogeneous columns, so no need to catch ClassCastException here. 1943 1944 return addFlattenedColumn(o, rows, c, compat); 1945 } 1946 1947 /** 1948 * <p> 1949 * Adds a new variable-length data column, populating it with the specified data object. Unlike 1950 * {@link #addColumn(Object)} which will use fixed-size data storage provided the data allows it, this method forces 1951 * the use of variable-sized storage regardless of the data layout -- for example to accommodate addiing rows / 1952 * elements of different sized at a later time. 1953 * </p> 1954 * <p> 1955 * The new column will be named as "Column <i>n</i>" (where <i>n</i> is the 1-based index of the column) by default, 1956 * which can be changed by {@link ColumnDesc#name(String)} after. 1957 * </p> 1958 * 1959 * @param o An array containing one entry per row. Multi-dimensional entries will be flattened to 1D 1960 * for storage on the heap. 1961 * 1962 * @return the number of table columns after the addition. 1963 * 1964 * @throws FitsException if the column could not be created as requested. 1965 * 1966 * @see #addColumn(Object) 1967 * @see #addColumn(ColumnDesc) 1968 * @see ColumnDesc#createForVariableSize(Class) 1969 * @see ColumnDesc#isVariableSize() 1970 * 1971 * @since 1.18 1972 */ 1973 public int addVariableSizeColumn(Object o) throws FitsException { 1974 Class<?> base = ArrayFuncs.getBaseClass(o); 1975 ColumnDesc c = ColumnDesc.createForVariableSize(base); 1976 return addVariableSizeColumn(o, c); 1977 } 1978 1979 /** 1980 * Adds a new column with data directly, without performing any checks on the data. This should only be use 1981 * internally, after ansuring the data integrity and suitability for this table. 1982 * 1983 * @param o the column data, whose integrity was verified previously 1984 * @param rows the number of rows the data contains (in flattened form) 1985 * @param c the new column's descriptor 1986 * 1987 * @return the number of table columns after the addition 1988 * 1989 * @throws FitsException if the data is not the right type or format for internal storage. 1990 */ 1991 private synchronized int addDirectColumn(Object o, int rows, ColumnDesc c) throws FitsException { 1992 c.offset = rowLen; 1993 rowLen += c.rowLen(); 1994 1995 // Load any deferred data (we will not be able to do that once we alter the column structure) 1996 ensureData(); 1997 1998 // Set the default column name 1999 c.name(TableHDU.getDefaultColumnName(columns.size())); 2000 2001 table.addColumn(o, c.getTableBaseCount()); 2002 columns.add(c); 2003 2004 if (nRow == 0) { 2005 // Set the table row count to match first colum 2006 nRow = rows; 2007 } 2008 2009 return columns.size(); 2010 } 2011 2012 private int addVariableSizeColumn(Object o, ColumnDesc c) throws FitsException { 2013 checkRowCount(o); 2014 2015 Object[] array = (Object[]) o; 2016 2017 o = Array.newInstance(c.pointerClass(), array.length * 2); 2018 2019 for (int i = 0; i < array.length; i++) { 2020 boolean multi = c.isComplex() ? array[i] instanceof Object[][] : array[i] instanceof Object[]; 2021 2022 if (multi) { 2023 boolean canBeComplex = false; 2024 2025 if (c.getFitsBase() == float.class || c.getFitsBase() == double.class) { 2026 int[] dim = ArrayFuncs.getDimensions(array[i]); 2027 if (dim[dim.length - 1] == 2) { 2028 canBeComplex = true; 2029 } 2030 } 2031 2032 if (!canBeComplex && !c.warnedFlatten) { 2033 LOG.warning("Table entries of " + array[i].getClass() 2034 + " will be stored as 1D arrays in variable-length columns. " 2035 + "Array shape(s) and intermittent null subarrays (if any) will be lost."); 2036 2037 c.warnedFlatten = true; 2038 } 2039 } 2040 2041 Object p = putOnHeap(c, array[i], null); 2042 System.arraycopy(p, 0, o, 2 * i, 2); 2043 } 2044 2045 return addDirectColumn(o, array.length, c); 2046 } 2047 2048 /** 2049 * Add a column where the data is already flattened. 2050 * 2051 * @param o The new column data. This should be a one-dimensional primitive array. 2052 * @param dims The dimensions of an element in the column, or null for singleton (scalar) columns 2053 * 2054 * @return the new column size 2055 * 2056 * @throws FitsException if the array could not be flattened 2057 * 2058 * @deprecated (<i>for internal use</i>) No longer used, will be removed in the future 2059 */ 2060 @Deprecated 2061 public int addFlattenedColumn(Object o, int... dims) throws FitsException { 2062 ColumnDesc c = new ColumnDesc(ArrayFuncs.getBaseClass(o)); 2063 2064 try { 2065 ArrayFuncs.checkRegularArray(o, c.isNullAllowed()); 2066 } catch (IllegalArgumentException e) { 2067 throw new FitsException("Irregular array: " + o.getClass() + ": " + e.getMessage(), e); 2068 } 2069 2070 if (c.isString()) { 2071 c.setStringLength(FitsUtil.maxStringLength(o)); 2072 } 2073 2074 int n = 1; 2075 2076 c.setLegacyShape(dims); 2077 for (int dim : dims) { 2078 n *= dim; 2079 } 2080 2081 int rows = Array.getLength(o) / n; 2082 2083 return addFlattenedColumn(o, rows, c, true); 2084 } 2085 2086 /** 2087 * Checks that a flattened column has a compatible size for storing in a fixed-width column. It will also log a 2088 * warning if the storage size of the object is zero. 2089 * 2090 * @param c the column descriptor 2091 * @param o the column data 2092 * 2093 * @throws FitsException if the data is not the right size for the column 2094 */ 2095 private synchronized void checkFlattenedColumnSize(ColumnDesc c, Object o) throws FitsException { 2096 if (c.getTableBaseCount() == 0) { 2097 LOG.warning("Elements of column + " + columns.size() + " have zero storage size."); 2098 } else if (columns.size() > 0) { 2099 // Check that the number of rows is consistent. 2100 int l = Array.getLength(o); 2101 if (nRow > 0 && l != nRow * c.getTableBaseCount()) { 2102 throw new TableException("Mismatched element count " + l + ", expected " + (nRow * c.getTableBaseCount())); 2103 } 2104 } 2105 } 2106 2107 /** 2108 * This function is needed since we had made addFlattenedColumn public so in principle a user might have called it 2109 * directly. 2110 * 2111 * @param o The new column data. This should be a one-dimensional primitive array. 2112 * @param c The column description 2113 * 2114 * @return the new column size 2115 * 2116 * @throws FitsException if the data type, format, or element count is inconsistent with this table. 2117 */ 2118 private int addFlattenedColumn(Object o, int rows, ColumnDesc c, boolean compat) throws FitsException { 2119 // For back compatibility this method will add boolean values as logicals always... 2120 if (compat) { 2121 c.isBits = false; 2122 } 2123 2124 if (c.isBits) { 2125 // Special handling for bits, which have to be segmented into bytes... 2126 boolean[] bits = (boolean[]) o; 2127 o = FitsUtil.bitsToBytes(bits, bits.length / rows); 2128 } else { 2129 o = javaToFits1D(c, o); 2130 } 2131 2132 checkFlattenedColumnSize(c, o); 2133 2134 return addDirectColumn(o, rows, c); 2135 } 2136 2137 /** 2138 * <p> 2139 * Adds a row to the table. If this is the first row in a new table, fixed-length columns will be created from the 2140 * data type automatically. If you want more control over the column formats, you may want to specify columns 2141 * beforehand such as: 2142 * </p> 2143 * 2144 * <pre> 2145 * BinaryTable table = new BinaryTable(); 2146 * 2147 * // A column containing 64-bit floating point scalar values, 1 per row... 2148 * table.addColumn(ColumnDesc.createForScalars(double.class)); 2149 * 2150 * // A column containing 5x4 arrays of single-precision complex values... 2151 * table.addColumn(ColumnDesc.createForArrays(ComplexValue.Float.class, 5, 4) 2152 * 2153 * // A column containing Strings of variable length using 32-bit heap pointers... 2154 * table.addColumn(ColumnDesc.creatForVariableStrings(false); 2155 * </pre> 2156 * <p> 2157 * For scalar columns of primitive types, the argument may be the corresponding java boxed type (new style), or a 2158 * primitive array of 1 (old style). Thus, you can write either: 2159 * </p> 2160 * 2161 * <pre> 2162 * table.addRow(1, 3.14159265); 2163 * </pre> 2164 * <p> 2165 * or, 2166 * </p> 2167 * 2168 * <pre> 2169 * table.addRow(new Object[] { new int[] {1}, new double[] {3.14159265} }; 2170 * </pre> 2171 * 2172 * @see #addColumn(ColumnDesc) 2173 */ 2174 @Override 2175 public synchronized int addRow(Object[] o) throws FitsException { 2176 if (columns.isEmpty()) { 2177 for (Object element : o) { 2178 if (element == null) { 2179 throw new TableException("Prototype row may not contain null"); 2180 } 2181 2182 Class<?> cl = element.getClass(); 2183 2184 if (cl.isArray()) { 2185 if (cl.getComponentType().isPrimitive() && Array.getLength(element) == 1) { 2186 // Primitives of 1 (e.g. short[1]) are wrapped and should be added as is. 2187 addColumn(element); 2188 } else { 2189 // Wrap into array of 1, as leading dimension becomes the number of rows, which must be 1... 2190 Object wrapped = Array.newInstance(element.getClass(), 1); 2191 Array.set(wrapped, 0, element); 2192 addColumn(wrapped); 2193 } 2194 } else { 2195 addColumn(ArrayFuncs.objectToArray(element, true)); 2196 } 2197 } 2198 2199 return 1; 2200 } 2201 2202 if (o.length != columns.size()) { 2203 throw new TableException("Mismatched row size: " + o.length + ", expected " + columns.size()); 2204 } 2205 2206 ensureData(); 2207 2208 Object[] flatRow = new Object[getNCols()]; 2209 2210 for (int i = 0; i < flatRow.length; i++) { 2211 ColumnDesc c = columns.get(i); 2212 if (c.isVariableSize()) { 2213 flatRow[i] = putOnHeap(c, o[i], null); 2214 } else { 2215 flatRow[i] = javaToFits1D(c, ArrayFuncs.flatten(o[i])); 2216 2217 int nexp = c.getElementCount(); 2218 if (c.stringLength > 0) { 2219 nexp *= c.stringLength; 2220 } 2221 2222 if (Array.getLength(flatRow[i]) != nexp) { 2223 throw new IllegalArgumentException("Mismatched element count for column " + i + ": got " 2224 + Array.getLength(flatRow[i]) + ", expected " + nexp); 2225 } 2226 } 2227 } 2228 2229 table.addRow(flatRow); 2230 nRow++; 2231 2232 return nRow; 2233 } 2234 2235 @Override 2236 public synchronized void deleteColumns(int start, int len) throws FitsException { 2237 ensureData(); 2238 2239 table.deleteColumns(start, len); 2240 2241 ArrayList<ColumnDesc> remain = new ArrayList<>(columns.size() - len); 2242 rowLen = 0; 2243 2244 for (int i = 0; i < columns.size(); i++) { 2245 if (i < start || i >= start + len) { 2246 ColumnDesc c = columns.get(i); 2247 c.offset = rowLen; 2248 rowLen += c.rowLen(); 2249 remain.add(c); 2250 } 2251 } 2252 columns = remain; 2253 } 2254 2255 @Override 2256 public synchronized void deleteRows(int row, int len) throws FitsException { 2257 ensureData(); 2258 table.deleteRows(row, len); 2259 nRow -= len; 2260 } 2261 2262 /** 2263 * Returns the Java type of elements returned or expected by the older srray-based access methods. It can be 2264 * confusing, because: 2265 * <ul> 2266 * <li>Columns with variable sized entries report <code>int.class</code> or <code>long.class</code> regardless of 2267 * data type.</li> 2268 * <li>Regular logical and bit columns bith report <code>boolean.class</code>.</li> 2269 * <li>Regular complex valued columns report <code>float.class</code> or <code>double.class</code>.</li> 2270 * </ul> 2271 * 2272 * @return the types in the table, not the underlying types (e.g., for varying length arrays or booleans). 2273 * 2274 * @deprecated (<i>for internal use</i>) Ambiguous, use {@link ColumnDesc#getElementClass()} instead. Will remove in 2275 * the future. 2276 */ 2277 @Deprecated 2278 public synchronized Class<?>[] getBases() { 2279 return table.getBases(); 2280 } 2281 2282 /** 2283 * <p> 2284 * Returns the data for a particular column in as an array of elements. See {@link #addColumn(Object)} for more 2285 * information about the format of data elements in general. 2286 * </p> 2287 * 2288 * @param col The zero-based column index. 2289 * 2290 * @return an array of primitives (for scalar columns), or else an <code>Object[]</code> array, or 2291 * possibly <code>null</code> 2292 * 2293 * @throws FitsException if the table could not be accessed 2294 * 2295 * @see #setColumn(int, Object) 2296 * @see #getElement(int, int) 2297 * @see #getNCols() 2298 */ 2299 @Override 2300 public synchronized Object getColumn(int col) throws FitsException { 2301 ColumnDesc c = columns.get(col); 2302 2303 if (!c.isVariableSize() && c.fitsDimension() == 0 && !c.isComplex()) { 2304 return getFlattenedColumn(col); 2305 } 2306 2307 ensureData(); 2308 2309 Object[] data = null; 2310 2311 for (int i = 0; i < nRow; i++) { 2312 Object e = getElement(i, col); 2313 if (data == null) { 2314 data = (Object[]) Array.newInstance(e.getClass(), nRow); 2315 } 2316 data[i] = e; 2317 } 2318 2319 return data; 2320 } 2321 2322 /** 2323 * Returns the Java index of the first column by the specified name. 2324 * 2325 * @param name the name of the column (case sensitive). 2326 * 2327 * @return The column index, or else -1 if this table does not contain a column by the specified name. 2328 * 2329 * @see #getDescriptor(String) 2330 * @see ColumnDesc#name(String) 2331 * 2332 * @since 1.20 2333 */ 2334 public int indexOf(String name) { 2335 for (int col = 0; col < columns.size(); col++) { 2336 if (name.equals(getDescriptor(col).name())) { 2337 return col; 2338 } 2339 } 2340 return -1; 2341 } 2342 2343 @Override 2344 protected synchronized ColumnTable<?> getCurrentData() { 2345 return table; 2346 } 2347 2348 @Override 2349 public ColumnTable<?> getData() throws FitsException { 2350 return (ColumnTable<?>) super.getData(); 2351 } 2352 2353 /** 2354 * Returns the dimensions of elements in each column. 2355 * 2356 * @return an array of arrays with the dimensions of each column's data. 2357 * 2358 * @see ColumnDesc#getDimens() 2359 * 2360 * @deprecated (<i>for internal use</i>) Use {@link ColumnDesc#getEntryShape()} to access the shape of Java elements 2361 * individually for columns instead. Not useful to users since it returns the dimensions of the 2362 * primitive storage types, which is not always the dimension of elements on the Java side (notably 2363 * for string entries). 2364 */ 2365 @Deprecated 2366 public int[][] getDimens() { 2367 int[][] dimens = new int[columns.size()][]; 2368 for (int i = 0; i < dimens.length; i++) { 2369 dimens[i] = columns.get(i).getDimens(); 2370 } 2371 return dimens; 2372 } 2373 2374 /** 2375 * @deprecated (<i>for internal use</i>) It may be private in the future. 2376 * 2377 * @return An array with flattened data, in which each column's data is represented by a 1D array 2378 * 2379 * @throws FitsException if the reading of the data failed. 2380 */ 2381 @Deprecated 2382 public Object[] getFlatColumns() throws FitsException { 2383 ensureData(); 2384 return table.getColumns(); 2385 } 2386 2387 /** 2388 * @deprecated (<i>for internal use</i>) It may be reduced to private visibility in the future. 2389 * 2390 * @return column in flattened format. This is sometimes useful for fixed-sized columns. 2391 * Variable-sized columns will still return an <code>Object[]</code> array in which 2392 * each entry is the variable-length data for a row. 2393 * 2394 * @param col the column to flatten 2395 * 2396 * @throws FitsException if the column could not be flattened 2397 */ 2398 @Deprecated 2399 public synchronized Object getFlattenedColumn(int col) throws FitsException { 2400 if (!validColumn(col)) { 2401 throw new TableException("Invalid column index " + col + " in table of " + getNCols() + " columns"); 2402 } 2403 2404 ColumnDesc c = columns.get(col); 2405 if (c.isVariableSize()) { 2406 throw new TableException("Cannot flatten variable-sized column data"); 2407 } 2408 2409 ensureData(); 2410 2411 if (c.isBits()) { 2412 boolean[] bits = new boolean[nRow * c.fitsCount]; 2413 for (int i = 0; i < nRow; i++) { 2414 boolean[] seg = (boolean[]) fitsToJava1D(c, table.getElement(i, col), c.fitsCount, false); 2415 System.arraycopy(seg, 0, bits, i * c.fitsCount, c.fitsCount); 2416 } 2417 return bits; 2418 } 2419 2420 return fitsToJava1D(c, table.getColumn(col), 0, false); 2421 } 2422 2423 /** 2424 * <p> 2425 * Reserves space for future addition of rows at the end of the regular table. In effect, this pushes the heap to 2426 * start at an offset value, leaving a gap between the main table and the heap in the FITS file. If your table 2427 * contains variable-length data columns, you may also want to reserve extra heap space for these via 2428 * {@link #reserveHeapSpace(int)}. 2429 * </p> 2430 * <p> 2431 * Note, that (C)FITSIO, as of version 4.4.0, has no proper support for offset heaps, and so you may want to be 2432 * careful using this function as the resulting FITS files, while standard, may not be readable by other tools due 2433 * to their own lack of support. Note, however, that you may also use this function to undo an offset heap with an 2434 * argument <=0; 2435 * </p> 2436 * 2437 * @param rows The number of future rows fow which space should be reserved (relative to the current table size) for 2438 * future additions, or <=0 to ensure that the heap always follows immediately after the main 2439 * table, e.g. for better (C)FITSIO interoperability. 2440 * 2441 * @see #reserveHeapSpace(int) 2442 * 2443 * @since 1.19.1 2444 */ 2445 public synchronized void reserveRowSpace(int rows) { 2446 heapAddress = rows > 0 ? getRegularTableSize() + (long) rows * getRowBytes() : 0; 2447 } 2448 2449 /** 2450 * Reserves space in the file at the end of the heap for future heap growth (e.g. different/longer or new VLA 2451 * entries). You may generally want to call this along with {@link #reserveRowSpace(int)} if yuor table contains 2452 * variable-length columns, to ensure storage for future data in these. You may call with <=0 to discards any 2453 * previously reserved space. 2454 * 2455 * @param bytes The number of bytes of unused space to reserve at the end of the heap, e.g. for future modifications 2456 * or additions, when writing the data to file. 2457 * 2458 * @see #reserveRowSpace(int) 2459 * 2460 * @since 1.19.1 2461 */ 2462 public synchronized void reserveHeapSpace(int bytes) { 2463 heapReserve = Math.max(0, bytes); 2464 } 2465 2466 /** 2467 * Returns the address of the heap from the star of the HDU in the file. 2468 * 2469 * @return (bytes) the start of the heap area from the beginning of the HDU. 2470 */ 2471 final synchronized long getHeapAddress() { 2472 long tableSize = getRegularTableSize(); 2473 return heapAddress > tableSize ? heapAddress : tableSize; 2474 } 2475 2476 /** 2477 * Returns the offset from the end of the main table 2478 * 2479 * @return the offset to the heap 2480 */ 2481 final long getHeapOffset() { 2482 return getHeapAddress() - getRegularTableSize(); 2483 } 2484 2485 /** 2486 * It returns the heap size for storing in the FITS, which is the larger of the actual space occupied by the current 2487 * heap, or the original heap size based on the header when the HDU was read from an input. In the former case it 2488 * will also include heap space reserved for future additions. 2489 * 2490 * @return (byte) the size of the heap in the FITS file. 2491 * 2492 * @see #compact() 2493 * @see #reserveHeapSpace(int) 2494 */ 2495 private synchronized int getHeapSize() { 2496 if (heap != null && heap.size() + heapReserve > heapFileSize) { 2497 return heap.size() + heapReserve; 2498 } 2499 return heapFileSize; 2500 } 2501 2502 /** 2503 * @return the size of the heap -- including the offset from the end of the table data, and reserved space after. 2504 */ 2505 synchronized long getParameterSize() { 2506 return getHeapOffset() + getHeapSize(); 2507 } 2508 2509 /** 2510 * Returns an empty row for the table. Such model rows are useful when low-level reading binary tables from an input 2511 * row-by-row. You can simply all {@link nom.tam.util.ArrayDataInput#readArrayFully(Object)} to populate it with 2512 * data from a stream. You may also use model rows to add additional rows to an existing table. 2513 * 2514 * @return a row that may be used for direct i/o to the table. 2515 * 2516 * @deprecated (<i>for internal use</i>) Use {@link #getElement(int, int)} instead for low-level reading of tables 2517 * in deferred mode. Not recommended for uses because it requires a deep understanding of how data 2518 * (especially varialbe length columns) are represented in the FITS. Will reduce visibility to 2519 * private in the future. 2520 */ 2521 @Deprecated 2522 public Object[] getModelRow() { 2523 Object[] modelRow = new Object[columns.size()]; 2524 for (int i = 0; i < modelRow.length; i++) { 2525 ColumnDesc c = columns.get(i); 2526 if (c.fitsDimension() < 2) { 2527 modelRow[i] = Array.newInstance(c.getTableBase(), c.getTableBaseCount()); 2528 } else { 2529 modelRow[i] = Array.newInstance(c.getTableBase(), c.fitsShape); 2530 } 2531 } 2532 return modelRow; 2533 } 2534 2535 @Override 2536 public int getNCols() { 2537 return columns.size(); 2538 } 2539 2540 @Override 2541 public synchronized int getNRows() { 2542 return nRow; 2543 } 2544 2545 /** 2546 * Reads a regular table element in the main table from the input. This method should never be called unless we have 2547 * a random-accessible input associated, which is a requirement for deferred read mode. 2548 * 2549 * @param o The array element to populate 2550 * @param c the column descriptor 2551 * @param row the zero-based row index of the element 2552 * 2553 * @throws IOException If there was an I/O error accessing the input 2554 * @throws FitsException If there was some other error 2555 */ 2556 private synchronized void readTableElement(Object o, ColumnDesc c, int row) throws IOException, FitsException { 2557 @SuppressWarnings("resource") 2558 RandomAccess in = getRandomAccessInput(); 2559 2560 synchronized (this) { 2561 in.position(getFileOffset() + row * (long) rowLen + c.offset); 2562 } 2563 2564 if (c.isLogical()) { 2565 in.readArrayFully(o); 2566 } else { 2567 in.readImage(o); 2568 } 2569 } 2570 2571 /** 2572 * Returns an unprocessed element from the table as a 1D array of the elements that are stored in the regular table 2573 * data, whithout reslving heap references. That is this call will return flattened versions of multidimensional 2574 * arrays, and will return only the heap locator (offset and size) for variable-sized columns. 2575 * 2576 * @return a particular element from the table but do no processing of this element (e.g., 2577 * dimension conversion or extraction of variable length array elements/) 2578 * 2579 * @param row The row of the element. 2580 * @param col The column of the element. 2581 * 2582 * @deprecated (<i>for internal use</i>) Will reduce visibility in the future. 2583 * 2584 * @throws FitsException if the operation failed 2585 */ 2586 @Deprecated 2587 public synchronized Object getRawElement(int row, int col) throws FitsException { 2588 if (!validRow(row) || !validColumn(col)) { 2589 throw new TableException("No such element (" + row + "," + col + ")"); 2590 } 2591 2592 if (table == null) { 2593 try { 2594 ColumnDesc c = columns.get(col); 2595 Object e = c.newInstance(1); 2596 readTableElement(e, c, row); 2597 return e; 2598 } catch (IOException e) { 2599 throw new FitsException("Error reading from input: " + e.getMessage(), e); 2600 } 2601 } 2602 2603 ensureData(); 2604 return table.getElement(row, col); 2605 } 2606 2607 /** 2608 * Returns a table element as a Java array. Consider using the more Java-friendly {@link #get(int, int)} or one of 2609 * the scalar access methods with implicit type conversion support. 2610 * 2611 * @see #get(int, int) 2612 * @see #getLogical(int, int) 2613 * @see #getNumber(int, int) 2614 * @see #getLong(int, int) 2615 * @see #getDouble(int, int) 2616 * @see #getString(int, int) 2617 */ 2618 @Override 2619 public Object getElement(int row, int col) throws FitsException { 2620 return getElement(row, col, false); 2621 } 2622 2623 /** 2624 * Returns a a table entry, with control over how FITS logical values are to be handled. 2625 * 2626 * @param row zero-based row index 2627 * @param col zero-based column index 2628 * @param isEnhanced Whether logicals should be returned as {@link Boolean} (rather than <code>boolean</code>) 2629 * and complex values as {@link ComplexValue} (rather than <code>float[2]</code> or 2630 * <code>double[2]</code>), or arrays thereof. Methods prior to 1.18 should set this to 2631 * <code>false</code> for back compatible behavior. 2632 * 2633 * @return The entry as a primitive array, or {@link String}, {@link Boolean} or {@link ComplexValue}, 2634 * or arrays thereof. 2635 * 2636 * @throws FitsException If the requested element could not be accessed. 2637 */ 2638 private Object getElement(int row, int col, boolean isEnhanced) throws FitsException { 2639 if (!validRow(row) || !validColumn(col)) { 2640 throw new TableException("No such element (" + row + "," + col + ")"); 2641 } 2642 2643 ColumnDesc c = columns.get(col); 2644 Object o = getRawElement(row, col); 2645 2646 if (c.isVariableSize()) { 2647 return getFromHeap(c, o, isEnhanced); 2648 } 2649 2650 o = fitsToJava1D(c, o, c.isBits() ? c.fitsCount : 0, isEnhanced); 2651 2652 if (c.legacyShape.length > 1) { 2653 return ArrayFuncs.curl(o, c.legacyShape); 2654 } 2655 2656 return o; 2657 } 2658 2659 /** 2660 * Returns a table element as an array of the FITS storage type. Similar to the original 2661 * {@link #getElement(int, int)}, except that FITS logicals are returned as arrays of <code>Boolean</code> (rather 2662 * than <code>boolean</code>), bits are returned as arrays of <code>boolean</code>, and complex values are returned 2663 * as arrays of {@link ComplexValue} rather than arrays of <code>double[2]</code> or <code>float[2]</code>. 2664 * Singleton (scalar) table elements are not boxed to an enclosing Java type (unlike {@link #get(int, int)}), an 2665 * instead returned as arrays of just one element. For example, a single logical as a <code>Boolean[1]</code>, a 2666 * single float as a <code>float[1]</code> or a single double-precision complex value as 2667 * <code>ComplexValue[1]</code>. 2668 * 2669 * @param row zero-based row index 2670 * @param col zero-based column index 2671 * 2672 * @return The table entry as an array of the stored Java type, without applying any type or quantization 2673 * conversions. 2674 * 2675 * @see #getArrayElementAs(int, int, Class) 2676 * @see #get(int, int) 2677 * 2678 * @since 1.20 2679 */ 2680 public Object getArrayElement(int row, int col) { 2681 return getElement(row, col, true); 2682 } 2683 2684 /** 2685 * <p> 2686 * Returns a numerical table element as an array of a specific underlying other numerical type. Similar 2687 * {@link #getArrayElement(int, int)} except that table entries are converted to the specified array type before 2688 * returning. If an integer-decimal conversion is involved, it will be performed through the column's quantizer (if 2689 * any) or else via a simple rounding as necessary. 2690 * </p> 2691 * <p> 2692 * For example, if you have an <code>short</code>-type column, and you want is an array of <code>double</code> 2693 * values that are represented by the 16-bit integers, then the conversion will use the column's quantizer scaling 2694 * and offset before returning the result either as an array of doubles, and the designated <code>short</code> 2695 * blanking values will be converted to NaNs. 2696 * </p> 2697 * 2698 * @param row zero-based row index 2699 * @param col zero-based column index 2700 * @param asType The desired underlying type, a primitive class or a {@link ComplexValue} type 2701 * for appropriate numerical arrays (with a trailing Java dimension of 2 for 2702 * the real/imaginary pairs). 2703 * 2704 * @return An array of the desired type (e.g. <code>double[][]</code> if 2705 * <code>asType</code> is <code>double.class</code> and the column contains 2D 2706 * arrays of some numerical type). 2707 * 2708 * @throws IllegalArgumentException if the numerical conversion is not possible for the given column type or if the 2709 * type argument is not a supported numerical primitive or {@link ComplexValue} 2710 * type. 2711 * 2712 * @see #getArrayElement(int, int) 2713 * 2714 * @since 1.20 2715 */ 2716 public Object getArrayElementAs(int row, int col, Class<?> asType) throws IllegalArgumentException { 2717 ColumnDesc c = getDescriptor(col); 2718 Object e = getElement(row, col, true); 2719 return asType.isAssignableFrom(c.getFitsBase()) ? e : ArrayFuncs.convertArray(e, asType, c.getQuantizer()); 2720 } 2721 2722 /** 2723 * <p> 2724 * Returns a table element using the usual Java boxing for primitive scalar (singleton) entries, or packaging 2725 * complex values as {@link ComplexValue}, or as appropriate primitive or object arrays. FITS string columns return 2726 * {@link String} values. Logical (<code>boolean</code> columns will return a {@link Boolean}, which may be 2727 * <code>null</code> if undefined (as per the FITS standard). Multibit FITS bits colums return arrays of 2728 * <code>boolean</code>. 2729 * </p> 2730 * <p> 2731 * As opposed to {@link #getElement(int, int)} scalar (singleton) values are not wrapped into primitive arrays, but 2732 * return either a singular object, such as a ({@link String}, or a {@link ComplexValue}, or a boxed Java primitive. 2733 * Thus, columns containing single <code>short</code> entries will return the selected element as a {@link Short}, 2734 * or columns containing single <code>double</code> values will return the element as a {@link Double} and so on. 2735 * </p> 2736 * <p> 2737 * Array columns will return the expected arrays of primitive values, or arrays of one of the mentioned types. Note 2738 * however, that logical arrays are returned as arrays of {@link Boolean}, e.g. <code>Boolean[][]</code>, <b>not</b> 2739 * <code>boolean[][]</code>. This is because FITS allows <code>null</code> values for logicals beyond <code> 2740 * true</code> and <code>false</code>, which is reproduced by the boxed type, but not by the primitive type. FITS 2741 * columns of bits (generally preferrably to logicals if support for <code>null</code> values is not required) will 2742 * return arrays of <code>boolean</code>. 2743 * </p> 2744 * <p> 2745 * Columns containing multidimensional arrays, will return the expected multidimensional array of the above 2746 * mentioned types for the FITS storage type. You can then convert numerical arrays to other types as required for 2747 * your application via {@link ArrayFuncs#convertArray(Object, Class, Quantizer)}, including any appropriate 2748 * quantization for the colummn (see {@link ColumnDesc#getQuantizer()}). 2749 * </p> 2750 * 2751 * @param row the zero-based row index 2752 * @param col the zero-based column index 2753 * 2754 * @return the element, either as a Java boxed type (for scalar entries), a singular Java Object, or 2755 * as a (possibly multi-dimensional) array of {@link String}, {@link Boolean}, 2756 * {@link ComplexValue}, or primitives. 2757 * 2758 * @throws FitsException if the element could not be obtained 2759 * 2760 * @see #getNumber(int, int) 2761 * @see #getLogical(int, int) 2762 * @see #getString(int, int) 2763 * @see #getArrayElementAs(int, int, Class) 2764 * @see #set(int, int, Object) 2765 * 2766 * @since 1.18 2767 */ 2768 public Object get(int row, int col) throws FitsException { 2769 ColumnDesc c = columns.get(col); 2770 Object e = getElement(row, col, true); 2771 return (c.isSingleton() && e.getClass().isArray()) ? Array.get(e, 0) : e; 2772 } 2773 2774 /** 2775 * Returns the numerical value, if possible, for scalar elements. Scalar numerical columns return the boxed type of 2776 * their primitive type. Thus, a column of <code>long</code> values will return {@link Long}, whereas a column of 2777 * <code>float</code> values will return a {@link Float}. Logical columns will return 1 if <code>true</code> or 0 if 2778 * <code>false</code>, or <code>null</code> if undefined. Array columns and other column types will throw an 2779 * exception. 2780 * 2781 * @param row the zero-based row index 2782 * @param col the zero-based column index 2783 * 2784 * @return the number value of the specified scalar entry 2785 * 2786 * @throws FitsException if the element could not be obtained 2787 * @throws ClassCastException if the specified column in not a numerical scalar type. 2788 * @throws NumberFormatException if the it's a string column but the entry does not seem to be a number 2789 * 2790 * @see #getDouble(int, int) 2791 * @see #getLong(int, int) 2792 * @see #get(int, int) 2793 * 2794 * @since 1.18 2795 */ 2796 public final Number getNumber(int row, int col) throws FitsException, ClassCastException, NumberFormatException { 2797 Object o = get(row, col); 2798 if (o instanceof String) { 2799 try { 2800 return Long.parseLong((String) o); 2801 } catch (NumberFormatException e) { 2802 return Double.parseDouble((String) o); 2803 } 2804 } 2805 if (o instanceof Boolean) { 2806 return ((Boolean) o) ? 1 : 0; 2807 } 2808 return (Number) o; 2809 } 2810 2811 /** 2812 * <p> 2813 * Returns the decimal value, if possible, of a scalar table entry. See {@link #getNumber(int, int)} for more 2814 * information on the conversion process. 2815 * </p> 2816 * <p> 2817 * Since version 1.20, if the column has a quantizer and stores integer elements, the conversion to double-precision 2818 * will account for the quantization of the column, if any, and will return NaN if the stored integer is the 2819 * designated blanking value (if any). To bypass quantization, you can use {@link #getNumber(int, int)} instead 2820 * followed by {@link Number#doubleValue()} to to get the stored integer values as a double. 2821 * </p> 2822 * 2823 * @param row the zero-based row index 2824 * @param col the zero-based column index 2825 * 2826 * @return the number value of the specified scalar entry 2827 * 2828 * @throws FitsException if the element could not be obtained 2829 * @throws ClassCastException if the specified column in not a numerical scalar type. 2830 * 2831 * @see #getNumber(int, int) 2832 * @see #getLong(int, int) 2833 * @see #get(int, int) 2834 * @see ColumnDesc#getQuantizer() 2835 * 2836 * @since 1.18 2837 */ 2838 public final double getDouble(int row, int col) throws FitsException, ClassCastException { 2839 Number n = getNumber(row, col); 2840 2841 if (!(n instanceof Float || n instanceof Double)) { 2842 Quantizer q = getDescriptor(col).getQuantizer(); 2843 if (q != null) { 2844 return q.toDouble(n.longValue()); 2845 } 2846 } 2847 2848 return n == null ? Double.NaN : n.doubleValue(); 2849 } 2850 2851 /** 2852 * <p> 2853 * Returns a 64-bit integer value, if possible, of a scalar table entry. Boolean columns will return 1 if 2854 * <code>true</code> or 0 if <code>false</code>, or throw a {@link NullPointerException} if undefined. See 2855 * {@link #getNumber(int, int)} for more information on the conversion process of the stored data element. 2856 * </p> 2857 * <p> 2858 * Additionally, since version 1.20, if the column has a quantizer and stores floating-point elements, the 2859 * conversion to integer will include the quantization, and NaN values will be converted to the designated integer 2860 * blanking values. To bypass quantization, you can use {@link #getNumber(int, int)} instead followed by 2861 * {@link Number#longValue()} to to get the stored floating point values rounded directly to a long. 2862 * </p> 2863 * 2864 * @param row the zero-based row index 2865 * @param col the zero-based column index 2866 * 2867 * @return the 64-bit integer number value of the specified scalar table entry. 2868 * 2869 * @throws FitsException if the element could not be obtained 2870 * @throws ClassCastException if the specified column in not a numerical scalar type. 2871 * @throws IllegalStateException if the column contains a undefined (blanking value), such as a {@link Double#NaN} 2872 * when no quantizer is set for the column, or a {@link Boolean} <code>null</code> 2873 * value. 2874 * 2875 * @see #getNumber(int, int) 2876 * @see #getDouble(int, int) 2877 * @see #get(int, int) 2878 * 2879 * @since 1.18 2880 */ 2881 public final long getLong(int row, int col) throws FitsException, ClassCastException, IllegalStateException { 2882 Number n = getNumber(row, col); 2883 2884 if (n instanceof Float || n instanceof Double) { 2885 Quantizer q = getDescriptor(col).getQuantizer(); 2886 if (q != null) { 2887 return q.toLong(n.doubleValue()); 2888 } 2889 } 2890 2891 if (Double.isNaN(n.doubleValue())) { 2892 throw new IllegalStateException("Cannot convert NaN to long without Quantizer"); 2893 } 2894 return n.longValue(); 2895 } 2896 2897 /** 2898 * Returns the boolean value, if possible, for scalar elements. It will will return<code>true</code>, or 2899 * <code>false</code>, or <code>null</code> if undefined. Numerical columns will return <code>null</code> if the 2900 * corresponding decimal value is NaN, or <code>false</code> if the value is 0, or else <code>true</code> for all 2901 * non-zero values (just like in C). 2902 * 2903 * @param row the zero-based row index 2904 * @param col the zero-based column index 2905 * 2906 * @return the boolean value of the specified scalar entry, or <code>null</code> if undefined. 2907 * 2908 * @throws ClassCastException if the specified column in not a scalar boolean type. 2909 * @throws FitsException if the element could not be obtained 2910 * 2911 * @see #get(int, int) 2912 * 2913 * @since 1.18 2914 */ 2915 @SuppressFBWarnings(value = "NP_BOOLEAN_RETURN_NULL", justification = "null has specific meaning here") 2916 public final Boolean getLogical(int row, int col) throws FitsException, ClassCastException { 2917 Object o = get(row, col); 2918 if (o == null) { 2919 return null; 2920 } 2921 2922 if (o instanceof Number) { 2923 Number n = (Number) o; 2924 if (Double.isNaN(n.doubleValue())) { 2925 return null; 2926 } 2927 return n.longValue() != 0; 2928 } 2929 2930 if (o instanceof Character) { 2931 char c = (Character) o; 2932 if (c == 'T' || c == 't' || c == '1') { 2933 return true; 2934 } 2935 if (c == 'F' || c == 'f' || c == '0') { 2936 return false; 2937 } 2938 return null; 2939 } 2940 2941 if (o instanceof String) { 2942 return FitsUtil.parseLogical((String) o); 2943 } 2944 2945 return (Boolean) o; 2946 } 2947 2948 /** 2949 * Returns the string value, if possible, for scalar elements. All scalar columns will return the string 2950 * representation of their values, while <code>byte[]</code> and <code>char[]</code> are converted to appropriate 2951 * strings. 2952 * 2953 * @param row the zero-based row index 2954 * @param col the zero-based column index 2955 * 2956 * @return the string representatiof the specified table entry 2957 * 2958 * @throws ClassCastException if the specified column contains array elements other than <code>byte[]</code> or 2959 * <code>char[]</code> 2960 * @throws FitsException if the element could not be obtained 2961 * 2962 * @see #get(int, int) 2963 * 2964 * @since 1.18 2965 */ 2966 public final String getString(int row, int col) throws FitsException, ClassCastException { 2967 ColumnDesc c = columns.get(col); 2968 Object value = get(row, col); 2969 2970 if (value == null) { 2971 return "null"; 2972 } 2973 2974 if (!value.getClass().isArray()) { 2975 return value.toString(); 2976 } 2977 2978 if (c.fitsDimension() > 1) { 2979 throw new ClassCastException("Cannot convert multi-dimensional array element to String"); 2980 } 2981 2982 if (value instanceof char[]) { 2983 return String.valueOf((char[]) value).trim(); 2984 } 2985 if (value instanceof byte[]) { 2986 return AsciiFuncs.asciiString((byte[]) value).trim(); 2987 } 2988 2989 throw new ClassCastException("Cannot convert " + value.getClass().getName() + " to String."); 2990 } 2991 2992 @Override 2993 public Object[] getRow(int row) throws FitsException { 2994 if (!validRow(row)) { 2995 throw new TableException("Invalid row index " + row + " in table of " + getNRows() + " rows"); 2996 } 2997 2998 Object[] data = new Object[columns.size()]; 2999 for (int col = 0; col < data.length; col++) { 3000 data[col] = getElement(row, col); 3001 } 3002 return data; 3003 } 3004 3005 /** 3006 * Returns the flattened (1D) size of elements in each column of this table. As of 1.18, this method returns a copy 3007 * ot the array used internally, which is safe to modify. 3008 * 3009 * @return an array with the byte sizes of each column 3010 * 3011 * @deprecated (<i>for internal use</i>) Use {@link ColumnDesc#getElementCount()} instead. This one returns the 3012 * number of elements in the FITS representation, not in the java representation. For example, for 3013 * {@link String} entries, this returns the number of bytes stored, not the number of strings. 3014 * Similarly, for complex values it returns the number of components not the number of values. 3015 */ 3016 @Deprecated 3017 public int[] getSizes() { 3018 int[] sizes = new int[columns.size()]; 3019 for (int i = 0; i < sizes.length; i++) { 3020 sizes[i] = columns.get(i).getTableBaseCount(); 3021 } 3022 return sizes; 3023 } 3024 3025 /** 3026 * Returns the size of the regular table data, before the heap area. 3027 * 3028 * @return the size of the regular table in bytes 3029 */ 3030 private synchronized long getRegularTableSize() { 3031 return (long) nRow * rowLen; 3032 } 3033 3034 @Override 3035 protected long getTrueSize() { 3036 return getRegularTableSize() + getParameterSize(); 3037 } 3038 3039 /** 3040 * Get the characters describing the base classes of the columns. As of 1.18, this method returns a copy ot the 3041 * array used internally, which is safe to modify. 3042 * 3043 * @return An array of type characters (Java array types), one for each column. 3044 * 3045 * @deprecated (<i>for internal use</i>) Use {@link ColumnDesc#getElementClass()} instead. Not very useful to users 3046 * since this returns the FITS primitive storage type for the data column. 3047 */ 3048 @Deprecated 3049 public char[] getTypes() { 3050 char[] types = new char[columns.size()]; 3051 for (int i = 0; i < columns.size(); i++) { 3052 types[i] = ElementType.forClass(columns.get(i).getTableBase()).type(); 3053 } 3054 return types; 3055 } 3056 3057 @Override 3058 public synchronized void setColumn(int col, Object o) throws FitsException { 3059 ColumnDesc c = columns.get(col); 3060 3061 if (c.isVariableSize()) { 3062 Object[] array = (Object[]) o; 3063 for (int i = 0; i < nRow; i++) { 3064 Object p = putOnHeap(c, ArrayFuncs.flatten(array[i]), getRawElement(i, col)); 3065 setTableElement(i, col, p); 3066 } 3067 } else { 3068 setFlattenedColumn(col, o); 3069 } 3070 } 3071 3072 /** 3073 * Writes an element directly into the random accessible FITS file. Note, this call will not modify the table in 3074 * memory (if loaded). This method should never be called unless we have a valid encoder object that can handle the 3075 * writing, which is a requirement for deferred read mode. 3076 * 3077 * @param row the zero-based row index 3078 * @param col the zero-based column index 3079 * @param array an array object containing primitive types, in FITS storage format. It may be 3080 * multi-dimensional. 3081 * 3082 * @throws IOException the there was an error writing to the FITS output 3083 * 3084 * @see #setTableElement(int, int, Object) 3085 */ 3086 @SuppressWarnings("resource") 3087 private void writeTableElement(int row, int col, Object array) throws IOException { 3088 synchronized (this) { 3089 ColumnDesc c = columns.get(col); 3090 getRandomAccessInput().position(getFileOffset() + row * (long) rowLen + c.offset); 3091 } 3092 encoder.writeArray(array); 3093 } 3094 3095 /** 3096 * Sets a table element to an array in the FITS storage format. If the data is in deferred mode it will write the 3097 * table entry directly into the file. Otherwise it will update the table entry in memory. For variable sized 3098 * column, the heap will always be updated in memory, so you may want to call {@link #rewrite()} when done updating 3099 * all entries. 3100 * 3101 * @param row the zero-based row index 3102 * @param col the zero-based column index 3103 * @param o an array object containing primitive types, in FITS storage format. It may be 3104 * multi-dimensional. 3105 * 3106 * @throws FitsException if the array is invalid for the given column, or if the table could not be accessed in the 3107 * file / input. 3108 * 3109 * @see #setTableElement(int, int, Object) 3110 * @see #getRawElement(int, int) 3111 */ 3112 private synchronized void setTableElement(int row, int col, Object o) throws FitsException { 3113 if (table == null) { 3114 try { 3115 writeTableElement(row, col, o); 3116 } catch (IOException e) { 3117 throw new FitsException(e.getMessage(), e); 3118 } 3119 } else { 3120 ensureData(); 3121 table.setElement(row, col, o); 3122 } 3123 } 3124 3125 /** 3126 * Consider using the more Java-friendly {@link #set(int, int, Object)} with implicit scalar type conversions. 3127 * 3128 * @see #set(int, int, Object) 3129 */ 3130 @Override 3131 public void setElement(int row, int col, Object o) throws FitsException { 3132 ColumnDesc c = columns.get(col); 3133 o = c.isVariableSize() ? putOnHeap(c, o, getRawElement(row, col)) : javaToFits1D(c, ArrayFuncs.flatten(o)); 3134 setTableElement(row, col, o); 3135 } 3136 3137 /** 3138 * <p> 3139 * The Swiss-army knife of setting table entries, including Java boxing, and with some support for automatic type 3140 * conversions. The argument may be one of the following type: 3141 * </p> 3142 * <ul> 3143 * <li>Scalar values -- any Java primitive with its boxed type, such as a {@link Double}, or a 3144 * {@link Character}.</li> 3145 * <li>A single {@link String} or {@link ComplexValue} object. 3146 * <li>An array (including multidimensional) of primitive types, or that of {@link Boolean}, {@link ComplexValue}, 3147 * or {@link String}.</li> 3148 * </ul> 3149 * <p> 3150 * For array-type columns the argument needs to match the column type exactly. However, you may call 3151 * {@link ArrayFuncs#convertArray(Object, Class, Quantizer)} prior to setting values to convert arrays to the 3152 * desired numerical types, including the quantization that is appropriate for the column (see 3153 * {@link ColumnDesc#getQuantizer()}). 3154 * </p> 3155 * <p> 3156 * For scalar (single element) columns, automatic type conversions may apply, to make setting scalar columns more 3157 * flexible: 3158 * </p> 3159 * <ul> 3160 * <li>Any numerical column can take any {@link Number} value. The conversion is as if an explicit Java cast were 3161 * applied. For example, if setting a <code>double</code> value for a column of single <code>short</code> values it 3162 * as if a <code>(short)</code> cast were applied to the value.</li> 3163 * <li>Numerical colums can also take {@link Boolean} values which set the entry to 1, or 0, or to 3164 * {@link Double#isNaN()} (or the equivalent integer minimum value) if the argument is <code>null</code>. Numerical 3165 * columns can also set {@link String} values, by parsing the string according to the numerical type of the 3166 * column.</li> 3167 * <li>Logical columns can set {@link Boolean} values, including <code>null</code>values, but also any 3168 * {@link Number} type. In case of numbers, zero values map to <code>false</code> while definite non-zero values map 3169 * to <code>true</code>. {@link Double#isNaN()} maps to a <code>null</code> (or undefined) entry. Loginal columns 3170 * can be also set to the {@link String} values of 'true' or 'false', or to a {@link Character} of 'T'/'F' (or 3171 * equivalently '1'/'0') and 0 (undefined)</li> 3172 * <li>Singular string columns can be set to any scalar type owing to Java's {@link #toString()} method performing 3173 * the conversion, as long as the string representation fits into the size constraints (if any) for the string 3174 * column.</li> 3175 * </ul> 3176 * <p> 3177 * Additionally, scalar columns can take single-element array arguments, just like 3178 * {@link #setElement(int, int, Object)}. 3179 * </p> 3180 * 3181 * @param row the zero-based row index 3182 * @param col the zero-based column index 3183 * @param o the new value to set. For array columns this must match the Java array type 3184 * exactly, but for scalar columns additional flexibility is provided for fuzzy 3185 * type matching (see description above). 3186 * 3187 * @throws FitsException if the column could not be set 3188 * @throws IllegalArgumentException if the argument cannot be converted to a value for the specified column type. 3189 * 3190 * @since 1.18 3191 * 3192 * @see #get(int, int) 3193 */ 3194 public void set(int row, int col, Object o) throws FitsException, IllegalArgumentException { 3195 ColumnDesc c = columns.get(col); 3196 3197 if (o == null) { 3198 // Only logicals and strings support 'null' values 3199 if (!c.isSingleton()) { 3200 throw new TableException("No null values allowed for column of " + c.getLegacyBase() + " arrays."); 3201 } else if (c.isString()) { 3202 setElement(row, col, ""); 3203 } else { 3204 setLogical(row, col, null); 3205 } 3206 } else if (o.getClass().isArray()) { 3207 Class<?> eType = ArrayFuncs.getBaseClass(o); 3208 if (!c.getFitsBase().isAssignableFrom(eType) && c.isNumeric()) { 3209 o = ArrayFuncs.convertArray(o, c.getFitsBase(), c.getQuantizer()); 3210 } 3211 setElement(row, col, o); 3212 } else if (o instanceof String) { 3213 setString(row, col, (String) o); 3214 } else if (!c.isSingleton()) { 3215 throw new TableException("Cannot set scalar values in non-scalar columns"); 3216 } else if (c.isString()) { 3217 setElement(row, col, o.toString()); 3218 } else if (o instanceof Boolean) { 3219 setLogical(row, col, (Boolean) o); 3220 } else if (o instanceof Character) { 3221 setCharacter(row, col, (Character) o); 3222 } else if (o instanceof Number) { 3223 setNumber(row, col, (Number) o); 3224 } else if (o instanceof ComplexValue) { 3225 setElement(row, col, o); 3226 } else { 3227 throw new IllegalArgumentException("Unsupported scalar type: " + o.getClass()); 3228 } 3229 } 3230 3231 /** 3232 * Sets a scalar table entry to the specified numerical value. 3233 * 3234 * @param row the zero-based row index 3235 * @param col the zero-based column index 3236 * @param value the new number value 3237 * 3238 * @throws ClassCastException if the specified column in not a numerical scalar type. 3239 * @throws FitsException if the table element could not be altered 3240 * 3241 * @see #getNumber(int, int) 3242 * @see #set(int, int, Object) 3243 * 3244 * @since 1.18 3245 */ 3246 private void setNumber(int row, int col, Number value) throws FitsException, ClassCastException { 3247 ColumnDesc c = columns.get(col); 3248 3249 // Already checked before calling... 3250 // if (!c.isSingleton()) { 3251 // throw new ClassCastException("Cannot set scalar value for array column " + col); 3252 // } 3253 3254 if (c.isLogical()) { 3255 Boolean b = null; 3256 if (!Double.isNaN(value.doubleValue())) { 3257 b = value.longValue() != 0; 3258 } 3259 setTableElement(row, col, new byte[] {FitsEncoder.byteForBoolean(b)}); 3260 return; 3261 } 3262 3263 Class<?> base = c.getLegacyBase(); 3264 3265 // quantize / unquantize as necessary... 3266 Quantizer q = c.getQuantizer(); 3267 3268 if (q != null) { 3269 boolean decimalBase = (base == float.class || base == double.class); 3270 boolean decimalValue = (value instanceof Float || value instanceof Double || value instanceof BigInteger 3271 || value instanceof BigDecimal); 3272 3273 if (decimalValue && !decimalBase) { 3274 value = q.toLong(value.doubleValue()); 3275 } else if (!decimalValue && decimalBase) { 3276 value = q.toDouble(value.longValue()); 3277 } 3278 } 3279 3280 Object wrapped = null; 3281 3282 if (base == byte.class) { 3283 wrapped = new byte[] {value.byteValue()}; 3284 } else if (base == short.class) { 3285 wrapped = new short[] {value.shortValue()}; 3286 } else if (base == int.class) { 3287 wrapped = new int[] {value.intValue()}; 3288 } else if (base == long.class) { 3289 wrapped = new long[] {value.longValue()}; 3290 } else if (base == float.class) { 3291 wrapped = new float[] {value.floatValue()}; 3292 } else if (base == double.class) { 3293 wrapped = new double[] {value.doubleValue()}; 3294 } else { 3295 // This could be a char based column... 3296 throw new ClassCastException("Cannot set number value for column of type " + base); 3297 } 3298 3299 setTableElement(row, col, wrapped); 3300 } 3301 3302 /** 3303 * Sets a boolean scalar table entry to the specified value. 3304 * 3305 * @param row the zero-based row index 3306 * @param col the zero-based column index 3307 * @param value the new boolean value 3308 * 3309 * @throws ClassCastException if the specified column in not a boolean scalar type. 3310 * @throws FitsException if the table element could not be altered 3311 * 3312 * @see #getLogical(int, int) 3313 * @see #set(int, int, Object) 3314 * 3315 * @since 1.18 3316 */ 3317 private void setLogical(int row, int col, Boolean value) throws FitsException, ClassCastException { 3318 ColumnDesc c = columns.get(col); 3319 3320 // Already checked before calling... 3321 // if (!c.isSingleton()) { 3322 // throw new ClassCastException("Cannot set scalar value for array column " + col); 3323 // } 3324 3325 if (c.isLogical()) { 3326 setTableElement(row, col, new byte[] {FitsEncoder.byteForBoolean(value)}); 3327 } else if (c.getLegacyBase() == char.class) { 3328 setTableElement(row, col, new char[] {value == null ? '\0' : (value ? 'T' : 'F')}); 3329 } else { 3330 setNumber(row, col, value == null ? Double.NaN : (value ? 1 : 0)); 3331 } 3332 } 3333 3334 /** 3335 * Sets a Unicode character scalar table entry to the specified value. 3336 * 3337 * @param row the zero-based row index 3338 * @param col the zero-based column index 3339 * @param value the new Unicode character value 3340 * 3341 * @throws ClassCastException if the specified column in not a boolean scalar type. 3342 * @throws FitsException if the table element could not be altered 3343 * 3344 * @see #getString(int, int) 3345 * 3346 * @since 1.18 3347 */ 3348 private void setCharacter(int row, int col, Character value) throws FitsException, ClassCastException { 3349 ColumnDesc c = columns.get(col); 3350 3351 // Already checked before calling... 3352 // if (!c.isSingleton()) { 3353 // throw new IllegalArgumentException("Cannot set scalar value for array column " + col); 3354 // } 3355 3356 if (c.isLogical()) { 3357 setLogical(row, col, FitsUtil.parseLogical(value.toString())); 3358 } else if (c.fitsBase == char.class) { 3359 setTableElement(row, col, new char[] {value}); 3360 } else if (c.fitsBase == byte.class) { 3361 setTableElement(row, col, new byte[] {(byte) (value & FitsIO.BYTE_MASK)}); 3362 } else { 3363 throw new ClassCastException("Cannot convert char value to " + c.fitsBase.getName()); 3364 } 3365 } 3366 3367 /** 3368 * Sets a table entry to the specified string value. Scalar column will attempt to parse the value, while 3369 * <code>byte[]</code> and <coce>char[]</code> type columns will convert the string provided the string's length 3370 * does not exceed the entry size for these columns (the array elements will be padded with zeroes). Note, that 3371 * scalar <code>byte</code> columns will parse the string as a number (not as a single ASCII character). 3372 * 3373 * @param row the zero-based row index 3374 * @param col the zero-based column index 3375 * @param value the new boolean value 3376 * 3377 * @throws ClassCastException if the specified column is not a scalar type, and neither it is a 3378 * <code>byte[]</code> or <coce>char[]</code> column. 3379 * @throws IllegalArgumentException if the String is too long to contain in the column. 3380 * @throws NumberFormatException if the numerical value could not be parsed. 3381 * @throws FitsException if the table element could not be altered 3382 * 3383 * @see #getString(int, int) 3384 * @see #set(int, int, Object) 3385 * 3386 * @since 1.18 3387 */ 3388 private void setString(int row, int col, String value) 3389 throws FitsException, ClassCastException, IllegalArgumentException, NumberFormatException { 3390 ColumnDesc c = columns.get(col); 3391 3392 // Already checked before calling... 3393 // if (!c.isSingleton()) { 3394 // throw new IllegalArgumentException("Cannot set scalar value for array column " + col); 3395 // } 3396 3397 if (c.isLogical()) { 3398 setLogical(row, col, FitsUtil.parseLogical(value)); 3399 } else if (value.length() == 1) { 3400 setCharacter(row, col, value.charAt(0)); 3401 } else if (c.fitsDimension() > 1) { 3402 throw new ClassCastException("Cannot convert String to multi-dimensional array"); 3403 } else if (c.fitsDimension() == 1) { 3404 if (c.fitsBase != char.class && c.fitsBase != byte.class) { 3405 throw new ClassCastException("Cannot cast String to " + c.fitsBase.getName()); 3406 } 3407 int len = c.isVariableSize() ? value.length() : c.fitsCount; 3408 if (value.length() > len) { 3409 throw new IllegalArgumentException("String size " + value.length() + " exceeds entry size of " + len); 3410 } 3411 if (c.fitsBase == char.class) { 3412 setTableElement(row, col, Arrays.copyOf(value.toCharArray(), len)); 3413 } else { 3414 setTableElement(row, col, FitsUtil.stringToByteArray(value, len)); 3415 } 3416 } else { 3417 try { 3418 setNumber(row, col, Long.parseLong(value)); 3419 } catch (NumberFormatException e) { 3420 setNumber(row, col, Double.parseDouble(value)); 3421 } 3422 } 3423 } 3424 3425 /** 3426 * @deprecated (<i>for internal use</i>) It may be reduced to private visibility in the future. Sets a 3427 * column with the data already flattened. 3428 * 3429 * @param col The index of the column to be replaced. 3430 * @param data The new data array. This should be a one-d primitive array. 3431 * 3432 * @throws FitsException Thrown if the type of length of the replacement data differs from the original. 3433 */ 3434 @Deprecated 3435 public synchronized void setFlattenedColumn(int col, Object data) throws FitsException { 3436 ensureData(); 3437 3438 Object oldCol = table.getColumn(col); 3439 if (data.getClass() != oldCol.getClass() || Array.getLength(data) != Array.getLength(oldCol)) { 3440 throw new TableException("Replacement column mismatch at column:" + col); 3441 } 3442 table.setColumn(col, javaToFits1D(columns.get(col), data)); 3443 } 3444 3445 @Override 3446 public void setRow(int row, Object[] data) throws FitsException { 3447 ensureData(); 3448 3449 if (data.length != getNCols()) { 3450 throw new TableException("Mismatched number of columns: " + data.length + ", expected " + getNCols()); 3451 } 3452 3453 for (int col = 0; col < data.length; col++) { 3454 set(row, col, data[col]); 3455 } 3456 } 3457 3458 /** 3459 * @deprecated It is not entirely foolproof for keeping the header in sync -- it is better to (re)wrap tables in a 3460 * new HDU after column deletions, and then edit the new header as necessary to incorporate custom 3461 * entries. May be removed from the API in the future. 3462 */ 3463 @Deprecated 3464 @Override 3465 public synchronized void updateAfterDelete(int oldNcol, Header hdr) throws FitsException { 3466 hdr.addValue(Standard.NAXIS1, rowLen); 3467 int l = 0; 3468 for (ColumnDesc d : columns) { 3469 d.offset = l; 3470 l += d.rowLen(); 3471 } 3472 } 3473 3474 @SuppressWarnings("resource") 3475 @Override 3476 public void write(ArrayDataOutput os) throws FitsException { 3477 synchronized (this) { 3478 3479 try { 3480 if (isDeferred() && os == getRandomAccessInput()) { 3481 // It it's a deferred mode re-write, then data were edited in place if at all, 3482 // so we can skip the main table. 3483 ((RandomAccess) os).skipAllBytes(getRegularTableSize()); 3484 } else { 3485 // otherwise make sure we loaded all data before writing to the output 3486 ensureData(); 3487 3488 // Write the regular table (if any) 3489 if (getRegularTableSize() > 0) { 3490 table.write(os); 3491 } 3492 } 3493 3494 // Now check if we need to write the heap 3495 if (getParameterSize() > 0) { 3496 for (long rem = getHeapOffset(); rem > 0;) { 3497 byte[] b = new byte[(int) Math.min(getHeapOffset(), 1 << Short.SIZE)]; 3498 os.write(b); 3499 rem -= b.length; 3500 } 3501 3502 getHeap().write(os); 3503 3504 if (heapReserve > 0) { 3505 byte[] b = new byte[heapReserve]; 3506 os.write(b); 3507 } 3508 } 3509 3510 FitsUtil.pad(os, getTrueSize(), (byte) 0); 3511 } catch (IOException e) { 3512 throw new FitsException("Unable to write table:" + e, e); 3513 } 3514 } 3515 } 3516 3517 /** 3518 * Returns the heap offset component from a pointer. 3519 * 3520 * @param p the pointer, either a <code>int[2]</code> or a <code>long[2]</code>. 3521 * 3522 * @return the offset component from the pointer 3523 */ 3524 private long getPointerOffset(Object p) { 3525 return (p instanceof long[]) ? ((long[]) p)[1] : ((int[]) p)[1]; 3526 } 3527 3528 /** 3529 * Returns the number of elements reported in a heap pointer. 3530 * 3531 * @param p the pointer, either a <code>int[2]</code> or a <code>long[2]</code>. 3532 * 3533 * @return the element count component from the pointer 3534 */ 3535 private long getPointerCount(Object p) { 3536 return (p instanceof long[]) ? ((long[]) p)[0] : ((int[]) p)[0]; 3537 } 3538 3539 /** 3540 * Puts a FITS data array onto our heap, returning its locator pointer. The data will overwrite the previous heap 3541 * entry, if provided, so long as the new data fits in the same place. Otherwise the new data is placed at the end 3542 * of the heap. 3543 * 3544 * @param c The column descriptor, specifying the data type 3545 * @param o The variable-length data 3546 * @param p The heap pointer, where this element was stored on the heap before, or <code>null</code> if 3547 * we aren't replacing an earlier entry. 3548 * 3549 * @return the heap pointer information, either <code>int[2]</code> or else a <code>long[2]</code> 3550 * 3551 * @throws FitsException if the data could not be accessed in full from the heap. 3552 */ 3553 private Object putOnHeap(ColumnDesc c, Object o, Object oldPointer) throws FitsException { 3554 return putOnHeap(getHeap(), c, o, oldPointer); 3555 } 3556 3557 /** 3558 * Puts a FITS data array onto a specific heap, returning its locator pointer. The data will overwrite the previous 3559 * heap entry, if provided, so long as the new data fits in the same place. Otherwise the new data is placed at the 3560 * end of the heap. 3561 * 3562 * @param h The heap object to use. 3563 * @param c The column descriptor, specifying the data type 3564 * @param o The variable-length data in Java form. 3565 * @param p The heap pointer, where this element was stored on the heap before, or <code>null</code> if 3566 * we aren't replacing an earlier entry. 3567 * 3568 * @return the heap pointer information, either <code>int[2]</code> or else a <code>long[2]</code> 3569 * 3570 * @throws FitsException if the data could not be accessed in full from the heap. 3571 */ 3572 private Object putOnHeap(FitsHeap h, ColumnDesc c, Object o, Object oldPointer) throws FitsException { 3573 // Flatten data for heap 3574 o = ArrayFuncs.flatten(o); 3575 3576 // By default put data at the end of the heap; 3577 int off = h.size(); 3578 3579 // The number of Java elements is the same as the number of FITS elements, except for strings and complex 3580 // numbers 3581 int len = (c.isComplex() || c.isString()) ? -1 : Array.getLength(o); 3582 3583 // Convert to FITS storage array 3584 o = javaToFits1D(c, o); 3585 3586 // For complex values and strings, determine length from converted object.... 3587 if (len < 0) { 3588 len = Array.getLength(o); 3589 3590 // If complex in primitive 1D form, then length is half the number of elements. 3591 if (c.isComplex() && o.getClass().getComponentType().isPrimitive()) { 3592 len >>>= 1; 3593 } 3594 } 3595 3596 if (oldPointer != null) { 3597 if (len <= getPointerCount(oldPointer)) { 3598 // Write data back at the old heap location 3599 off = (int) getPointerOffset(oldPointer); 3600 } 3601 } 3602 3603 h.putData(o, off); 3604 3605 return c.hasLongPointers() ? new long[] {len, off} : new int[] {len, off}; 3606 } 3607 3608 /** 3609 * Returns a FITS data array from the heap 3610 * 3611 * @param c The column descriptor, specifying the data type 3612 * @param p The heap pointer, either <code>int[2]</code> or else a <code>long[2]</code> 3613 * @param isEnhanced Whether logicals should be returned as {@link Boolean} (rather than <code>boolean</code>) 3614 * and complex values as {@link ComplexValue} (rather than <code>float[2]</code> or 3615 * <code>double[2]</code>), or arrays thereof. Methods prior to 1.18 should set this to 3616 * <code>false</code> for back compatible behavior. 3617 * 3618 * @return the FITS array object retrieved from the heap 3619 * 3620 * @throws FitsException if the data could not be accessed in full from the heap. 3621 */ 3622 protected Object getFromHeap(ColumnDesc c, Object p, boolean isEnhanced) throws FitsException { 3623 long len = getPointerCount(p); 3624 long off = getPointerOffset(p); 3625 3626 if (off > Integer.MAX_VALUE || len > Integer.MAX_VALUE) { 3627 throw new FitsException("Data located beyond 32-bit accessible heap limit: off=" + off + ", len=" + len); 3628 } 3629 3630 Object e = null; 3631 3632 if (c.isComplex()) { 3633 e = Array.newInstance(c.getFitsBase(), (int) len, 2); 3634 } else { 3635 e = Array.newInstance(c.getFitsBase(), c.getFitsBaseCount((int) len)); 3636 } 3637 3638 readHeap(off, e); 3639 3640 return fitsToJava1D(c, e, (int) len, isEnhanced); 3641 } 3642 3643 /** 3644 * Convert Java arrays to their FITS representation. Transformation include boolean → 'T'/'F' or '\0'; 3645 * Strings → byte arrays; variable length arrays → pointers (after writing data to heap). 3646 * 3647 * @param c The column descritor 3648 * @param o A one-dimensional Java array 3649 * 3650 * @return An one-dimensional array with values as stored in FITS. 3651 * 3652 * @throws FitsException if the operation failed 3653 */ 3654 private static Object javaToFits1D(ColumnDesc c, Object o) throws FitsException { 3655 3656 if (c.isBits()) { 3657 if (o instanceof Boolean && c.isSingleton()) { 3658 // Scalar boxed boolean... 3659 return FitsUtil.bitsToBytes(new boolean[] {(Boolean) o}); 3660 } 3661 return FitsUtil.bitsToBytes((boolean[]) o); 3662 } 3663 3664 if (c.isLogical()) { 3665 // Convert true/false to 'T'/'F', or null to '\0' 3666 return FitsUtil.booleansToBytes(o); 3667 } 3668 3669 if (c.isComplex()) { 3670 if (o instanceof ComplexValue || o instanceof ComplexValue[]) { 3671 return ArrayFuncs.complexToDecimals(o, c.fitsBase); 3672 } 3673 } 3674 3675 if (c.isString()) { 3676 // Convert strings to array of bytes. 3677 if (o == null) { 3678 if (c.isVariableSize()) { 3679 return new byte[0]; 3680 } 3681 3682 return Array.newInstance(byte.class, c.fitsShape); 3683 } 3684 3685 if (o instanceof String) { 3686 int l = c.getStringLength(); 3687 if (l < 0) { 3688 // Not fixed width, write the whole string. 3689 l = ((String) o).length(); 3690 } 3691 return FitsUtil.stringToByteArray((String) o, l); 3692 } 3693 3694 if (c.isVariableSize() && c.delimiter != 0) { 3695 // Write variable-length string arrays in delimited form 3696 3697 for (String s : (String[]) o) { 3698 // We set the string length to that of the longest element + 1 3699 c.setStringLength(Math.max(c.stringLength, s == null ? 1 : s.length() + 1)); 3700 } 3701 3702 return FitsUtil.stringsToDelimitedBytes((String[]) o, c.getStringLength(), c.delimiter); 3703 } 3704 3705 // Fixed length substring array (not delimited). 3706 // For compatibility with tools that do not process array dimension, ASCII NULL should not 3707 // be used between components (permissible only at the end of all strings) 3708 return FitsUtil.stringsToByteArray((String[]) o, c.getStringLength(), FitsUtil.BLANK_SPACE); 3709 } 3710 3711 return ArrayFuncs.objectToArray(o, true); 3712 } 3713 3714 /** 3715 * Converts from the FITS representation of data to their basic Java array representation. 3716 * 3717 * @param c The column descritor 3718 * @param o A one-dimensional array of values as stored in FITS 3719 * @param bits A bit count for bit arrays (otherwise unused). 3720 * @param isEnhanced Whether logicals should be returned as {@link Boolean} (rather than <code>boolean</code>) 3721 * and complex values as {@link ComplexValue} (rather than <code>float[2]</code> or 3722 * <code>double[2]</code>), or arrays thereof. Methods prior to 1.18 should set this to 3723 * <code>false</code> for back compatible behavior. 3724 * 3725 * @return A {@link String} or a one-dimensional array with the matched basic Java type 3726 * 3727 * @throws FitsException if the operation failed 3728 */ 3729 private Object fitsToJava1D(ColumnDesc c, Object o, int bits, boolean isEnhanced) { 3730 3731 if (c.isBits()) { 3732 return FitsUtil.bytesToBits((byte[]) o, bits); 3733 } 3734 3735 if (c.isLogical()) { 3736 return isEnhanced ? FitsUtil.bytesToBooleanObjects(o) : FitsUtil.byteToBoolean((byte[]) o); 3737 } 3738 3739 if (c.isComplex() && isEnhanced) { 3740 return ArrayFuncs.decimalsToComplex(o); 3741 } 3742 3743 if (c.isString()) { 3744 byte[] bytes = (byte[]) o; 3745 3746 int len = c.getStringLength(); 3747 3748 if (c.isVariableSize()) { 3749 if (c.delimiter != 0) { 3750 // delimited array of strings 3751 return FitsUtil.delimitedBytesToStrings(bytes, c.getStringLength(), c.delimiter); 3752 } 3753 } 3754 3755 // If fixed or variable length arrays of strings... 3756 if (c.isSingleton()) { 3757 // Single fixed string -- get it all but trim trailing spaces 3758 return FitsUtil.extractString(bytes, new ParsePosition(0), bytes.length, FitsUtil.ASCII_NULL); 3759 } 3760 3761 // Array of fixed-length strings -- we trim trailing spaces in each component 3762 String[] s = new String[bytes.length / len]; 3763 for (int i = 0; i < s.length; i++) { 3764 s[i] = FitsUtil.extractString(bytes, new ParsePosition(i * len), len, FitsUtil.ASCII_NULL); 3765 } 3766 return s; 3767 } 3768 3769 return o; 3770 } 3771 3772 /** 3773 * Create a column table with the specified number of rows. This is used when we defer instantiation of the 3774 * ColumnTable until the user requests data from the table. 3775 * 3776 * @param rows the number of rows to allocate 3777 * 3778 * @throws FitsException if the operation failed 3779 */ 3780 protected synchronized void createTable(int rows) throws FitsException { 3781 int nfields = columns.size(); 3782 Object[] data = new Object[nfields]; 3783 int[] sizes = new int[nfields]; 3784 for (int i = 0; i < nfields; i++) { 3785 ColumnDesc c = columns.get(i); 3786 sizes[i] = c.getTableBaseCount(); 3787 data[i] = c.newInstance(rows); 3788 } 3789 3790 table = createColumnTable(data, sizes); 3791 nRow = rows; 3792 } 3793 3794 /** 3795 * Sets the input to use for reading (and possibly writing) this table. If the input implements 3796 * {@link ReadWriteAccess}, then it can be used for both reading and (re)writing the data, including editing in 3797 * deferred mode. 3798 * 3799 * @param in The input from which we can read the table data. 3800 */ 3801 private void setInput(ArrayDataInput in) { 3802 encoder = (in instanceof ReadWriteAccess) ? new FitsEncoder((ReadWriteAccess) in) : null; 3803 } 3804 3805 @Override 3806 public void read(ArrayDataInput in) throws FitsException { 3807 setInput(in); 3808 super.read(in); 3809 } 3810 3811 @Override 3812 protected void loadData(ArrayDataInput in) throws IOException, FitsException { 3813 setInput(in); 3814 synchronized (this) { 3815 createTable(nRow); 3816 } 3817 readTrueData(in); 3818 } 3819 3820 /** 3821 * Extracts a column descriptor from the FITS header for a given column index 3822 * 3823 * @param header the FITS header containing the column description(s) 3824 * @param col zero-based column index 3825 * 3826 * @return the Descriptor for that column. 3827 * 3828 * @throws FitsException if the header deswcription is invalid or incomplete 3829 */ 3830 public static ColumnDesc getDescriptor(Header header, int col) throws FitsException { 3831 String tform = header.getStringValue(Standard.TFORMn.n(col + 1)); 3832 3833 if (tform == null) { 3834 throw new FitsException("Missing TFORM" + (col + 1)); 3835 } 3836 3837 int count = 1; 3838 char type = 0; 3839 3840 ParsePosition pos = new ParsePosition(0); 3841 3842 try { 3843 count = AsciiFuncs.parseInteger(tform, pos); 3844 } catch (Exception e) { 3845 // Keep going... 3846 } 3847 3848 try { 3849 type = Character.toUpperCase(AsciiFuncs.extractChar(tform, pos)); 3850 } catch (Exception e) { 3851 throw new FitsException("Missing data type in TFORM: [" + tform + "]"); 3852 } 3853 3854 ColumnDesc c = new ColumnDesc(); 3855 3856 if (header.containsKey(Standard.TTYPEn.n(col + 1))) { 3857 c.name(header.getStringValue(Standard.TTYPEn.n(col + 1))); 3858 } 3859 3860 if (type == POINTER_INT || type == POINTER_LONG) { 3861 // Variable length column... 3862 c.setVariableSize(type == POINTER_LONG); 3863 3864 // Get the data type... 3865 try { 3866 type = Character.toUpperCase(AsciiFuncs.extractChar(tform, pos)); 3867 } catch (Exception e) { 3868 throw new FitsException("Missing variable-length data type in TFORM: [" + tform + "]"); 3869 } 3870 } 3871 3872 // The special types... 3873 if (type == 'C' || type == 'M') { 3874 c.isComplex = true; 3875 } else if (type == 'X') { 3876 c.isBits = true; 3877 } 3878 3879 if (!c.setFitsType(type)) { 3880 throw new FitsException("Invalid type '" + type + "' in column:" + col); 3881 } 3882 3883 if (!c.isVariableSize()) { 3884 // Fixed sized column... 3885 int[] dims = parseTDims(header.getStringValue(Standard.TDIMn.n(col + 1))); 3886 3887 if (dims == null) { 3888 c.setFitsShape((count == 1 && type != 'A') ? SINGLETON_SHAPE : new int[] {count}); 3889 c.stringLength = -1; // T.B.D. further below... 3890 } else { 3891 c.setFitsShape(dims); 3892 } 3893 } 3894 3895 if (c.isString()) { 3896 // For vairable-length columns or of TDIM was not defined determine substring length from TFORM. 3897 c.parseSubstringConvention(tform, pos, c.getStringLength() < 0); 3898 } 3899 3900 // Force to use the count in the header, even if it does not match up with the dimension otherwise. 3901 c.fitsCount = count; 3902 3903 c.quant = Quantizer.fromTableHeader(header, col); 3904 if (c.quant.isDefault()) { 3905 c.quant = null; 3906 } 3907 3908 return c; 3909 } 3910 3911 /** 3912 * Process one column from a FITS Header. 3913 * 3914 * @throws FitsException if the operation failed 3915 */ 3916 private int processCol(Header header, int col, int offset) throws FitsException { 3917 ColumnDesc c = getDescriptor(header, col); 3918 c.offset = offset; 3919 columns.add(c); 3920 3921 return c.rowLen(); 3922 } 3923 3924 /** 3925 * @deprecated (<i>for internal use</i>) Used Only by {@link nom.tam.image.compression.hdu.CompressedTableData} so 3926 * it would make a better private method in there.. ` 3927 */ 3928 @Deprecated 3929 protected void addByteVaryingColumn() { 3930 addColumn(ColumnDesc.createForVariableSize(byte.class)); 3931 } 3932 3933 /** 3934 * @deprecated (<i>for internal use</i>) This method should have visibility reduced to private 3935 */ 3936 @SuppressWarnings("javadoc") 3937 @Deprecated 3938 protected ColumnTable<?> createColumnTable(Object[] arrCol, int[] sizes) throws TableException { 3939 return new ColumnTable<>(arrCol, sizes); 3940 } 3941 3942 /** 3943 * Returns the heap, after initializing it from the input as necessary 3944 * 3945 * @return the initialized heap 3946 * 3947 * @throws FitsException if we had trouble initializing it from the input. 3948 */ 3949 @SuppressWarnings("resource") 3950 private synchronized FitsHeap getHeap() throws FitsException { 3951 if (heap == null) { 3952 readHeap(getRandomAccessInput()); 3953 } 3954 return heap; 3955 } 3956 3957 /** 3958 * Reads an array from the heap. Subclasses may override this, for example to provide read-only access to a related 3959 * table's heap area. 3960 * 3961 * @param offset the heap offset 3962 * @param array the array to populate from the heap area 3963 * 3964 * @throws FitsException if there was an issue accessing the heap 3965 */ 3966 protected void readHeap(long offset, Object array) throws FitsException { 3967 getHeap().getData((int) offset, array); 3968 } 3969 3970 /** 3971 * Read the heap which contains the data for variable length arrays. A. Kovacs (4/1/08) Separated heap reading, s.t. 3972 * the heap can be properly initialized even if in deferred read mode. columnToArray() checks and initializes the 3973 * heap as necessary. 3974 * 3975 * @param input stream to read from. 3976 * 3977 * @throws FitsException if the heap could not be read from the stream 3978 * 3979 * @deprecated (<i>for internal use</i>) unused. 3980 */ 3981 @Deprecated 3982 protected synchronized void readHeap(ArrayDataInput input) throws FitsException { 3983 if (input instanceof RandomAccess) { 3984 FitsUtil.reposition(input, getFileOffset() + getHeapAddress()); 3985 } 3986 heap = new FitsHeap(heapFileSize); 3987 if (input != null) { 3988 heap.read(input); 3989 } 3990 } 3991 3992 /** 3993 * Read table, heap and padding 3994 * 3995 * @param i the stream to read the data from. 3996 * 3997 * @throws FitsException if the reading failed 3998 */ 3999 protected synchronized void readTrueData(ArrayDataInput i) throws FitsException { 4000 try { 4001 table.read(i); 4002 i.skipAllBytes(getHeapOffset()); 4003 if (heap == null) { 4004 readHeap(i); 4005 } 4006 } catch (IOException e) { 4007 throw new FitsException("Error reading binary table data:" + e, e); 4008 } 4009 } 4010 4011 /** 4012 * Check if the column number is valid. 4013 * 4014 * @param j The Java index (first=0) of the column to check. 4015 * 4016 * @return <code>true</code> if the column is valid 4017 */ 4018 protected boolean validColumn(int j) { 4019 return j >= 0 && j < getNCols(); 4020 } 4021 4022 /** 4023 * Check to see if this is a valid row. 4024 * 4025 * @param i The Java index (first=0) of the row to check. 4026 * 4027 * @return <code>true</code> if the row is valid 4028 */ 4029 protected boolean validRow(int i) { 4030 return getNRows() > 0 && i >= 0 && i < getNRows(); 4031 } 4032 4033 /** 4034 * @deprecated (<i>for internal use</i>) Visibility should be reduced to protected. 4035 */ 4036 @Deprecated 4037 @Override 4038 public void fillHeader(Header h) throws FitsException { 4039 fillHeader(h, true); 4040 } 4041 4042 /** 4043 * Fills (updates) the essential header description of this table in the header, optionally updating the essential 4044 * column descriptions also if desired. 4045 * 4046 * @param h The FITS header to populate 4047 * @param updateColumns Whether to update the essential column descriptions also 4048 * 4049 * @throws FitsException if there was an error accessing the header. 4050 */ 4051 void fillHeader(Header h, boolean updateColumns) throws FitsException { 4052 h.deleteKey(Standard.SIMPLE); 4053 h.deleteKey(Standard.EXTEND); 4054 4055 Standard.context(BinaryTable.class); 4056 4057 Cursor<String, HeaderCard> c = h.iterator(); 4058 c.add(HeaderCard.create(Standard.XTENSION, Standard.XTENSION_BINTABLE)); 4059 c.add(HeaderCard.create(Standard.BITPIX, Bitpix.BYTE.getHeaderValue())); 4060 c.add(HeaderCard.create(Standard.NAXIS, 2)); 4061 4062 synchronized (this) { 4063 c.add(HeaderCard.create(Standard.NAXIS1, rowLen)); 4064 c.add(HeaderCard.create(Standard.NAXIS2, nRow)); 4065 } 4066 4067 if (h.getLongValue(Standard.PCOUNT, -1L) < getParameterSize()) { 4068 c.add(HeaderCard.create(Standard.PCOUNT, getParameterSize())); 4069 } 4070 4071 c.add(HeaderCard.create(Standard.GCOUNT, 1)); 4072 c.add(HeaderCard.create(Standard.TFIELDS, columns.size())); 4073 4074 if (getHeapOffset() == 0) { 4075 h.deleteKey(Standard.THEAP); 4076 } else { 4077 c.add(HeaderCard.create(Standard.THEAP, getHeapAddress())); 4078 } 4079 4080 if (updateColumns) { 4081 for (int i = 0; i < columns.size(); i++) { 4082 c.setKey(Standard.TFORMn.n(i + 1).key()); 4083 fillForColumn(h, c, i); 4084 } 4085 } 4086 4087 Standard.context(null); 4088 } 4089 4090 /** 4091 * Update the header to reflect the details of a given column. 4092 * 4093 * @throws FitsException if the operation failed 4094 */ 4095 void fillForColumn(Header header, Cursor<String, HeaderCard> hc, int col) throws FitsException { 4096 ColumnDesc c = columns.get(col); 4097 4098 try { 4099 Standard.context(BinaryTable.class); 4100 4101 if (c.name() != null) { 4102 hc.add(HeaderCard.create(Standard.TTYPEn.n(col + 1), c.name())); 4103 } 4104 4105 hc.add(HeaderCard.create(Standard.TFORMn.n(col + 1), c.getTFORM())); 4106 4107 String tdim = c.getTDIM(); 4108 if (tdim != null) { 4109 hc.add(HeaderCard.create(Standard.TDIMn.n(col + 1), tdim)); 4110 } 4111 4112 if (c.quant != null) { 4113 c.quant.editTableHeader(header, col); 4114 } 4115 4116 } finally { 4117 Standard.context(null); 4118 } 4119 } 4120 4121 /** 4122 * Returns the column descriptor of a given column in this table 4123 * 4124 * @param column the zero-based column index 4125 * 4126 * @return the column's descriptor 4127 * 4128 * @throws ArrayIndexOutOfBoundsException if this table does not contain a column with that index. 4129 * 4130 * @see #getDescriptor(String) 4131 */ 4132 public ColumnDesc getDescriptor(int column) throws ArrayIndexOutOfBoundsException { 4133 return columns.get(column); 4134 } 4135 4136 /** 4137 * Returns the (first) column descriptor whose name matches the specified value. 4138 * 4139 * @param name The column name (case sensitive). 4140 * 4141 * @return The descriptor of the first column by that name, or <code>null</code> if the table contains no 4142 * column by that name. 4143 * 4144 * @see #getDescriptor(int) 4145 * @see #indexOf(String) 4146 * 4147 * @since 1.20 4148 */ 4149 public ColumnDesc getDescriptor(String name) { 4150 int col = indexOf(name); 4151 return col < 0 ? null : getDescriptor(col); 4152 } 4153 4154 /** 4155 * Converts a column from FITS logical values to bits. Null values (allowed in logical columns) will map to 4156 * <code>false</code>. 4157 * 4158 * @param col The zero-based index of the column to be reset. 4159 * 4160 * @return Whether the conversion was possible. * 4161 * 4162 * @since 1.18 4163 */ 4164 public boolean convertToBits(int col) { 4165 ColumnDesc c = columns.get(col); 4166 4167 if (c.isBits) { 4168 return true; 4169 } 4170 4171 if (c.base != boolean.class) { 4172 return false; 4173 } 4174 4175 c.isBits = true; 4176 return true; 4177 } 4178 4179 /** 4180 * Convert a column from float/double to float complex/double complex. This is only possible for certain columns. 4181 * The return status indicates if the conversion is possible. 4182 * 4183 * @param index The zero-based index of the column to be reset. 4184 * 4185 * @return Whether the conversion is possible. * 4186 * 4187 * @throws FitsException if the operation failed 4188 * 4189 * @since 1.18 4190 * 4191 * @see ColumnDesc#isComplex() 4192 * @see #addComplexColumn(Object, Class) 4193 */ 4194 public synchronized boolean setComplexColumn(int index) throws FitsException { 4195 4196 if (!validColumn(index)) { 4197 return false; 4198 } 4199 4200 ColumnDesc c = columns.get(index); 4201 if (c.isComplex()) { 4202 return true; 4203 } 4204 4205 if (c.base != float.class && c.base != double.class) { 4206 return false; 4207 } 4208 4209 if (!c.isVariableSize()) { 4210 if (c.getLastFitsDim() != 2) { 4211 return false; 4212 } 4213 // Set the column to complex 4214 c.isComplex = true; 4215 4216 // Update the legacy (wrapped array) shape 4217 c.setLegacyShape(c.fitsShape); 4218 return true; 4219 } 4220 4221 // We need to make sure that for every row, there are 4222 // an even number of elements so that we can 4223 // convert to an integral number of complex numbers. 4224 for (int i = 1; i < nRow; i++) { 4225 if (getPointerCount(getRawElement(i, index)) % 2 != 0) { 4226 return false; 4227 } 4228 } 4229 4230 // Halve the length component of array descriptors (2 reals = 1 complex) 4231 for (int i = 1; i < nRow; i++) { 4232 Object p = getRawElement(i, index); 4233 long len = getPointerCount(p) >>> 1; 4234 if (c.hasLongPointers()) { 4235 ((long[]) p)[0] = len; 4236 } else { 4237 ((int[]) p)[0] = (int) len; 4238 } 4239 setTableElement(i, index, p); 4240 } 4241 4242 // Set the column to complex 4243 c.isComplex = true; 4244 4245 return true; 4246 } 4247 4248 /** 4249 * Checks if this table contains a heap for storing variable length arrays (VLAs). 4250 * 4251 * @return <code>true</code> if the table contains a heap, or else <code>false</code>. 4252 * 4253 * @since 1.19.1 4254 */ 4255 public final boolean containsHeap() { 4256 return getParameterSize() > 0; 4257 } 4258 4259 /** 4260 * <p> 4261 * Defragments the heap area of this table, compacting the heap area, and returning the number of bytes by which the 4262 * heap size has been reduced. When tables with variable-sized columns are modified, the heap may retain old data as 4263 * columns are removed or elements get replaced with new data of different size. The data order in the heap may also 4264 * get jumbled, causing what would appear to be sequential reads to jump all over the heap space with the caching. 4265 * And, depending on how the heap was constructed in the first place, it may not be optimal for the row-after-row 4266 * table access that is the most typical use case. 4267 * </p> 4268 * <p> 4269 * This method rebuilds the heap by taking elements in table read order (by rows, and columns) and puts them on a 4270 * new heap. 4271 * </p> 4272 * <p> 4273 * For best squential read performance, you should defragment all tables that have been built column-by-column 4274 * before writing them to a FITS file. The only time defragmentation is really not needed is if the table was built 4275 * row-by-row, with no modifications to variable-length content after the fact. 4276 * </p> 4277 * 4278 * @return the number of bytes by which the heap has shrunk as a result of defragmentation. 4279 * 4280 * @throws FitsException if there was an error accessing the heap or the main data table comntaining the heap 4281 * locators. In case of an error the table content may be left in a damaged state. 4282 * 4283 * @see #compact() 4284 * @see #setElement(int, int, Object) 4285 * @see #addColumn(Object) 4286 * @see #deleteColumns(int, int) 4287 * @see #setColumn(int, Object) 4288 * 4289 * @since 1.18 4290 */ 4291 public synchronized long defragment() throws FitsException { 4292 if (!containsHeap()) { 4293 return 0L; 4294 } 4295 4296 int[] eSize = new int[columns.size()]; 4297 4298 for (int j = 0; j < columns.size(); j++) { 4299 ColumnDesc c = columns.get(j); 4300 if (c.isVariableSize()) { 4301 eSize[j] = ElementType.forClass(c.getFitsBase()).size(); 4302 } 4303 } 4304 4305 FitsHeap hp = getHeap(); 4306 long oldSize = hp.size(); 4307 FitsHeap compact = new FitsHeap(0); 4308 4309 for (int i = 0; i < nRow; i++) { 4310 for (int j = 0; j < columns.size(); j++) { 4311 ColumnDesc c = columns.get(j); 4312 if (c.isVariableSize()) { 4313 Object p = getRawElement(i, j); 4314 4315 int len = (int) getPointerCount(p); 4316 4317 // Copy to new heap... 4318 int pos = compact.copyFrom(hp, (int) getPointerOffset(p), c.getFitsBaseCount(len) * eSize[j]); 4319 4320 // Same length as before... 4321 if (p instanceof long[]) { 4322 ((long[]) p)[1] = pos; 4323 } else { 4324 ((int[]) p)[1] = pos; 4325 } 4326 4327 // Update pointers in table 4328 setTableElement(i, j, p); 4329 } 4330 } 4331 } 4332 4333 heap = compact; 4334 return oldSize - compact.size(); 4335 } 4336 4337 /** 4338 * Discard the information about the original heap size (if this table was read from an input), and instead use the 4339 * real size of the actual heap (plus reserved space around it) when writing to an output. Compacted tables may not 4340 * be re-writeable to the same file from which they were read, since they may be shorter than the original, but they 4341 * can always be written to a different file, which may at times be smaller than the original. It may be used along 4342 * with {@link #defragment()} to create FITS files with optimized storage from FITS files that may contain wasted 4343 * space. 4344 * 4345 * @see #defragment() 4346 * 4347 * @since 1.19.1 4348 */ 4349 public synchronized void compact() { 4350 heapFileSize = 0; 4351 } 4352 4353 @Override 4354 public BinaryTableHDU toHDU() throws FitsException { 4355 Header h = new Header(); 4356 fillHeader(h); 4357 return new BinaryTableHDU(h, this); 4358 } 4359 }