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