4 * Copyright: Thomas McGlynn 1997-1998.
5 * This code may be used for any purpose, non-commercial
6 * or commercial so long as this copyright notice is retained
7 * in the source code or included in or referred to in any
11 import java.lang.reflect.Array;
13 /** A data table is conventionally considered to consist of rows and
14 * columns, where the structure within each column is constant, but
15 * different columns may have different structures. I.e., structurally
16 * columns may differ but rows are identical.
17 * Typically tabular data is usually stored in row order which can
18 * make it extremely difficult to access efficiently using Java.
19 * This class provides efficient
20 * access to data which is stored in row order and allows users to
21 * get and set the elements of the table.
22 * The table can consist only of arrays of primitive types.
23 * Data stored in column order can
24 * be efficiently read and written using the
25 * BufferedDataXputStream classes.
27 * The table is represented entirely as a set of one-dimensional primitive
28 * arrays. For a given column, a row consists of some number of
29 * contiguous elements of the array. Each column is required to have
30 * the same number of rows.
32 public class ColumnTable implements DataTable {
34 /** The columns to be read/written */
35 private Object[] arrays;
36 /** The number of elements in a row for each column */
38 /** The number of rows */
40 /** The number or rows to read/write in one I/O. */
42 /** The size of a row in bytes */
44 /** The base type of each row (using the second character
45 * of the [x class names of the arrays.
48 private Class[] bases;
49 // The following arrays are used to avoid having to check
50 // casts during the I/O loops.
51 // They point to elements of arrays.
52 private byte[][] bytePointers;
53 private short[][] shortPointers;
54 private int[][] intPointers;
55 private long[][] longPointers;
56 private float[][] floatPointers;
57 private double[][] doublePointers;
58 private char[][] charPointers;
59 private boolean[][] booleanPointers;
61 /** Create the object after checking consistency.
62 * @param arrays An array of one-d primitive arrays.
63 * @param sizes The number of elements in each row
64 * for the corresponding column
66 public ColumnTable(Object[] arrays, int[] sizes) throws TableException {
70 /** Actually perform the initialization.
72 protected void setup(Object[] arrays, int[] sizes) throws TableException {
74 checkArrayConsistency(arrays, sizes);
80 /** Get the number of rows in the table.
82 public int getNRows() {
86 /** Get the number of columns in the table.
88 public int getNCols() {
92 /** Get a particular column.
93 * @param col The column desired.
94 * @return an object containing the column data desired.
95 * This will be an instance of a 1-d primitive array.
97 public Object getColumn(int col) {
102 * Set the values in a particular column. The new values must match the old in
103 * length but not necessarily in type.
106 * The column to modify.
108 * The new column data. This should be a primitive array.
109 * @exception TableException
110 * Thrown when the new data is not commenserable with information
113 public void setColumn(int col, Object newColumn) throws TableException {
115 boolean reset = newColumn.getClass() != arrays[col].getClass()
116 || Array.getLength(newColumn) != Array.getLength(arrays[col]);
117 arrays[col] = newColumn;
119 setup(arrays, sizes);
121 // This is required, because otherwise the typed pointer may point to the old
122 // array, which has been replaced by newColumn. Added by Jeroen de Jong, 1 Aug 2006
123 initializePointers();
128 public void addColumn(Object newColumn, int size) throws TableException {
130 String classname = newColumn.getClass().getName();
131 nrow = checkColumnConsistency(newColumn, classname, nrow, size);
133 rowSize += nrow * ArrayFuncs.getBaseLength(newColumn);
137 int ncol = arrays.length;
139 Object[] newArrays = new Object[ncol + 1];
140 int[] newSizes = new int[ncol + 1];
141 Class[] newBases = new Class[ncol + 1];
142 char[] newTypes = new char[ncol + 1];
144 System.arraycopy(arrays, 0, newArrays, 0, ncol);
145 System.arraycopy(sizes, 0, newSizes, 0, ncol);
146 System.arraycopy(bases, 0, newBases, 0, ncol);
147 System.arraycopy(types, 0, newTypes, 0, ncol);
154 arrays[ncol] = newColumn;
156 bases[ncol] = ArrayFuncs.getBaseClass(newColumn);
157 types[ncol] = classname.charAt(1);
158 addPointer(newColumn);
161 /** Add a row to the table. This method is very inefficient
162 * for adding multiple rows and should be avoided if possible.
164 public void addRow(Object[] row) throws TableException {
166 if (arrays.length == 0) {
168 for (int i = 0; i < row.length; i += 1) {
169 addColumn(row[i], Array.getLength(row[i]));
174 if (row.length != arrays.length) {
175 throw new TableException("Row length mismatch");
178 for (int i = 0; i < row.length; i += 1) {
179 if (row[i].getClass() != arrays[i].getClass()
180 || Array.getLength(row[i]) != sizes[i]) {
181 throw new TableException("Row column mismatch at column:" + i);
183 Object xarray = ArrayFuncs.newInstance(bases[i], (nrow + 1) * sizes[i]);
184 System.arraycopy(arrays[i], 0, xarray, 0, nrow * sizes[i]);
185 System.arraycopy(row[i], 0, xarray, nrow * sizes[i], sizes[i]);
188 initializePointers();
193 /** Get a element of the table.
194 * @param row The row desired.
195 * @param col The column desired.
196 * @return A primitive array containing the information. Note
197 * that an array will be returned even if the element
200 public Object getElement(int row, int col) {
202 Object x = ArrayFuncs.newInstance(bases[col], sizes[col]);
203 System.arraycopy(arrays[col], sizes[col] * row, x, 0, sizes[col]);
207 /** Modify an element of the table.
208 * @param row The row containing the element.
209 * @param col The column containing the element.
210 * @param x The new datum. This should be 1-d primitive
212 * @exception TableException Thrown when the new data
213 * is not of the same type as
214 * the data it replaces.
216 public void setElement(int row, int col, Object x)
217 throws TableException {
219 String classname = x.getClass().getName();
221 if (!classname.equals("[" + types[col])) {
222 throw new TableException("setElement: Incompatible element type");
225 if (Array.getLength(x) != sizes[col]) {
226 throw new TableException("setElement: Incompatible element size");
229 System.arraycopy(x, 0, arrays[col], sizes[col] * row, sizes[col]);
232 /** Get a row of data.
233 * @param The row desired.
234 * @return An array of objects each containing a primitive array.
236 public Object getRow(int row) {
238 Object[] x = new Object[arrays.length];
239 for (int col = 0; col < arrays.length; col += 1) {
240 x[col] = getElement(row, col);
245 /** Modify a row of data.
246 * @param row The row to be modified.
247 * @param x The data to be modified. This should be an
248 * array of objects. It is described as an Object
249 * here since other table implementations may
250 * use other methods to store the data (e.g.,
251 * @see ColumnTable.getColumn.
253 public void setRow(int row, Object x) throws TableException {
255 if (!(x instanceof Object[])) {
256 throw new TableException("setRow: Incompatible row");
259 for (int col = 0; col < arrays.length; col += 1) {
260 setElement(row, col, ((Object[]) x)[col]);
264 /** Check that the columns and sizes are consistent.
265 * Inconsistencies include:
267 * <li> arrays and sizes have different lengths.
268 * <li> an element of arrays is not a primitive array.
269 * <li> the size of an array is not divisible by the sizes entry.
270 * <li> the number of rows differs for the columns.
272 * @param arrays The arrays defining the columns.
273 * @param sizes The number of elements in each row for the column.
275 protected void checkArrayConsistency(Object[] arrays, int[] sizes)
276 throws TableException {
278 // This routine throws an error if it detects an inconsistency
279 // between the arrays being read in.
281 // First check that the lengths of the two arrays are the same.
282 if (arrays.length != sizes.length) {
283 throw new TableException("readArraysAsColumns: Incompatible arrays and sizes.");
286 // Now check that that we fill up all of the arrays exactly.
290 this.types = new char[arrays.length];
291 this.bases = new Class[arrays.length];
293 // Check for a null table.
294 boolean nullTable = true;
296 for (int i = 0; i < arrays.length; i += 1) {
298 String classname = arrays[i].getClass().getName();
300 ratio = checkColumnConsistency(arrays[i], classname, ratio, sizes[i]);
302 rowSize += sizes[i] * ArrayFuncs.getBaseLength(arrays[i]);
303 types[i] = classname.charAt(1);
304 bases[i] = ArrayFuncs.getBaseClass(arrays[i]);
308 this.rowSize = rowSize;
309 this.arrays = arrays;
313 private int checkColumnConsistency(Object data, String classname, int ratio, int size)
314 throws TableException {
317 if (classname.charAt(0) != '[' || classname.length() != 2) {
318 throw new TableException("Non-primitive array for column");
321 int thisSize = Array.getLength(data);
322 if ((thisSize == 0 && size != 0 && ratio != 0)
323 || (thisSize != 0 && size == 0)) {
324 throw new TableException("Size mismatch in column: " + thisSize + " != " + size);
327 // The row size must evenly divide the size of the array.
328 if (size != 0 && thisSize % size != 0) {
329 throw new TableException("Row size does not divide array for column");
332 // Finally the ratio of sizes must be the same for all columns -- this
333 // is the number of rows in the table.
336 thisRatio = thisSize / size;
338 if (ratio != 0 && (thisRatio != ratio)) {
339 throw new TableException("Different number of rows in different columns");
350 /** Calculate the number of rows to read/write at a time.
351 * @param rowSize The size of a row in bytes.
352 * @param nrows The number of rows in the table.
354 protected void getNumberOfRows() {
358 // If a row is larger than bufSize, then read one row at a time.
362 } else if (rowSize > bufSize) {
365 // If the entire set is not too big, just read it all.
366 } else if (bufSize / rowSize >= nrow) {
369 this.chunk = bufSize / rowSize + 1;
374 /** Set the pointer arrays for the eight primitive types
375 * to point to the appropriate elements of arrays.
377 protected void initializePointers() {
379 int nbyte, nshort, nint, nlong, nfloat, ndouble, nchar, nboolean;
381 // Count how many of each type we have.
391 for (int col = 0; col < arrays.length; col += 1) {
392 switch (types[col]) {
421 // Allocate the pointer arrays. Note that many will be
424 bytePointers = new byte[nbyte][];
425 shortPointers = new short[nshort][];
426 intPointers = new int[nint][];
427 longPointers = new long[nlong][];
428 floatPointers = new float[nfloat][];
429 doublePointers = new double[ndouble][];
430 charPointers = new char[nchar][];
431 booleanPointers = new boolean[nboolean][];
433 // Now set the pointers.
443 for (int col = 0; col < arrays.length; col += 1) {
444 switch (types[col]) {
447 bytePointers[nbyte] = (byte[]) arrays[col];
451 shortPointers[nshort] = (short[]) arrays[col];
455 intPointers[nint] = (int[]) arrays[col];
459 longPointers[nlong] = (long[]) arrays[col];
463 floatPointers[nfloat] = (float[]) arrays[col];
467 doublePointers[ndouble] = (double[]) arrays[col];
471 charPointers[nchar] = (char[]) arrays[col];
475 booleanPointers[nboolean] = (boolean[]) arrays[col];
482 // Add a pointer in the pointer lists.
483 protected void addPointer(Object data) throws TableException {
484 String classname = data.getClass().getName();
485 char type = classname.charAt(1);
489 byte[][] xb = new byte[bytePointers.length + 1][];
490 System.arraycopy(bytePointers, 0, xb, 0, bytePointers.length);
491 xb[bytePointers.length] = (byte[]) data;
496 boolean[][] xb = new boolean[booleanPointers.length + 1][];
497 System.arraycopy(booleanPointers, 0, xb, 0, booleanPointers.length);
498 xb[booleanPointers.length] = (boolean[]) data;
499 booleanPointers = xb;
503 short[][] xb = new short[shortPointers.length + 1][];
504 System.arraycopy(shortPointers, 0, xb, 0, shortPointers.length);
505 xb[shortPointers.length] = (short[]) data;
510 char[][] xb = new char[charPointers.length + 1][];
511 System.arraycopy(charPointers, 0, xb, 0, charPointers.length);
512 xb[charPointers.length] = (char[]) data;
517 int[][] xb = new int[intPointers.length + 1][];
518 System.arraycopy(intPointers, 0, xb, 0, intPointers.length);
519 xb[intPointers.length] = (int[]) data;
524 long[][] xb = new long[longPointers.length + 1][];
525 System.arraycopy(longPointers, 0, xb, 0, longPointers.length);
526 xb[longPointers.length] = (long[]) data;
531 float[][] xb = new float[floatPointers.length + 1][];
532 System.arraycopy(floatPointers, 0, xb, 0, floatPointers.length);
533 xb[floatPointers.length] = (float[]) data;
538 double[][] xb = new double[doublePointers.length + 1][];
539 System.arraycopy(doublePointers, 0, xb, 0, doublePointers.length);
540 xb[doublePointers.length] = (double[]) data;
545 throw new TableException("Invalid type for added column:" + classname);
550 * @param is The input stream to read from.
552 public int read(ArrayDataInput is) throws IOException {
556 // While we have not finished reading the table..
557 for (int row = 0; row < nrow; row += 1) {
568 // Loop over the columns within the row.
569 for (int col = 0; col < arrays.length; col += 1) {
571 int arrOffset = sizes[col] * row;
572 int size = sizes[col];
574 switch (types[col]) {
575 // In anticpated order of use.
577 int[] ia = intPointers[iint];
579 is.read(ia, arrOffset, size);
583 short[] s = shortPointers[ishort];
585 is.read(s, arrOffset, size);
589 byte[] b = bytePointers[ibyte];
591 is.read(b, arrOffset, size);
595 float[] f = floatPointers[ifloat];
597 is.read(f, arrOffset, size);
601 double[] d = doublePointers[idouble];
603 is.read(d, arrOffset, size);
607 char[] c = charPointers[ichar];
609 is.read(c, arrOffset, size);
613 long[] l = longPointers[ilong];
615 is.read(l, arrOffset, size);
620 boolean[] bool = booleanPointers[iboolean];
622 is.read(bool, arrOffset, size);
628 // All done if we get here...
629 return rowSize * nrow;
633 * @param os the output stream to write to.
635 public int write(ArrayDataOutput os) throws IOException {
641 for (int row = 0; row < nrow; row += 1) {
652 // Loop over the columns within the row.
653 for (int col = 0; col < arrays.length; col += 1) {
655 int arrOffset = sizes[col] * row;
656 int size = sizes[col];
658 switch (types[col]) {
659 // In anticpated order of use.
661 int[] ia = intPointers[iint];
663 os.write(ia, arrOffset, size);
667 short[] s = shortPointers[ishort];
669 os.write(s, arrOffset, size);
673 byte[] b = bytePointers[ibyte];
675 os.write(b, arrOffset, size);
679 float[] f = floatPointers[ifloat];
681 os.write(f, arrOffset, size);
685 double[] d = doublePointers[idouble];
687 os.write(d, arrOffset, size);
691 char[] c = charPointers[ichar];
693 os.write(c, arrOffset, size);
697 long[] l = longPointers[ilong];
699 os.write(l, arrOffset, size);
703 boolean[] bool = booleanPointers[iboolean];
705 os.write(bool, arrOffset, size);
713 // All done if we get here...
714 return rowSize * nrow;
717 /** Get the base classes of the columns.
718 * @return An array of Class objects, one for each column.
720 public Class[] getBases() {
724 /** Get the characters describing the base classes of the columns.
725 * @return An array of char's, one for each column.
727 public char[] getTypes() {
731 /** Get the actual data arrays */
732 public Object[] getColumns() {
736 public int[] getSizes() {
740 /** Delete a row from the table.
741 * @param row The row (0-indexed) to be deleted.
743 public void deleteRow(int row) throws TableException {
747 /** Delete a contiguous set of rows from the table.
748 * @param row The row (0-indexed) to be deleted.
749 * @param length The number of rows to be deleted.
750 * @throws TableException if the request goes outside
751 * the boundaries of the table or if the length is negative.
753 public void deleteRows(int row, int length) throws TableException {
755 if (row < 0 || length < 0 || row + length > nrow) {
756 throw new TableException("Invalid request to delete rows start: " + row + " length:" + length
757 + " for table with " + nrow + " rows.");
764 for (int col = 0; col < arrays.length; col += 1) {
767 int newSize = sz * (nrow - length);
768 Object newArr = ArrayFuncs.newInstance(bases[col], newSize);
770 // Copy whatever comes before the deletion
771 System.arraycopy(arrays[col], 0, newArr, 0, row * sz);
773 // Copy whatever comes after the deletion
774 System.arraycopy(arrays[col], (row + length) * sz, newArr, row * sz, (nrow - row - length) * sz);
775 arrays[col] = newArr;
778 initializePointers();
781 /** Delete a contiguous set of columns from the table.
782 * @param col The column (0-indexed) to be deleted.
783 * @param length The number of rows to be deleted.
784 * @throws TableException if the request goes outside
785 * the boundaries of the table or if the length is negative.
787 public int deleteColumns(int start, int len) throws TableException {
789 int ncol = arrays.length;
791 if (start < 0 || len < 0 || start + len > ncol) {
792 throw new TableException("Invalid request to delete columns start: " + start + " length:" + len
793 + " for table with " + ncol + " columns.");
800 for (int i = start; i < start + len; i += 1) {
801 rowSize -= sizes[i] * ArrayFuncs.getBaseLength(arrays[i]);
807 Object[] newArrays = new Object[ncol];
808 int[] newSizes = new int[ncol];
809 Class[] newBases = new Class[ncol];
810 char[] newTypes = new char[ncol];
812 System.arraycopy(arrays, 0, newArrays, 0, start);
813 System.arraycopy(sizes, 0, newSizes, 0, start);
814 System.arraycopy(bases, 0, newBases, 0, start);
815 System.arraycopy(types, 0, newTypes, 0, start);
817 int rem = ocol - (start + len);
819 System.arraycopy(arrays, start + len, newArrays, start, rem);
820 System.arraycopy(sizes, start + len, newSizes, start, rem);
821 System.arraycopy(bases, start + len, newBases, start, rem);
822 System.arraycopy(types, start + len, newTypes, start, rem);
830 initializePointers();