View Javadoc
1   package nom.tam.util;
2   
3   /*
4    * #%L
5    * nom.tam FITS library
6    * %%
7    * Copyright (C) 2004 - 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.EOFException;
35  import java.io.IOException;
36  import java.lang.reflect.Array;
37  import java.util.ArrayList;
38  import java.util.Arrays;
39  
40  import nom.tam.util.type.ElementType;
41  
42  /**
43   * <p>
44   * Table data that is stored (internally) in column major format. This class has been completely re-written by A. Kovacs
45   * for 1.18. We keep the old API for compatibility, but make some practical additions to it.
46   * </p>
47   * Note that while column tables are fine to use for accessing data from FITS ASCII tables, they are generally not
48   * suitable for user-access of binary table data, because the column tables contain data in the format that is used for
49   * storing values in the regular FITS table in the file. That is:
50   * <ul>
51   * <li>logical values are represented by <code>byte</code> entries of 'T', 'F' or '0'.</li>
52   * <li>String values are represented as ASCII arrays bytes.</li>
53   * <li>Complex values are represented by <code>float[2]</code> or <code>double[2]</code>.</li>
54   * <li>Variable length columns of all types are represented by heap pointers of <code>int[2]</code> or
55   * <code>long[2]</code>.</li>
56   * </ul>
57   *
58   * @param <T> dummy generic type parameter that is no longer used. We'll stick to it a a memento of the bad design
59   *                decisions of the past...
60   */
61  public class ColumnTable<T> implements DataTable, Cloneable {
62  
63      /** A list of columns contained in this table */
64      private ArrayList<Column<?>> columns = new ArrayList<>();
65  
66      /** The number of rows */
67      private int nrow = 0;
68  
69      /** The smallest dynamic allocation when addig / deleting rows */
70      private static final int MIN_CAPACITY = 16;
71  
72      /**
73       * Allow the client to provide opaque data.
74       */
75      private T extraState;
76  
77      /**
78       * Creates an empty column table.
79       * 
80       * @since 1.18
81       */
82      public ColumnTable() {
83      }
84  
85      /**
86       * Create the object after checking consistency.
87       *
88       * @param  arrays         An array of one-d primitive arrays.
89       * @param  sizes          The number of elements in each row for the corresponding column
90       *
91       * @throws TableException if the structure of the columns is not consistent
92       */
93      public ColumnTable(Object[] arrays, int[] sizes) throws TableException {
94          for (int i = 0; i < arrays.length; i++) {
95              addColumn(arrays[i], sizes[i]);
96          }
97      }
98  
99      /**
100      * Checks if the table is empty (contains no data and no column definitions)
101      * 
102      * @return <code>true</code> if the table has no columns defined, otherwise <code>false</code>
103      * 
104      * @since  1.18
105      * 
106      * @see    #clear()
107      * @see    #deleteAllRows()
108      */
109     public final boolean isEmpty() {
110         return columns.isEmpty();
111     }
112 
113     /**
114      * Clears the table, discarding all columns.
115      * 
116      * @see   #deleteAllRows()
117      * 
118      * @since 1.18
119      */
120     public void clear() {
121         columns.clear();
122         nrow = 0;
123     }
124 
125     /**
126      * Makes sure that the table is expanded to hold up to the specified number of rows without having to grow
127      * dynamically. Typically it not necessary when adding new rows to the table, as the automatic dynamic allocation is
128      * quite efficient, but if you know in advance how many rows you want to add to the table, it does not hurt to just
129      * grow the table once, raher than a few times potentially. Note, that if deleting rows may annul the effect of this
130      * call, and shrink the table to a reasonable size after the deletions.
131      * 
132      * @param rows the number of rows we will want the table to hold at some point in the future...
133      * 
134      * @see        #addRow(Object[])
135      */
136     public void ensureSize(int rows) {
137         for (Column<?> c : columns) {
138             c.ensureSize(rows);
139         }
140     }
141 
142     /**
143      * Checks a column data, in which elements have been already wrapped to ensure that the column is self consistent,
144      * containing
145      * 
146      * @param  newColumn      An array holding the column data with each entry corresponding to hte data for a row.
147      * 
148      * @return                the element count
149      * 
150      * @throws TableException if the data is inconsistent, or contains null, or non-arrays
151      */
152     private int checkWrappedColumn(Object newColumn) throws TableException, NullPointerException {
153         // For array elements, check consistency...
154         if (!(newColumn instanceof Object[])) {
155             // Check as scalar column...
156             checkFlatColumn(newColumn, 1);
157             return 1;
158         }
159 
160         int[] dims = null;
161 
162         try {
163             dims = ArrayFuncs.checkRegularArray(newColumn, false);
164         } catch (Exception e) {
165             throw new TableException(e);
166         }
167 
168         if (dims.length != 2) {
169             throw new TableException("Not a 2D array: " + newColumn.getClass());
170         }
171 
172         Object[] entries = (Object[]) newColumn;
173         if (entries.length == 0) {
174             return 0;
175         }
176 
177         Object first = entries[0];
178         if (!first.getClass().getComponentType().isPrimitive()) {
179             throw new TableException("Entries are not a primitive arrays: " + first.getClass());
180         }
181 
182         if (getNRows() > 0 && dims[0] != getNRows()) {
183             throw new TableException("Mismatched row count: got " + dims[0] + ", expected " + getNRows());
184         }
185 
186         return Array.getLength(first);
187     }
188 
189     /**
190      * Adds a column as an array of scalars or regular 1D primitve array elements.
191      *
192      * @param  newColumn      the column to add, either as a 1D array of scalar primitives, or a regular 2D array of
193      *                            primitives, in which each row contains the same type of 1D primitive array of the same
194      *                            sizes.
195      *
196      * @throws TableException if the new column is not a 1D or 2D array of primitives, or it it does not match the
197      *                            number of existing table rows or if the column contains an irregular 2D array.
198      * 
199      * @since                 1.18
200      * 
201      * @see                   #getWrappedColumn(int)
202      * @see                   #setWrappedColumn(int, Object)
203      */
204     @SuppressWarnings("unchecked")
205     public void addWrappedColumn(Object newColumn) throws TableException {
206         if (newColumn == null) {
207             throw new TableException("Cannot add a null column.");
208         }
209 
210         int eCount = checkWrappedColumn(newColumn);
211 
212         Class<?> eType = newColumn.getClass().getComponentType();
213         if (eType.isArray()) {
214             eType = eType.getComponentType();
215         }
216 
217         @SuppressWarnings("rawtypes")
218         Column c = createColumn(eType, eCount);
219         c.data = newColumn;
220         nrow = Array.getLength(newColumn);
221         columns.add(c);
222     }
223 
224     /**
225      * Adds a new column with the specified primitive base class and element count.
226      * 
227      * @param  type           the primitive class of the elements in this colymn, such as <code>boolean.class</code>
228      * @param  size           the number of primitive elements (use 1 to create scalar columns)
229      * 
230      * @throws TableException if the type is not a primitive type or the size is invalid.
231      * 
232      * @see                   #addColumn(Object, int)
233      * 
234      * @since                 1.18
235      */
236     public void addColumn(Class<?> type, int size) throws TableException {
237         columns.add(createColumn(type, size));
238     }
239 
240     /**
241      * Converts a one-dimensional flat array of elements to a wrapped column, in which each top-level entry contains the
242      * specified number of row elements
243      * 
244      * @param  data           a one-domensional primitive array
245      * @param  size           the number of column data elements per row
246      * 
247      * @return                the wrapped column data, in which each tp-level entry contains data for a specific row.
248      * 
249      * @throws TableException If the data could not be converted
250      */
251     private Object wrapColumn(Object data, int size) throws TableException {
252 
253         checkFlatColumn(data, size);
254 
255         // Fold the 1D array into 2D array of subarrays for storing
256         Class<?> type = data.getClass().getComponentType();
257         int len = size == 0 ? nrow : Array.getLength(data) / size;
258 
259         // The parent array
260         Object[] array = (Object[]) Array.newInstance(data.getClass(), len);
261 
262         int offset = 0;
263         for (int i = 0; i < len; i++, offset += size) {
264             // subarrays...
265             array[i] = Array.newInstance(type, size);
266             System.arraycopy(data, offset, array[i], 0, size);
267         }
268 
269         return array;
270     }
271 
272     /**
273      * Adds a column in flattened 1D format, specifying the size of array 'elements'.
274      *
275      * @param  newColumn      the column to add.
276      * @param  size           size for the column
277      *
278      * @throws TableException if the new column is not a 1D array of primitives, or it it does not conform to the number
279      *                            of existing table rows for the given element size.
280      * 
281      * @see                   #addColumn(Class, int)
282      * @see                   #setColumn(int, Object)
283      */
284     @SuppressWarnings("unchecked")
285     public void addColumn(Object newColumn, int size) throws TableException {
286         if (newColumn == null) {
287             throw new TableException("Cannot add a null column: we don't know its type.");
288         }
289 
290         if (size < 0) {
291             throw new TableException("Invalid element size: " + size);
292         }
293 
294         if (size == 1 && newColumn.getClass().getComponentType().isPrimitive()) {
295             // Add scalar columns as is...
296             addWrappedColumn(newColumn);
297             return;
298         }
299 
300         checkFlatColumn(newColumn, size);
301 
302         @SuppressWarnings("rawtypes")
303         Column c = createColumn(newColumn.getClass().getComponentType(), size);
304         c.data = wrapColumn(newColumn, size);
305 
306         columns.add(c);
307         nrow = Array.getLength(c.data);
308     }
309 
310     /**
311      * Returns the next standard table capacity step that will allow adding at least one more row to the table.
312      * 
313      * @return the standard capacity (number of rows) to which we'd want to grow to contain another table row.
314      */
315     private int nextLargerCapacity() {
316         return nextLargerCapacity(nrow);
317     }
318 
319     /**
320      * Returns the next standard table capacity step that will allow adding at least one more row to the table.
321      * 
322      * @param  rows The number of rows we should be able to store.
323      * 
324      * @return      the standard capacity (number of rows) to which we'd want to grow to contain another table row.
325      */
326     private int nextLargerCapacity(int rows) {
327         if (rows < MIN_CAPACITY) {
328             return MIN_CAPACITY;
329         }
330         // Predictable doubling beyond the minimum size...
331         return (int) Math.min(Long.highestOneBit(rows) << 1, Integer.MAX_VALUE);
332     }
333 
334     /**
335      * Checks the integrity of an array containing element for a new row, with each element corresponding to an entry to
336      * the respective table column.
337      * 
338      * @param  row            An array containing data for a new table row.
339      * 
340      * @throws TableException If the row structure is inconsistent with that of the table.
341      */
342     private void checkRow(Object[] row) throws TableException {
343         // Check that the row matches existing columns
344         if (row.length != columns.size()) {
345             throw new TableException("Mismatched row size: " + row.length + ", expected " + columns.size());
346         }
347 
348         for (int col = 0; col < row.length; col++) {
349             Column<?> c = columns.get(col);
350             c.checkEntry(row[col]);
351         }
352     }
353 
354     /**
355      * Add a row to the table. Each element of the row must be a 1D primitive array. If this is not the first row in the
356      * table, each element must match the types and sizes of existing rows.
357      *
358      * @param  row            the row to add
359      *
360      * @throws TableException if the row contains other than 1D primitive array elements or if the elements do not match
361      *                            the types and sizes of existing table columns.
362      * 
363      * @see                   #deleteRow(int)
364      * @see                   #ensureSize(int)
365      */
366     public void addRow(Object[] row) throws TableException {
367         if (row == null) {
368             throw new TableException("Cannot add null row");
369         }
370 
371         if (nrow == Integer.MAX_VALUE) {
372             throw new TableException("Table has reached its capacity limit");
373         }
374 
375         if (isEmpty()) {
376             // This is the first row in the table, create columns with their elements if possible
377             try {
378                 for (int col = 0; col < row.length; col++) {
379                     addColumn(row[col], Array.getLength(row[col]));
380                 }
381                 return;
382             } catch (TableException e) {
383                 // The row was invalid, clear the table and re-throw the exception
384                 columns = new ArrayList<>();
385                 throw e;
386             }
387         }
388 
389         checkRow(row);
390 
391         // Get the size we'll grow to if we must...
392         int capacity = nextLargerCapacity();
393 
394         for (int i = 0; i < row.length; i++) {
395             Column<?> c = columns.get(i);
396             c.ensureSize(capacity);
397             c.checkEntry(row[i]);
398             c.setArrayElement(nrow, row[i]);
399         }
400         nrow++;
401     }
402 
403     /**
404      * Checks if a data in column format is consistent with this table, for example before adding it.
405      * 
406      * @param  data           A one-dimensional representation of the column data
407      * @param  size           the number of primitive elements per table row
408      * 
409      * @throws TableException if the column is not consistent with the current table structure.
410      */
411     private void checkFlatColumn(Object data, int size) throws TableException {
412         if (!data.getClass().isArray()) {
413             throw new TableException("Argument is not an array: " + data.getClass());
414         }
415 
416         int len = Array.getLength(data);
417 
418         // Data cannot be null here (we check upstream)
419         // if (data == null) {
420         // throw new TableException("Unexpected null column");
421         // }
422 
423         if (size > 0 && len % size != 0) {
424             throw new TableException("The column size " + len + " is not a multiple of the element size " + size);
425         }
426 
427         if (nrow == 0 || size == 0) {
428             return;
429         }
430 
431         if (len != nrow * size) {
432             throw new TableException("Mismatched element count: " + len + ", expected " + (nrow * size) + " for " + nrow
433                     + " rows of size " + size);
434         }
435     }
436 
437     @Override
438     @SuppressWarnings("unchecked")
439     protected ColumnTable<T> clone() {
440         try {
441             return (ColumnTable<T>) super.clone();
442         } catch (CloneNotSupportedException e) {
443             return null;
444         }
445     }
446 
447     /**
448      * Returns a deep copy of this column table, such that modification to either the original or the copy will not
449      * affect the other.
450      * 
451      * @return                A deep (independent) copy of this column table
452      * 
453      * @throws TableException (<i>for back compatibility</i>) never thrown.
454      */
455     @SuppressWarnings("cast")
456     public ColumnTable<T> copy() throws TableException {
457         ColumnTable<T> copy = (ColumnTable<T>) clone();
458         copy.columns = new ArrayList<>(columns.size());
459         for (Column<?> c : columns) {
460             copy.columns.add(c.copy(nrow));
461         }
462         return copy;
463     }
464 
465     /**
466      * Deletes a column from this table.
467      * 
468      * @param  col            the 0-based column index.
469      * 
470      * @throws TableException if the column index is out of bounds
471      * 
472      * @see                   #deleteColumns(int, int)
473      * 
474      * @since                 1.18
475      */
476     public void deleteColumn(int col) throws TableException {
477         if (col < 0 || col >= columns.size()) {
478             throw new TableException("Column out of bounds: col=" + col + ", size=" + columns.size());
479         }
480         columns.remove(col);
481 
482         if (isEmpty()) {
483             nrow = 0;
484         }
485     }
486 
487     /**
488      * Delete a contiguous set of columns from the table.
489      *
490      * @param  start          The first column (0-indexed) to be deleted.
491      * @param  len            The number of columns to be deleted.
492      *
493      * @throws TableException if the request goes outside the boundaries of the table or if the length is negative.
494      * 
495      * @see                   #deleteColumn(int)
496      */
497     public void deleteColumns(int start, int len) throws TableException {
498         if (len == 0) {
499             return;
500         }
501 
502         if (start < 0 || len < 0 || start + len > columns.size()) {
503             throw new TableException(
504                     "Column eange out of bounds: start=" + start + ", len=" + len + ", size=" + columns.size());
505         }
506 
507         ArrayList<Column<?>> c = new ArrayList<>();
508         int i;
509         for (i = 0; i < start; i++) {
510             c.add(columns.get(i));
511         }
512         i += len;
513         for (; i < columns.size(); i++) {
514             c.add(columns.get(i));
515         }
516         columns = c;
517 
518         if (isEmpty()) {
519             nrow = 0;
520         }
521     }
522 
523     /**
524      * Delete all rows from the table, but leaving the column structure intact.
525      *
526      * @throws TableException if the request goes outside the boundaries of the table or if the length is negative.
527      * 
528      * @see                   #deleteRows(int, int)
529      * @see                   #clear()
530      */
531     public final void deleteAllRows() throws TableException {
532         nrow = 0;
533         for (Column<?> c : columns) {
534             c.trim(MIN_CAPACITY);
535         }
536     }
537 
538     /**
539      * Delete a row from the table.
540      *
541      * @param  row            The row (0-indexed) to be deleted.
542      *
543      * @throws TableException if the request goes outside the boundaries of the table or if the length is negative.
544      * 
545      * @see                   #deleteRows(int, int)
546      */
547     public final void deleteRow(int row) throws TableException {
548         deleteRows(row, 1);
549     }
550 
551     /**
552      * Delete a contiguous set of rows from the table.
553      *
554      * @param  from           the 0-based index of the first tow to be deleted.
555      * @param  length         The number of rows to be deleted.
556      *
557      * @throws TableException if the request goes outside the boundaries of the table or if the length is negative.
558      * 
559      * @see                   #deleteRow(int)
560      */
561     public void deleteRows(int from, int length) throws TableException {
562         if (length == 0) {
563             return;
564         }
565 
566         if (from < 0 || length < 0 || from + length > nrow) {
567             throw new TableException("Row range out of bounds: start=" + from + ", len=" + length + ", size=" + nrow);
568         }
569 
570         int maxSize = nextLargerCapacity(nrow - length);
571         for (Column<?> c : columns) {
572             c.deleteRows(from, length, nrow, maxSize);
573         }
574 
575         nrow -= length;
576     }
577 
578     /**
579      * Returns the primitive based class of the elements in a given colum.
580      * 
581      * @param  col the 0-based column index
582      * 
583      * @return     the primitive base class of the elements in the column, e.g. <code>short.class</code>.
584      * 
585      * @since      1.18
586      */
587     public final Class<?> getElementClass(int col) {
588         return columns.get(col).baseType();
589     }
590 
591     /**
592      * Get the base classes of the columns. As of 1.18, this method returns a copy ot the array used internally, which
593      * is safe to modify.
594      *
595      * @return     An array of Class objects, one for each column.
596      * 
597      * @deprecated Use {@link #getElementClass(int)} instead. This method may be removed in the future.
598      * 
599      * @see        #getElementClass(int)
600      */
601     @Deprecated
602     public Class<?>[] getBases() {
603         Class<?>[] bases = new Class<?>[columns.size()];
604         for (int i = 0; i < bases.length; i++) {
605             bases[i] = getElementClass(i);
606         }
607         return bases;
608     }
609 
610     /**
611      * Returns the wrapped column data, in which each entry correspond to data for a given row. If the column contains
612      * non-scalar elements, then each entry in the returned array will be a primitive array of the column's element
613      * size.
614      * 
615      * @param  col the 0-based column index
616      * 
617      * @return     the array in which each entry corresponds to table row. For scalar columns this will be a primitive
618      *                 array of {@link #getNRows()} entries. Otherwise, it will be an array, whch contains the primitive
619      *                 array elements for each row.
620      * 
621      * @see        #getColumn(int)
622      * @see        #addWrappedColumn(Object)
623      * @see        #setWrappedColumn(int, Object)
624      * 
625      * @since      1.18
626      */
627     public Object getWrappedColumn(int col) {
628         Column<?> c = columns.get(col);
629         c.trim(nrow);
630         return c.getData();
631     }
632 
633     /**
634      * <p>
635      * Returns the data for a particular column in as a single 1D array of primitives. See
636      * {@link nom.tam.fits.TableData#addColumn(Object)} for more information about the format of data elements in
637      * general.
638      * </p>
639      * 
640      * @param  col The 0-based column index.
641      * 
642      * @return     an array of primitives (for scalar columns), or else an <code>Object[]</code> array, or possibly
643      *                 <code>null</code>
644      *
645      * @see        #getWrappedColumn(int)
646      * @see        #setColumn(int, Object)
647      * @see        #getElement(int, int)
648      * @see        #getNCols()
649      */
650     @Override
651     public Object getColumn(int col) {
652         Column<?> c = columns.get(col);
653         c.trim(nrow);
654         return c.getFlatData();
655     }
656 
657     /**
658      * Returns the data for all columns as an array of flattened 1D primitive arrays.
659      * 
660      * @return An array containing the flattened data for each column. Each columns's data is represented by a single 1D
661      *             array holding all elements for that column. Because this is not an easily digestible format, you are
662      *             probably better off using {@link #getElement(int, int)} or {@link #getRow(int)} instead.
663      * 
664      * @see    #getColumn(int)
665      */
666     public Object[] getColumns() {
667         Object[] table = new Object[columns.size()];
668         for (int i = 0; i < table.length; i++) {
669             table[i] = getColumn(i);
670         }
671         return table;
672     }
673 
674     @Override
675     public Object getElement(int row, int col) {
676         if (row < 0 || row >= nrow) {
677             throw new ArrayIndexOutOfBoundsException(row);
678         }
679         return columns.get(col).getArrayElement(row);
680     }
681 
682     /**
683      * Returns the extra state information of the table. The type and nature of this information is implementation
684      * dependent.
685      * 
686      * @return     the object capturing the implementation-specific table state
687      * 
688      * @deprecated (<i>for internal use</i>) No longer used, will be removed in the future.
689      */
690     @Deprecated
691     public T getExtraState() {
692         return extraState;
693     }
694 
695     @Override
696     public final int getNCols() {
697         return columns.size();
698     }
699 
700     @Override
701     public final int getNRows() {
702         return nrow;
703     }
704 
705     @Override
706     public Object getRow(int row) {
707         if (row < 0 || row >= nrow) {
708             throw new ArrayIndexOutOfBoundsException(row);
709         }
710 
711         Object[] x = new Object[columns.size()];
712         for (int col = 0; col < x.length; col++) {
713             x[col] = getElement(row, col);
714         }
715         return x;
716     }
717 
718     /**
719      * Returns the size of 1D array elements stored in a given column
720      * 
721      * @param  col the 0-based column index
722      * 
723      * @return     the array size in the column (scalars return 1)
724      * 
725      * @since      1.18
726      */
727     public final int getElementSize(int col) {
728         return columns.get(col).elementCount();
729     }
730 
731     /**
732      * Returns the flattened (1D) size of elements in each column of this table. As of 1.18, this method returns a copy
733      * ot the array used internally, which is safe to modify.
734      * 
735      * @return     an array with the byte sizes of each column
736      * 
737      * @see        #getElementSize(int)
738      * 
739      * @deprecated Use {@link #getElementSize(int)} instead. This method may be removed in the future.
740      * 
741      * @since      1.18
742      */
743     @Deprecated
744     public int[] getSizes() {
745         int[] sizes = new int[columns.size()];
746         for (int i = 0; i < sizes.length; i++) {
747             sizes[i] = getElementSize(i);
748         }
749         return sizes;
750     }
751 
752     /**
753      * Returns the Java array type character for the elements stored in a given column.
754      * 
755      * @param  col the 0-based column index
756      * 
757      * @return     the Java array type of the elements in the column, such as 'I' if the array is class is
758      *                 <code>I[</code> (that is for <code>int[]</code>).
759      */
760     public final char getTypeChar(int col) {
761         return columns.get(col).getElementType().type();
762     }
763 
764     /**
765      * Get the characters describing the base classes of the columns. As of 1.18, this method returns a copy ot the
766      * array used internally, which is safe to modify.
767      *
768      * @return     An array of type characters (Java array types), one for each column.
769      * 
770      * @deprecated Use {@link #getTypeChar(int)} instead. This method may be removed in the future.
771      * 
772      * @see        #getTypeChar(int)
773      */
774     @Deprecated
775     public char[] getTypes() {
776         char[] types = new char[columns.size()];
777         for (int i = 0; i < types.length; i++) {
778             types[i] = getTypeChar(i);
779         }
780         return types;
781     }
782 
783     /**
784      * Reads the table's data from the input, in row-major format
785      *
786      * @param  in           The input to read from.
787      *
788      * @throws EOFException is already at the end of file.
789      * @throws IOException  if the reading failed
790      * 
791      * @see                 #write(ArrayDataOutput)
792      */
793     public void read(ArrayDataInput in) throws EOFException, IOException {
794         for (int row = 0; row < nrow; row++) {
795             for (Column<?> c : columns) {
796                 c.read(row, in);
797             }
798         }
799     }
800 
801     @SuppressWarnings("unchecked")
802     @Override
803     public void setColumn(int col, Object newColumn) throws TableException {
804         if (newColumn == null) {
805             throw new TableException("Cannot set column data to null");
806         }
807 
808         if (!newColumn.getClass().isArray()) {
809             throw new TableException("Not an array: " + newColumn.getClass().getName());
810         }
811 
812         @SuppressWarnings("rawtypes")
813         Column c = columns.get(col);
814 
815         if (!c.baseType().equals(newColumn.getClass().getComponentType())) {
816             throw new TableException(
817                     "Mismatched type " + newColumn.getClass().getName() + ", expected " + c.baseType().getName());
818         }
819 
820         if (Array.getLength(newColumn) != nrow * c.elementCount()) {
821             throw new TableException(
822                     "Mismatched size " + Array.getLength(newColumn) + ", expected " + (nrow * c.elementCount()));
823         }
824 
825         c.data = c.elementCount() > 1 ? wrapColumn(newColumn, c.elementCount()) : newColumn;
826     }
827 
828     /**
829      * Sets new data for a column, in wrapped format. The argument is an 1D or 2D array of primitives, in which the
830      * eading dimension must match the number of rows already in the table (if any).
831      *
832      * @param  col            the zero-based column index
833      * @param  newColumn      the new column data, either as a 1D array of scalar primitives, or a regular 2D array of
834      *                            primitives, in which each row contains the same type of 1D primitive array of the same
835      *                            sizes.
836      *
837      * @throws TableException if the new column data is not a 1D or 2D array of primitives, or it it does not match the
838      *                            number of existing table rows or if the column contains an irregular 2D array.
839      * 
840      * @since                 1.18
841      * 
842      * @see                   #getWrappedColumn(int)
843      * @see                   #addWrappedColumn(Object)
844      */
845     @SuppressWarnings("unchecked")
846     public void setWrappedColumn(int col, Object newColumn) throws TableException {
847 
848         if (!newColumn.getClass().isArray()) {
849             throw new TableException("Not an array: " + newColumn.getClass().getName());
850         }
851 
852         if (Array.getLength(newColumn) != nrow) {
853             throw new TableException("Mismatched row count " + Array.getLength(newColumn) + ", expected " + nrow);
854         }
855 
856         @SuppressWarnings("rawtypes")
857         Column c = columns.get(col);
858 
859         int eSize = 0;
860 
861         try {
862             eSize = checkWrappedColumn(newColumn);
863         } catch (Exception e) {
864             throw new TableException(e);
865         }
866 
867         if (eSize != c.elementCount()) {
868             throw new TableException("Mismatched element size " + eSize + ", expected " + c.elementCount());
869         }
870 
871         Class<?> eType = newColumn.getClass().getComponentType();
872         if (newColumn instanceof Object[]) {
873             eType = eType.getComponentType();
874         }
875 
876         if (!c.baseType().equals(eType)) {
877             throw new TableException(
878                     "Mismatched type " + newColumn.getClass().getName() + ", expected " + c.baseType().getName());
879         }
880 
881         c.data = newColumn;
882     }
883 
884     @Override
885     public void setElement(int row, int col, Object x) throws TableException {
886         if (row < 0 || row >= nrow) {
887             throw new ArrayIndexOutOfBoundsException(row);
888         }
889         Column<?> c = columns.get(col);
890         c.checkEntry(x);
891         c.setArrayElement(row, x);
892     }
893 
894     /**
895      * Store additional information that may be needed by the client to regenerate initial arrays.
896      *
897      * @param      opaque the extra state to set.
898      * 
899      * @deprecated        (<i>for internal use</i>) No longer used, will be removed in the future. We used the extra
900      *                        state to carry properties of an enclosing class in this enclosed object, so we could
901      *                        inherit those to new enclosing class instances. This is bad practie. If one needs data
902      *                        from the enclosing object, it should be handled by passing the enclsing object, and not
903      *                        this enclosed table.
904      */
905     @Deprecated
906     public void setExtraState(T opaque) {
907         extraState = opaque;
908     }
909 
910     @Override
911     public void setRow(int row, Object data) throws TableException {
912         if (row < 0 || row >= nrow) {
913             throw new ArrayIndexOutOfBoundsException(row);
914         }
915 
916         if (data == null) {
917             throw new TableException("Unexpected null data for row " + row);
918         }
919 
920         if (!(data instanceof Object[])) {
921             throw new TableException("Not an Object[] array: " + data.getClass().getName());
922         }
923 
924         Object[] r = (Object[]) data;
925 
926         checkRow(r);
927 
928         for (int i = 0; i < columns.size(); i++) {
929             Column<?> c = columns.get(i);
930             c.checkEntry(r[i]);
931             c.setArrayElement(row, r[i]);
932         }
933 
934     }
935 
936     /**
937      * Writes the table's data to an output, in row-major format.
938      *
939      * @param  out         the output stream to write to.
940      *
941      * @throws IOException if the write operation failed
942      * 
943      * @see                #read(ArrayDataInput)
944      */
945     public void write(ArrayDataOutput out) throws IOException {
946         for (int row = 0; row < nrow; row++) {
947             for (Column<?> c : columns) {
948                 c.write(row, out);
949             }
950         }
951     }
952 
953     /**
954      * Write a column of a table.
955      *
956      * @param  out         the output stream to write to.
957      * @param  rowStart    first row to write
958      * @param  rowEnd      the exclusive ending row index (not witten)
959      * @param  col         the zero-based column index to write.
960      *
961      * @throws IOException if the write operation failed
962      */
963     public void write(ArrayDataOutput out, int rowStart, int rowEnd, int col) throws IOException {
964         columns.get(col).write(rowStart, rowEnd - rowStart, out);
965     }
966 
967     /**
968      * Reads a column of a table from a
969      *
970      * @param  in           The input stream to read from.
971      * @param  rowStart     first row to read
972      * @param  rowEnd       the exclusive ending row index (not read)
973      * @param  col          the zero-based column index to read.
974      *
975      * @throws EOFException is already at the end of file.
976      * @throws IOException  if the reading failed
977      */
978     public void read(ArrayDataInput in, int rowStart, int rowEnd, int col) throws EOFException, IOException {
979         columns.get(col).read(rowStart, rowEnd - rowStart, in);
980     }
981 
982     private Column<?> createColumn(Class<?> type, int size) throws TableException {
983         if (type == null) {
984             throw new TableException("Column type cannot be null.");
985         }
986 
987         if (!type.isPrimitive()) {
988             throw new TableException("Not a primitive base type: " + type.getName());
989         }
990 
991         if (size == 1) {
992             if (type.equals(byte.class)) {
993                 return new Bytes();
994             }
995             if (type.equals(boolean.class)) {
996                 return new Booleans();
997             }
998             if (type.equals(char.class)) {
999                 return new Chars();
1000             }
1001             if (type.equals(short.class)) {
1002                 return new Shorts();
1003             }
1004             if (type.equals(int.class)) {
1005                 return new Integers();
1006             }
1007             if (type.equals(long.class)) {
1008                 return new Longs();
1009             }
1010             if (type.equals(float.class)) {
1011                 return new Floats();
1012             }
1013             if (type.equals(double.class)) {
1014                 return new Doubles();
1015             }
1016         }
1017         return new Generic(type, size);
1018     }
1019 
1020     /**
1021      * Container for one column in the table.
1022      * 
1023      * @author        Attila Kovacs
1024      *
1025      * @param  <Data> The generic type of the data elements held in the table
1026      */
1027     private abstract static class Column<Data> implements Cloneable {
1028         protected Data data;
1029         private ElementType<?> fitsType;
1030 
1031         /**
1032          * Instantiates a new column data container
1033          * 
1034          * @param fitsType The primitive element type that is used to store data for this column in the FITS
1035          */
1036         Column(ElementType<?> fitsType) {
1037             this.fitsType = fitsType;
1038             init(fitsType.primitiveClass());
1039         }
1040 
1041         @SuppressWarnings("unchecked")
1042         void init(Class<?> storeType) {
1043             data = (Data) Array.newInstance(storeType, 0);
1044         }
1045 
1046         /**
1047          * Returns the data array that holds all elements (one entry per row) for this column. It may be a primitive
1048          * array or an object array.
1049          * 
1050          * @return the array that holds data for this column.
1051          */
1052         Data getData() {
1053             return data;
1054         }
1055 
1056         /**
1057          * Returns the data a a 1D array containing all elements for this column. It may be a primitive array or an
1058          * object array.
1059          * 
1060          * @return the array that holds data for this column.
1061          */
1062         Object getFlatData() {
1063             return data;
1064         }
1065 
1066         /**
1067          * Returns the FITS element type contained in this column.
1068          * 
1069          * @return the FITS type of data elements as they are stored in FITS
1070          */
1071         ElementType<?> getElementType() {
1072             return fitsType;
1073         }
1074 
1075         /**
1076          * Returns the primitive element type which reprensents data for this column in FITS.
1077          * 
1078          * @return the primitive type that is used when writing this column's data into FITS
1079          */
1080         Class<?> baseType() {
1081             return fitsType.primitiveClass();
1082         }
1083 
1084         /**
1085          * Returns the array data class that stores elements in this column.
1086          * 
1087          * @return the class of array that holds elements in this column
1088          */
1089         Class<?> arrayType() {
1090             return data.getClass();
1091         }
1092 
1093         /**
1094          * Returns the number of basic elements stored per row in this column.
1095          * 
1096          * @return the number of basic elements per row
1097          */
1098         int elementCount() {
1099             return 1;
1100         }
1101 
1102         /**
1103          * Returns the number of rows currently allocated in this column, which may exceed the number of entries
1104          * currently populated.
1105          * 
1106          * @return the number of rows allocated at present
1107          */
1108         int capacity() {
1109             return Array.getLength(data);
1110         }
1111 
1112         void deleteRows(int from, int len, int size, int maxCapacity) {
1113             int end = from + len;
1114             System.arraycopy(data, end, data, from, size - end);
1115             trim(maxCapacity);
1116         }
1117 
1118         /**
1119          * Reads a single table entry from an input
1120          * 
1121          * @param  index        the zero-based row index of the column entry
1122          * @param  in           the input to read from
1123          * 
1124          * @return              the number of bytes read from the input.
1125          * 
1126          * @throws EOFException if already at the end of file.
1127          * @throws IOException  if the entry could not be read
1128          */
1129         abstract int read(int index, ArrayDataInput in) throws EOFException, IOException;
1130 
1131         /**
1132          * Writes a since table entry to an output
1133          * 
1134          * @param  index       the zero-based row index of the column entry
1135          * @param  out         the output to write to
1136          * 
1137          * @throws IOException if the entry could not be written
1138          */
1139         abstract void write(int index, ArrayDataOutput out) throws IOException;
1140 
1141         /**
1142          * Reads a sequence of consecutive table entries from an input
1143          * 
1144          * @param  from         the zero-based row index of the first column entry to read
1145          * @param  n            the number of consecutive rows to read
1146          * @param  in           the input to read from
1147          * 
1148          * @return              the number of bytes read from the input.
1149          * 
1150          * @throws EOFException if already at the end of file.
1151          * @throws IOException  if the entry could not be read
1152          */
1153         abstract int read(int from, int n, ArrayDataInput in) throws EOFException, IOException;
1154 
1155         /**
1156          * Writes a sequence of consecutive table entries to an output
1157          * 
1158          * @param  from        the zero-based row index of the first column entry to write
1159          * @param  n           the number of consecutive rows to write
1160          * @param  in          the output to write to
1161          * 
1162          * @throws IOException if the entry could not be written
1163          */
1164         abstract void write(int from, int n, ArrayDataOutput out) throws IOException;
1165 
1166         /**
1167          * Checks if an object is consistent with becoming an entry in this column.
1168          * 
1169          * @param  x              an object that is expected to be a compatible column entry
1170          * 
1171          * @throws TableException if the object is not consistent with the data expected for this column.
1172          */
1173         void checkEntry(Object x) throws TableException {
1174 
1175             if (x == null) {
1176                 throw new TableException("Unexpected null element");
1177             }
1178 
1179             if (!baseType().equals(x.getClass().getComponentType())) {
1180                 throw new TableException(
1181                         "Incompatible element type: " + x.getClass().getName() + ", expected " + arrayType());
1182             }
1183 
1184             if (Array.getLength(x) != elementCount()) {
1185                 throw new TableException(
1186                         "Incompatible element size: " + Array.getLength(x) + ", expected " + elementCount());
1187             }
1188 
1189         }
1190 
1191         /**
1192          * Returns the entry at the specified index as an array. Primitive elements will be wrapped into an array of one
1193          * 
1194          * @param  i the zero-based row index of the entry.
1195          * 
1196          * @return   the entry as an array. Primitive values will be returned as a new array of one.
1197          */
1198         abstract Object getArrayElement(int i);
1199 
1200         /**
1201          * Sets a new array entry at the specified index. Primitive values must be be wrapped into an array of one.
1202          * 
1203          * @param i the zero-based row index of the entry.
1204          * @param o the new entry as an array. Primitive values must be wrapped into an array of one.
1205          */
1206         abstract void setArrayElement(int i, Object o);
1207 
1208         /**
1209          * Resized this columns storage to an allocation of the specified size. The size should be greater or equals to
1210          * the number of elements already contained to avoid loss of data.
1211          * 
1212          * @param size the new allocation (number of rows that can be contained)
1213          */
1214         abstract void resize(int size);
1215 
1216         /**
1217          * Ensures that the column has storage allocated to contain the speciifed number of entries, and grows the
1218          * column's allocation as necessaty to contain the specified number of rows in the future.
1219          * 
1220          * @param size the number of rows that the table should be able to contain
1221          */
1222         void ensureSize(int size) {
1223             if (size > capacity()) {
1224                 resize(size);
1225             }
1226         }
1227 
1228         /**
1229          * Trims the table to the specified size, as needed. The size should be greater or equals to the number of
1230          * elements already contained to avoid loss of data.
1231          * 
1232          * @param size the new allocation (number of rows that can be contained)
1233          */
1234         void trim(int size) {
1235             if (capacity() > size) {
1236                 resize(size);
1237             }
1238         }
1239 
1240         @SuppressWarnings("unchecked")
1241         @Override
1242         protected Column<Data> clone() {
1243             try {
1244                 return (Column<Data>) super.clone();
1245             } catch (CloneNotSupportedException e) {
1246                 return null;
1247             }
1248         }
1249 
1250         @SuppressWarnings("unchecked")
1251         Data copyData(int length) {
1252             Data copy = (Data) Array.newInstance(baseType(), length);
1253             System.arraycopy(data, 0, copy, 0, Math.min(length, Array.getLength(data)));
1254             return copy;
1255         }
1256 
1257         /**
1258          * Makes a copy of this column of the specified allocation for the number of rows in the copy. If the size is
1259          * less than the number of entries this column contains, the excess entries will be discared from the copy.
1260          * 
1261          * @param  size the number of rows the copy might contain.
1262          * 
1263          * @return      A copy of this column with storage for the specified number of rows.
1264          */
1265         Column<Data> copy(int size) {
1266             Column<Data> c = clone();
1267             c.data = copyData(size);
1268             return c;
1269         }
1270     }
1271 
1272     /**
1273      * A column for data consisting of 8-bit bytes.
1274      * 
1275      * @author Attila Kovacs
1276      */
1277     private static class Bytes extends Column<byte[]> {
1278 
1279         /** Construct as new container for a byte-based data column */
1280         Bytes() {
1281             super(ElementType.BYTE);
1282         }
1283 
1284         @Override
1285         void resize(int size) {
1286             data = Arrays.copyOf(data, size);
1287         }
1288 
1289         @Override
1290         int read(int index, ArrayDataInput in) throws IOException {
1291             int i = in.read();
1292             if (i < 0) {
1293                 throw new EOFException();
1294             }
1295             data[index] = (byte) i;
1296             return 1;
1297         }
1298 
1299         @Override
1300         void write(int index, ArrayDataOutput out) throws IOException {
1301             out.write(data[index]);
1302         }
1303 
1304         @Override
1305         int read(int from, int n, ArrayDataInput in) throws IOException {
1306             int got = in.read(data, from, n);
1307             if (got < 0) {
1308                 throw new EOFException();
1309             }
1310             return -1;
1311         }
1312 
1313         @Override
1314         void write(int from, int n, ArrayDataOutput out) throws IOException {
1315             out.write(data, from, n);
1316         }
1317 
1318         @Override
1319         byte[] getArrayElement(int i) {
1320             return new byte[] {data[i]};
1321         }
1322 
1323         @Override
1324         void setArrayElement(int i, Object o) {
1325             data[i] = ((byte[]) o)[0];
1326         }
1327 
1328     }
1329 
1330     /**
1331      * A column for data consisting of boolean values.
1332      * 
1333      * @author Attila Kovacs
1334      */
1335     private static class Booleans extends Column<boolean[]> {
1336 
1337         /** Construct as new container for a boolean-based data column */
1338         Booleans() {
1339             super(ElementType.BOOLEAN);
1340         }
1341 
1342         @Override
1343         void resize(int size) {
1344             data = Arrays.copyOf(data, size);
1345         }
1346 
1347         @Override
1348         int read(int index, ArrayDataInput in) throws IOException {
1349             data[index] = in.readBoolean();
1350             return 1;
1351         }
1352 
1353         @Override
1354         void write(int index, ArrayDataOutput out) throws IOException {
1355             out.writeBoolean(data[index]);
1356         }
1357 
1358         @Override
1359         int read(int from, int n, ArrayDataInput in) throws IOException {
1360             return in.read(data, from, n);
1361         }
1362 
1363         @Override
1364         void write(int from, int n, ArrayDataOutput out) throws IOException {
1365             out.write(data, from, n);
1366         }
1367 
1368         @Override
1369         boolean[] getArrayElement(int i) {
1370             return new boolean[] {data[i]};
1371         }
1372 
1373         @Override
1374         void setArrayElement(int i, Object o) {
1375             data[i] = ((boolean[]) o)[0];
1376         }
1377     }
1378 
1379     /**
1380      * A column for data consisting of 16-bit unicode character values.
1381      * 
1382      * @author Attila Kovacs
1383      */
1384     private static class Chars extends Column<char[]> {
1385 
1386         /** Construct as new container for a unicode-based data column */
1387         Chars() {
1388             super(ElementType.CHAR);
1389         }
1390 
1391         @Override
1392         void resize(int size) {
1393             data = Arrays.copyOf(data, size);
1394         }
1395 
1396         @Override
1397         int read(int index, ArrayDataInput in) throws IOException {
1398             data[index] = in.readChar();
1399             return ElementType.CHAR.size();
1400         }
1401 
1402         @Override
1403         void write(int index, ArrayDataOutput out) throws IOException {
1404             out.writeChar(data[index]);
1405         }
1406 
1407         @Override
1408         int read(int from, int n, ArrayDataInput in) throws IOException {
1409             return in.read(data, from, n);
1410         }
1411 
1412         @Override
1413         void write(int from, int n, ArrayDataOutput out) throws IOException {
1414             out.write(data, from, n);
1415         }
1416 
1417         @Override
1418         char[] getArrayElement(int i) {
1419             return new char[] {data[i]};
1420         }
1421 
1422         @Override
1423         void setArrayElement(int i, Object o) {
1424             data[i] = ((char[]) o)[0];
1425         }
1426     }
1427 
1428     /**
1429      * A column for data consisting of 16-bit integer values.
1430      * 
1431      * @author Attila Kovacs
1432      */
1433     private static class Shorts extends Column<short[]> {
1434 
1435         /** Construct as new container for a 16-bit integer based data column */
1436         Shorts() {
1437             super(ElementType.SHORT);
1438         }
1439 
1440         @Override
1441         void resize(int size) {
1442             data = Arrays.copyOf(data, size);
1443         }
1444 
1445         @Override
1446         int read(int index, ArrayDataInput in) throws IOException {
1447             int i = in.readUnsignedShort();
1448             if (i < 0) {
1449                 throw new EOFException();
1450             }
1451             data[index] = (short) i;
1452             return Short.BYTES;
1453         }
1454 
1455         @Override
1456         void write(int index, ArrayDataOutput out) throws IOException {
1457             out.writeShort(data[index]);
1458         }
1459 
1460         @Override
1461         int read(int from, int n, ArrayDataInput in) throws IOException {
1462             return in.read(data, from, n);
1463         }
1464 
1465         @Override
1466         void write(int from, int n, ArrayDataOutput out) throws IOException {
1467             out.write(data, from, n);
1468         }
1469 
1470         @Override
1471         short[] getArrayElement(int i) {
1472             return new short[] {data[i]};
1473         }
1474 
1475         @Override
1476         void setArrayElement(int i, Object o) {
1477             data[i] = ((short[]) o)[0];
1478         }
1479     }
1480 
1481     /**
1482      * A column for data consisting of 32-bit integer values.
1483      * 
1484      * @author Attila Kovacs
1485      */
1486     private static class Integers extends Column<int[]> {
1487 
1488         /** Construct as new container for a 32-bit integer based data column */
1489         Integers() {
1490             super(ElementType.INT);
1491         }
1492 
1493         @Override
1494         void resize(int size) {
1495             data = Arrays.copyOf(data, size);
1496         }
1497 
1498         @Override
1499         int read(int index, ArrayDataInput in) throws IOException {
1500             data[index] = in.readInt();
1501             return Integer.BYTES;
1502         }
1503 
1504         @Override
1505         void write(int index, ArrayDataOutput out) throws IOException {
1506             out.writeInt(data[index]);
1507         }
1508 
1509         @Override
1510         int read(int from, int n, ArrayDataInput in) throws IOException {
1511             return in.read(data, from, n);
1512         }
1513 
1514         @Override
1515         void write(int from, int n, ArrayDataOutput out) throws IOException {
1516             out.write(data, from, n);
1517         }
1518 
1519         @Override
1520         int[] getArrayElement(int i) {
1521             return new int[] {data[i]};
1522         }
1523 
1524         @Override
1525         void setArrayElement(int i, Object o) {
1526             data[i] = ((int[]) o)[0];
1527         }
1528     }
1529 
1530     /**
1531      * A column for data consisting of 64-bit integer values.
1532      * 
1533      * @author Attila Kovacs
1534      */
1535     private static class Longs extends Column<long[]> {
1536 
1537         /** Construct as new container for a 64-bit integer based data column */
1538         Longs() {
1539             super(ElementType.LONG);
1540         }
1541 
1542         @Override
1543         void resize(int size) {
1544             data = Arrays.copyOf(data, size);
1545         }
1546 
1547         @Override
1548         int read(int index, ArrayDataInput in) throws IOException {
1549             data[index] = in.readLong();
1550             return Long.BYTES;
1551         }
1552 
1553         @Override
1554         void write(int index, ArrayDataOutput out) throws IOException {
1555             out.writeLong(data[index]);
1556         }
1557 
1558         @Override
1559         int read(int from, int n, ArrayDataInput in) throws IOException {
1560             return in.read(data, from, n);
1561         }
1562 
1563         @Override
1564         void write(int from, int n, ArrayDataOutput out) throws IOException {
1565             out.write(data, from, n);
1566         }
1567 
1568         @Override
1569         long[] getArrayElement(int i) {
1570             return new long[] {data[i]};
1571         }
1572 
1573         @Override
1574         void setArrayElement(int i, Object o) {
1575             data[i] = ((long[]) o)[0];
1576         }
1577     }
1578 
1579     /**
1580      * A column for data consisting of 32-bit floating point values.
1581      * 
1582      * @author Attila Kovacs
1583      */
1584     private static class Floats extends Column<float[]> {
1585 
1586         /** Construct as new container for a 32-bit floating-point based data column */
1587         Floats() {
1588             super(ElementType.FLOAT);
1589         }
1590 
1591         @Override
1592         void resize(int size) {
1593             data = Arrays.copyOf(data, size);
1594         }
1595 
1596         @Override
1597         int read(int index, ArrayDataInput in) throws IOException {
1598             data[index] = in.readFloat();
1599             return Float.BYTES;
1600         }
1601 
1602         @Override
1603         void write(int index, ArrayDataOutput out) throws IOException {
1604             out.writeFloat(data[index]);
1605         }
1606 
1607         @Override
1608         int read(int from, int n, ArrayDataInput in) throws IOException {
1609             return in.read(data, from, n);
1610         }
1611 
1612         @Override
1613         void write(int from, int n, ArrayDataOutput out) throws IOException {
1614             out.write(data, from, n);
1615         }
1616 
1617         @Override
1618         float[] getArrayElement(int i) {
1619             return new float[] {data[i]};
1620         }
1621 
1622         @Override
1623         void setArrayElement(int i, Object o) {
1624             data[i] = ((float[]) o)[0];
1625         }
1626     }
1627 
1628     /**
1629      * A column for data consisting of 64-bit floating point values.
1630      * 
1631      * @author Attila Kovacs
1632      */
1633     private static class Doubles extends Column<double[]> {
1634 
1635         /** Construct as new container for a 32-bit floating-point based data column */
1636         Doubles() {
1637             super(ElementType.DOUBLE);
1638         }
1639 
1640         @Override
1641         void resize(int size) {
1642             data = Arrays.copyOf(data, size);
1643         }
1644 
1645         @Override
1646         int read(int index, ArrayDataInput in) throws IOException {
1647             data[index] = in.readDouble();
1648             return Double.BYTES;
1649         }
1650 
1651         @Override
1652         void write(int index, ArrayDataOutput out) throws IOException {
1653             out.writeDouble(data[index]);
1654         }
1655 
1656         @Override
1657         int read(int from, int n, ArrayDataInput in) throws IOException {
1658             return in.read(data, from, n);
1659         }
1660 
1661         @Override
1662         void write(int from, int n, ArrayDataOutput out) throws IOException {
1663             out.write(data, from, n);
1664         }
1665 
1666         @Override
1667         double[] getArrayElement(int i) {
1668             return new double[] {data[i]};
1669         }
1670 
1671         @Override
1672         void setArrayElement(int i, Object o) {
1673             data[i] = ((double[]) o)[0];
1674         }
1675     }
1676 
1677     /**
1678      * A column for data consisting of Objects (primitive arrays).
1679      * 
1680      * @author Attila Kovacs
1681      */
1682     private static class Generic extends Column<Object[]> {
1683         private Class<?> type;
1684         private int size;
1685 
1686         /**
1687          * Construct as new container for an object (primitive array) based data column
1688          * 
1689          * @param type the primitive type of elements this columns contains
1690          * @param size the primitive array size per column entry
1691          */
1692         Generic(Class<?> type, int size) {
1693             super(ElementType.forClass(type));
1694             this.type = type;
1695             this.size = size;
1696         }
1697 
1698         @Override
1699         void init(Class<?> storeType) {
1700             data = (Object[]) Array.newInstance(storeType, 0, 0);
1701         }
1702 
1703         @Override
1704         int elementCount() {
1705             return size;
1706         }
1707 
1708         @Override
1709         void resize(int newSize) {
1710             data = Arrays.copyOf(data, newSize);
1711         }
1712 
1713         @Override
1714         int read(int index, ArrayDataInput in) throws IOException {
1715             in.readArrayFully(data[index]);
1716             return size * getElementType().size();
1717         }
1718 
1719         @Override
1720         void write(int index, ArrayDataOutput out) throws IOException {
1721             out.writeArray(data[index]);
1722         }
1723 
1724         @Override
1725         int read(int from, int n, ArrayDataInput in) throws IOException {
1726             int to = from + n;
1727             for (int i = from; i < to; i++) {
1728                 in.readArrayFully(data[i]);
1729             }
1730             return n * size * getElementType().size();
1731         }
1732 
1733         @Override
1734         void write(int from, int n, ArrayDataOutput out) throws IOException {
1735             int to = from + n;
1736             for (int i = from; i < to; i++) {
1737                 out.writeArray(data[i]);
1738             }
1739         }
1740 
1741         @Override
1742         Object getArrayElement(int i) {
1743             return data[i];
1744         }
1745 
1746         @Override
1747         void setArrayElement(int i, Object o) {
1748             data[i] = o;
1749         }
1750 
1751         @Override
1752         Object[] copyData(int length) {
1753             Object[] array = Arrays.copyOf(data, length);
1754             for (int i = 0; i < length; i++) {
1755                 array[i] = Array.newInstance(type, size);
1756                 System.arraycopy(data[i], 0, array[i], 0, size);
1757             }
1758             return array;
1759         }
1760 
1761         @Override
1762         Object getFlatData() {
1763             Object array = Array.newInstance(type, data.length * size);
1764             int offset = 0;
1765             for (int i = 0; i < data.length; i++, offset += size) {
1766                 System.arraycopy(data[i], 0, array, offset, size);
1767             }
1768             return array;
1769         }
1770     }
1771 
1772 }