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 }