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 * For thread synchronization 1364 */ 1365 private Object lock = new Object(); 1366 1367 /** 1368 * Creates an empty binary table, which can be populated with columns / rows as desired. 1369 */ 1370 public BinaryTable() { 1371 table = new ColumnTable<>(); 1372 columns = new ArrayList<>(); 1373 heap = new FitsHeap(0); 1374 nRow = 0; 1375 rowLen = 0; 1376 } 1377 1378 /** 1379 * Creates a binary table from an existing column table. <b>WARNING!</b>, as of 1.18 we no longer use the column 1380 * data extra state to carry information about an enclosing class, because it is horribly bad practice. You should 1381 * not use this constructor to create imperfect copies of binary tables. Rather, use {@link #copy()} if you want to 1382 * create a new binary table, which properly inherits <b>ALL</b> of the properties of an original one. As for this 1383 * constructor, you should assume that it will not use anything beyond what's available in any generic vanilla 1384 * column table. 1385 * 1386 * @param tab the column table to create the binary table from. It must be a regular column table 1387 * that contains regular data of scalar or fixed 1D arrays only (not heap pointers). 1388 * No information beyond what a generic vanilla column table provides will be used. 1389 * Column tables don't store imensions for their elements, and don't have 1390 * variable-sized entries. Thus, if the table was the used in another binary table to 1391 * store flattened multidimensional data, we'll detect that data as 1D arrays. Andm if 1392 * the table was used to store heap pointers for variable length arrays, we'll detect 1393 * these as regular <code>int[2]</code> or <code>long[2]</code> values. 1394 * 1395 * @deprecated DO NOT USE -- it will be removed in the future. 1396 * 1397 * @throws FitsException if the table could not be copied and threw a {@link nom.tam.util.TableException}, which 1398 * is preserved as the cause. 1399 * 1400 * @see #copy() 1401 */ 1402 @Deprecated 1403 public BinaryTable(ColumnTable<?> tab) throws FitsException { 1404 this(); 1405 1406 table = new ColumnTable<>(); 1407 nRow = tab.getNRows(); 1408 columns = new ArrayList<>(); 1409 1410 for (int i = 0; i < tab.getNCols(); i++) { 1411 int n = tab.getElementSize(i); 1412 ColumnDesc c = new ColumnDesc(tab.getElementClass(i), n > 1 ? new int[] {n} : SINGLETON_SHAPE); 1413 addFlattenedColumn(tab.getColumn(i), nRow, c, true); 1414 } 1415 1416 } 1417 1418 /** 1419 * Creates a binary table from a given FITS header description. The table columns are initialized but no data will 1420 * be available, at least initially. Data may be loaded later (e.g. deferred read mode), provided the table is 1421 * associated to an input (usually only if this constructor is called from a {@link Fits} object reading an input). 1422 * When the table has an input configured via a {@link Fits} object, the table entries may be accessed in-situ in 1423 * the file while in deferred read mode, but operations affecting significant portions of the table (e.g. retrieving 1424 * all data via {@link #getData()} or accessing entire columns) may load the data in memory. You can also call 1425 * {@link #detach()} any time to force loading the data into memory, so that alterations after that will not be 1426 * reflected in the original file, at least not unitl {@link #rewrite()} is called explicitly. 1427 * 1428 * @param header A FITS header describing what the binary table should look like. 1429 * 1430 * @throws FitsException if the specified header is not usable for a binary table 1431 * 1432 * @deprecated (<i>for internal use</i>) This constructor should only be called from a {@link Fits} 1433 * object reading an input; visibility may be reduced to the package level in the 1434 * future. 1435 * 1436 * @see #isDeferred() 1437 */ 1438 @Deprecated 1439 public BinaryTable(Header header) throws FitsException { 1440 String ext = header.getStringValue(Standard.XTENSION, Standard.XTENSION_IMAGE); 1441 1442 if (!ext.equalsIgnoreCase(Standard.XTENSION_BINTABLE) && !ext.equalsIgnoreCase(NonStandard.XTENSION_A3DTABLE)) { 1443 throw new FitsException( 1444 "Not a binary table header (XTENSION = " + header.getStringValue(Standard.XTENSION) + ")"); 1445 } 1446 1447 nRow = header.getIntValue(Standard.NAXIS2); 1448 1449 long tableSize = nRow * header.getLongValue(Standard.NAXIS1); 1450 long paramSizeL = header.getLongValue(Standard.PCOUNT); 1451 long heapOffsetL = header.getLongValue(Standard.THEAP, tableSize); 1452 1453 // Subtract out the size of the regular table from 1454 // the heap offset. 1455 long heapSizeL = (tableSize + paramSizeL) - heapOffsetL; 1456 1457 if (heapSizeL < 0) { 1458 throw new FitsException("Inconsistent THEAP and PCOUNT"); 1459 } 1460 if (heapSizeL > Integer.MAX_VALUE) { 1461 throw new FitsException("Heap size > 2 GB"); 1462 } 1463 if (heapSizeL == 0L) { 1464 // There is no heap. Forget the offset 1465 heapAddress = 0; 1466 } 1467 1468 heapAddress = (int) heapOffsetL; 1469 heapFileSize = (int) heapSizeL; 1470 1471 int nCol = header.getIntValue(Standard.TFIELDS); 1472 1473 synchronized (lock) { 1474 rowLen = 0; 1475 1476 columns = new ArrayList<>(); 1477 for (int col = 0; col < nCol; col++) { 1478 rowLen += processCol(header, col, rowLen); 1479 } 1480 1481 HeaderCard card = header.getCard(Standard.NAXIS1); 1482 card.setValue(rowLen); 1483 } 1484 } 1485 1486 /** 1487 * Creates a binary table from existing table data int row-major format. That is the first array index is the row 1488 * index while the second array index is the column index. 1489 * 1490 * @param rowColTable Row / column array. Scalars elements are wrapped in arrays of 1, s.t. a single 1491 * <code>int</code> elements is stored as <code>int[1]</code> at its 1492 * <code>[row][col]</code> index. 1493 * 1494 * @throws FitsException if the argument is not a suitable representation of data in rows. 1495 * 1496 * @deprecated The constructor is ambiguous, use {@link #fromRowMajor(Object[][])} instead. You can 1497 * have a column-major array that has no scalar primitives which would also be an 1498 * <code>Object[][]</code> and could be passed erroneously. 1499 */ 1500 @Deprecated 1501 public BinaryTable(Object[][] rowColTable) throws FitsException { 1502 this(); 1503 for (Object[] row : rowColTable) { 1504 addRow(row); 1505 } 1506 } 1507 1508 /** 1509 * Creates a binary table from existing table data in row-major format. That is the first array index is the row 1510 * index while the second array index is the column index; 1511 * 1512 * @param table Row / column array. Scalars elements are wrapped in arrays of 1, s.t. a single 1513 * <code>int</code> elements is stored as <code>int[1]</code> at its 1514 * <code>[row][col]</code> index. 1515 * 1516 * @return a new binary table with the data. The tables data may be partially independent from the 1517 * argument. Modifications to the table data, or that to the argument have undefined 1518 * effect on the other object. If it is important to decouple them, you can use a 1519 * {@link ArrayFuncs#deepClone(Object)} of your original data as an argument. 1520 * 1521 * @throws FitsException if the argument is not a suitable representation of FITS data in rows. 1522 * 1523 * @see #fromColumnMajor(Object[]) 1524 * 1525 * @since 1.18 1526 */ 1527 public static BinaryTable fromRowMajor(Object[][] table) throws FitsException { 1528 BinaryTable tab = new BinaryTable(); 1529 for (Object[] row : table) { 1530 tab.addRow(row); 1531 } 1532 return tab; 1533 } 1534 1535 /** 1536 * Create a binary table from existing data in column-major format order. 1537 * 1538 * @param columns array of columns. The data for scalar entries is a primive array. For all else, the 1539 * entry is an <code>Object[]</code> array of sorts. 1540 * 1541 * @throws FitsException if the data for the columns could not be used as coulumns 1542 * 1543 * @deprecated The constructor is ambiguous, use {@link #fromColumnMajor(Object[])} instead. One could 1544 * call this method with any row-major <code>Object[][]</code> table by mistake. 1545 * 1546 * @see #defragment() 1547 */ 1548 @Deprecated 1549 public BinaryTable(Object[] columns) throws FitsException { 1550 this(); 1551 1552 for (Object element : columns) { 1553 addColumn(element); 1554 } 1555 } 1556 1557 /** 1558 * Creates a binary table from existing data in column-major format order. 1559 * 1560 * @param columns array of columns. The data for scalar entries is a primive array. For all else, the entry 1561 * is an <code>Object[]</code> array of sorts. 1562 * 1563 * @return a new binary table with the data. The tables data may be partially independent from the 1564 * argument. Modifications to the table data, or that to the argument have undefined 1565 * effect on the other object. If it is important to decouple them, you can use a 1566 * {@link ArrayFuncs#deepClone(Object)} of your original data as an argument. 1567 * 1568 * @throws FitsException if the argument is not a suitable representation of FITS data in rows. 1569 * 1570 * @see #fromColumnMajor(Object[]) 1571 * 1572 * @since 1.18 1573 */ 1574 public static BinaryTable fromColumnMajor(Object[] columns) throws FitsException { 1575 BinaryTable t = new BinaryTable(); 1576 for (Object element : columns) { 1577 t.addColumn(element); 1578 } 1579 return t; 1580 } 1581 1582 @Override 1583 protected BinaryTable clone() { 1584 try { 1585 return (BinaryTable) super.clone(); 1586 } catch (CloneNotSupportedException e) { 1587 return null; 1588 } 1589 } 1590 1591 /** 1592 * Returns an independent copy of the binary table. 1593 * 1594 * @return a new binary that tnat contains an exact copy of us, but is completely independent. 1595 * 1596 * @throws FitsException if the table could not be copied 1597 * 1598 * @since 1.18 1599 */ 1600 public BinaryTable copy() throws FitsException { 1601 BinaryTable copy = clone(); 1602 1603 synchronized (lock) { 1604 if (table != null) { 1605 copy.table = table.copy(); 1606 } 1607 if (heap != null) { 1608 copy.heap = heap.copy(); 1609 } 1610 1611 copy.columns = new ArrayList<>(); 1612 for (ColumnDesc c : columns) { 1613 c = c.clone(); 1614 copy.columns.add(c); 1615 } 1616 } 1617 1618 return copy; 1619 } 1620 1621 /** 1622 * (<i>for internal use</i>) Discards all variable-length arrays from this table, that is all data stored on the 1623 * heap, and resets all heap descritors to (0,0). 1624 * 1625 * @since 1.19.1 1626 */ 1627 protected void discardVLAs() { 1628 synchronized (lock) { 1629 for (int col = 0; col < columns.size(); col++) { 1630 ColumnDesc c = columns.get(col); 1631 1632 if (c.isVariableSize()) { 1633 for (int row = 0; row < nRow; row++) { 1634 table.setElement(row, col, c.hasLongPointers() ? new long[2] : new int[2]); 1635 } 1636 } 1637 } 1638 1639 heap = new FitsHeap(0); 1640 } 1641 } 1642 1643 /** 1644 * Returns the number of bytes per regular table row 1645 * 1646 * @return the number of bytes in a regular table row. 1647 */ 1648 final int getRowBytes() { 1649 synchronized (lock) { 1650 return rowLen; 1651 } 1652 } 1653 1654 /** 1655 * @deprecated (<i>for internal use</i>) It may become a private method in the future. 1656 * 1657 * @param table the table to create the column data. 1658 * 1659 * @throws FitsException if the data could not be created. 1660 */ 1661 @Deprecated 1662 public static void createColumnDataFor(BinaryTable table) throws FitsException { 1663 synchronized (table) { 1664 table.createTable(table.nRow); 1665 } 1666 } 1667 1668 /** 1669 * @deprecated (<i>for internal use</i>) It may be reduced to private visibility in the future. 1670 * Parse the TDIMS value. If the TDIMS value cannot be deciphered a one-d array 1671 * with the size given in arrsiz is returned. 1672 * 1673 * @param tdims The value of the TDIMSn card. 1674 * 1675 * @return An int array of the desired dimensions. Note that the order of the tdims is the 1676 * inverse of the order in the TDIMS key. 1677 * 1678 * @throws HeaderCardException if the argument does not confirm to the FITS specification for the TDIMn keyword. 1679 */ 1680 @Deprecated 1681 public static int[] parseTDims(String tdims) throws HeaderCardException { 1682 if (tdims == null) { 1683 return null; 1684 } 1685 1686 // The TDIMs value should be of the form: "(i,j...)" 1687 int start = tdims.indexOf('('); 1688 1689 if (start < 0) { 1690 return null; 1691 } 1692 1693 int end = tdims.indexOf(')', start); 1694 if (end < 0) { 1695 end = tdims.length(); 1696 } 1697 1698 StringTokenizer st = new StringTokenizer(tdims.substring(start + 1, end), ","); 1699 int dim = st.countTokens(); 1700 1701 if (dim > 0) { 1702 int[] dims = new int[dim]; 1703 for (int i = dim; --i >= 0;) { 1704 try { 1705 dims[i] = Integer.parseInt(st.nextToken().trim()); 1706 } catch (NumberFormatException e) { 1707 throw new HeaderCardException("Invalid TDIMn value: '" + tdims + "'"); 1708 } 1709 1710 if (dims[i] < 0) { 1711 throw new HeaderCardException("Invalid TDIMn value: '" + tdims + "'"); 1712 } 1713 } 1714 return dims; 1715 } 1716 1717 return null; 1718 } 1719 1720 /** 1721 * <p> 1722 * Adds a column of complex values stored as the specified decimal type of components in the FITS. While you can 1723 * also use {@link #addColumn(Object)} to add complex values, that method will always add them as 64-bit 1724 * double-precision values. So, this method is provided to allow users more control over how they want their complex 1725 * data be stored. 1726 * </p> 1727 * <p> 1728 * 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, 1729 * which can be changed by {@link ColumnDesc#name(String)} after. 1730 * </p> 1731 * 1732 * @param o A {@link ComplexValue} or an array (possibly multi-dimensional) thereof. 1733 * @param decimalType <code>float.class</code> or <code>double.class</code> (all other values default to 1734 * <code>double.class</code>). 1735 * 1736 * @return the number of column in the table including the new column. 1737 * 1738 * @throws FitsException if the object contains values other than {@link ComplexValue} types or if the array is not 1739 * suitable for storing in the FITS, e.g. because it is multi-dimensional but varying in 1740 * shape / size. 1741 * 1742 * @since 1.18 1743 * 1744 * @see #addColumn(Object) 1745 */ 1746 public int addComplexColumn(Object o, Class<?> decimalType) throws FitsException { 1747 int col = columns.size(); 1748 int eSize = addColumn(ArrayFuncs.complexToDecimals(o, decimalType)); 1749 ColumnDesc c = columns.get(col); 1750 c.isComplex = true; 1751 c.setLegacyShape(c.fitsShape); 1752 return eSize; 1753 } 1754 1755 /** 1756 * <p> 1757 * Adds a column of string values (one per row), optimized for storage size. Unlike {@link #addColumn(Object)}, 1758 * which always store strings in fixed format, this method will automatically use variable-length columns for 1759 * storing the strings if their lengths vary sufficiently to make that form of storage more efficient, or if the 1760 * array contains nulls (which may be defined later). 1761 * </p> 1762 * <p> 1763 * 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, 1764 * which can be changed by {@link ColumnDesc#name(String)} after. 1765 * </p> 1766 * 1767 * @param o A 1D string array, with 1 string element per table row. The array may contain 1768 * <code>null</code> entries, in which case variable-length columns will be used, since 1769 * these may be defined later... 1770 * 1771 * @return the number of column in the table including the new column. 1772 * 1773 * @throws FitsException if the object contains values other than {@link ComplexValue} types or if the array is not 1774 * suitable for storing in the FITS, e.g. because it is multi-dimensional but varying in 1775 * shape / size. 1776 * 1777 * @since 1.18 1778 * 1779 * @see #addColumn(Object) 1780 */ 1781 public int addStringColumn(String[] o) throws FitsException { 1782 checkRowCount(o); 1783 1784 ColumnDesc c = new ColumnDesc(String.class); 1785 1786 // Check if we should be using variable-length strings 1787 // (provided its a scalar string column with sufficiently varied strings sizes to make it worth.. 1788 int min = FitsUtil.minStringLength(o); 1789 int max = FitsUtil.maxStringLength(o); 1790 1791 if (max - min > 2 * ElementType.forClass(c.pointerClass()).size()) { 1792 c = ColumnDesc.createForVariableSize(String.class); 1793 return addVariableSizeColumn(o, c); 1794 } 1795 1796 c = ColumnDesc.createForStrings(max); 1797 return addFlattenedColumn(o, o.length, c, false); 1798 } 1799 1800 /** 1801 * <p> 1802 * Adds a column of bits. This uses much less space than if adding boolean values as logicals (the default behaviot 1803 * of {@link #addColumn(Object)}, since logicals take up 1 byte per element, whereas bits are really single bits. 1804 * </p> 1805 * <p> 1806 * 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, 1807 * which can be changed by {@link ColumnDesc#name(String)} after. 1808 * </p> 1809 * 1810 * @param o An any-dimensional array of <code>boolean</code> values. 1811 * 1812 * @return the number of column in the table including the new column. 1813 * 1814 * @throws IllegalArgumentException if the argument is not an array of <code>boolean</code> values. 1815 * @throws FitsException if the object is not an array of <code>boolean</code> values. 1816 * 1817 * @since 1.18 1818 * 1819 * @see #addColumn(Object) 1820 */ 1821 public int addBitsColumn(Object o) throws FitsException { 1822 if (ArrayFuncs.getBaseClass(o) != boolean.class) { 1823 throw new IllegalArgumentException("Not an array of booleans: " + o.getClass()); 1824 } 1825 return addColumn(o, false); 1826 } 1827 1828 /** 1829 * <p> 1830 * Adds a new empty column to the table to the specification. This is useful when the user may want ot have more 1831 * control on how columns are configured before calling {@link #addRow(Object[])} to start populating. The new 1832 * column will be named as "Column <i>n</i>" (where <i>n</i> is the 1-based index of the column) by default, unless 1833 * already named otherwise. 1834 * </p> 1835 * <p> 1836 * 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, 1837 * which can be changed by {@link ColumnDesc#name(String)} after. 1838 * </p> 1839 * 1840 * @param descriptor the column descriptor 1841 * 1842 * @return the number of table columns after the addition 1843 * 1844 * @throws IllegalStateException if the table already contains data rows that prevent the addition of empty 1845 * comlumns. 1846 * 1847 * @see #addRow(Object[]) 1848 * @see ColumnDesc#name(String) 1849 */ 1850 public int addColumn(ColumnDesc descriptor) throws IllegalStateException { 1851 synchronized (lock) { 1852 if (nRow != 0) { 1853 throw new IllegalStateException("Cannot add empty columns to table already containing data rows"); 1854 } 1855 1856 descriptor.offset = rowLen; 1857 rowLen += descriptor.rowLen(); 1858 1859 if (descriptor.name() == null) { 1860 // Set default column name; 1861 descriptor.name(TableHDU.getDefaultColumnName(columns.size())); 1862 } 1863 columns.add(descriptor); 1864 return columns.size(); 1865 } 1866 } 1867 1868 /** 1869 * <p> 1870 * Adds a new column with the specified data array, with some default mappings. This method will always use 1871 * double-precision representation for {@link ComplexValue}-based data, and will represent <code>boolean</code> 1872 * based array data as one-byte-per element FITS logical values (for back compatibility). It will also store strings 1873 * as fixed sized (sized for the longest string element contained). 1874 * </p> 1875 * <p> 1876 * 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, 1877 * which can be changed by {@link ColumnDesc#name(String)} after. 1878 * </p> 1879 * <p> 1880 * If you want other complex-valued representations use {@link #addComplexColumn(Object, Class)} instead, and if you 1881 * want to pack <code>boolean</code>-based data more efficiently (using up to 8 times less space), use 1882 * {@link #addBitsColumn(Object)} instead, or else convert the column to bits afterwards using 1883 * {@link #convertToBits(int)}. And, if you want to allow storing strings more effiently in variable-length columns, 1884 * you should use {@link #addStringColumn(String[])} instead. 1885 * </p> 1886 * <p> 1887 * As of 1.18, the argument can be a boxed primitive for a coulmn containing a single scalar-valued entry (row). 1888 * </p> 1889 * 1890 * @param o column data array 1891 * 1892 * @see #addVariableSizeColumn(Object) 1893 * @see #addComplexColumn(Object, Class) 1894 * @see #addBitsColumn(Object) 1895 * @see #convertToBits(int) 1896 * @see #addStringColumn(String[]) 1897 * @see ColumnDesc#name(String) 1898 */ 1899 @Override 1900 public int addColumn(Object o) throws FitsException { 1901 return addColumn(o, true); 1902 } 1903 1904 private int checkRowCount(Object o) throws FitsException { 1905 if (!o.getClass().isArray()) { 1906 throw new TableException("Not an array: " + o.getClass().getName()); 1907 } 1908 1909 int rows = Array.getLength(o); 1910 1911 synchronized (lock) { 1912 if (columns.size() != 0 && rows != nRow) { 1913 throw new TableException("Mismatched number of rows: " + rows + ", expected " + nRow); 1914 } 1915 } 1916 1917 return rows; 1918 } 1919 1920 /** 1921 * Like {@link #addColumn(Object)}, but allows specifying whether we use back compatible mode. This mainly just 1922 * affects how <code>boolean</code> arrays are stored (as logical bytes in compatibility mode, or as packed bits 1923 * otherwise). 1924 * 1925 * @param o The column data array 1926 * @param compat Whether to add the column in a back compatibility mode with versions prior to 1.18. If 1927 * <code>true</code> <code>boolean</code> arrays will stored as logical bytes, otherwise as packed 1928 * bits. 1929 */ 1930 private int addColumn(Object o, boolean compat) throws FitsException { 1931 o = ArrayFuncs.objectToArray(o, compat); 1932 1933 int rows = checkRowCount(o); 1934 1935 ColumnDesc c = new ColumnDesc(ArrayFuncs.getBaseClass(o)); 1936 1937 if (ArrayFuncs.getBaseClass(o) == ComplexValue.class) { 1938 o = ArrayFuncs.complexToDecimals(o, double.class); 1939 c.isComplex = true; 1940 } 1941 1942 try { 1943 int[] dim = ArrayFuncs.checkRegularArray(o, c.isNullAllowed()); 1944 1945 if (c.isString()) { 1946 c.setStringLength(FitsUtil.maxStringLength(o)); 1947 } 1948 1949 if (c.isComplex) { 1950 // Drop the railing 2 dimension, keep only outer dims... 1951 dim = Arrays.copyOf(dim, dim.length - 1); 1952 o = ArrayFuncs.flatten(o); 1953 } 1954 1955 if (dim.length <= 1) { 1956 c.setSingleton(); 1957 } else { 1958 int[] shape = new int[dim.length - 1]; 1959 System.arraycopy(dim, 1, shape, 0, shape.length); 1960 c.setLegacyShape(shape); 1961 o = ArrayFuncs.flatten(o); 1962 } 1963 } catch (IllegalArgumentException e) { 1964 c.setVariableSize(false); 1965 return addVariableSizeColumn(o, c); 1966 } 1967 // getBaseClass() prevents heterogeneous columns, so no need to catch ClassCastException here. 1968 1969 return addFlattenedColumn(o, rows, c, compat); 1970 } 1971 1972 /** 1973 * <p> 1974 * Adds a new variable-length data column, populating it with the specified data object. Unlike 1975 * {@link #addColumn(Object)} which will use fixed-size data storage provided the data allows it, this method forces 1976 * the use of variable-sized storage regardless of the data layout -- for example to accommodate addiing rows / 1977 * elements of different sized at a later time. 1978 * </p> 1979 * <p> 1980 * 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, 1981 * which can be changed by {@link ColumnDesc#name(String)} after. 1982 * </p> 1983 * 1984 * @param o An array containing one entry per row. Multi-dimensional entries will be flattened to 1D 1985 * for storage on the heap. 1986 * 1987 * @return the number of table columns after the addition. 1988 * 1989 * @throws FitsException if the column could not be created as requested. 1990 * 1991 * @see #addColumn(Object) 1992 * @see #addColumn(ColumnDesc) 1993 * @see ColumnDesc#createForVariableSize(Class) 1994 * @see ColumnDesc#isVariableSize() 1995 * 1996 * @since 1.18 1997 */ 1998 public int addVariableSizeColumn(Object o) throws FitsException { 1999 Class<?> base = ArrayFuncs.getBaseClass(o); 2000 ColumnDesc c = ColumnDesc.createForVariableSize(base); 2001 return addVariableSizeColumn(o, c); 2002 } 2003 2004 /** 2005 * Adds a new column with data directly, without performing any checks on the data. This should only be use 2006 * internally, after ansuring the data integrity and suitability for this table. 2007 * 2008 * @param o the column data, whose integrity was verified previously 2009 * @param rows the number of rows the data contains (in flattened form) 2010 * @param c the new column's descriptor 2011 * 2012 * @return the number of table columns after the addition 2013 * 2014 * @throws FitsException if the data is not the right type or format for internal storage. 2015 */ 2016 private int addDirectColumn(Object o, int rows, ColumnDesc c) throws FitsException { 2017 synchronized (lock) { 2018 c.offset = rowLen; 2019 rowLen += c.rowLen(); 2020 2021 // Load any deferred data (we will not be able to do that once we alter the column structure) 2022 ensureData(); 2023 2024 // Set the default column name 2025 c.name(TableHDU.getDefaultColumnName(columns.size())); 2026 2027 table.addColumn(o, c.getTableBaseCount()); 2028 columns.add(c); 2029 2030 if (nRow == 0) { 2031 // Set the table row count to match first colum 2032 nRow = rows; 2033 } 2034 2035 return columns.size(); 2036 } 2037 } 2038 2039 private int addVariableSizeColumn(Object o, ColumnDesc c) throws FitsException { 2040 checkRowCount(o); 2041 2042 Object[] array = (Object[]) o; 2043 2044 o = Array.newInstance(c.pointerClass(), array.length * 2); 2045 2046 for (int i = 0; i < array.length; i++) { 2047 boolean multi = c.isComplex() ? array[i] instanceof Object[][] : array[i] instanceof Object[]; 2048 2049 if (multi) { 2050 boolean canBeComplex = false; 2051 2052 if (c.getFitsBase() == float.class || c.getFitsBase() == double.class) { 2053 int[] dim = ArrayFuncs.getDimensions(array[i]); 2054 if (dim[dim.length - 1] == 2) { 2055 canBeComplex = true; 2056 } 2057 } 2058 2059 if (!canBeComplex && !c.warnedFlatten) { 2060 LOG.warning("Table entries of " + array[i].getClass() 2061 + " will be stored as 1D arrays in variable-length columns. " 2062 + "Array shape(s) and intermittent null subarrays (if any) will be lost."); 2063 2064 c.warnedFlatten = true; 2065 } 2066 } 2067 2068 Object p = putOnHeap(c, array[i], null); 2069 System.arraycopy(p, 0, o, 2 * i, 2); 2070 } 2071 2072 return addDirectColumn(o, array.length, c); 2073 } 2074 2075 /** 2076 * Add a column where the data is already flattened. 2077 * 2078 * @param o The new column data. This should be a one-dimensional primitive array. 2079 * @param dims The dimensions of an element in the column, or null for singleton (scalar) columns 2080 * 2081 * @return the new column size 2082 * 2083 * @throws FitsException if the array could not be flattened 2084 * 2085 * @deprecated (<i>for internal use</i>) No longer used, will be removed in the future 2086 */ 2087 @Deprecated 2088 public int addFlattenedColumn(Object o, int... dims) throws FitsException { 2089 ColumnDesc c = new ColumnDesc(ArrayFuncs.getBaseClass(o)); 2090 2091 try { 2092 ArrayFuncs.checkRegularArray(o, c.isNullAllowed()); 2093 } catch (IllegalArgumentException e) { 2094 throw new FitsException("Irregular array: " + o.getClass() + ": " + e.getMessage(), e); 2095 } 2096 2097 if (c.isString()) { 2098 c.setStringLength(FitsUtil.maxStringLength(o)); 2099 } 2100 2101 int n = 1; 2102 2103 c.setLegacyShape(dims); 2104 for (int dim : dims) { 2105 n *= dim; 2106 } 2107 2108 int rows = Array.getLength(o) / n; 2109 2110 return addFlattenedColumn(o, rows, c, true); 2111 } 2112 2113 /** 2114 * Checks that a flattened column has a compatible size for storing in a fixed-width column. It will also log a 2115 * warning if the storage size of the object is zero. 2116 * 2117 * @param c the column descriptor 2118 * @param o the column data 2119 * 2120 * @throws FitsException if the data is not the right size for the column 2121 */ 2122 private void checkFlattenedColumnSize(ColumnDesc c, Object o) throws FitsException { 2123 synchronized (lock) { 2124 if (c.getTableBaseCount() == 0) { 2125 LOG.warning("Elements of column + " + columns.size() + " have zero storage size."); 2126 } else if (columns.size() > 0) { 2127 // Check that the number of rows is consistent. 2128 int l = Array.getLength(o); 2129 if (nRow > 0 && l != nRow * c.getTableBaseCount()) { 2130 throw new TableException( 2131 "Mismatched element count " + l + ", expected " + (nRow * c.getTableBaseCount())); 2132 } 2133 } 2134 } 2135 } 2136 2137 /** 2138 * This function is needed since we had made addFlattenedColumn public so in principle a user might have called it 2139 * directly. 2140 * 2141 * @param o The new column data. This should be a one-dimensional primitive array. 2142 * @param c The column description 2143 * 2144 * @return the new column size 2145 * 2146 * @throws FitsException if the data type, format, or element count is inconsistent with this table. 2147 */ 2148 private int addFlattenedColumn(Object o, int rows, ColumnDesc c, boolean compat) throws FitsException { 2149 // For back compatibility this method will add boolean values as logicals always... 2150 if (compat) { 2151 c.isBits = false; 2152 } 2153 2154 if (c.isBits) { 2155 // Special handling for bits, which have to be segmented into bytes... 2156 boolean[] bits = (boolean[]) o; 2157 o = FitsUtil.bitsToBytes(bits, bits.length / rows); 2158 } else { 2159 o = javaToFits1D(c, o); 2160 } 2161 2162 checkFlattenedColumnSize(c, o); 2163 2164 return addDirectColumn(o, rows, c); 2165 } 2166 2167 /** 2168 * <p> 2169 * Adds a row to the table. If this is the first row in a new table, fixed-length columns will be created from the 2170 * data type automatically. If you want more control over the column formats, you may want to specify columns 2171 * beforehand such as: 2172 * </p> 2173 * 2174 * <pre> 2175 * BinaryTable table = new BinaryTable(); 2176 * 2177 * // A column containing 64-bit floating point scalar values, 1 per row... 2178 * table.addColumn(ColumnDesc.createForScalars(double.class)); 2179 * 2180 * // A column containing 5x4 arrays of single-precision complex values... 2181 * table.addColumn(ColumnDesc.createForArrays(ComplexValue.Float.class, 5, 4) 2182 * 2183 * // A column containing Strings of variable length using 32-bit heap pointers... 2184 * table.addColumn(ColumnDesc.creatForVariableStrings(false); 2185 * </pre> 2186 * <p> 2187 * For scalar columns of primitive types, the argument may be the corresponding java boxed type (new style), or a 2188 * primitive array of 1 (old style). Thus, you can write either: 2189 * </p> 2190 * 2191 * <pre> 2192 * table.addRow(1, 3.14159265); 2193 * </pre> 2194 * <p> 2195 * or, 2196 * </p> 2197 * 2198 * <pre> 2199 * table.addRow(new Object[] { new int[] {1}, new double[] {3.14159265} }; 2200 * </pre> 2201 * 2202 * @see #addColumn(ColumnDesc) 2203 */ 2204 @Override 2205 public int addRow(Object[] o) throws FitsException { 2206 synchronized (lock) { 2207 if (columns.isEmpty()) { 2208 for (Object element : o) { 2209 if (element == null) { 2210 throw new TableException("Prototype row may not contain null"); 2211 } 2212 2213 Class<?> cl = element.getClass(); 2214 2215 if (cl.isArray()) { 2216 if (cl.getComponentType().isPrimitive() && Array.getLength(element) == 1) { 2217 // Primitives of 1 (e.g. short[1]) are wrapped and should be added as is. 2218 addColumn(element); 2219 } else { 2220 // Wrap into array of 1, as leading dimension becomes the number of rows, which must be 1... 2221 Object wrapped = Array.newInstance(element.getClass(), 1); 2222 Array.set(wrapped, 0, element); 2223 addColumn(wrapped); 2224 } 2225 } else { 2226 addColumn(ArrayFuncs.objectToArray(element, true)); 2227 } 2228 } 2229 2230 return 1; 2231 } 2232 2233 if (o.length != columns.size()) { 2234 throw new TableException("Mismatched row size: " + o.length + ", expected " + columns.size()); 2235 } 2236 2237 ensureData(); 2238 2239 Object[] flatRow = new Object[getNCols()]; 2240 2241 for (int i = 0; i < flatRow.length; i++) { 2242 ColumnDesc c = columns.get(i); 2243 if (c.isVariableSize()) { 2244 flatRow[i] = putOnHeap(c, o[i], null); 2245 } else { 2246 flatRow[i] = javaToFits1D(c, ArrayFuncs.flatten(o[i])); 2247 2248 int nexp = c.getElementCount(); 2249 if (c.stringLength > 0) { 2250 nexp *= c.stringLength; 2251 } 2252 2253 if (Array.getLength(flatRow[i]) != nexp) { 2254 throw new IllegalArgumentException("Mismatched element count for column " + i + ": got " 2255 + Array.getLength(flatRow[i]) + ", expected " + nexp); 2256 } 2257 } 2258 } 2259 2260 table.addRow(flatRow); 2261 nRow++; 2262 2263 return nRow; 2264 } 2265 } 2266 2267 @Override 2268 public void deleteColumns(int start, int len) throws FitsException { 2269 synchronized (lock) { 2270 ensureData(); 2271 2272 table.deleteColumns(start, len); 2273 2274 ArrayList<ColumnDesc> remain = new ArrayList<>(columns.size() - len); 2275 rowLen = 0; 2276 2277 for (int i = 0; i < columns.size(); i++) { 2278 if (i < start || i >= start + len) { 2279 ColumnDesc c = columns.get(i); 2280 c.offset = rowLen; 2281 rowLen += c.rowLen(); 2282 remain.add(c); 2283 } 2284 } 2285 columns = remain; 2286 } 2287 } 2288 2289 @Override 2290 public void deleteRows(int row, int len) throws FitsException { 2291 synchronized (lock) { 2292 ensureData(); 2293 table.deleteRows(row, len); 2294 nRow -= len; 2295 } 2296 } 2297 2298 /** 2299 * Returns the Java type of elements returned or expected by the older srray-based access methods. It can be 2300 * confusing, because: 2301 * <ul> 2302 * <li>Columns with variable sized entries report <code>int.class</code> or <code>long.class</code> regardless of 2303 * data type.</li> 2304 * <li>Regular logical and bit columns bith report <code>boolean.class</code>.</li> 2305 * <li>Regular complex valued columns report <code>float.class</code> or <code>double.class</code>.</li> 2306 * </ul> 2307 * 2308 * @return the types in the table, not the underlying types (e.g., for varying length arrays or booleans). 2309 * 2310 * @deprecated (<i>for internal use</i>) Ambiguous, use {@link ColumnDesc#getElementClass()} instead. Will remove in 2311 * the future. 2312 */ 2313 @Deprecated 2314 public Class<?>[] getBases() { 2315 synchronized (lock) { 2316 return table.getBases(); 2317 } 2318 } 2319 2320 /** 2321 * <p> 2322 * Returns the data for a particular column in as an array of elements. See {@link #addColumn(Object)} for more 2323 * information about the format of data elements in general. 2324 * </p> 2325 * 2326 * @param col The zero-based column index. 2327 * 2328 * @return an array of primitives (for scalar columns), or else an <code>Object[]</code> array, or 2329 * possibly <code>null</code> 2330 * 2331 * @throws FitsException if the table could not be accessed 2332 * 2333 * @see #setColumn(int, Object) 2334 * @see #getElement(int, int) 2335 * @see #getNCols() 2336 */ 2337 @Override 2338 public Object getColumn(int col) throws FitsException { 2339 synchronized (lock) { 2340 ColumnDesc c = columns.get(col); 2341 2342 if (!c.isVariableSize() && c.fitsDimension() == 0 && !c.isComplex()) { 2343 return getFlattenedColumn(col); 2344 } 2345 2346 ensureData(); 2347 2348 Object[] data = null; 2349 2350 for (int i = 0; i < nRow; i++) { 2351 Object e = getElement(i, col); 2352 if (data == null) { 2353 data = (Object[]) Array.newInstance(e.getClass(), nRow); 2354 } 2355 data[i] = e; 2356 } 2357 2358 return data; 2359 } 2360 } 2361 2362 /** 2363 * Returns the Java index of the first column by the specified name. 2364 * 2365 * @param name the name of the column (case sensitive). 2366 * 2367 * @return The column index, or else -1 if this table does not contain a column by the specified name. 2368 * 2369 * @see #getDescriptor(String) 2370 * @see ColumnDesc#name(String) 2371 * 2372 * @since 1.20 2373 */ 2374 public int indexOf(String name) { 2375 for (int col = 0; col < columns.size(); col++) { 2376 if (name.equals(getDescriptor(col).name())) { 2377 return col; 2378 } 2379 } 2380 return -1; 2381 } 2382 2383 @Override 2384 protected ColumnTable<?> getCurrentData() { 2385 synchronized (lock) { 2386 return table; 2387 } 2388 } 2389 2390 @Override 2391 public ColumnTable<?> getData() throws FitsException { 2392 return (ColumnTable<?>) super.getData(); 2393 } 2394 2395 /** 2396 * Returns the dimensions of elements in each column. 2397 * 2398 * @return an array of arrays with the dimensions of each column's data. 2399 * 2400 * @see ColumnDesc#getDimens() 2401 * 2402 * @deprecated (<i>for internal use</i>) Use {@link ColumnDesc#getEntryShape()} to access the shape of Java elements 2403 * individually for columns instead. Not useful to users since it returns the dimensions of the 2404 * primitive storage types, which is not always the dimension of elements on the Java side (notably 2405 * for string entries). 2406 */ 2407 @Deprecated 2408 public int[][] getDimens() { 2409 int[][] dimens = new int[columns.size()][]; 2410 for (int i = 0; i < dimens.length; i++) { 2411 dimens[i] = columns.get(i).getDimens(); 2412 } 2413 return dimens; 2414 } 2415 2416 /** 2417 * @deprecated (<i>for internal use</i>) It may be private in the future. 2418 * 2419 * @return An array with flattened data, in which each column's data is represented by a 1D array 2420 * 2421 * @throws FitsException if the reading of the data failed. 2422 */ 2423 @Deprecated 2424 public Object[] getFlatColumns() throws FitsException { 2425 ensureData(); 2426 return table.getColumns(); 2427 } 2428 2429 /** 2430 * @deprecated (<i>for internal use</i>) It may be reduced to private visibility in the future. 2431 * 2432 * @return column in flattened format. This is sometimes useful for fixed-sized columns. 2433 * Variable-sized columns will still return an <code>Object[]</code> array in which 2434 * each entry is the variable-length data for a row. 2435 * 2436 * @param col the column to flatten 2437 * 2438 * @throws FitsException if the column could not be flattened 2439 */ 2440 @Deprecated 2441 public Object getFlattenedColumn(int col) throws FitsException { 2442 synchronized (lock) { 2443 if (!validColumn(col)) { 2444 throw new TableException("Invalid column index " + col + " in table of " + getNCols() + " columns"); 2445 } 2446 2447 ColumnDesc c = columns.get(col); 2448 if (c.isVariableSize()) { 2449 throw new TableException("Cannot flatten variable-sized column data"); 2450 } 2451 2452 ensureData(); 2453 2454 if (c.isBits()) { 2455 boolean[] bits = new boolean[nRow * c.fitsCount]; 2456 for (int i = 0; i < nRow; i++) { 2457 boolean[] seg = (boolean[]) fitsToJava1D(c, table.getElement(i, col), c.fitsCount, false); 2458 System.arraycopy(seg, 0, bits, i * c.fitsCount, c.fitsCount); 2459 } 2460 return bits; 2461 } 2462 2463 return fitsToJava1D(c, table.getColumn(col), 0, false); 2464 } 2465 } 2466 2467 /** 2468 * <p> 2469 * Reserves space for future addition of rows at the end of the regular table. In effect, this pushes the heap to 2470 * start at an offset value, leaving a gap between the main table and the heap in the FITS file. If your table 2471 * contains variable-length data columns, you may also want to reserve extra heap space for these via 2472 * {@link #reserveHeapSpace(int)}. 2473 * </p> 2474 * <p> 2475 * Note, that (C)FITSIO, as of version 4.4.0, has no proper support for offset heaps, and so you may want to be 2476 * careful using this function as the resulting FITS files, while standard, may not be readable by other tools due 2477 * to their own lack of support. Note, however, that you may also use this function to undo an offset heap with an 2478 * argument <=0; 2479 * </p> 2480 * 2481 * @param rows The number of future rows fow which space should be reserved (relative to the current table size) for 2482 * future additions, or <=0 to ensure that the heap always follows immediately after the main 2483 * table, e.g. for better (C)FITSIO interoperability. 2484 * 2485 * @see #reserveHeapSpace(int) 2486 * 2487 * @since 1.19.1 2488 */ 2489 public void reserveRowSpace(int rows) { 2490 synchronized (lock) { 2491 heapAddress = rows > 0 ? getRegularTableSize() + (long) rows * getRowBytes() : 0; 2492 } 2493 } 2494 2495 /** 2496 * Reserves space in the file at the end of the heap for future heap growth (e.g. different/longer or new VLA 2497 * entries). You may generally want to call this along with {@link #reserveRowSpace(int)} if yuor table contains 2498 * variable-length columns, to ensure storage for future data in these. You may call with <=0 to discards any 2499 * previously reserved space. 2500 * 2501 * @param bytes The number of bytes of unused space to reserve at the end of the heap, e.g. for future modifications 2502 * or additions, when writing the data to file. 2503 * 2504 * @see #reserveRowSpace(int) 2505 * 2506 * @since 1.19.1 2507 */ 2508 public void reserveHeapSpace(int bytes) { 2509 synchronized (lock) { 2510 heapReserve = Math.max(0, bytes); 2511 } 2512 } 2513 2514 /** 2515 * Returns the address of the heap from the star of the HDU in the file. 2516 * 2517 * @return (bytes) the start of the heap area from the beginning of the HDU. 2518 */ 2519 final long getHeapAddress() { 2520 synchronized (lock) { 2521 long tableSize = getRegularTableSize(); 2522 return heapAddress > tableSize ? heapAddress : tableSize; 2523 } 2524 } 2525 2526 /** 2527 * Returns the offset from the end of the main table 2528 * 2529 * @return the offset to the heap 2530 */ 2531 final long getHeapOffset() { 2532 return getHeapAddress() - getRegularTableSize(); 2533 } 2534 2535 /** 2536 * It returns the heap size for storing in the FITS, which is the larger of the actual space occupied by the current 2537 * heap, or the original heap size based on the header when the HDU was read from an input. In the former case it 2538 * will also include heap space reserved for future additions. 2539 * 2540 * @return (byte) the size of the heap in the FITS file. 2541 * 2542 * @see #compact() 2543 * @see #reserveHeapSpace(int) 2544 */ 2545 private int getHeapSize() { 2546 synchronized (lock) { 2547 if (heap != null && heap.size() + heapReserve > heapFileSize) { 2548 return heap.size() + heapReserve; 2549 } 2550 return heapFileSize; 2551 } 2552 } 2553 2554 /** 2555 * @return the size of the heap -- including the offset from the end of the table data, and reserved space after. 2556 */ 2557 long getParameterSize() { 2558 synchronized (lock) { 2559 return getHeapOffset() + getHeapSize(); 2560 } 2561 } 2562 2563 /** 2564 * Returns an empty row for the table. Such model rows are useful when low-level reading binary tables from an input 2565 * row-by-row. You can simply all {@link nom.tam.util.ArrayDataInput#readArrayFully(Object)} to populate it with 2566 * data from a stream. You may also use model rows to add additional rows to an existing table. 2567 * 2568 * @return a row that may be used for direct i/o to the table. 2569 * 2570 * @deprecated (<i>for internal use</i>) Use {@link #getElement(int, int)} instead for low-level reading of tables 2571 * in deferred mode. Not recommended for uses because it requires a deep understanding of how data 2572 * (especially varialbe length columns) are represented in the FITS. Will reduce visibility to 2573 * private in the future. 2574 */ 2575 @Deprecated 2576 public Object[] getModelRow() { 2577 Object[] modelRow = new Object[columns.size()]; 2578 for (int i = 0; i < modelRow.length; i++) { 2579 ColumnDesc c = columns.get(i); 2580 if (c.fitsDimension() < 2) { 2581 modelRow[i] = Array.newInstance(c.getTableBase(), c.getTableBaseCount()); 2582 } else { 2583 modelRow[i] = Array.newInstance(c.getTableBase(), c.fitsShape); 2584 } 2585 } 2586 return modelRow; 2587 } 2588 2589 @Override 2590 public int getNCols() { 2591 return columns.size(); 2592 } 2593 2594 @Override 2595 public int getNRows() { 2596 synchronized (lock) { 2597 return nRow; 2598 } 2599 } 2600 2601 /** 2602 * Reads a regular table element in the main table from the input. This method should never be called unless we have 2603 * a random-accessible input associated, which is a requirement for deferred read mode. 2604 * 2605 * @param o The array element to populate 2606 * @param c the column descriptor 2607 * @param row the zero-based row index of the element 2608 * 2609 * @throws IOException If there was an I/O error accessing the input 2610 * @throws FitsException If there was some other error 2611 */ 2612 private void readTableElement(Object o, ColumnDesc c, int row) throws IOException, FitsException { 2613 synchronized (lock) { 2614 @SuppressWarnings("resource") 2615 RandomAccess in = getRandomAccessInput(); 2616 2617 in.position(getFileOffset() + row * (long) rowLen + c.offset); 2618 2619 if (c.isLogical()) { 2620 in.readArrayFully(o); 2621 } else { 2622 in.readImage(o); 2623 } 2624 } 2625 } 2626 2627 /** 2628 * Returns an unprocessed element from the table as a 1D array of the elements that are stored in the regular table 2629 * data, whithout reslving heap references. That is this call will return flattened versions of multidimensional 2630 * arrays, and will return only the heap locator (offset and size) for variable-sized columns. 2631 * 2632 * @return a particular element from the table but do no processing of this element (e.g., 2633 * dimension conversion or extraction of variable length array elements/) 2634 * 2635 * @param row The row of the element. 2636 * @param col The column of the element. 2637 * 2638 * @deprecated (<i>for internal use</i>) Will reduce visibility in the future. 2639 * 2640 * @throws FitsException if the operation failed 2641 */ 2642 @Deprecated 2643 public Object getRawElement(int row, int col) throws FitsException { 2644 synchronized (lock) { 2645 if (!validRow(row) || !validColumn(col)) { 2646 throw new TableException("No such element (" + row + "," + col + ")"); 2647 } 2648 2649 if (table == null) { 2650 try { 2651 ColumnDesc c = columns.get(col); 2652 Object e = c.newInstance(1); 2653 readTableElement(e, c, row); 2654 return e; 2655 } catch (IOException e) { 2656 throw new FitsException("Error reading from input: " + e.getMessage(), e); 2657 } 2658 } 2659 2660 ensureData(); 2661 return table.getElement(row, col); 2662 } 2663 } 2664 2665 /** 2666 * Returns a table element as a Java array. Consider using the more Java-friendly {@link #get(int, int)} or one of 2667 * the scalar access methods with implicit type conversion support. 2668 * 2669 * @see #get(int, int) 2670 * @see #getLogical(int, int) 2671 * @see #getNumber(int, int) 2672 * @see #getLong(int, int) 2673 * @see #getDouble(int, int) 2674 * @see #getString(int, int) 2675 */ 2676 @Override 2677 public Object getElement(int row, int col) throws FitsException { 2678 return getElement(row, col, false); 2679 } 2680 2681 /** 2682 * Returns a a table entry, with control over how FITS logical values are to be handled. 2683 * 2684 * @param row zero-based row index 2685 * @param col zero-based column index 2686 * @param isEnhanced Whether logicals should be returned as {@link Boolean} (rather than <code>boolean</code>) 2687 * and complex values as {@link ComplexValue} (rather than <code>float[2]</code> or 2688 * <code>double[2]</code>), or arrays thereof. Methods prior to 1.18 should set this to 2689 * <code>false</code> for back compatible behavior. 2690 * 2691 * @return The entry as a primitive array, or {@link String}, {@link Boolean} or {@link ComplexValue}, 2692 * or arrays thereof. 2693 * 2694 * @throws FitsException If the requested element could not be accessed. 2695 */ 2696 private Object getElement(int row, int col, boolean isEnhanced) throws FitsException { 2697 if (!validRow(row) || !validColumn(col)) { 2698 throw new TableException("No such element (" + row + "," + col + ")"); 2699 } 2700 2701 ColumnDesc c = columns.get(col); 2702 Object o = getRawElement(row, col); 2703 2704 if (c.isVariableSize()) { 2705 return getFromHeap(c, o, isEnhanced); 2706 } 2707 2708 o = fitsToJava1D(c, o, c.isBits() ? c.fitsCount : 0, isEnhanced); 2709 2710 if (c.legacyShape.length > 1) { 2711 return ArrayFuncs.curl(o, c.legacyShape); 2712 } 2713 2714 return o; 2715 } 2716 2717 /** 2718 * Returns a table element as an array of the FITS storage type. Similar to the original 2719 * {@link #getElement(int, int)}, except that FITS logicals are returned as arrays of <code>Boolean</code> (rather 2720 * than <code>boolean</code>), bits are returned as arrays of <code>boolean</code>, and complex values are returned 2721 * as arrays of {@link ComplexValue} rather than arrays of <code>double[2]</code> or <code>float[2]</code>. 2722 * Singleton (scalar) table elements are not boxed to an enclosing Java type (unlike {@link #get(int, int)}), an 2723 * instead returned as arrays of just one element. For example, a single logical as a <code>Boolean[1]</code>, a 2724 * single float as a <code>float[1]</code> or a single double-precision complex value as 2725 * <code>ComplexValue[1]</code>. 2726 * 2727 * @param row zero-based row index 2728 * @param col zero-based column index 2729 * 2730 * @return The table entry as an array of the stored Java type, without applying any type or quantization 2731 * conversions. 2732 * 2733 * @see #getArrayElementAs(int, int, Class) 2734 * @see #get(int, int) 2735 * 2736 * @since 1.20 2737 */ 2738 public Object getArrayElement(int row, int col) { 2739 return getElement(row, col, true); 2740 } 2741 2742 /** 2743 * <p> 2744 * Returns a numerical table element as an array of a specific underlying other numerical type. Similar 2745 * {@link #getArrayElement(int, int)} except that table entries are converted to the specified array type before 2746 * returning. If an integer-decimal conversion is involved, it will be performed through the column's quantizer (if 2747 * any) or else via a simple rounding as necessary. 2748 * </p> 2749 * <p> 2750 * For example, if you have an <code>short</code>-type column, and you want is an array of <code>double</code> 2751 * values that are represented by the 16-bit integers, then the conversion will use the column's quantizer scaling 2752 * and offset before returning the result either as an array of doubles, and the designated <code>short</code> 2753 * blanking values will be converted to NaNs. 2754 * </p> 2755 * 2756 * @param row zero-based row index 2757 * @param col zero-based column index 2758 * @param asType The desired underlying type, a primitive class or a {@link ComplexValue} type 2759 * for appropriate numerical arrays (with a trailing Java dimension of 2 for 2760 * the real/imaginary pairs). 2761 * 2762 * @return An array of the desired type (e.g. <code>double[][]</code> if 2763 * <code>asType</code> is <code>double.class</code> and the column contains 2D 2764 * arrays of some numerical type). 2765 * 2766 * @throws IllegalArgumentException if the numerical conversion is not possible for the given column type or if the 2767 * type argument is not a supported numerical primitive or {@link ComplexValue} 2768 * type. 2769 * 2770 * @see #getArrayElement(int, int) 2771 * 2772 * @since 1.20 2773 */ 2774 public Object getArrayElementAs(int row, int col, Class<?> asType) throws IllegalArgumentException { 2775 ColumnDesc c = getDescriptor(col); 2776 Object e = getElement(row, col, true); 2777 return asType.isAssignableFrom(c.getFitsBase()) ? e : ArrayFuncs.convertArray(e, asType, c.getQuantizer()); 2778 } 2779 2780 /** 2781 * <p> 2782 * Returns a table element using the usual Java boxing for primitive scalar (singleton) entries, or packaging 2783 * complex values as {@link ComplexValue}, or as appropriate primitive or object arrays. FITS string columns return 2784 * {@link String} values. Logical (<code>boolean</code> columns will return a {@link Boolean}, which may be 2785 * <code>null</code> if undefined (as per the FITS standard). Multibit FITS bits colums return arrays of 2786 * <code>boolean</code>. 2787 * </p> 2788 * <p> 2789 * As opposed to {@link #getElement(int, int)} scalar (singleton) values are not wrapped into primitive arrays, but 2790 * return either a singular object, such as a ({@link String}, or a {@link ComplexValue}, or a boxed Java primitive. 2791 * Thus, columns containing single <code>short</code> entries will return the selected element as a {@link Short}, 2792 * or columns containing single <code>double</code> values will return the element as a {@link Double} and so on. 2793 * </p> 2794 * <p> 2795 * Array columns will return the expected arrays of primitive values, or arrays of one of the mentioned types. Note 2796 * however, that logical arrays are returned as arrays of {@link Boolean}, e.g. <code>Boolean[][]</code>, <b>not</b> 2797 * <code>boolean[][]</code>. This is because FITS allows <code>null</code> values for logicals beyond <code> 2798 * true</code> and <code>false</code>, which is reproduced by the boxed type, but not by the primitive type. FITS 2799 * columns of bits (generally preferrably to logicals if support for <code>null</code> values is not required) will 2800 * return arrays of <code>boolean</code>. 2801 * </p> 2802 * <p> 2803 * Columns containing multidimensional arrays, will return the expected multidimensional array of the above 2804 * mentioned types for the FITS storage type. You can then convert numerical arrays to other types as required for 2805 * your application via {@link ArrayFuncs#convertArray(Object, Class, Quantizer)}, including any appropriate 2806 * quantization for the colummn (see {@link ColumnDesc#getQuantizer()}). 2807 * </p> 2808 * 2809 * @param row the zero-based row index 2810 * @param col the zero-based column index 2811 * 2812 * @return the element, either as a Java boxed type (for scalar entries), a singular Java Object, or 2813 * as a (possibly multi-dimensional) array of {@link String}, {@link Boolean}, 2814 * {@link ComplexValue}, or primitives. 2815 * 2816 * @throws FitsException if the element could not be obtained 2817 * 2818 * @see #getNumber(int, int) 2819 * @see #getLogical(int, int) 2820 * @see #getString(int, int) 2821 * @see #getArrayElementAs(int, int, Class) 2822 * @see #set(int, int, Object) 2823 * 2824 * @since 1.18 2825 */ 2826 public Object get(int row, int col) throws FitsException { 2827 ColumnDesc c = columns.get(col); 2828 Object e = getElement(row, col, true); 2829 return (c.isSingleton() && e.getClass().isArray()) ? Array.get(e, 0) : e; 2830 } 2831 2832 /** 2833 * Returns the numerical value, if possible, for scalar elements. Scalar numerical columns return the boxed type of 2834 * their primitive type. Thus, a column of <code>long</code> values will return {@link Long}, whereas a column of 2835 * <code>float</code> values will return a {@link Float}. Logical columns will return 1 if <code>true</code> or 0 if 2836 * <code>false</code>, or <code>null</code> if undefined. Array columns and other column types will throw an 2837 * exception. 2838 * 2839 * @param row the zero-based row index 2840 * @param col the zero-based column index 2841 * 2842 * @return the number value of the specified scalar entry 2843 * 2844 * @throws FitsException if the element could not be obtained 2845 * @throws ClassCastException if the specified column in not a numerical scalar type. 2846 * @throws NumberFormatException if the it's a string column but the entry does not seem to be a number 2847 * 2848 * @see #getDouble(int, int) 2849 * @see #getLong(int, int) 2850 * @see #get(int, int) 2851 * 2852 * @since 1.18 2853 */ 2854 public final Number getNumber(int row, int col) throws FitsException, ClassCastException, NumberFormatException { 2855 Object o = get(row, col); 2856 if (o instanceof String) { 2857 try { 2858 return Long.parseLong((String) o); 2859 } catch (NumberFormatException e) { 2860 return Double.parseDouble((String) o); 2861 } 2862 } 2863 if (o instanceof Boolean) { 2864 return ((Boolean) o) ? 1 : 0; 2865 } 2866 return (Number) o; 2867 } 2868 2869 /** 2870 * <p> 2871 * Returns the decimal value, if possible, of a scalar table entry. See {@link #getNumber(int, int)} for more 2872 * information on the conversion process. 2873 * </p> 2874 * <p> 2875 * Since version 1.20, if the column has a quantizer and stores integer elements, the conversion to double-precision 2876 * will account for the quantization of the column, if any, and will return NaN if the stored integer is the 2877 * designated blanking value (if any). To bypass quantization, you can use {@link #getNumber(int, int)} instead 2878 * followed by {@link Number#doubleValue()} to to get the stored integer values as a double. 2879 * </p> 2880 * 2881 * @param row the zero-based row index 2882 * @param col the zero-based column index 2883 * 2884 * @return the number value of the specified scalar entry 2885 * 2886 * @throws FitsException if the element could not be obtained 2887 * @throws ClassCastException if the specified column in not a numerical scalar type. 2888 * 2889 * @see #getNumber(int, int) 2890 * @see #getLong(int, int) 2891 * @see #get(int, int) 2892 * @see ColumnDesc#getQuantizer() 2893 * 2894 * @since 1.18 2895 */ 2896 public final double getDouble(int row, int col) throws FitsException, ClassCastException { 2897 Number n = getNumber(row, col); 2898 2899 if (n == null) { 2900 return Double.NaN; 2901 } 2902 2903 if (!(n instanceof Float || n instanceof Double)) { 2904 Quantizer q = getDescriptor(col).getQuantizer(); 2905 if (q != null) { 2906 return q.toDouble(n.longValue()); 2907 } 2908 } 2909 2910 return n.doubleValue(); 2911 } 2912 2913 /** 2914 * <p> 2915 * Returns a 64-bit integer value, if possible, of a scalar table entry. Boolean columns will return 1 if 2916 * <code>true</code> or 0 if <code>false</code>, or throw a {@link NullPointerException} if undefined. See 2917 * {@link #getNumber(int, int)} for more information on the conversion process of the stored data element. 2918 * </p> 2919 * <p> 2920 * Additionally, since version 1.20, if the column has a quantizer and stores floating-point elements, the 2921 * conversion to integer will include the quantization, and NaN values will be converted to the designated integer 2922 * blanking values. To bypass quantization, you can use {@link #getNumber(int, int)} instead followed by 2923 * {@link Number#longValue()} to to get the stored floating point values rounded directly to a long. 2924 * </p> 2925 * 2926 * @param row the zero-based row index 2927 * @param col the zero-based column index 2928 * 2929 * @return the 64-bit integer number value of the specified scalar table entry. 2930 * 2931 * @throws FitsException if the element could not be obtained 2932 * @throws ClassCastException if the specified column in not a numerical scalar type. 2933 * @throws IllegalStateException if the column contains a undefined (blanking value), such as a {@link Double#NaN} 2934 * when no quantizer is set for the column, or a {@link Boolean} <code>null</code> 2935 * value. 2936 * 2937 * @see #getNumber(int, int) 2938 * @see #getDouble(int, int) 2939 * @see #get(int, int) 2940 * 2941 * @since 1.18 2942 */ 2943 public final long getLong(int row, int col) throws FitsException, ClassCastException, IllegalStateException { 2944 Number n = getNumber(row, col); 2945 2946 if (n instanceof Float || n instanceof Double) { 2947 Quantizer q = getDescriptor(col).getQuantizer(); 2948 if (q != null) { 2949 return q.toLong(n.doubleValue()); 2950 } 2951 } 2952 2953 if (Double.isNaN(n.doubleValue())) { 2954 throw new IllegalStateException("Cannot convert NaN to long without Quantizer"); 2955 } 2956 return n.longValue(); 2957 } 2958 2959 /** 2960 * Returns the boolean value, if possible, for scalar elements. It will will return<code>true</code>, or 2961 * <code>false</code>, or <code>null</code> if undefined. Numerical columns will return <code>null</code> if the 2962 * corresponding decimal value is NaN, or <code>false</code> if the value is 0, or else <code>true</code> for all 2963 * non-zero values (just like in C). 2964 * 2965 * @param row the zero-based row index 2966 * @param col the zero-based column index 2967 * 2968 * @return the boolean value of the specified scalar entry, or <code>null</code> if undefined. 2969 * 2970 * @throws ClassCastException if the specified column in not a scalar boolean type. 2971 * @throws FitsException if the element could not be obtained 2972 * 2973 * @see #get(int, int) 2974 * 2975 * @since 1.18 2976 */ 2977 @SuppressFBWarnings(value = "NP_BOOLEAN_RETURN_NULL", justification = "null has specific meaning here") 2978 public final Boolean getLogical(int row, int col) throws FitsException, ClassCastException { 2979 Object o = get(row, col); 2980 if (o == null) { 2981 return null; 2982 } 2983 2984 if (o instanceof Number) { 2985 Number n = (Number) o; 2986 if (Double.isNaN(n.doubleValue())) { 2987 return null; 2988 } 2989 return n.longValue() != 0; 2990 } 2991 2992 if (o instanceof Character) { 2993 char c = (Character) o; 2994 if (c == 'T' || c == 't' || c == '1') { 2995 return true; 2996 } 2997 if (c == 'F' || c == 'f' || c == '0') { 2998 return false; 2999 } 3000 return null; 3001 } 3002 3003 if (o instanceof String) { 3004 return FitsUtil.parseLogical((String) o); 3005 } 3006 3007 return (Boolean) o; 3008 } 3009 3010 /** 3011 * Returns the string value, if possible, for scalar elements. All scalar columns will return the string 3012 * representation of their values, while <code>byte[]</code> and <code>char[]</code> are converted to appropriate 3013 * strings. 3014 * 3015 * @param row the zero-based row index 3016 * @param col the zero-based column index 3017 * 3018 * @return the string representatiof the specified table entry 3019 * 3020 * @throws ClassCastException if the specified column contains array elements other than <code>byte[]</code> or 3021 * <code>char[]</code> 3022 * @throws FitsException if the element could not be obtained 3023 * 3024 * @see #get(int, int) 3025 * 3026 * @since 1.18 3027 */ 3028 public final String getString(int row, int col) throws FitsException, ClassCastException { 3029 ColumnDesc c = columns.get(col); 3030 Object value = get(row, col); 3031 3032 if (value == null) { 3033 return "null"; 3034 } 3035 3036 if (!value.getClass().isArray()) { 3037 return value.toString(); 3038 } 3039 3040 if (c.fitsDimension() > 1) { 3041 throw new ClassCastException("Cannot convert multi-dimensional array element to String"); 3042 } 3043 3044 if (value instanceof char[]) { 3045 return String.valueOf((char[]) value).trim(); 3046 } 3047 if (value instanceof byte[]) { 3048 return AsciiFuncs.asciiString((byte[]) value).trim(); 3049 } 3050 3051 throw new ClassCastException("Cannot convert " + value.getClass().getName() + " to String."); 3052 } 3053 3054 @Override 3055 public Object[] getRow(int row) throws FitsException { 3056 if (!validRow(row)) { 3057 throw new TableException("Invalid row index " + row + " in table of " + getNRows() + " rows"); 3058 } 3059 3060 Object[] data = new Object[columns.size()]; 3061 for (int col = 0; col < data.length; col++) { 3062 data[col] = getElement(row, col); 3063 } 3064 return data; 3065 } 3066 3067 /** 3068 * Returns the flattened (1D) size of elements in each column of this table. As of 1.18, this method returns a copy 3069 * ot the array used internally, which is safe to modify. 3070 * 3071 * @return an array with the byte sizes of each column 3072 * 3073 * @deprecated (<i>for internal use</i>) Use {@link ColumnDesc#getElementCount()} instead. This one returns the 3074 * number of elements in the FITS representation, not in the java representation. For example, for 3075 * {@link String} entries, this returns the number of bytes stored, not the number of strings. 3076 * Similarly, for complex values it returns the number of components not the number of values. 3077 */ 3078 @Deprecated 3079 public int[] getSizes() { 3080 int[] sizes = new int[columns.size()]; 3081 for (int i = 0; i < sizes.length; i++) { 3082 sizes[i] = columns.get(i).getTableBaseCount(); 3083 } 3084 return sizes; 3085 } 3086 3087 /** 3088 * Returns the size of the regular table data, before the heap area. 3089 * 3090 * @return the size of the regular table in bytes 3091 */ 3092 private long getRegularTableSize() { 3093 synchronized (lock) { 3094 return (long) nRow * rowLen; 3095 } 3096 } 3097 3098 @Override 3099 protected long getTrueSize() { 3100 return getRegularTableSize() + getParameterSize(); 3101 } 3102 3103 /** 3104 * Get the characters describing the base classes of the columns. As of 1.18, this method returns a copy ot the 3105 * array used internally, which is safe to modify. 3106 * 3107 * @return An array of type characters (Java array types), one for each column. 3108 * 3109 * @deprecated (<i>for internal use</i>) Use {@link ColumnDesc#getElementClass()} instead. Not very useful to users 3110 * since this returns the FITS primitive storage type for the data column. 3111 */ 3112 @Deprecated 3113 public char[] getTypes() { 3114 char[] types = new char[columns.size()]; 3115 for (int i = 0; i < columns.size(); i++) { 3116 types[i] = ElementType.forClass(columns.get(i).getTableBase()).type(); 3117 } 3118 return types; 3119 } 3120 3121 @Override 3122 public void setColumn(int col, Object o) throws FitsException { 3123 synchronized (lock) { 3124 ColumnDesc c = columns.get(col); 3125 3126 if (c.isVariableSize()) { 3127 Object[] array = (Object[]) o; 3128 for (int i = 0; i < nRow; i++) { 3129 Object p = putOnHeap(c, ArrayFuncs.flatten(array[i]), getRawElement(i, col)); 3130 setTableElement(i, col, p); 3131 } 3132 } else { 3133 setFlattenedColumn(col, o); 3134 } 3135 } 3136 } 3137 3138 /** 3139 * Writes an element directly into the random accessible FITS file. Note, this call will not modify the table in 3140 * memory (if loaded). This method should never be called unless we have a valid encoder object that can handle the 3141 * writing, which is a requirement for deferred read mode. 3142 * 3143 * @param row the zero-based row index 3144 * @param col the zero-based column index 3145 * @param array an array object containing primitive types, in FITS storage format. It may be 3146 * multi-dimensional. 3147 * 3148 * @throws IOException the there was an error writing to the FITS output 3149 * 3150 * @see #setTableElement(int, int, Object) 3151 */ 3152 @SuppressWarnings("resource") 3153 private void writeTableElement(int row, int col, Object array) throws IOException { 3154 synchronized (lock) { 3155 ColumnDesc c = columns.get(col); 3156 getRandomAccessInput().position(getFileOffset() + row * (long) rowLen + c.offset); 3157 } 3158 encoder.writeArray(array); 3159 } 3160 3161 /** 3162 * Sets a table element to an array in the FITS storage format. If the data is in deferred mode it will write the 3163 * table entry directly into the file. Otherwise it will update the table entry in memory. For variable sized 3164 * column, the heap will always be updated in memory, so you may want to call {@link #rewrite()} when done updating 3165 * all entries. 3166 * 3167 * @param row the zero-based row index 3168 * @param col the zero-based column index 3169 * @param o an array object containing primitive types, in FITS storage format. It may be 3170 * multi-dimensional. 3171 * 3172 * @throws FitsException if the array is invalid for the given column, or if the table could not be accessed in the 3173 * file / input. 3174 * 3175 * @see #setTableElement(int, int, Object) 3176 * @see #getRawElement(int, int) 3177 */ 3178 private void setTableElement(int row, int col, Object o) throws FitsException { 3179 synchronized (lock) { 3180 if (table == null) { 3181 try { 3182 writeTableElement(row, col, o); 3183 } catch (IOException e) { 3184 throw new FitsException(e.getMessage(), e); 3185 } 3186 } else { 3187 ensureData(); 3188 table.setElement(row, col, o); 3189 } 3190 } 3191 } 3192 3193 /** 3194 * Consider using the more Java-friendly {@link #set(int, int, Object)} with implicit scalar type conversions. 3195 * 3196 * @see #set(int, int, Object) 3197 */ 3198 @Override 3199 public void setElement(int row, int col, Object o) throws FitsException { 3200 ColumnDesc c = columns.get(col); 3201 o = c.isVariableSize() ? putOnHeap(c, o, getRawElement(row, col)) : javaToFits1D(c, ArrayFuncs.flatten(o)); 3202 setTableElement(row, col, o); 3203 } 3204 3205 /** 3206 * <p> 3207 * The Swiss-army knife of setting table entries, including Java boxing, and with some support for automatic type 3208 * conversions. The argument may be one of the following type: 3209 * </p> 3210 * <ul> 3211 * <li>Scalar values -- any Java primitive with its boxed type, such as a {@link Double}, or a 3212 * {@link Character}.</li> 3213 * <li>A single {@link String} or {@link ComplexValue} object. 3214 * <li>An array (including multidimensional) of primitive types, or that of {@link Boolean}, {@link ComplexValue}, 3215 * or {@link String}.</li> 3216 * </ul> 3217 * <p> 3218 * For array-type columns the argument needs to match the column type exactly. However, you may call 3219 * {@link ArrayFuncs#convertArray(Object, Class, Quantizer)} prior to setting values to convert arrays to the 3220 * desired numerical types, including the quantization that is appropriate for the column (see 3221 * {@link ColumnDesc#getQuantizer()}). 3222 * </p> 3223 * <p> 3224 * For scalar (single element) columns, automatic type conversions may apply, to make setting scalar columns more 3225 * flexible: 3226 * </p> 3227 * <ul> 3228 * <li>Any numerical column can take any {@link Number} value. The conversion is as if an explicit Java cast were 3229 * applied. For example, if setting a <code>double</code> value for a column of single <code>short</code> values it 3230 * as if a <code>(short)</code> cast were applied to the value.</li> 3231 * <li>Numerical colums can also take {@link Boolean} values which set the entry to 1, or 0, or to 3232 * {@link Double#isNaN()} (or the equivalent integer minimum value) if the argument is <code>null</code>. Numerical 3233 * columns can also set {@link String} values, by parsing the string according to the numerical type of the 3234 * column.</li> 3235 * <li>Logical columns can set {@link Boolean} values, including <code>null</code>values, but also any 3236 * {@link Number} type. In case of numbers, zero values map to <code>false</code> while definite non-zero values map 3237 * to <code>true</code>. {@link Double#isNaN()} maps to a <code>null</code> (or undefined) entry. Loginal columns 3238 * can be also set to the {@link String} values of 'true' or 'false', or to a {@link Character} of 'T'/'F' (or 3239 * equivalently '1'/'0') and 0 (undefined)</li> 3240 * <li>Singular string columns can be set to any scalar type owing to Java's {@link #toString()} method performing 3241 * the conversion, as long as the string representation fits into the size constraints (if any) for the string 3242 * column.</li> 3243 * </ul> 3244 * <p> 3245 * Additionally, scalar columns can take single-element array arguments, just like 3246 * {@link #setElement(int, int, Object)}. 3247 * </p> 3248 * 3249 * @param row the zero-based row index 3250 * @param col the zero-based column index 3251 * @param o the new value to set. For array columns this must match the Java array type 3252 * exactly, but for scalar columns additional flexibility is provided for fuzzy 3253 * type matching (see description above). 3254 * 3255 * @throws FitsException if the column could not be set 3256 * @throws IllegalArgumentException if the argument cannot be converted to a value for the specified column type. 3257 * 3258 * @since 1.18 3259 * 3260 * @see #get(int, int) 3261 */ 3262 public void set(int row, int col, Object o) throws FitsException, IllegalArgumentException { 3263 ColumnDesc c = columns.get(col); 3264 3265 if (o == null) { 3266 // Only logicals and strings support 'null' values 3267 if (!c.isSingleton()) { 3268 throw new TableException("No null values allowed for column of " + c.getLegacyBase() + " arrays."); 3269 } else if (c.isString()) { 3270 setElement(row, col, ""); 3271 } else { 3272 setLogical(row, col, null); 3273 } 3274 } else if (o.getClass().isArray()) { 3275 Class<?> eType = ArrayFuncs.getBaseClass(o); 3276 if (!c.getFitsBase().isAssignableFrom(eType) && c.isNumeric()) { 3277 o = ArrayFuncs.convertArray(o, c.getFitsBase(), c.getQuantizer()); 3278 } 3279 setElement(row, col, o); 3280 } else if (o instanceof String) { 3281 setString(row, col, (String) o); 3282 } else if (!c.isSingleton()) { 3283 throw new TableException("Cannot set scalar values in non-scalar columns"); 3284 } else if (c.isString()) { 3285 setElement(row, col, o.toString()); 3286 } else if (o instanceof Boolean) { 3287 setLogical(row, col, (Boolean) o); 3288 } else if (o instanceof Character) { 3289 setCharacter(row, col, (Character) o); 3290 } else if (o instanceof Number) { 3291 setNumber(row, col, (Number) o); 3292 } else if (o instanceof ComplexValue) { 3293 setElement(row, col, o); 3294 } else { 3295 throw new IllegalArgumentException("Unsupported scalar type: " + o.getClass()); 3296 } 3297 } 3298 3299 /** 3300 * Sets a scalar table entry to the specified numerical value. 3301 * 3302 * @param row the zero-based row index 3303 * @param col the zero-based column index 3304 * @param value the new number value 3305 * 3306 * @throws ClassCastException if the specified column in not a numerical scalar type. 3307 * @throws FitsException if the table element could not be altered 3308 * 3309 * @see #getNumber(int, int) 3310 * @see #set(int, int, Object) 3311 * 3312 * @since 1.18 3313 */ 3314 private void setNumber(int row, int col, Number value) throws FitsException, ClassCastException { 3315 ColumnDesc c = columns.get(col); 3316 3317 // Already checked before calling... 3318 // if (!c.isSingleton()) { 3319 // throw new ClassCastException("Cannot set scalar value for array column " + col); 3320 // } 3321 3322 if (c.isLogical()) { 3323 Boolean b = null; 3324 if (!Double.isNaN(value.doubleValue())) { 3325 b = value.longValue() != 0; 3326 } 3327 setTableElement(row, col, new byte[] {FitsEncoder.byteForBoolean(b)}); 3328 return; 3329 } 3330 3331 Class<?> base = c.getLegacyBase(); 3332 3333 // quantize / unquantize as necessary... 3334 Quantizer q = c.getQuantizer(); 3335 3336 if (q != null) { 3337 boolean decimalBase = (base == float.class || base == double.class); 3338 boolean decimalValue = (value instanceof Float || value instanceof Double || value instanceof BigInteger 3339 || value instanceof BigDecimal); 3340 3341 if (decimalValue && !decimalBase) { 3342 value = q.toLong(value.doubleValue()); 3343 } else if (!decimalValue && decimalBase) { 3344 value = q.toDouble(value.longValue()); 3345 } 3346 } 3347 3348 Object wrapped = null; 3349 3350 if (base == byte.class) { 3351 wrapped = new byte[] {value.byteValue()}; 3352 } else if (base == short.class) { 3353 wrapped = new short[] {value.shortValue()}; 3354 } else if (base == int.class) { 3355 wrapped = new int[] {value.intValue()}; 3356 } else if (base == long.class) { 3357 wrapped = new long[] {value.longValue()}; 3358 } else if (base == float.class) { 3359 wrapped = new float[] {value.floatValue()}; 3360 } else if (base == double.class) { 3361 wrapped = new double[] {value.doubleValue()}; 3362 } else { 3363 // This could be a char based column... 3364 throw new ClassCastException("Cannot set number value for column of type " + base); 3365 } 3366 3367 setTableElement(row, col, wrapped); 3368 } 3369 3370 /** 3371 * Sets a boolean scalar table entry to the specified value. 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 in not a boolean scalar type. 3378 * @throws FitsException if the table element could not be altered 3379 * 3380 * @see #getLogical(int, int) 3381 * @see #set(int, int, Object) 3382 * 3383 * @since 1.18 3384 */ 3385 private void setLogical(int row, int col, Boolean value) throws FitsException, ClassCastException { 3386 ColumnDesc c = columns.get(col); 3387 3388 // Already checked before calling... 3389 // if (!c.isSingleton()) { 3390 // throw new ClassCastException("Cannot set scalar value for array column " + col); 3391 // } 3392 3393 if (c.isLogical()) { 3394 setTableElement(row, col, new byte[] {FitsEncoder.byteForBoolean(value)}); 3395 } else if (c.getLegacyBase() == char.class) { 3396 setTableElement(row, col, new char[] {value == null ? '\0' : (value ? 'T' : 'F')}); 3397 } else { 3398 setNumber(row, col, value == null ? Double.NaN : (value ? 1 : 0)); 3399 } 3400 } 3401 3402 /** 3403 * Sets a Unicode character scalar table entry to the specified value. 3404 * 3405 * @param row the zero-based row index 3406 * @param col the zero-based column index 3407 * @param value the new Unicode character value 3408 * 3409 * @throws ClassCastException if the specified column in not a boolean scalar type. 3410 * @throws FitsException if the table element could not be altered 3411 * 3412 * @see #getString(int, int) 3413 * 3414 * @since 1.18 3415 */ 3416 private void setCharacter(int row, int col, Character value) throws FitsException, ClassCastException { 3417 ColumnDesc c = columns.get(col); 3418 3419 // Already checked before calling... 3420 // if (!c.isSingleton()) { 3421 // throw new IllegalArgumentException("Cannot set scalar value for array column " + col); 3422 // } 3423 3424 if (c.isLogical()) { 3425 setLogical(row, col, FitsUtil.parseLogical(value.toString())); 3426 } else if (c.fitsBase == char.class) { 3427 setTableElement(row, col, new char[] {value}); 3428 } else if (c.fitsBase == byte.class) { 3429 setTableElement(row, col, new byte[] {(byte) (value & FitsIO.BYTE_MASK)}); 3430 } else { 3431 throw new ClassCastException("Cannot convert char value to " + c.fitsBase.getName()); 3432 } 3433 } 3434 3435 /** 3436 * Sets a table entry to the specified string value. Scalar column will attempt to parse the value, while 3437 * <code>byte[]</code> and <code>char[]</code> type columns will convert the string provided the string's length 3438 * does not exceed the entry size for these columns (the array elements will be padded with zeroes). Note, that 3439 * scalar <code>byte</code> columns will parse the string as a number (not as a single ASCII character). 3440 * 3441 * @param row the zero-based row index 3442 * @param col the zero-based column index 3443 * @param value the new boolean value 3444 * 3445 * @throws ClassCastException if the specified column is not a scalar type, and neither it is a 3446 * <code>byte[]</code> or <code>char[]</code> column. 3447 * @throws IllegalArgumentException if the String is too long to contain in the column. 3448 * @throws NumberFormatException if the numerical value could not be parsed. 3449 * @throws FitsException if the table element could not be altered 3450 * 3451 * @see #getString(int, int) 3452 * @see #set(int, int, Object) 3453 * 3454 * @since 1.18 3455 */ 3456 private void setString(int row, int col, String value) 3457 throws FitsException, ClassCastException, IllegalArgumentException, NumberFormatException { 3458 ColumnDesc c = columns.get(col); 3459 3460 // Already checked before calling... 3461 // if (!c.isSingleton()) { 3462 // throw new IllegalArgumentException("Cannot set scalar value for array column " + col); 3463 // } 3464 3465 if (c.isLogical()) { 3466 setLogical(row, col, FitsUtil.parseLogical(value)); 3467 } else if (value.length() == 1) { 3468 setCharacter(row, col, value.charAt(0)); 3469 } else if (c.fitsDimension() > 1) { 3470 throw new ClassCastException("Cannot convert String to multi-dimensional array"); 3471 } else if (c.fitsDimension() == 1) { 3472 if (c.fitsBase != char.class && c.fitsBase != byte.class) { 3473 throw new ClassCastException("Cannot cast String to " + c.fitsBase.getName()); 3474 } 3475 int len = c.isVariableSize() ? value.length() : c.fitsCount; 3476 if (value.length() > len) { 3477 throw new IllegalArgumentException("String size " + value.length() + " exceeds entry size of " + len); 3478 } 3479 if (c.fitsBase == char.class) { 3480 setTableElement(row, col, Arrays.copyOf(value.toCharArray(), len)); 3481 } else { 3482 setTableElement(row, col, FitsUtil.stringToByteArray(value, len)); 3483 } 3484 } else { 3485 try { 3486 setNumber(row, col, Long.parseLong(value)); 3487 } catch (NumberFormatException e) { 3488 setNumber(row, col, Double.parseDouble(value)); 3489 } 3490 } 3491 } 3492 3493 /** 3494 * @deprecated (<i>for internal use</i>) It may be reduced to private visibility in the future. Sets a 3495 * column with the data already flattened. 3496 * 3497 * @param col The index of the column to be replaced. 3498 * @param data The new data array. This should be a one-d primitive array. 3499 * 3500 * @throws FitsException Thrown if the type of length of the replacement data differs from the original. 3501 */ 3502 @Deprecated 3503 public void setFlattenedColumn(int col, Object data) throws FitsException { 3504 synchronized (lock) { 3505 ensureData(); 3506 3507 Object oldCol = table.getColumn(col); 3508 if (data.getClass() != oldCol.getClass() || Array.getLength(data) != Array.getLength(oldCol)) { 3509 throw new TableException("Replacement column mismatch at column:" + col); 3510 } 3511 table.setColumn(col, javaToFits1D(columns.get(col), data)); 3512 } 3513 } 3514 3515 @Override 3516 public void setRow(int row, Object[] data) throws FitsException { 3517 ensureData(); 3518 3519 if (data.length != getNCols()) { 3520 throw new TableException("Mismatched number of columns: " + data.length + ", expected " + getNCols()); 3521 } 3522 3523 for (int col = 0; col < data.length; col++) { 3524 set(row, col, data[col]); 3525 } 3526 } 3527 3528 /** 3529 * @deprecated It is not entirely foolproof for keeping the header in sync -- it is better to (re)wrap tables in a 3530 * new HDU after column deletions, and then edit the new header as necessary to incorporate custom 3531 * entries. May be removed from the API in the future. 3532 */ 3533 @Deprecated 3534 @Override 3535 public void updateAfterDelete(int oldNcol, Header hdr) throws FitsException { 3536 synchronized (lock) { 3537 hdr.addValue(Standard.NAXIS1, rowLen); 3538 int l = 0; 3539 for (ColumnDesc d : columns) { 3540 d.offset = l; 3541 l += d.rowLen(); 3542 } 3543 } 3544 } 3545 3546 @SuppressWarnings("resource") 3547 @Override 3548 public void write(ArrayDataOutput os) throws FitsException { 3549 synchronized (lock) { 3550 try { 3551 if (isDeferred() && os == getRandomAccessInput()) { 3552 // It it's a deferred mode re-write, then data were edited in place if at all, 3553 // so we can skip the main table. 3554 ((RandomAccess) os).skipAllBytes(getRegularTableSize()); 3555 } else { 3556 // otherwise make sure we loaded all data before writing to the output 3557 ensureData(); 3558 3559 // Write the regular table (if any) 3560 if (getRegularTableSize() > 0) { 3561 table.write(os); 3562 } 3563 } 3564 3565 // Now check if we need to write the heap 3566 if (getParameterSize() > 0) { 3567 for (long rem = getHeapOffset(); rem > 0;) { 3568 byte[] b = new byte[(int) Math.min(getHeapOffset(), 1 << Short.SIZE)]; 3569 os.write(b); 3570 rem -= b.length; 3571 } 3572 3573 getHeap().write(os); 3574 3575 if (heapReserve > 0) { 3576 byte[] b = new byte[heapReserve]; 3577 os.write(b); 3578 } 3579 } 3580 3581 FitsUtil.pad(os, getTrueSize(), (byte) 0); 3582 } catch (IOException e) { 3583 throw new FitsException("Unable to write table:" + e, e); 3584 } 3585 } 3586 } 3587 3588 /** 3589 * Returns the heap offset component from a pointer. 3590 * 3591 * @param p the pointer, either a <code>int[2]</code> or a <code>long[2]</code>. 3592 * 3593 * @return the offset component from the pointer 3594 */ 3595 private long getPointerOffset(Object p) { 3596 return (p instanceof long[]) ? ((long[]) p)[1] : ((int[]) p)[1]; 3597 } 3598 3599 /** 3600 * Returns the number of elements reported in a heap pointer. 3601 * 3602 * @param p the pointer, either a <code>int[2]</code> or a <code>long[2]</code>. 3603 * 3604 * @return the element count component from the pointer 3605 */ 3606 private long getPointerCount(Object p) { 3607 return (p instanceof long[]) ? ((long[]) p)[0] : ((int[]) p)[0]; 3608 } 3609 3610 /** 3611 * Puts a FITS data array onto our heap, returning its locator pointer. The data will overwrite the previous heap 3612 * entry, if provided, so long as the new data fits in the same place. Otherwise the new data is placed at the end 3613 * of the heap. 3614 * 3615 * @param c The column descriptor, specifying the data type 3616 * @param o The variable-length data 3617 * @param oldPointer The heap pointer, where this element was stored on the heap before, or <code>null</code> if 3618 * we aren't replacing an earlier entry. 3619 * 3620 * @return the heap pointer information, either <code>int[2]</code> or else a <code>long[2]</code> 3621 * 3622 * @throws FitsException if the data could not be accessed in full from the heap. 3623 */ 3624 private Object putOnHeap(ColumnDesc c, Object o, Object oldPointer) throws FitsException { 3625 return putOnHeap(getHeap(), c, o, oldPointer); 3626 } 3627 3628 /** 3629 * Puts a FITS data array onto a specific heap, returning its locator pointer. The data will overwrite the previous 3630 * heap entry, if provided, so long as the new data fits in the same place. Otherwise the new data is placed at the 3631 * end of the heap. 3632 * 3633 * @param h The heap object to use. 3634 * @param c The column descriptor, specifying the data type 3635 * @param o The variable-length data in Java form. 3636 * @param oldPointer The heap pointer, where this element was stored on the heap before, or <code>null</code> if 3637 * we aren't replacing an earlier entry. 3638 * 3639 * @return the heap pointer information, either <code>int[2]</code> or else a <code>long[2]</code> 3640 * 3641 * @throws FitsException if the data could not be accessed in full from the heap. 3642 */ 3643 private Object putOnHeap(FitsHeap h, ColumnDesc c, Object o, Object oldPointer) throws FitsException { 3644 // Flatten data for heap 3645 o = ArrayFuncs.flatten(o); 3646 3647 // By default put data at the end of the heap; 3648 int off = h.size(); 3649 3650 // The number of Java elements is the same as the number of FITS elements, except for strings and complex 3651 // numbers 3652 int len = (c.isComplex() || c.isString()) ? -1 : Array.getLength(o); 3653 3654 // Convert to FITS storage array 3655 o = javaToFits1D(c, o); 3656 3657 // For complex values and strings, determine length from converted object.... 3658 if (len < 0) { 3659 len = Array.getLength(o); 3660 3661 // If complex in primitive 1D form, then length is half the number of elements. 3662 if (c.isComplex() && o.getClass().getComponentType().isPrimitive()) { 3663 len >>>= 1; 3664 } 3665 } 3666 3667 if (oldPointer != null) { 3668 if (len <= getPointerCount(oldPointer)) { 3669 // Write data back at the old heap location 3670 off = (int) getPointerOffset(oldPointer); 3671 } 3672 } 3673 3674 h.putData(o, off); 3675 3676 return c.hasLongPointers() ? new long[] {len, off} : new int[] {len, off}; 3677 } 3678 3679 /** 3680 * Returns a FITS data array from the heap 3681 * 3682 * @param c The column descriptor, specifying the data type 3683 * @param p The heap pointer, either <code>int[2]</code> or else a <code>long[2]</code> 3684 * @param isEnhanced Whether logicals should be returned as {@link Boolean} (rather than <code>boolean</code>) 3685 * and complex values as {@link ComplexValue} (rather than <code>float[2]</code> or 3686 * <code>double[2]</code>), or arrays thereof. Methods prior to 1.18 should set this to 3687 * <code>false</code> for back compatible behavior. 3688 * 3689 * @return the FITS array object retrieved from the heap 3690 * 3691 * @throws FitsException if the data could not be accessed in full from the heap. 3692 */ 3693 protected Object getFromHeap(ColumnDesc c, Object p, boolean isEnhanced) throws FitsException { 3694 long len = getPointerCount(p); 3695 long off = getPointerOffset(p); 3696 3697 if (off > Integer.MAX_VALUE || len > Integer.MAX_VALUE) { 3698 throw new FitsException("Data located beyond 32-bit accessible heap limit: off=" + off + ", len=" + len); 3699 } 3700 3701 Object e = null; 3702 3703 if (c.isComplex()) { 3704 e = Array.newInstance(c.getFitsBase(), (int) len, 2); 3705 } else { 3706 e = Array.newInstance(c.getFitsBase(), c.getFitsBaseCount((int) len)); 3707 } 3708 3709 readHeap(off, e); 3710 3711 return fitsToJava1D(c, e, (int) len, isEnhanced); 3712 } 3713 3714 /** 3715 * Convert Java arrays to their FITS representation. Transformation include boolean → 'T'/'F' or '\0'; 3716 * Strings → byte arrays; variable length arrays → pointers (after writing data to heap). 3717 * 3718 * @param c The column descritor 3719 * @param o A one-dimensional Java array 3720 * 3721 * @return An one-dimensional array with values as stored in FITS. 3722 * 3723 * @throws FitsException if the operation failed 3724 */ 3725 private static Object javaToFits1D(ColumnDesc c, Object o) throws FitsException { 3726 3727 if (c.isBits()) { 3728 if (o instanceof Boolean && c.isSingleton()) { 3729 // Scalar boxed boolean... 3730 return FitsUtil.bitsToBytes(new boolean[] {(Boolean) o}); 3731 } 3732 return FitsUtil.bitsToBytes((boolean[]) o); 3733 } 3734 3735 if (c.isLogical()) { 3736 // Convert true/false to 'T'/'F', or null to '\0' 3737 return FitsUtil.booleansToBytes(o); 3738 } 3739 3740 if (c.isComplex()) { 3741 if (o instanceof ComplexValue || o instanceof ComplexValue[]) { 3742 return ArrayFuncs.complexToDecimals(o, c.fitsBase); 3743 } 3744 } 3745 3746 if (c.isString()) { 3747 // Convert strings to array of bytes. 3748 if (o == null) { 3749 if (c.isVariableSize()) { 3750 return new byte[0]; 3751 } 3752 3753 return Array.newInstance(byte.class, c.fitsShape); 3754 } 3755 3756 if (o instanceof String) { 3757 int l = c.getStringLength(); 3758 if (l < 0) { 3759 // Not fixed width, write the whole string. 3760 l = ((String) o).length(); 3761 } 3762 return FitsUtil.stringToByteArray((String) o, l); 3763 } 3764 3765 if (c.isVariableSize() && c.delimiter != 0) { 3766 // Write variable-length string arrays in delimited form 3767 3768 for (String s : (String[]) o) { 3769 // We set the string length to that of the longest element + 1 3770 c.setStringLength(Math.max(c.stringLength, s == null ? 1 : s.length() + 1)); 3771 } 3772 3773 return FitsUtil.stringsToDelimitedBytes((String[]) o, c.getStringLength(), c.delimiter); 3774 } 3775 3776 // Fixed length substring array (not delimited). 3777 // For compatibility with tools that do not process array dimension, ASCII NULL should not 3778 // be used between components (permissible only at the end of all strings) 3779 return FitsUtil.stringsToByteArray((String[]) o, c.getStringLength(), FitsUtil.BLANK_SPACE); 3780 } 3781 3782 return ArrayFuncs.objectToArray(o, true); 3783 } 3784 3785 /** 3786 * Converts from the FITS representation of data to their basic Java array representation. 3787 * 3788 * @param c The column descritor 3789 * @param o A one-dimensional array of values as stored in FITS 3790 * @param bits A bit count for bit arrays (otherwise unused). 3791 * @param isEnhanced Whether logicals should be returned as {@link Boolean} (rather than <code>boolean</code>) 3792 * and complex values as {@link ComplexValue} (rather than <code>float[2]</code> or 3793 * <code>double[2]</code>), or arrays thereof. Methods prior to 1.18 should set this to 3794 * <code>false</code> for back compatible behavior. 3795 * 3796 * @return A {@link String} or a one-dimensional array with the matched basic Java type 3797 * 3798 * @throws FitsException if the operation failed 3799 */ 3800 private Object fitsToJava1D(ColumnDesc c, Object o, int bits, boolean isEnhanced) { 3801 3802 if (c.isBits()) { 3803 return FitsUtil.bytesToBits((byte[]) o, bits); 3804 } 3805 3806 if (c.isLogical()) { 3807 return isEnhanced ? FitsUtil.bytesToBooleanObjects(o) : FitsUtil.byteToBoolean((byte[]) o); 3808 } 3809 3810 if (c.isComplex() && isEnhanced) { 3811 return ArrayFuncs.decimalsToComplex(o); 3812 } 3813 3814 if (c.isString()) { 3815 byte[] bytes = (byte[]) o; 3816 3817 int len = c.getStringLength(); 3818 3819 if (c.isVariableSize()) { 3820 if (c.delimiter != 0) { 3821 // delimited array of strings 3822 return FitsUtil.delimitedBytesToStrings(bytes, c.getStringLength(), c.delimiter); 3823 } 3824 } 3825 3826 // If fixed or variable length arrays of strings... 3827 if (c.isSingleton()) { 3828 // Single fixed string -- get it all but trim trailing spaces 3829 return FitsUtil.extractString(bytes, new ParsePosition(0), bytes.length, FitsUtil.ASCII_NULL); 3830 } 3831 3832 // Array of fixed-length strings -- we trim trailing spaces in each component 3833 String[] s = new String[bytes.length / len]; 3834 for (int i = 0; i < s.length; i++) { 3835 s[i] = FitsUtil.extractString(bytes, new ParsePosition(i * len), len, FitsUtil.ASCII_NULL); 3836 } 3837 return s; 3838 } 3839 3840 return o; 3841 } 3842 3843 /** 3844 * Create a column table with the specified number of rows. This is used when we defer instantiation of the 3845 * ColumnTable until the user requests data from the table. 3846 * 3847 * @param rows the number of rows to allocate 3848 * 3849 * @throws FitsException if the operation failed 3850 */ 3851 protected void createTable(int rows) throws FitsException { 3852 synchronized (lock) { 3853 int nfields = columns.size(); 3854 Object[] data = new Object[nfields]; 3855 int[] sizes = new int[nfields]; 3856 for (int i = 0; i < nfields; i++) { 3857 ColumnDesc c = columns.get(i); 3858 sizes[i] = c.getTableBaseCount(); 3859 data[i] = c.newInstance(rows); 3860 } 3861 3862 table = createColumnTable(data, sizes); 3863 nRow = rows; 3864 } 3865 } 3866 3867 /** 3868 * Sets the input to use for reading (and possibly writing) this table. If the input implements 3869 * {@link ReadWriteAccess}, then it can be used for both reading and (re)writing the data, including editing in 3870 * deferred mode. 3871 * 3872 * @param in The input from which we can read the table data. 3873 */ 3874 private void setInput(ArrayDataInput in) { 3875 encoder = (in instanceof ReadWriteAccess) ? new FitsEncoder((ReadWriteAccess) in) : null; 3876 } 3877 3878 @Override 3879 public void read(ArrayDataInput in) throws FitsException { 3880 setInput(in); 3881 super.read(in); 3882 } 3883 3884 @Override 3885 protected void loadData(ArrayDataInput in) throws IOException, FitsException { 3886 synchronized (lock) { 3887 setInput(in); 3888 createTable(nRow); 3889 readTrueData(in); 3890 } 3891 } 3892 3893 /** 3894 * Extracts a column descriptor from the FITS header for a given column index 3895 * 3896 * @param header the FITS header containing the column description(s) 3897 * @param col zero-based column index 3898 * 3899 * @return the Descriptor for that column. 3900 * 3901 * @throws FitsException if the header deswcription is invalid or incomplete 3902 */ 3903 public static ColumnDesc getDescriptor(Header header, int col) throws FitsException { 3904 String tform = header.getStringValue(Standard.TFORMn.n(col + 1)); 3905 3906 if (tform == null) { 3907 throw new FitsException("Missing TFORM" + (col + 1)); 3908 } 3909 3910 int count = 1; 3911 char type = 0; 3912 3913 ParsePosition pos = new ParsePosition(0); 3914 3915 try { 3916 count = AsciiFuncs.parseInteger(tform, pos); 3917 } catch (Exception e) { 3918 // Keep going... 3919 } 3920 3921 try { 3922 type = Character.toUpperCase(AsciiFuncs.extractChar(tform, pos)); 3923 } catch (Exception e) { 3924 throw new FitsException("Missing data type in TFORM: [" + tform + "]"); 3925 } 3926 3927 ColumnDesc c = new ColumnDesc(); 3928 3929 if (header.containsKey(Standard.TTYPEn.n(col + 1))) { 3930 c.name(header.getStringValue(Standard.TTYPEn.n(col + 1))); 3931 } 3932 3933 if (type == POINTER_INT || type == POINTER_LONG) { 3934 // Variable length column... 3935 c.setVariableSize(type == POINTER_LONG); 3936 3937 // Get the data type... 3938 try { 3939 type = Character.toUpperCase(AsciiFuncs.extractChar(tform, pos)); 3940 } catch (Exception e) { 3941 throw new FitsException("Missing variable-length data type in TFORM: [" + tform + "]"); 3942 } 3943 } 3944 3945 // The special types... 3946 if (type == 'C' || type == 'M') { 3947 c.isComplex = true; 3948 } else if (type == 'X') { 3949 c.isBits = true; 3950 } 3951 3952 if (!c.setFitsType(type)) { 3953 throw new FitsException("Invalid type '" + type + "' in column:" + col); 3954 } 3955 3956 if (!c.isVariableSize()) { 3957 // Fixed sized column... 3958 int[] dims = parseTDims(header.getStringValue(Standard.TDIMn.n(col + 1))); 3959 3960 if (dims == null) { 3961 c.setFitsShape((count == 1 && type != 'A') ? SINGLETON_SHAPE : new int[] {count}); 3962 c.stringLength = -1; // T.B.D. further below... 3963 } else { 3964 c.setFitsShape(dims); 3965 } 3966 } 3967 3968 if (c.isString()) { 3969 // For vairable-length columns or of TDIM was not defined determine substring length from TFORM. 3970 c.parseSubstringConvention(tform, pos, c.getStringLength() < 0); 3971 } 3972 3973 // Force to use the count in the header, even if it does not match up with the dimension otherwise. 3974 c.fitsCount = count; 3975 3976 c.quant = Quantizer.fromTableHeader(header, col); 3977 if (c.quant.isDefault()) { 3978 c.quant = null; 3979 } 3980 3981 return c; 3982 } 3983 3984 /** 3985 * Process one column from a FITS Header. 3986 * 3987 * @throws FitsException if the operation failed 3988 */ 3989 private int processCol(Header header, int col, int offset) throws FitsException { 3990 ColumnDesc c = getDescriptor(header, col); 3991 c.offset = offset; 3992 columns.add(c); 3993 3994 return c.rowLen(); 3995 } 3996 3997 /** 3998 * @deprecated (<i>for internal use</i>) Used Only by {@link nom.tam.image.compression.hdu.CompressedTableData} so 3999 * it would make a better private method in there.. ` 4000 */ 4001 @Deprecated 4002 protected void addByteVaryingColumn() { 4003 addColumn(ColumnDesc.createForVariableSize(byte.class)); 4004 } 4005 4006 /** 4007 * @deprecated (<i>for internal use</i>) This method should have visibility reduced to private 4008 */ 4009 @SuppressWarnings("javadoc") 4010 @Deprecated 4011 protected ColumnTable<?> createColumnTable(Object[] arrCol, int[] sizes) throws TableException { 4012 return new ColumnTable<>(arrCol, sizes); 4013 } 4014 4015 /** 4016 * Returns the heap, after initializing it from the input as necessary 4017 * 4018 * @return the initialized heap 4019 * 4020 * @throws FitsException if we had trouble initializing it from the input. 4021 */ 4022 @SuppressWarnings("resource") 4023 private FitsHeap getHeap() throws FitsException { 4024 synchronized (lock) { 4025 if (heap == null) { 4026 readHeap(getRandomAccessInput()); 4027 } 4028 return heap; 4029 } 4030 } 4031 4032 /** 4033 * Reads an array from the heap. Subclasses may override this, for example to provide read-only access to a related 4034 * table's heap area. 4035 * 4036 * @param offset the heap offset 4037 * @param array the array to populate from the heap area 4038 * 4039 * @throws FitsException if there was an issue accessing the heap 4040 */ 4041 protected void readHeap(long offset, Object array) throws FitsException { 4042 getHeap().getData((int) offset, array); 4043 } 4044 4045 /** 4046 * Read the heap which contains the data for variable length arrays. A. Kovacs (4/1/08) Separated heap reading, s.t. 4047 * the heap can be properly initialized even if in deferred read mode. columnToArray() checks and initializes the 4048 * heap as necessary. 4049 * 4050 * @param input stream to read from. 4051 * 4052 * @throws FitsException if the heap could not be read from the stream 4053 * 4054 * @deprecated (<i>for internal use</i>) unused. 4055 */ 4056 @Deprecated 4057 protected void readHeap(ArrayDataInput input) throws FitsException { 4058 synchronized (lock) { 4059 if (input instanceof RandomAccess) { 4060 FitsUtil.reposition(input, getFileOffset() + getHeapAddress()); 4061 } 4062 4063 heap = new FitsHeap(heapFileSize); 4064 if (input != null) { 4065 heap.read(input); 4066 } 4067 } 4068 } 4069 4070 /** 4071 * Read table, heap and padding 4072 * 4073 * @param i the stream to read the data from. 4074 * 4075 * @throws FitsException if the reading failed 4076 */ 4077 protected void readTrueData(ArrayDataInput i) throws FitsException { 4078 try { 4079 synchronized (lock) { 4080 table.read(i); 4081 i.skipAllBytes(getHeapOffset()); 4082 if (heap == null) { 4083 readHeap(i); 4084 } 4085 } 4086 } catch (IOException e) { 4087 throw new FitsException("Error reading binary table data:" + e, e); 4088 } 4089 } 4090 4091 /** 4092 * Check if the column number is valid. 4093 * 4094 * @param j The Java index (first=0) of the column to check. 4095 * 4096 * @return <code>true</code> if the column is valid 4097 */ 4098 protected boolean validColumn(int j) { 4099 return j >= 0 && j < getNCols(); 4100 } 4101 4102 /** 4103 * Check to see if this is a valid row. 4104 * 4105 * @param i The Java index (first=0) of the row to check. 4106 * 4107 * @return <code>true</code> if the row is valid 4108 */ 4109 protected boolean validRow(int i) { 4110 return getNRows() > 0 && i >= 0 && i < getNRows(); 4111 } 4112 4113 /** 4114 * @deprecated (<i>for internal use</i>) Visibility should be reduced to protected. 4115 */ 4116 @Deprecated 4117 @Override 4118 public void fillHeader(Header h) throws FitsException { 4119 fillHeader(h, true); 4120 } 4121 4122 /** 4123 * Fills (updates) the essential header description of this table in the header, optionally updating the essential 4124 * column descriptions also if desired. 4125 * 4126 * @param h The FITS header to populate 4127 * @param updateColumns Whether to update the essential column descriptions also 4128 * 4129 * @throws FitsException if there was an error accessing the header. 4130 */ 4131 void fillHeader(Header h, boolean updateColumns) throws FitsException { 4132 h.deleteKey(Standard.SIMPLE); 4133 h.deleteKey(Standard.EXTEND); 4134 4135 Standard.context(BinaryTable.class); 4136 4137 Cursor<String, HeaderCard> c = h.iterator(); 4138 c.add(HeaderCard.create(Standard.XTENSION, Standard.XTENSION_BINTABLE)); 4139 c.add(HeaderCard.create(Standard.BITPIX, Bitpix.BYTE.getHeaderValue())); 4140 c.add(HeaderCard.create(Standard.NAXIS, 2)); 4141 4142 synchronized (lock) { 4143 c.add(HeaderCard.create(Standard.NAXIS1, rowLen)); 4144 c.add(HeaderCard.create(Standard.NAXIS2, nRow)); 4145 } 4146 4147 if (h.getLongValue(Standard.PCOUNT, -1L) < getParameterSize()) { 4148 c.add(HeaderCard.create(Standard.PCOUNT, getParameterSize())); 4149 } 4150 4151 c.add(HeaderCard.create(Standard.GCOUNT, 1)); 4152 c.add(HeaderCard.create(Standard.TFIELDS, columns.size())); 4153 4154 if (getHeapOffset() == 0) { 4155 h.deleteKey(Standard.THEAP); 4156 } else { 4157 c.add(HeaderCard.create(Standard.THEAP, getHeapAddress())); 4158 } 4159 4160 if (updateColumns) { 4161 for (int i = 0; i < columns.size(); i++) { 4162 c.setKey(Standard.TFORMn.n(i + 1).key()); 4163 fillForColumn(h, c, i); 4164 } 4165 } 4166 4167 Standard.context(null); 4168 } 4169 4170 /** 4171 * Update the header to reflect the details of a given column. 4172 * 4173 * @throws FitsException if the operation failed 4174 */ 4175 void fillForColumn(Header header, Cursor<String, HeaderCard> hc, int col) throws FitsException { 4176 ColumnDesc c = columns.get(col); 4177 4178 try { 4179 Standard.context(BinaryTable.class); 4180 4181 if (c.name() != null) { 4182 hc.add(HeaderCard.create(Standard.TTYPEn.n(col + 1), c.name())); 4183 } 4184 4185 hc.add(HeaderCard.create(Standard.TFORMn.n(col + 1), c.getTFORM())); 4186 4187 String tdim = c.getTDIM(); 4188 if (tdim != null) { 4189 hc.add(HeaderCard.create(Standard.TDIMn.n(col + 1), tdim)); 4190 } 4191 4192 if (c.quant != null) { 4193 c.quant.editTableHeader(header, col); 4194 } 4195 4196 } finally { 4197 Standard.context(null); 4198 } 4199 } 4200 4201 /** 4202 * Returns the column descriptor of a given column in this table 4203 * 4204 * @param column the zero-based column index 4205 * 4206 * @return the column's descriptor 4207 * 4208 * @throws ArrayIndexOutOfBoundsException if this table does not contain a column with that index. 4209 * 4210 * @see #getDescriptor(String) 4211 */ 4212 public ColumnDesc getDescriptor(int column) throws ArrayIndexOutOfBoundsException { 4213 return columns.get(column); 4214 } 4215 4216 /** 4217 * Returns the (first) column descriptor whose name matches the specified value. 4218 * 4219 * @param name The column name (case sensitive). 4220 * 4221 * @return The descriptor of the first column by that name, or <code>null</code> if the table contains no 4222 * column by that name. 4223 * 4224 * @see #getDescriptor(int) 4225 * @see #indexOf(String) 4226 * 4227 * @since 1.20 4228 */ 4229 public ColumnDesc getDescriptor(String name) { 4230 int col = indexOf(name); 4231 return col < 0 ? null : getDescriptor(col); 4232 } 4233 4234 /** 4235 * Converts a column from FITS logical values to bits. Null values (allowed in logical columns) will map to 4236 * <code>false</code>. 4237 * 4238 * @param col The zero-based index of the column to be reset. 4239 * 4240 * @return Whether the conversion was possible. * 4241 * 4242 * @since 1.18 4243 */ 4244 public boolean convertToBits(int col) { 4245 ColumnDesc c = columns.get(col); 4246 4247 if (c.isBits) { 4248 return true; 4249 } 4250 4251 if (c.base != boolean.class) { 4252 return false; 4253 } 4254 4255 c.isBits = true; 4256 return true; 4257 } 4258 4259 /** 4260 * Convert a column from float/double to float complex/double complex. This is only possible for certain columns. 4261 * The return status indicates if the conversion is possible. 4262 * 4263 * @param index The zero-based index of the column to be reset. 4264 * 4265 * @return Whether the conversion is possible. * 4266 * 4267 * @throws FitsException if the operation failed 4268 * 4269 * @since 1.18 4270 * 4271 * @see ColumnDesc#isComplex() 4272 * @see #addComplexColumn(Object, Class) 4273 */ 4274 public boolean setComplexColumn(int index) throws FitsException { 4275 synchronized (lock) { 4276 if (!validColumn(index)) { 4277 return false; 4278 } 4279 4280 ColumnDesc c = columns.get(index); 4281 if (c.isComplex()) { 4282 return true; 4283 } 4284 4285 if (c.base != float.class && c.base != double.class) { 4286 return false; 4287 } 4288 4289 if (!c.isVariableSize()) { 4290 if (c.getLastFitsDim() != 2) { 4291 return false; 4292 } 4293 // Set the column to complex 4294 c.isComplex = true; 4295 4296 // Update the legacy (wrapped array) shape 4297 c.setLegacyShape(c.fitsShape); 4298 return true; 4299 } 4300 4301 // We need to make sure that for every row, there are 4302 // an even number of elements so that we can 4303 // convert to an integral number of complex numbers. 4304 for (int i = 1; i < nRow; i++) { 4305 if (getPointerCount(getRawElement(i, index)) % 2 != 0) { 4306 return false; 4307 } 4308 } 4309 4310 // Halve the length component of array descriptors (2 reals = 1 complex) 4311 for (int i = 1; i < nRow; i++) { 4312 Object p = getRawElement(i, index); 4313 long len = getPointerCount(p) >>> 1; 4314 if (c.hasLongPointers()) { 4315 ((long[]) p)[0] = len; 4316 } else { 4317 ((int[]) p)[0] = (int) len; 4318 } 4319 setTableElement(i, index, p); 4320 } 4321 4322 // Set the column to complex 4323 c.isComplex = true; 4324 } 4325 4326 return true; 4327 4328 } 4329 4330 /** 4331 * Checks if this table contains a heap for storing variable length arrays (VLAs). 4332 * 4333 * @return <code>true</code> if the table contains a heap, or else <code>false</code>. 4334 * 4335 * @since 1.19.1 4336 */ 4337 public final boolean containsHeap() { 4338 return getParameterSize() > 0; 4339 } 4340 4341 /** 4342 * <p> 4343 * Defragments the heap area of this table, compacting the heap area, and returning the number of bytes by which the 4344 * heap size has been reduced. When tables with variable-sized columns are modified, the heap may retain old data as 4345 * columns are removed or elements get replaced with new data of different size. The data order in the heap may also 4346 * get jumbled, causing what would appear to be sequential reads to jump all over the heap space with the caching. 4347 * And, depending on how the heap was constructed in the first place, it may not be optimal for the row-after-row 4348 * table access that is the most typical use case. 4349 * </p> 4350 * <p> 4351 * This method rebuilds the heap by taking elements in table read order (by rows, and columns) and puts them on a 4352 * new heap. 4353 * </p> 4354 * <p> 4355 * For best squential read performance, you should defragment all tables that have been built column-by-column 4356 * before writing them to a FITS file. The only time defragmentation is really not needed is if the table was built 4357 * row-by-row, with no modifications to variable-length content after the fact. 4358 * </p> 4359 * 4360 * @return the number of bytes by which the heap has shrunk as a result of defragmentation. 4361 * 4362 * @throws FitsException if there was an error accessing the heap or the main data table comntaining the heap 4363 * locators. In case of an error the table content may be left in a damaged state. 4364 * 4365 * @see #compact() 4366 * @see #setElement(int, int, Object) 4367 * @see #addColumn(Object) 4368 * @see #deleteColumns(int, int) 4369 * @see #setColumn(int, Object) 4370 * 4371 * @since 1.18 4372 */ 4373 public long defragment() throws FitsException { 4374 if (!containsHeap()) { 4375 return 0L; 4376 } 4377 4378 synchronized (lock) { 4379 4380 int[] eSize = new int[columns.size()]; 4381 4382 for (int j = 0; j < columns.size(); j++) { 4383 ColumnDesc c = columns.get(j); 4384 if (c.isVariableSize()) { 4385 eSize[j] = ElementType.forClass(c.getFitsBase()).size(); 4386 } 4387 } 4388 4389 FitsHeap hp = getHeap(); 4390 long oldSize = hp.size(); 4391 FitsHeap compact = new FitsHeap(0); 4392 4393 for (int i = 0; i < nRow; i++) { 4394 for (int j = 0; j < columns.size(); j++) { 4395 ColumnDesc c = columns.get(j); 4396 if (c.isVariableSize()) { 4397 Object p = getRawElement(i, j); 4398 4399 int len = (int) getPointerCount(p); 4400 4401 // Copy to new heap... 4402 int pos = compact.copyFrom(hp, (int) getPointerOffset(p), c.getFitsBaseCount(len) * eSize[j]); 4403 4404 // Same length as before... 4405 if (p instanceof long[]) { 4406 ((long[]) p)[1] = pos; 4407 } else { 4408 ((int[]) p)[1] = pos; 4409 } 4410 4411 // Update pointers in table 4412 setTableElement(i, j, p); 4413 } 4414 } 4415 } 4416 4417 heap = compact; 4418 return oldSize - compact.size(); 4419 } 4420 } 4421 4422 /** 4423 * Discard the information about the original heap size (if this table was read from an input), and instead use the 4424 * real size of the actual heap (plus reserved space around it) when writing to an output. Compacted tables may not 4425 * be re-writeable to the same file from which they were read, since they may be shorter than the original, but they 4426 * can always be written to a different file, which may at times be smaller than the original. It may be used along 4427 * with {@link #defragment()} to create FITS files with optimized storage from FITS files that may contain wasted 4428 * space. 4429 * 4430 * @see #defragment() 4431 * 4432 * @since 1.19.1 4433 */ 4434 public void compact() { 4435 synchronized (lock) { 4436 heapFileSize = 0; 4437 } 4438 } 4439 4440 @Override 4441 public BinaryTableHDU toHDU() throws FitsException { 4442 Header h = new Header(); 4443 fillHeader(h); 4444 return new BinaryTableHDU(h, this); 4445 } 4446 }