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