diff --git a/src/main/java/org/scijava/table/DefaultDoubleTable.java b/src/main/java/org/scijava/table/DefaultDoubleTable.java new file mode 100644 index 0000000..36b1061 --- /dev/null +++ b/src/main/java/org/scijava/table/DefaultDoubleTable.java @@ -0,0 +1,60 @@ +/* + * #%L + * ImageJ software for multidimensional image processing and analysis. + * %% + * Copyright (C) 2009 - 2017 Board of Regents of the University of + * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck + * Institute of Molecular Cell Biology and Genetics. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.table; + +/** + * Default implementation of {@link DoubleTable}. + * + * @author Jan Eglinger + */ +public class DefaultDoubleTable extends AbstractTable + implements DoubleTable +{ + + /** Creates an empty results table. */ + public DefaultDoubleTable() { + super(); + } + + /** Creates a results table with the given row and column dimensions. */ + public DefaultDoubleTable(final int columnCount, final int rowCount) { + super(columnCount, rowCount); + } + + // -- Internal methods -- + + @Override + protected DoubleColumn createColumn(final String header) { + return new DoubleColumn(header); + } + +} diff --git a/src/main/java/org/scijava/table/DefaultTableDisplay.java b/src/main/java/org/scijava/table/DefaultTableDisplay.java new file mode 100644 index 0000000..1b5bca9 --- /dev/null +++ b/src/main/java/org/scijava/table/DefaultTableDisplay.java @@ -0,0 +1,124 @@ +/* + * #%L + * ImageJ software for multidimensional image processing and analysis. + * %% + * Copyright (C) 2009 - 2017 Board of Regents of the University of + * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck + * Institute of Molecular Cell Biology and Genetics. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.table; + +import org.scijava.display.AbstractDisplay; +import org.scijava.display.Display; +import org.scijava.plugin.Plugin; + +/** + * Default display for {@link Table}s, including {@link DoubleTable}s. + * + * @author Curtis Rueden + */ +@Plugin(type = Display.class) +public class DefaultTableDisplay extends AbstractDisplay> implements + TableDisplay +{ + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public DefaultTableDisplay() { + super((Class) Table.class); + } + + // -- Display methods -- + + @Override + public boolean canDisplay(final Class c) { + if (c == double[].class || c == double[][].class) return true; + return super.canDisplay(c); + } + + @Override + public void display(final Object o) { + // wrap 1D array as results table + if (o instanceof double[]) { + display(wrapArrayAsTable(new double[][] { (double[]) o })); + return; + } + // wrap 2D array as results table + if (o instanceof double[][]) { + display(wrapArrayAsTable((double[][]) o)); + return; + } + + super.display(o); + } + + @Override + public boolean isDisplaying(final Object o) { + if (super.isDisplaying(o)) return true; + + // check for wrapped arrays + if (o instanceof double[]) { + arrayEqualsTable(new double[][] {(double[]) o}); + } + if (o instanceof double[][]) { + arrayEqualsTable((double[][]) o); + } + + return false; + } + + // -- Helper methods -- + + private GenericTable wrapArrayAsTable(final double[][] array) { + final GenericTable table = new DefaultGenericTable(); + int rowCount = 0; + for (int d = 0; d < array.length; d++) { + final DoubleColumn column = new DoubleColumn(); + column.setArray(array[d]); + table.add(column); + if (rowCount < array[d].length) rowCount = array[d].length; + } + table.setRowCount(rowCount); + return table; + } + + private boolean arrayEqualsTable(final double[][] array) { + for (final Table table : this) { + if (!(table instanceof DoubleTable)) continue; + final DoubleTable resultsTable = (DoubleTable) table; + if (array.length != resultsTable.getColumnCount()) continue; + boolean equal = true; + for (int c = 0; c < array.length; c++) { + if (array[c] != resultsTable.get(c).getArray()) { + equal = false; + break; + } + } + return equal; + } + return false; + } + +} diff --git a/src/main/java/org/scijava/table/DoubleTable.java b/src/main/java/org/scijava/table/DoubleTable.java new file mode 100644 index 0000000..9aa6479 --- /dev/null +++ b/src/main/java/org/scijava/table/DoubleTable.java @@ -0,0 +1,51 @@ +/* + * #%L + * ImageJ software for multidimensional image processing and analysis. + * %% + * Copyright (C) 2009 - 2017 Board of Regents of the University of + * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck + * Institute of Molecular Cell Biology and Genetics. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.table; + +/** + * A table of double-precision floating point values. + * + * @author Jan Eglinger + */ +public interface DoubleTable extends Table { + + /** Gets the value of the given table cell. */ + default double getValue(final int col, final int row) { + return get(col).getValue(row); + } + + /** Sets the value of the given table cell. */ + default void setValue(final int col, final int row, final double value) { + get(col).setValue(row, value); + } + +} diff --git a/src/main/java/org/scijava/table/TableLoader.java b/src/main/java/org/scijava/table/TableLoader.java new file mode 100644 index 0000000..30e7307 --- /dev/null +++ b/src/main/java/org/scijava/table/TableLoader.java @@ -0,0 +1,208 @@ +/* + * #%L + * ImageJ software for multidimensional image processing and analysis. + * %% + * Copyright (C) 2009 - 2017 Board of Regents of the University of + * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck + * Institute of Molecular Cell Biology and Genetics. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.table; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StreamTokenizer; +import java.net.URL; + +// note: adapted from Wayne Rasband's IJ1 TextReader class + +/** + * Loads a text file containing comma separated values into a + * {@link DoubleTable}. + * + * @author Barry DeZonia + * @author Wayne Rasband + */ +public class TableLoader { + + // -- instance variables -- + + private int rows, cols; + + // -- private legacy text file support methods -- + + /** + * Loads the values of a table stored in a text file as a ResultsTable. Given + * BufferedInputStream must be marked to hold entire contents in buffer. This + * method rewinds the buggered stream so it can read it twice. + * + * @param str The BufferedInputStream containing the data of the text table + * @return A ResultsTable containing the values (and headers) + * @throws IOException + */ + public DoubleTable valuesFromTextFile(BufferedInputStream str) + throws IOException + { + countRowsAndCols(str); + if (rows == 0) return null; + DoubleTable values = new DefaultDoubleTable(cols, rows); + str.reset(); + read(str, values); + int firstRowNaNCount = 0; + for (int i = 0; i < cols; i++) { + if (Double.isNaN(values.getValue(i, 0))) firstRowNaNCount++; + } + if (firstRowNaNCount == cols) { // assume first row is header + // throw away first row of non-values + rows--; + DoubleTable oldValues = values; + values = new DefaultDoubleTable(cols, rows); + for (int c = 0; c < cols; c++) { + String colHeader = oldValues.getColumnHeader(c); + values.setColumnHeader(c, colHeader); + } + for (int row = 0; row < rows; row++) { + for (int col = 0; col < cols; col++) { + double val = oldValues.getValue(col, row + 1); + values.setValue(col, row, val); + } + } + } + return values; + } + + /** + * Loads the values of a table stored in a text file as a ResultsTable. + * + * @param urlString The url (as a string) of the file containing the text + * table + * @return A ResultsTable containing the values (and headers) + * @throws IOException + */ + public DoubleTable valuesFromTextFile(String urlString) throws IOException { + return valuesFromTextFile(new URL(urlString)); + } + + /** + * Loads the values of a table stored in a text file as a ResultsTable. + * + * @param file The File containing the text table + * @return A ResultsTable containing the values (and headers) + * @throws IOException + */ + public DoubleTable valuesFromTextFile(File file) throws IOException { + FileInputStream fstr = new FileInputStream(file); + BufferedInputStream stream = new BufferedInputStream(fstr); + stream.mark((int) file.length()); + return valuesFromTextFile(stream); + } + + /** + * Loads the values of a table stored at a URL as a ResultsTable. + * + * @param url The URL location of the file containing the text table + * @return A ResultsTable containing the values (and headers) + * @throws IOException + */ + public DoubleTable valuesFromTextFile(URL url) throws IOException { + InputStream istr = url.openStream(); + BufferedInputStream stream = new BufferedInputStream(istr); + stream.mark(8000000); // about 8 megabytes: FIXME HACK + return valuesFromTextFile(stream); + } + + // -- private helpers - + + private void countRowsAndCols(InputStream str) throws IOException { + Reader r = new BufferedReader(new InputStreamReader(str)); + StreamTokenizer tok = new StreamTokenizer(r); + tok.resetSyntax(); + tok.wordChars(43, 43); + tok.wordChars(45, 126); + tok.whitespaceChars(0, 42); + tok.whitespaceChars(44, 44); + tok.whitespaceChars(127, 255); + tok.eolIsSignificant(true); + + int words = 0, wordsPrevLine = 0; + while (tok.nextToken() != StreamTokenizer.TT_EOF) { + switch (tok.ttype) { + case StreamTokenizer.TT_EOL: + rows++; + if (words == 0) rows--; // ignore empty lines + if (rows == 1 && words > 0) cols = words; + if (rows > 1 && words != 0 && words != wordsPrevLine) { + throw new IOException("Line " + rows + + " is not the same length as the first line."); + } + if (words != 0) wordsPrevLine = words; + words = 0; + break; + case StreamTokenizer.TT_WORD: + // System.out.println("read word " + tok.sval); + words++; + break; + } + } + if (words == cols) rows++; // last line does not end with EOL + } + + private void read(InputStream str, DoubleTable values) throws IOException { + Reader r = new BufferedReader(new InputStreamReader(str)); + StreamTokenizer tok = new StreamTokenizer(r); + tok.resetSyntax(); + tok.wordChars(43, 43); + tok.wordChars(45, 126); + tok.whitespaceChars(0, 42); + tok.whitespaceChars(44, 44); + tok.whitespaceChars(127, 255); + + int row = 0, col = 0; + while (tok.nextToken() != StreamTokenizer.TT_EOF) { + if (tok.ttype == StreamTokenizer.TT_WORD) { + double value; + try { + value = Double.parseDouble(tok.sval); + } + catch (NumberFormatException e) { + value = Double.NaN; + if (row == 0) values.setColumnHeader(col, tok.sval); + } + values.setValue(col, row, value); + col++; + if (col == cols) { + row++; + col = 0; + } + } + } + } +} diff --git a/src/test/java/org/scijava/table/DefaultDoubleTableTest.java b/src/test/java/org/scijava/table/DefaultDoubleTableTest.java new file mode 100644 index 0000000..5d00fcb --- /dev/null +++ b/src/test/java/org/scijava/table/DefaultDoubleTableTest.java @@ -0,0 +1,430 @@ +/* + * #%L + * ImageJ software for multidimensional image processing and analysis. + * %% + * Copyright (C) 2009 - 2017 Board of Regents of the University of + * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck + * Institute of Molecular Cell Biology and Genetics. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.table; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +import java.util.List; + +import org.junit.Test; + +/** + * Tests {@link DefaultDoubleTable}. + * + * @author Curtis Rueden + * @author Alison Walter + */ +public class DefaultDoubleTableTest { + + private static final String[] HEADERS = { "Year", "Age", "BA" }; + + // Paul Molitor + private static final double[][] DATA = { + {1978, 21, .273}, + {1979, 22, .322}, + {1980, 23, .304}, + {1981, 24, .267}, + {1982, 25, .302}, + {1983, 26, .270}, + {1984, 27, .217}, + {1985, 28, .297}, + {1986, 29, .281}, + {1987, 30, .353}, + {1988, 31, .312}, + {1989, 32, .315}, + {1990, 33, .285}, + {1991, 34, .325}, + {1992, 35, .320}, + {1993, 36, .332}, + {1994, 37, .341}, + {1995, 38, .270}, + {1996, 39, .341}, + {1997, 40, .305}, + {1998, 41, .281}, + }; + + @Test + public void testStructure() { + final DoubleTable table = createTable(); + assertEquals(3, table.getColumnCount()); + assertEquals(21, table.getRowCount()); + for (final DoubleColumn column : table) { + assertEquals(21, column.size()); + } + + assertEquals("Year", table.getColumnHeader(0)); + assertEquals("Age", table.getColumnHeader(1)); + assertEquals("BA", table.getColumnHeader(2)); + + for (int c = 0; c < table.getColumnCount(); c++) { + final DoubleColumn columnByHeader = table.get(HEADERS[c]); + final DoubleColumn columnByIndex = table.get(c); + assertSame(columnByHeader, columnByIndex); + assertEquals(DATA.length, columnByHeader.size()); + for (int r = 0; r < table.getRowCount(); r++) { + assertEquals(DATA[r][c], table.getValue(c, r), 0); + assertEquals(DATA[r][c], columnByHeader.getValue(r), 0); + } + } + } + + @Test + public void testGetColumnType() { + final DoubleTable table = createTable(); + final DoubleColumn col = table.get(0); + assertEquals(col.getType(), Double.class); + } + + @Test + public void testAppendColumn() { + final DoubleTable table = createTable(); + final double[] values = + { -0.25, 0.5, 0.625, -1.25, 0.0, 0.0325, 100.5, 13.25, 110.5, -2.25, + 4.625, -3.0, 100.0, 1209.25, -10.5, 16.25, -200.0, -0.0325, + 940385034958.5, -301284390284.25, 17.25 }; + + final DoubleColumn col = table.appendColumn("Header4"); + col.fill(values); + + // Test appending a column + assertEquals(table.getColumnCount(), 4); + assertEquals(table.get(3).getHeader(), "Header4"); + + checkTableModifiedColumn(table, values, 3); + } + + @Test + public void testRemoveColumn() { + final DoubleTable table = createTable(); + final DoubleColumn col = table.removeColumn(0); + + // Test removing a column + for (int i = 0; i < col.size(); i++) { + assertEquals(col.getValue(i), DATA[i][0], 0); + } + assertEquals(table.getColumnCount(), 2); + + checkTableModifiedColumn(table, null, 0); + } + + @Test + public void testInsertColumn() { + final DoubleTable table = createTable(); + final double[] values = + { -0.25, 0.5, 0.625, -1.25, 0.0, 0.0325, 100.5, 13.25, 110.5, -2.25, + 4.625, -3.0, 100.0, 1209.25, -10.5, 16.25, -200.0, -0.0325, + 940385034958.5, -301284390284.25, 17.25 }; + + final DoubleColumn col = table.insertColumn(1, "Header4"); + col.fill(values); + + assertEquals(table.getColumnCount(), 4); + assertEquals(table.get(1).getHeader(), "Header4"); + + checkTableModifiedColumn(table, values, 1); + } + + @Test + public void testAppendColumns() { + final DoubleTable table = createTable(); + final double[][] values = + { + { -0.25, 0.5, 0.625, -1.25, 0.0, 0.0325, 100.5, 13.25, 110.5, -2.25, + 4.625, -3.0, 100.0, 1209.25, -10.5, 16.25, -200.0, -0.0325, + 940385034958.5, -301284390284.25, 17.25 }, + { 0.5, 0.5, 0.25, 1.25, 0.0, 0.625, 100.5, 13.25, 11.5, -112.25, 4.625, + -3.5, 105.0, 19.625, -10.5, 16.25, 200.0325, -0.0325, 940385034958.5, + -1.25, 17.25 } }; + + final String[] headers = { "Header4", "Header5" }; + final List col = table.appendColumns(headers); + col.get(0).fill(values[0]); + col.get(1).fill(values[1]); + + // Test appending a column + assertEquals(table.getColumnCount(), 5); + assertEquals(table.get(3).getHeader(), "Header4"); + assertEquals(table.get(4).getHeader(), "Header5"); + + checkTableModifiedColumns(table, values, 3, 4); + } + + @Test + public void testRemoveColumns() { + final DoubleTable table = createTable(); + + final List col = table.removeColumns(0, 2); + + // Test removing a column + for (int q = 0; q < col.size(); q++) { + for (int i = 0; i < col.get(0).size(); i++) { + assertEquals(col.get(q).getValue(i), DATA[i][q], 0); + } + } + assertEquals(table.getColumnCount(), 1); + + checkTableModifiedColumns(table, null, 0, 2); + } + + @Test + public void testInsertColumns() { + final DoubleTable table = createTable(); + final double[][] values = + { + { -0.25, 0.5, 0.625, -1.25, 0.0, 0.0325, 100.5, 13.25, 110.5, -2.25, + 4.625, -3.0, 100.0, 1209.25, -10.5, 16.25, -200.0, -0.0325, + 940385034958.5, -301284390284.25, 17.25 }, + { 0.5, 0.5, 0.25, 1.25, 0.0, 0.625, 100.5, 13.25, 11.5, -112.25, 4.625, + -3.5, 105.0, 19.625, -10.5, 16.25, 200.0325, -0.0325, 940385034958.5, + -1.25, 17.25 } }; + + final String[] headers = { "Header4", "Header5" }; + final List col = table.insertColumns(1, headers); + col.get(0).fill(values[0]); + col.get(1).fill(values[1]); + + assertEquals(table.getColumnCount(), 5); + assertEquals(table.get(1).getHeader(), "Header4"); + assertEquals(table.get(2).getHeader(), "Header5"); + + checkTableModifiedColumns(table, values, 1, 2); + } + + @Test + public void testAppendRow() { + final DoubleTable table = createTable(); + final double[] values = { 1999, 42, 0.0 }; + + // Test appending a row + table.appendRow(); + assertEquals(table.getRowCount(), 22); + for (int i = 0; i < values.length; i++) { + table.setValue(i, 21, values[i]); + assertEquals(table.getValue(i, 21), values[i], 0); + } + + checkTableModifiedRow(table, values, 21); + } + + @Test + public void testRemoveRow() { + final DoubleTable table = createTable(); + + table.removeRow(4); + + assertEquals(table.getRowCount(), 20); + for (int i = 0; i < table.getColumnCount(); i++) { + assertEquals(table.getValue(i, 4), DATA[5][i], 0); + } + + checkTableModifiedRow(table, null, 4); + } + + @Test + public void testInsertRow() { + final DoubleTable table = createTable(); + final double[] values = { 1999, 42, 0.0 }; + + table.insertRow(6); + + assertEquals(table.getRowCount(), 22); + for (int i = 0; i < table.getColumnCount(); i++) { + table.setValue(i, 6, values[i]); + } + + checkTableModifiedRow(table, values, 6); + } + + @Test + public void testAppendRows() { + final DoubleTable table = createTable(); + final double[][] values = { + { 1999, 42, 0.123 }, + { 2000, 43, 0.006 }, + { 2001, 44, 0.89 }, + { 2002, 45, 0.0 }, + }; + + // Test appending a row + table.appendRows(4); + assertEquals(table.getRowCount(), 25); + for (int r = 0; r < values.length; r++) { + for (int c = 0; c < values[0].length; c++) { + table.setValue(c, r + 21, values[r][c]); + assertEquals(table.getValue(c, r + 21), values[r][c], 0); + } + } + + checkTableModifiedRows(table, values, 21, 24); + } + + @Test + public void testRemoveRows() { + final DoubleTable table = createTable(); + table.removeRows(11, 6); + assertEquals(table.getRowCount(), 15); + + checkTableModifiedRows(table, null, 11, 16); + } + + @Test + public void testInsertRows() { + final DoubleTable table = createTable(); + final double[][] values = { + { 1999, 42, 0.123 }, + { 2000, 43, 0.006 }, + { 2001, 44, 0.89 }, + { 2002, 45, 0.0 }, + }; + + table.insertRows(3, 4); + + assertEquals(table.getRowCount(), 25); + for (int r = 0; r < values.length; r++) { + for (int c = 0; c < values[0].length; c++) { + table.setValue(c, r + 3, values[r][c]); + assertEquals(table.getValue(c, r + 3), values[r][c], 0); + } + } + + checkTableModifiedRows(table, values, 3, 6); + } + + // TODO - Add more tests. + + // -- Helper methods -- + + private DoubleTable createTable() { + final DoubleTable table = + new DefaultDoubleTable(DATA[0].length, DATA.length); + + for (int c = 0; c < HEADERS.length; c++) { + table.setColumnHeader(c, HEADERS[c]); + } + + for (int r = 0; r < DATA.length; r++) { + for (int c = 0; c < DATA[r].length; c++) { + table.setValue(c, r, DATA[r][c]); + } + } + + return table; + } + + private void checkTableModifiedColumn(final DoubleTable table, + final double[] values, final int mod) + { + for (int r = 0; r < table.getRowCount(); r++) { + for (int c = 0; c < table.getColumnCount(); c++) { + if (c == mod && values != null) { + assertEquals(table.getValue(c, r), values[r], 0); + } + else if (c > mod && values != null) { + assertEquals(table.getValue(c, r), DATA[r][c - 1], 0); + } + else if (c >= mod && values == null) { + assertEquals(table.getValue(c, r), DATA[r][c + 1], 0); + } + else { + assertEquals(table.getValue(c, r), DATA[r][c], 0); + } + } + } + } + + private void checkTableModifiedRow(final DoubleTable table, + final double[] values, final int mod) + { + for (int r = 0; r < table.getRowCount(); r++) { + for (int c = 0; c < table.getColumnCount(); c++) { + if (r == mod && values != null) { + assertEquals(table.getValue(c, r), values[c], 0); + } + else if (r > mod && values != null) { + assertEquals(table.getValue(c, r), DATA[r - 1][c], 0); + } + else if (r >= mod && values == null) { + assertEquals(table.getValue(c, r), DATA[r + 1][c], 0); + } + else { + assertEquals(table.getValue(c, r), DATA[r][c], 0); + } + } + } + } + + private void checkTableModifiedColumns(final DoubleTable table, + final double[][] values, final int startMod, final int endMod) + { + for (int r = 0; r < table.getRowCount(); r++) { + for (int c = 0; c < table.getColumnCount(); c++) { + if (c >= startMod && c <= endMod && values != null) { + assertEquals(table.getValue(c, r), values[c - startMod][r], 0); + } + else if (c > endMod && values != null) { + assertEquals(table.getValue(c, r), DATA[r][c - values.length], 0); + } + else if (c >= startMod && values == null) { + assertEquals(table.getValue(c, r), DATA[r][c + (endMod - startMod)], + 0); + } + else { + assertEquals(table.getValue(c, r), DATA[r][c], 0); + } + } + } + } + + private void checkTableModifiedRows(final DoubleTable table, + final double[][] values, final int startMod, final int endMod) + { + for (int r = 0; r < table.getRowCount(); r++) { + for (int c = 0; c < table.getColumnCount(); c++) { + if (r >= startMod && r <= endMod && values != null) { + assertEquals(table.getValue(c, r), values[r - startMod][c], 0); + } + else if (r > endMod && values != null) { + assertEquals(table.getValue(c, r), DATA[r - values.length][c], 0); + } + else if (r >= startMod && values == null) { + assertEquals(table.getValue(c, r), + DATA[r + (endMod - startMod + 1)][c], 0); + } + else { + assertEquals(table.getValue(c, r), DATA[r][c], 0); + } + } + } + } + +}