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