XML[] sheets =
xml.getChildren("office:body/office:spreadsheet/table:table");
boolean found = false;
for (XML sheet : sheets) {
// System.out.println(sheet.getAttribute("table:name"));
if (worksheet == null || worksheet.equals(sheet.getString("table:name"))) {
odsParseSheet(sheet, header);
found = true;
if (worksheet == null) {
break; // only read the first sheet
}
}
}
if (!found) {
if (worksheet == null) {
throw new RuntimeException("No worksheets found in the ODS file.");
} else {
throw new RuntimeException("No worksheet named " + worksheet +
" found in the ODS file.");
}
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
}
}
/**
* Parses a single sheet of XML from this file.
* @param The XML object for a single worksheet from the ODS file
*/
private void odsParseSheet(XML sheet, boolean header) {
// Extra or tags inside the text tag for the cell will be stripped.
// Different from showing formulas, and not quite the same as 'save as
// displayed' option when saving from inside OpenOffice. Only time we
// wouldn't want this would be so that we could parse hyperlinks and
// styling information intact, but that's out of scope for the p5 version.
final boolean ignoreTags = true;
XML[] rows = sheet.getChildren("table:table-row");
//xml.getChildren("office:body/office:spreadsheet/table:table/table:table-row");
int rowIndex = 0;
for (XML row : rows) {
int rowRepeat = row.getInt("table:number-rows-repeated", 1);
// if (rowRepeat != 1) {
// System.out.println(rowRepeat + " " + rowCount + " " + (rowCount + rowRepeat));
// }
boolean rowNotNull = false;
XML[] cells = row.getChildren();
int columnIndex = 0;
for (XML cell : cells) {
int cellRepeat = cell.getInt("table:number-columns-repeated", 1);
//
// 4150.00
//
String cellData = ignoreTags ? cell.getString("office:value") : null;
// if there's an office:value in the cell, just roll with that
if (cellData == null) {
int cellKids = cell.getChildCount();
if (cellKids != 0) {
XML[] paragraphElements = cell.getChildren("text:p");
if (paragraphElements.length != 1) {
for (XML el : paragraphElements) {
System.err.println(el.toString());
}
throw new RuntimeException("found more than one text:p element");
}
XML textp = paragraphElements[0];
String textpContent = textp.getContent();
// if there are sub-elements, the content shows up as a child element
// (for which getName() returns null.. which seems wrong)
if (textpContent != null) {
cellData = textpContent; // nothing fancy, the text is in the text:p element
} else {
XML[] textpKids = textp.getChildren();
StringBuilder cellBuffer = new StringBuilder();
for (XML kid : textpKids) {
String kidName = kid.getName();
if (kidName == null) {
odsAppendNotNull(kid, cellBuffer);
} else if (kidName.equals("text:s")) {
int spaceCount = kid.getInt("text:c", 1);
for (int space = 0; space < spaceCount; space++) {
cellBuffer.append(' ');
}
} else if (kidName.equals("text:span")) {
odsAppendNotNull(kid, cellBuffer);
} else if (kidName.equals("text:a")) {
// blah.com
if (ignoreTags) {
cellBuffer.append(kid.getString("xlink:href"));
} else {
odsAppendNotNull(kid, cellBuffer);
}
} else {
odsAppendNotNull(kid, cellBuffer);
System.err.println(getClass().getName() + ": don't understand: " + kid);
//throw new RuntimeException("I'm not used to this.");
}
}
cellData = cellBuffer.toString();
}
//setString(rowIndex, columnIndex, c); //text[0].getContent());
//columnIndex++;
}
}
for (int r = 0; r < cellRepeat; r++) {
if (cellData != null) {
//System.out.println("setting " + rowIndex + "," + columnIndex + " to " + cellData);
setString(rowIndex, columnIndex, cellData);
}
columnIndex++;
if (cellData != null) {
// if (columnIndex > columnMax) {
// columnMax = columnIndex;
// }
rowNotNull = true;
}
}
}
if (header) {
removeTitleRow(); // efficient enough on the first row
header = false; // avoid infinite loop
} else {
if (rowNotNull && rowRepeat > 1) {
String[] rowStrings = getStringRow(rowIndex);
for (int r = 1; r < rowRepeat; r++) {
addRow(rowStrings);
}
}
rowIndex += rowRepeat;
}
}
}
private void odsAppendNotNull(XML kid, StringBuilder buffer) {
String content = kid.getContent();
if (content != null) {
buffer.append(content);
}
}
// A 'Class' object is used here, so the syntax for this function is:
// Table t = loadTable("cars3.tsv", "header");
// Record[] records = (Record[]) t.parse(Record.class);
// While t.parse("Record") might be nicer, the class is likely to be an
// inner class (another tab in a PDE sketch) or even inside a package,
// so additional information would be needed to locate it. The name of the
// inner class would be "SketchName$Record" which isn't acceptable syntax
// to make people use. Better to just introduce the '.class' syntax.
// Unlike the Table class itself, this accepts char and boolean fields in
// the target class, since they're much more prevalent, and don't require
// a zillion extra methods and special cases in the rest of the class here.
// since this is likely an inner class, needs a reference to its parent,
// because that's passed to the constructor parameter (inserted by the
// compiler) of an inner class by the runtime.
/** incomplete, do not use */
public void parseInto(Object enclosingObject, String fieldName) {
Class> target = null;
Object outgoing = null;
Field targetField = null;
try {
// Object targetObject,
// Class target -> get this from the type of fieldName
// Class sketchClass = sketch.getClass();
Class> sketchClass = enclosingObject.getClass();
targetField = sketchClass.getDeclaredField(fieldName);
// PApplet.println("found " + targetField);
Class> targetArray = targetField.getType();
if (!targetArray.isArray()) {
// fieldName is not an array
} else {
target = targetArray.getComponentType();
outgoing = Array.newInstance(target, getRowCount());
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
// Object enclosingObject = sketch;
// PApplet.println("enclosing obj is " + enclosingObject);
Class> enclosingClass = target.getEnclosingClass();
Constructor> con = null;
try {
if (enclosingClass == null) {
con = target.getDeclaredConstructor(); //new Class[] { });
// PApplet.println("no enclosing class");
} else {
con = target.getDeclaredConstructor(new Class[] { enclosingClass });
// PApplet.println("enclosed by " + enclosingClass.getName());
}
if (!con.isAccessible()) {
// System.out.println("setting constructor to public");
con.setAccessible(true);
}
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
Field[] fields = target.getDeclaredFields();
ArrayList inuse = new ArrayList<>();
for (Field field : fields) {
String name = field.getName();
if (getColumnIndex(name, false) != -1) {
// System.out.println("found field " + name);
if (!field.isAccessible()) {
// PApplet.println(" changing field access");
field.setAccessible(true);
}
inuse.add(field);
} else {
// System.out.println("skipping field " + name);
}
}
int index = 0;
try {
for (TableRow row : rows()) {
Object item = null;
if (enclosingClass == null) {
//item = target.newInstance();
item = con.newInstance();
} else {
item = con.newInstance(new Object[] { enclosingObject });
}
//Object item = defaultCons.newInstance(new Object[] { });
for (Field field : inuse) {
String name = field.getName();
//PApplet.println("gonna set field " + name);
if (field.getType() == String.class) {
field.set(item, row.getString(name));
} else if (field.getType() == Integer.TYPE) {
field.setInt(item, row.getInt(name));
} else if (field.getType() == Long.TYPE) {
field.setLong(item, row.getLong(name));
} else if (field.getType() == Float.TYPE) {
field.setFloat(item, row.getFloat(name));
} else if (field.getType() == Double.TYPE) {
field.setDouble(item, row.getDouble(name));
} else if (field.getType() == Boolean.TYPE) {
String content = row.getString(name);
if (content != null) {
// Only bother setting if it's true,
// otherwise false by default anyway.
if (content.toLowerCase().equals("true") ||
content.equals("1")) {
field.setBoolean(item, true);
}
}
// if (content == null) {
// field.setBoolean(item, false); // necessary?
// } else if (content.toLowerCase().equals("true")) {
// field.setBoolean(item, true);
// } else if (content.equals("1")) {
// field.setBoolean(item, true);
// } else {
// field.setBoolean(item, false); // necessary?
// }
} else if (field.getType() == Character.TYPE) {
String content = row.getString(name);
if (content != null && content.length() > 0) {
// Otherwise set to \0 anyway
field.setChar(item, content.charAt(0));
}
}
}
// list.add(item);
Array.set(outgoing, index++, item);
}
if (!targetField.isAccessible()) {
// PApplet.println("setting target field to public");
targetField.setAccessible(true);
}
// Set the array in the sketch
// targetField.set(sketch, outgoing);
targetField.set(enclosingObject, outgoing);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
public boolean save(File file, String options) throws IOException {
return save(PApplet.createOutput(file),
Table.extensionOptions(false, file.getName(), options));
}
public boolean save(OutputStream output, String options) {
PrintWriter writer = PApplet.createWriter(output);
String extension = null;
if (options == null) {
throw new IllegalArgumentException("No extension specified for saving this Table");
}
String[] opts = PApplet.trim(PApplet.split(options, ','));
// Only option for save is the extension, so we can safely grab the last
extension = opts[opts.length - 1];
boolean found = false;
for (String ext : saveExtensions) {
if (extension.equals(ext)) {
found = true;
break;
}
}
// Not providing a fallback; let's make users specify an extension
if (!found) {
throw new IllegalArgumentException("'" + extension + "' not available for Table");
}
if (extension.equals("csv")) {
writeCSV(writer);
} else if (extension.equals("tsv")) {
writeTSV(writer);
} else if (extension.equals("ods")) {
try {
saveODS(output);
} catch (IOException e) {
e.printStackTrace();
return false;
}
} else if (extension.equals("html")) {
writeHTML(writer);
} else if (extension.equals("bin")) {
try {
saveBinary(output);
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
writer.flush();
writer.close();
return true;
}
protected void writeTSV(PrintWriter writer) {
if (columnTitles != null) {
for (int col = 0; col < columns.length; col++) {
if (col != 0) {
writer.print('\t');
}
if (columnTitles[col] != null) {
writer.print(columnTitles[col]);
}
}
writer.println();
}
for (int row = 0; row < rowCount; row++) {
for (int col = 0; col < getColumnCount(); col++) {
if (col != 0) {
writer.print('\t');
}
String entry = getString(row, col);
// just write null entries as blanks, rather than spewing 'null'
// all over the spreadsheet file.
if (entry != null) {
writer.print(entry);
}
}
writer.println();
}
writer.flush();
}
protected void writeCSV(PrintWriter writer) {
if (columnTitles != null) {
for (int col = 0; col < getColumnCount(); col++) {
if (col != 0) {
writer.print(',');
}
try {
if (columnTitles[col] != null) { // col < columnTitles.length &&
writeEntryCSV(writer, columnTitles[col]);
}
} catch (ArrayIndexOutOfBoundsException e) {
PApplet.printArray(columnTitles);
PApplet.printArray(columns);
throw e;
}
}
writer.println();
}
for (int row = 0; row < rowCount; row++) {
for (int col = 0; col < getColumnCount(); col++) {
if (col != 0) {
writer.print(',');
}
String entry = getString(row, col);
// just write null entries as blanks, rather than spewing 'null'
// all over the spreadsheet file.
if (entry != null) {
writeEntryCSV(writer, entry);
}
}
// Prints the newline for the row, even if it's missing
writer.println();
}
writer.flush();
}
protected void writeEntryCSV(PrintWriter writer, String entry) {
if (entry != null) {
if (entry.indexOf('\"') != -1) { // convert quotes to double quotes
char[] c = entry.toCharArray();
writer.print('\"');
for (int i = 0; i < c.length; i++) {
if (c[i] == '\"') {
writer.print("\"\"");
} else {
writer.print(c[i]);
}
}
writer.print('\"');
// add quotes if commas or CR/LF are in the entry
} else if (entry.indexOf(',') != -1 ||
entry.indexOf('\n') != -1 ||
entry.indexOf('\r') != -1) {
writer.print('\"');
writer.print(entry);
writer.print('\"');
// add quotes if leading or trailing space
} else if ((entry.length() > 0) &&
(entry.charAt(0) == ' ' ||
entry.charAt(entry.length() - 1) == ' ')) {
writer.print('\"');
writer.print(entry);
writer.print('\"');
} else {
writer.print(entry);
}
}
}
protected void writeHTML(PrintWriter writer) {
writer.println("");
// writer.println("");
// writer.println("");
writer.println("");
writer.println("");
writer.println(" ");
writer.println("");
writer.println("");
writer.println(" ");
if (hasColumnTitles()) {
writer.println(" ");
for (String entry : getColumnTitles()) {
writer.print(" ");
if (entry != null) {
writeEntryHTML(writer, entry);
}
writer.println(" | ");
}
writer.println("
");
}
for (int row = 0; row < getRowCount(); row++) {
writer.println(" ");
for (int col = 0; col < getColumnCount(); col++) {
String entry = getString(row, col);
writer.print(" ");
if (entry != null) {
// probably not a great idea to mess w/ the export
// if (entry.startsWith("<") && entry.endsWith(">")) {
// writer.print(entry);
// } else {
writeEntryHTML(writer, entry);
// }
}
writer.println(" | ");
}
writer.println("
");
}
writer.println("
");
writer.println("");
writer.println("");
writer.flush();
}
protected void writeEntryHTML(PrintWriter writer, String entry) {
//char[] chars = entry.toCharArray();
for (char c : entry.toCharArray()) { //chars) {
if (c == '<') {
writer.print("<");
} else if (c == '>') {
writer.print(">");
} else if (c == '&') {
writer.print("&");
// } else if (c == '\'') { // only in XML
// writer.print("'");
} else if (c == '"') {
writer.print(""");
} else if (c < 32 || c > 127) { // keep in ASCII or Tidy complains
writer.print("");
writer.print((int) c);
writer.print(';');
} else {
writer.print(c);
}
}
}
protected void saveODS(OutputStream os) throws IOException {
ZipOutputStream zos = new ZipOutputStream(os);
final String xmlHeader = "";
ZipEntry entry = new ZipEntry("META-INF/manifest.xml");
String[] lines = new String[] {
xmlHeader,
"",
" ",
" ",
" ",
" ",
" ",
""
};
zos.putNextEntry(entry);
zos.write(PApplet.join(lines, "\n").getBytes());
zos.closeEntry();
/*
entry = new ZipEntry("meta.xml");
lines = new String[] {
xmlHeader,
""
};
zos.putNextEntry(entry);
zos.write(PApplet.join(lines, "\n").getBytes());
zos.closeEntry();
entry = new ZipEntry("meta.xml");
lines = new String[] {
xmlHeader,
""
};
zos.putNextEntry(entry);
zos.write(PApplet.join(lines, "\n").getBytes());
zos.closeEntry();
entry = new ZipEntry("settings.xml");
lines = new String[] {
xmlHeader,
""
};
zos.putNextEntry(entry);
zos.write(PApplet.join(lines, "\n").getBytes());
zos.closeEntry();
entry = new ZipEntry("styles.xml");
lines = new String[] {
xmlHeader,
""
};
zos.putNextEntry(entry);
zos.write(PApplet.join(lines, "\n").getBytes());
zos.closeEntry();
*/
final String[] dummyFiles = new String[] {
"meta.xml", "settings.xml", "styles.xml"
};
lines = new String[] {
xmlHeader,
""
};
byte[] dummyBytes = PApplet.join(lines, "\n").getBytes();
for (String filename : dummyFiles) {
entry = new ZipEntry(filename);
zos.putNextEntry(entry);
zos.write(dummyBytes);
zos.closeEntry();
}
//
entry = new ZipEntry("mimetype");
zos.putNextEntry(entry);
zos.write("application/vnd.oasis.opendocument.spreadsheet".getBytes());
zos.closeEntry();
//
entry = new ZipEntry("content.xml");
zos.putNextEntry(entry);
//lines = new String[] {
writeUTF(zos, new String[] {
xmlHeader,
"",
" ",
" ",
" "
});
//zos.write(PApplet.join(lines, "\n").getBytes());
byte[] rowStart = " \n".getBytes();
byte[] rowStop = " \n".getBytes();
if (hasColumnTitles()) {
zos.write(rowStart);
for (int i = 0; i < getColumnCount(); i++) {
saveStringODS(zos, columnTitles[i]);
}
zos.write(rowStop);
}
for (TableRow row : rows()) {
zos.write(rowStart);
for (int i = 0; i < getColumnCount(); i++) {
if (columnTypes[i] == STRING || columnTypes[i] == CATEGORY) {
saveStringODS(zos, row.getString(i));
} else {
saveNumberODS(zos, row.getString(i));
}
}
zos.write(rowStop);
}
//lines = new String[] {
writeUTF(zos, new String[] {
" ",
" ",
" ",
""
});
//zos.write(PApplet.join(lines, "\n").getBytes());
zos.closeEntry();
zos.flush();
zos.close();
}
void saveStringODS(OutputStream output, String text) throws IOException {
// At this point, I should have just used the XML library. But this does
// save us from having to create the entire document in memory again before
// writing to the file. So while it's dorky, the outcome is still useful.
StringBuilder sanitized = new StringBuilder();
if (text != null) {
char[] array = text.toCharArray();
for (char c : array) {
if (c == '&') {
sanitized.append("&");
} else if (c == '\'') {
sanitized.append("'");
} else if (c == '"') {
sanitized.append(""");
} else if (c == '<') {
sanitized.append("<");
} else if (c == '>') {
sanitized.append("&rt;");
} else if (c < 32 || c > 127) {
sanitized.append("" + ((int) c) + ";");
} else {
sanitized.append(c);
}
}
}
writeUTF(output,
" ",
" " + sanitized + "",
" ");
}
void saveNumberODS(OutputStream output, String text) throws IOException {
writeUTF(output,
" ",
" " + text + "",
" ");
}
static Charset utf8;
static void writeUTF(OutputStream output, String... lines) throws IOException {
if (utf8 == null) {
utf8 = Charset.forName("UTF-8");
}
for (String str : lines) {
output.write(str.getBytes(utf8));
output.write('\n');
}
}
protected void saveBinary(OutputStream os) throws IOException {
DataOutputStream output = new DataOutputStream(new BufferedOutputStream(os));
output.writeInt(0x9007AB1E); // version
output.writeInt(getRowCount());
output.writeInt(getColumnCount());
if (columnTitles != null) {
output.writeBoolean(true);
for (String title : columnTitles) {
output.writeUTF(title);
}
} else {
output.writeBoolean(false);
}
for (int i = 0; i < getColumnCount(); i++) {
//System.out.println(i + " is " + columnTypes[i]);
output.writeInt(columnTypes[i]);
}
for (int i = 0; i < getColumnCount(); i++) {
if (columnTypes[i] == CATEGORY) {
columnCategories[i].write(output);
}
}
if (missingString == null) {
output.writeBoolean(false);
} else {
output.writeBoolean(true);
output.writeUTF(missingString);
}
output.writeInt(missingInt);
output.writeLong(missingLong);
output.writeFloat(missingFloat);
output.writeDouble(missingDouble);
output.writeInt(missingCategory);
for (TableRow row : rows()) {
for (int col = 0; col < getColumnCount(); col++) {
switch (columnTypes[col]) {
case STRING:
String str = row.getString(col);
if (str == null) {
output.writeBoolean(false);
} else {
output.writeBoolean(true);
output.writeUTF(str);
}
break;
case INT:
output.writeInt(row.getInt(col));
break;
case LONG:
output.writeLong(row.getLong(col));
break;
case FLOAT:
output.writeFloat(row.getFloat(col));
break;
case DOUBLE:
output.writeDouble(row.getDouble(col));
break;
case CATEGORY:
String peace = row.getString(col);
if (peace.equals(missingString)) {
output.writeInt(missingCategory);
} else {
output.writeInt(columnCategories[col].index(peace));
}
break;
}
}
}
output.flush();
output.close();
}
protected void loadBinary(InputStream is) throws IOException {
DataInputStream input = new DataInputStream(new BufferedInputStream(is));
int magic = input.readInt();
if (magic != 0x9007AB1E) {
throw new IOException("Not a compatible binary table (magic was " + PApplet.hex(magic) + ")");
}
int rowCount = input.readInt();
setRowCount(rowCount);
int columnCount = input.readInt();
setColumnCount(columnCount);
boolean hasTitles = input.readBoolean();
if (hasTitles) {
columnTitles = new String[getColumnCount()];
for (int i = 0; i < columnCount; i++) {
//columnTitles[i] = input.readUTF();
setColumnTitle(i, input.readUTF());
}
}
for (int column = 0; column < columnCount; column++) {
int newType = input.readInt();
columnTypes[column] = newType;
switch (newType) {
case INT:
columns[column] = new int[rowCount];
break;
case LONG:
columns[column] = new long[rowCount];;
break;
case FLOAT:
columns[column] = new float[rowCount];;
break;
case DOUBLE:
columns[column] = new double[rowCount];;
break;
case STRING:
columns[column] = new String[rowCount];;
break;
case CATEGORY:
columns[column] = new int[rowCount];;
break;
default:
throw new IllegalArgumentException(newType + " is not a valid column type.");
}
}
for (int i = 0; i < columnCount; i++) {
if (columnTypes[i] == CATEGORY) {
columnCategories[i] = new HashMapBlows(input);
}
}
if (input.readBoolean()) {
missingString = input.readUTF();
} else {
missingString = null;
}
missingInt = input.readInt();
missingLong = input.readLong();
missingFloat = input.readFloat();
missingDouble = input.readDouble();
missingCategory = input.readInt();
for (int row = 0; row < rowCount; row++) {
for (int col = 0; col < columnCount; col++) {
switch (columnTypes[col]) {
case STRING:
String str = null;
if (input.readBoolean()) {
str = input.readUTF();
}
setString(row, col, str);
break;
case INT:
setInt(row, col, input.readInt());
break;
case LONG:
setLong(row, col, input.readLong());
break;
case FLOAT:
setFloat(row, col, input.readFloat());
break;
case DOUBLE:
setDouble(row, col, input.readDouble());
break;
case CATEGORY:
int index = input.readInt();
//String name = columnCategories[col].key(index);
setInt(row, col, index);
break;
}
}
}
input.close();
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
* @webref table:method
* @brief Adds a new column to a table
* @see Table#removeColumn(String)
*/
public void addColumn() {
addColumn(null, STRING);
}
/**
* @param title the title to be used for the new column
*/
public void addColumn(String title) {
addColumn(title, STRING);
}
/**
* @param type the type to be used for the new column: INT, LONG, FLOAT, DOUBLE, or STRING
*/
public void addColumn(String title, int type) {
insertColumn(columns.length, title, type);
}
public void insertColumn(int index) {
insertColumn(index, null, STRING);
}
public void insertColumn(int index, String title) {
insertColumn(index, title, STRING);
}
public void insertColumn(int index, String title, int type) {
if (title != null && columnTitles == null) {
columnTitles = new String[columns.length];
}
if (columnTitles != null) {
columnTitles = PApplet.splice(columnTitles, title, index);
columnIndices = null;
}
columnTypes = PApplet.splice(columnTypes, type, index);
// columnCategories = (HashMapBlows[])
// PApplet.splice(columnCategories, new HashMapBlows(), index);
HashMapBlows[] catTemp = new HashMapBlows[columns.length + 1];
// Faster than arrayCopy for a dozen or so entries
for (int i = 0; i < index; i++) {
catTemp[i] = columnCategories[i];
}
catTemp[index] = new HashMapBlows();
for (int i = index; i < columns.length; i++) {
catTemp[i+1] = columnCategories[i];
}
columnCategories = catTemp;
Object[] temp = new Object[columns.length + 1];
System.arraycopy(columns, 0, temp, 0, index);
System.arraycopy(columns, index, temp, index+1, columns.length - index);
columns = temp;
switch (type) {
case INT: columns[index] = new int[rowCount]; break;
case LONG: columns[index] = new long[rowCount]; break;
case FLOAT: columns[index] = new float[rowCount]; break;
case DOUBLE: columns[index] = new double[rowCount]; break;
case STRING: columns[index] = new String[rowCount]; break;
case CATEGORY: columns[index] = new int[rowCount]; break;
}
}
/**
* @webref table:method
* @brief Removes a column from a table
* @param columnName the title of the column to be removed
* @see Table#addColumn()
*/
public void removeColumn(String columnName) {
removeColumn(getColumnIndex(columnName));
}
/**
* @param column the index number of the column to be removed
*/
public void removeColumn(int column) {
int newCount = columns.length - 1;
Object[] columnsTemp = new Object[newCount];
HashMapBlows[] catTemp = new HashMapBlows[newCount];
for (int i = 0; i < column; i++) {
columnsTemp[i] = columns[i];
catTemp[i] = columnCategories[i];
}
for (int i = column; i < newCount; i++) {
columnsTemp[i] = columns[i+1];
catTemp[i] = columnCategories[i+1];
}
columns = columnsTemp;
columnCategories = catTemp;
if (columnTitles != null) {
String[] titlesTemp = new String[newCount];
for (int i = 0; i < column; i++) {
titlesTemp[i] = columnTitles[i];
}
for (int i = column; i < newCount; i++) {
titlesTemp[i] = columnTitles[i+1];
}
columnTitles = titlesTemp;
columnIndices = null;
}
}
/**
* @webref table:method
* @brief Gets the number of columns in a table
* @see Table#getRowCount()
*/
public int getColumnCount() {
return columns.length;
}
/**
* Change the number of columns in this table. Resizes all rows to ensure
* the same number of columns in each row. Entries in the additional (empty)
* columns will be set to null.
* @param newCount
*/
public void setColumnCount(int newCount) {
int oldCount = columns.length;
if (oldCount != newCount) {
columns = (Object[]) PApplet.expand(columns, newCount);
// create new columns, default to String as the data type
for (int c = oldCount; c < newCount; c++) {
columns[c] = new String[rowCount];
}
if (columnTitles != null) {
columnTitles = PApplet.expand(columnTitles, newCount);
}
columnTypes = PApplet.expand(columnTypes, newCount);
columnCategories = (HashMapBlows[])
PApplet.expand(columnCategories, newCount);
}
}
public void setColumnType(String columnName, String columnType) {
setColumnType(checkColumnIndex(columnName), columnType);
}
static int parseColumnType(String columnType) {
columnType = columnType.toLowerCase();
int type = -1;
if (columnType.equals("string")) {
type = STRING;
} else if (columnType.equals("int")) {
type = INT;
} else if (columnType.equals("long")) {
type = LONG;
} else if (columnType.equals("float")) {
type = FLOAT;
} else if (columnType.equals("double")) {
type = DOUBLE;
} else if (columnType.equals("category")) {
type = CATEGORY;
} else {
throw new IllegalArgumentException("'" + columnType + "' is not a valid column type.");
}
return type;
}
/**
* Set the data type for a column so that using it is more efficient.
* @param column the column to change
* @param columnType One of int, long, float, double, string, or category.
*/
public void setColumnType(int column, String columnType) {
setColumnType(column, parseColumnType(columnType));
}
public void setColumnType(String columnName, int newType) {
setColumnType(checkColumnIndex(columnName), newType);
}
/**
* Sets the column type. If data already exists, then it'll be converted to
* the new type.
* @param column the column whose type should be changed
* @param newType something fresh, maybe try an int or a float for size?
*/
public void setColumnType(int column, int newType) {
switch (newType) {
case INT: {
int[] intData = new int[rowCount];
for (int row = 0; row < rowCount; row++) {
String s = getString(row, column);
intData[row] = (s == null) ? missingInt : PApplet.parseInt(s, missingInt);
}
columns[column] = intData;
break;
}
case LONG: {
long[] longData = new long[rowCount];
for (int row = 0; row < rowCount; row++) {
String s = getString(row, column);
try {
longData[row] = (s == null) ? missingLong : Long.parseLong(s);
} catch (NumberFormatException nfe) {
longData[row] = missingLong;
}
}
columns[column] = longData;
break;
}
case FLOAT: {
float[] floatData = new float[rowCount];
for (int row = 0; row < rowCount; row++) {
String s = getString(row, column);
floatData[row] = (s == null) ? missingFloat : PApplet.parseFloat(s, missingFloat);
}
columns[column] = floatData;
break;
}
case DOUBLE: {
double[] doubleData = new double[rowCount];
for (int row = 0; row < rowCount; row++) {
String s = getString(row, column);
try {
doubleData[row] = (s == null) ? missingDouble : Double.parseDouble(s);
} catch (NumberFormatException nfe) {
doubleData[row] = missingDouble;
}
}
columns[column] = doubleData;
break;
}
case STRING: {
if (columnTypes[column] != STRING) {
String[] stringData = new String[rowCount];
for (int row = 0; row < rowCount; row++) {
stringData[row] = getString(row, column);
}
columns[column] = stringData;
}
break;
}
case CATEGORY: {
int[] indexData = new int[rowCount];
HashMapBlows categories = new HashMapBlows();
for (int row = 0; row < rowCount; row++) {
String s = getString(row, column);
indexData[row] = categories.index(s);
}
columnCategories[column] = categories;
columns[column] = indexData;
break;
}
default: {
throw new IllegalArgumentException("That's not a valid column type.");
}
}
// System.out.println("new type is " + newType);
columnTypes[column] = newType;
}
/**
* Set the entire table to a specific data type.
*/
public void setTableType(String type) {
for (int col = 0; col < getColumnCount(); col++) {
setColumnType(col, type);
}
}
public void setColumnTypes(int[] types) {
ensureColumn(types.length - 1);
for (int col = 0; col < types.length; col++) {
setColumnType(col, types[col]);
}
}
/**
* Set the titles (and if a second column is present) the data types for
* this table based on a file loaded separately. This will look for the
* title in column 0, and the type in column 1. Better yet, specify a
* column named "title" and another named "type" in the dictionary table
* to future-proof the code.
* @param dictionary
*/
public void setColumnTypes(final Table dictionary) {
ensureColumn(dictionary.getRowCount() - 1);
int titleCol = 0;
int typeCol = 1;
if (dictionary.hasColumnTitles()) {
titleCol = dictionary.getColumnIndex("title", true);
typeCol = dictionary.getColumnIndex("type", true);
}
setColumnTitles(dictionary.getStringColumn(titleCol));
final String[] typeNames = dictionary.getStringColumn(typeCol);
if (dictionary.getColumnCount() > 1) {
if (getRowCount() > 1000) {
int proc = Runtime.getRuntime().availableProcessors();
ExecutorService pool = Executors.newFixedThreadPool(proc/2);
for (int i = 0; i < dictionary.getRowCount(); i++) {
final int col = i;
pool.execute(new Runnable() {
public void run() {
setColumnType(col, typeNames[col]);
}
});
}
pool.shutdown();
while (!pool.isTerminated()) {
Thread.yield();
}
} else {
for (int col = 0; col < dictionary.getRowCount(); col++) {
// setColumnType(i, dictionary.getString(i, typeCol));
setColumnType(col, typeNames[col]);
}
}
}
}
public int getColumnType(String columnName) {
return getColumnType(getColumnIndex(columnName));
}
/** Returns one of Table.STRING, Table.INT, etc... */
public int getColumnType(int column) {
return columnTypes[column];
}
public int[] getColumnTypes() {
return columnTypes;
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
* Remove the first row from the data set, and use it as the column titles.
* Use loadTable("table.csv", "header") instead.
*/
@Deprecated
public String[] removeTitleRow() {
String[] titles = getStringRow(0);
removeRow(0);
setColumnTitles(titles);
return titles;
}
public void setColumnTitles(String[] titles) {
if (titles != null) {
ensureColumn(titles.length - 1);
}
columnTitles = titles;
columnIndices = null; // remove the cache
}
public void setColumnTitle(int column, String title) {
ensureColumn(column);
if (columnTitles == null) {
columnTitles = new String[getColumnCount()];
}
columnTitles[column] = title;
columnIndices = null; // reset these fellas
}
public boolean hasColumnTitles() {
return columnTitles != null;
}
public String[] getColumnTitles() {
return columnTitles;
}
public String getColumnTitle(int col) {
return (columnTitles == null) ? null : columnTitles[col];
}
public int getColumnIndex(String columnName) {
return getColumnIndex(columnName, true);
}
/**
* Get the index of a column.
* @param name Name of the column.
* @param report Whether to throw an exception if the column wasn't found.
* @return index of the found column, or -1 if not found.
*/
protected int getColumnIndex(String name, boolean report) {
if (columnTitles == null) {
if (report) {
throw new IllegalArgumentException("This table has no header, so no column titles are set.");
}
return -1;
}
// only create this on first get(). subsequent calls to set the title will
// also update this array, but only if it exists.
if (columnIndices == null) {
columnIndices = new HashMap<>();
for (int col = 0; col < columns.length; col++) {
columnIndices.put(columnTitles[col], col);
}
}
Integer index = columnIndices.get(name);
if (index == null) {
if (report) {
// Throws an exception here because the name is known and therefore most useful.
// (Rather than waiting for it to fail inside, say, getInt())
throw new IllegalArgumentException("This table has no column named '" + name + "'");
}
return -1;
}
return index.intValue();
}
/**
* Same as getColumnIndex(), but creates the column if it doesn't exist.
* Named this way to not conflict with checkColumn(), an internal function
* used to ensure that a columns exists, and also to denote that it returns
* an int for the column index.
* @param title column title
* @return index of a new or previously existing column
*/
public int checkColumnIndex(String title) {
int index = getColumnIndex(title, false);
if (index != -1) {
return index;
}
addColumn(title);
return getColumnCount() - 1;
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
* @webref table:method
* @brief Gets the number of rows in a table
* @see Table#getColumnCount()
*/
public int getRowCount() {
return rowCount;
}
public int lastRowIndex() {
return getRowCount() - 1;
}
/**
* @webref table:method
* @brief Removes all rows from a table
* @see Table#addRow()
* @see Table#removeRow(int)
*/
public void clearRows() {
setRowCount(0);
}
public void setRowCount(int newCount) {
if (newCount != rowCount) {
if (newCount > 1000000) {
System.out.print("Note: setting maximum row count to " + PApplet.nfc(newCount));
}
long t = System.currentTimeMillis();
for (int col = 0; col < columns.length; col++) {
switch (columnTypes[col]) {
case INT: columns[col] = PApplet.expand((int[]) columns[col], newCount); break;
case LONG: columns[col] = PApplet.expand((long[]) columns[col], newCount); break;
case FLOAT: columns[col] = PApplet.expand((float[]) columns[col], newCount); break;
case DOUBLE: columns[col] = PApplet.expand((double[]) columns[col], newCount); break;
case STRING: columns[col] = PApplet.expand((String[]) columns[col], newCount); break;
case CATEGORY: columns[col] = PApplet.expand((int[]) columns[col], newCount); break;
}
if (newCount > 1000000) {
try {
Thread.sleep(10); // gc time!
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
if (newCount > 1000000) {
int ms = (int) (System.currentTimeMillis() - t);
System.out.println(" (resize took " + PApplet.nfc(ms) + " ms)");
}
}
rowCount = newCount;
}
/**
* @webref table:method
* @brief Adds a row to a table
* @see Table#removeRow(int)
* @see Table#clearRows()
*/
public TableRow addRow() {
//if (rowIncrement == 0) {
setRowCount(rowCount + 1);
return new RowPointer(this, rowCount - 1);
}
/**
* @param source a reference to the original row to be duplicated
*/
public TableRow addRow(TableRow source) {
return setRow(rowCount, source);
}
public TableRow setRow(int row, TableRow source) {
// Make sure there are enough columns to add this data
ensureBounds(row, source.getColumnCount() - 1);
for (int col = 0; col < Math.min(source.getColumnCount(), columns.length); col++) {
switch (columnTypes[col]) {
case INT:
setInt(row, col, source.getInt(col));
break;
case LONG:
setLong(row, col, source.getLong(col));
break;
case FLOAT:
setFloat(row, col, source.getFloat(col));
break;
case DOUBLE:
setDouble(row, col, source.getDouble(col));
break;
case STRING:
setString(row, col, source.getString(col));
break;
case CATEGORY:
int index = source.getInt(col);
setInt(row, col, index);
if (!columnCategories[col].hasCategory(index)) {
columnCategories[col].setCategory(index, source.getString(col));
}
break;
default:
throw new RuntimeException("no types");
}
}
return new RowPointer(this, row);
}
/**
* @nowebref
*/
public TableRow addRow(Object[] columnData) {
setRow(getRowCount(), columnData);
return new RowPointer(this, rowCount - 1);
}
public void addRows(Table source) {
int index = getRowCount();
setRowCount(index + source.getRowCount());
for (TableRow row : source.rows()) {
setRow(index++, row);
}
}
public void insertRow(int insert, Object[] columnData) {
for (int col = 0; col < columns.length; col++) {
switch (columnTypes[col]) {
case CATEGORY:
case INT: {
int[] intTemp = new int[rowCount+1];
System.arraycopy(columns[col], 0, intTemp, 0, insert);
System.arraycopy(columns[col], insert, intTemp, insert+1, rowCount - insert);
columns[col] = intTemp;
break;
}
case LONG: {
long[] longTemp = new long[rowCount+1];
System.arraycopy(columns[col], 0, longTemp, 0, insert);
System.arraycopy(columns[col], insert, longTemp, insert+1, rowCount - insert);
columns[col] = longTemp;
break;
}
case FLOAT: {
float[] floatTemp = new float[rowCount+1];
System.arraycopy(columns[col], 0, floatTemp, 0, insert);
System.arraycopy(columns[col], insert, floatTemp, insert+1, rowCount - insert);
columns[col] = floatTemp;
break;
}
case DOUBLE: {
double[] doubleTemp = new double[rowCount+1];
System.arraycopy(columns[col], 0, doubleTemp, 0, insert);
System.arraycopy(columns[col], insert, doubleTemp, insert+1, rowCount - insert);
columns[col] = doubleTemp;
break;
}
case STRING: {
String[] stringTemp = new String[rowCount+1];
System.arraycopy(columns[col], 0, stringTemp, 0, insert);
System.arraycopy(columns[col], insert, stringTemp, insert+1, rowCount - insert);
columns[col] = stringTemp;
break;
}
}
}
// Need to increment before setRow(), because it calls ensureBounds()
// https://github.com/processing/processing/issues/5406
++rowCount;
setRow(insert, columnData);
}
/**
* @webref table:method
* @brief Removes a row from a table
* @param row ID number of the row to remove
* @see Table#addRow()
* @see Table#clearRows()
*/
public void removeRow(int row) {
for (int col = 0; col < columns.length; col++) {
switch (columnTypes[col]) {
case CATEGORY:
case INT: {
int[] intTemp = new int[rowCount-1];
// int[] intData = (int[]) columns[col];
// System.arraycopy(intData, 0, intTemp, 0, dead);
// System.arraycopy(intData, dead+1, intTemp, dead, (rowCount - dead) + 1);
System.arraycopy(columns[col], 0, intTemp, 0, row);
System.arraycopy(columns[col], row+1, intTemp, row, (rowCount - row) - 1);
columns[col] = intTemp;
break;
}
case LONG: {
long[] longTemp = new long[rowCount-1];
// long[] longData = (long[]) columns[col];
// System.arraycopy(longData, 0, longTemp, 0, dead);
// System.arraycopy(longData, dead+1, longTemp, dead, (rowCount - dead) + 1);
System.arraycopy(columns[col], 0, longTemp, 0, row);
System.arraycopy(columns[col], row+1, longTemp, row, (rowCount - row) - 1);
columns[col] = longTemp;
break;
}
case FLOAT: {
float[] floatTemp = new float[rowCount-1];
// float[] floatData = (float[]) columns[col];
// System.arraycopy(floatData, 0, floatTemp, 0, dead);
// System.arraycopy(floatData, dead+1, floatTemp, dead, (rowCount - dead) + 1);
System.arraycopy(columns[col], 0, floatTemp, 0, row);
System.arraycopy(columns[col], row+1, floatTemp, row, (rowCount - row) - 1);
columns[col] = floatTemp;
break;
}
case DOUBLE: {
double[] doubleTemp = new double[rowCount-1];
// double[] doubleData = (double[]) columns[col];
// System.arraycopy(doubleData, 0, doubleTemp, 0, dead);
// System.arraycopy(doubleData, dead+1, doubleTemp, dead, (rowCount - dead) + 1);
System.arraycopy(columns[col], 0, doubleTemp, 0, row);
System.arraycopy(columns[col], row+1, doubleTemp, row, (rowCount - row) - 1);
columns[col] = doubleTemp;
break;
}
case STRING: {
String[] stringTemp = new String[rowCount-1];
System.arraycopy(columns[col], 0, stringTemp, 0, row);
System.arraycopy(columns[col], row+1, stringTemp, row, (rowCount - row) - 1);
columns[col] = stringTemp;
}
}
}
rowCount--;
}
/*
public void setRow(int row, String[] pieces) {
checkSize(row, pieces.length - 1);
// pieces.length may be less than columns.length, so loop over pieces
for (int col = 0; col < pieces.length; col++) {
setRowCol(row, col, pieces[col]);
}
}
protected void setRowCol(int row, int col, String piece) {
switch (columnTypes[col]) {
case STRING:
String[] stringData = (String[]) columns[col];
stringData[row] = piece;
break;
case INT:
int[] intData = (int[]) columns[col];
intData[row] = PApplet.parseInt(piece, missingInt);
break;
case LONG:
long[] longData = (long[]) columns[col];
try {
longData[row] = Long.parseLong(piece);
} catch (NumberFormatException nfe) {
longData[row] = missingLong;
}
break;
case FLOAT:
float[] floatData = (float[]) columns[col];
floatData[row] = PApplet.parseFloat(piece, missingFloat);
break;
case DOUBLE:
double[] doubleData = (double[]) columns[col];
try {
doubleData[row] = Double.parseDouble(piece);
} catch (NumberFormatException nfe) {
doubleData[row] = missingDouble;
}
break;
case CATEGORY:
int[] indexData = (int[]) columns[col];
indexData[row] = columnCategories[col].index(piece);
break;
default:
throw new IllegalArgumentException("That's not a valid column type.");
}
}
*/
public void setRow(int row, Object[] pieces) {
ensureBounds(row, pieces.length - 1);
// pieces.length may be less than columns.length, so loop over pieces
for (int col = 0; col < pieces.length; col++) {
setRowCol(row, col, pieces[col]);
}
}
protected void setRowCol(int row, int col, Object piece) {
switch (columnTypes[col]) {
case STRING:
String[] stringData = (String[]) columns[col];
if (piece == null) {
stringData[row] = null;
// } else if (piece instanceof String) {
// stringData[row] = (String) piece;
} else {
// Calls toString() on the object, which is 'return this' for String
stringData[row] = String.valueOf(piece);
}
break;
case INT:
int[] intData = (int[]) columns[col];
//intData[row] = PApplet.parseInt(piece, missingInt);
if (piece == null) {
intData[row] = missingInt;
} else if (piece instanceof Integer) {
intData[row] = (Integer) piece;
} else {
intData[row] = PApplet.parseInt(String.valueOf(piece), missingInt);
}
break;
case LONG:
long[] longData = (long[]) columns[col];
if (piece == null) {
longData[row] = missingLong;
} else if (piece instanceof Long) {
longData[row] = (Long) piece;
} else {
try {
longData[row] = Long.parseLong(String.valueOf(piece));
} catch (NumberFormatException nfe) {
longData[row] = missingLong;
}
}
break;
case FLOAT:
float[] floatData = (float[]) columns[col];
if (piece == null) {
floatData[row] = missingFloat;
} else if (piece instanceof Float) {
floatData[row] = (Float) piece;
} else {
floatData[row] = PApplet.parseFloat(String.valueOf(piece), missingFloat);
}
break;
case DOUBLE:
double[] doubleData = (double[]) columns[col];
if (piece == null) {
doubleData[row] = missingDouble;
} else if (piece instanceof Double) {
doubleData[row] = (Double) piece;
} else {
try {
doubleData[row] = Double.parseDouble(String.valueOf(piece));
} catch (NumberFormatException nfe) {
doubleData[row] = missingDouble;
}
}
break;
case CATEGORY:
int[] indexData = (int[]) columns[col];
if (piece == null) {
indexData[row] = missingCategory;
} else {
String peace = String.valueOf(piece);
if (peace.equals(missingString)) { // missingString might be null
indexData[row] = missingCategory;
} else {
indexData[row] = columnCategories[col].index(peace);
}
}
break;
default:
throw new IllegalArgumentException("That's not a valid column type.");
}
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
* @webref table:method
* @brief Gets a row from a table
* @param row ID number of the row to get
* @see Table#rows()
* @see Table#findRow(String, int)
* @see Table#findRows(String, int)
* @see Table#matchRow(String, int)
* @see Table#matchRows(String, int)
*/
public TableRow getRow(int row) {
return new RowPointer(this, row);
}
/**
* Note that this one iterator instance is shared by any calls to iterate
* the rows of this table. This is very efficient, but not thread-safe.
* If you want to iterate in a multi-threaded manner, don't use the iterator.
*
* @webref table:method
* @brief Gets multiple rows from a table
* @see Table#getRow(int)
* @see Table#findRow(String, int)
* @see Table#findRows(String, int)
* @see Table#matchRow(String, int)
* @see Table#matchRows(String, int)
*/
public Iterable rows() {
return new Iterable() {
public Iterator iterator() {
if (rowIterator == null) {
rowIterator = new RowIterator(Table.this);
} else {
rowIterator.reset();
}
return rowIterator;
}
};
}
/**
* @nowebref
*/
public Iterable rows(final int[] indices) {
return new Iterable() {
public Iterator iterator() {
return new RowIndexIterator(Table.this, indices);
}
};
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
static class RowPointer implements TableRow {
Table table;
int row;
public RowPointer(Table table, int row) {
this.table = table;
this.row = row;
}
public void setRow(int row) {
this.row = row;
}
public String getString(int column) {
return table.getString(row, column);
}
public String getString(String columnName) {
return table.getString(row, columnName);
}
public int getInt(int column) {
return table.getInt(row, column);
}
public int getInt(String columnName) {
return table.getInt(row, columnName);
}
public long getLong(int column) {
return table.getLong(row, column);
}
public long getLong(String columnName) {
return table.getLong(row, columnName);
}
public float getFloat(int column) {
return table.getFloat(row, column);
}
public float getFloat(String columnName) {
return table.getFloat(row, columnName);
}
public double getDouble(int column) {
return table.getDouble(row, column);
}
public double getDouble(String columnName) {
return table.getDouble(row, columnName);
}
public void setString(int column, String value) {
table.setString(row, column, value);
}
public void setString(String columnName, String value) {
table.setString(row, columnName, value);
}
public void setInt(int column, int value) {
table.setInt(row, column, value);
}
public void setInt(String columnName, int value) {
table.setInt(row, columnName, value);
}
public void setLong(int column, long value) {
table.setLong(row, column, value);
}
public void setLong(String columnName, long value) {
table.setLong(row, columnName, value);
}
public void setFloat(int column, float value) {
table.setFloat(row, column, value);
}
public void setFloat(String columnName, float value) {
table.setFloat(row, columnName, value);
}
public void setDouble(int column, double value) {
table.setDouble(row, column, value);
}
public void setDouble(String columnName, double value) {
table.setDouble(row, columnName, value);
}
public int getColumnCount() {
return table.getColumnCount();
}
public int getColumnType(String columnName) {
return table.getColumnType(columnName);
}
public int getColumnType(int column) {
return table.getColumnType(column);
}
public int[] getColumnTypes() {
return table.getColumnTypes();
}
public String getColumnTitle(int column) {
return table.getColumnTitle(column);
}
public String[] getColumnTitles() {
return table.getColumnTitles();
}
public void print() {
write(new PrintWriter(System.out));
}
public void write(PrintWriter writer) {
for (int i = 0 ; i < getColumnCount(); i++) {
if (i != 0) {
writer.print('\t');
}
writer.print(getString(i));
}
}
}
static class RowIterator implements Iterator {
Table table;
RowPointer rp;
int row;
public RowIterator(Table table) {
this.table = table;
row = -1;
rp = new RowPointer(table, row);
}
public void remove() {
table.removeRow(row);
}
public TableRow next() {
rp.setRow(++row);
return rp;
}
public boolean hasNext() {
return row+1 < table.getRowCount();
}
public void reset() {
row = -1;
}
}
static class RowIndexIterator implements Iterator {
Table table;
RowPointer rp;
int[] indices;
int index;
public RowIndexIterator(Table table, int[] indices) {
this.table = table;
this.indices = indices;
index = -1;
// just set to something arbitrary
rp = new RowPointer(table, -1);
}
public void remove() {
table.removeRow(indices[index]);
}
public TableRow next() {
rp.setRow(indices[++index]);
return rp;
}
public boolean hasNext() {
//return row+1 < table.getRowCount();
return index + 1 < indices.length;
}
public void reset() {
index = -1;
}
}
/*
static public Iterator createIterator(final ResultSet rs) {
return new Iterator() {
boolean already;
public boolean hasNext() {
already = true;
try {
return rs.next();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public TableRow next() {
if (!already) {
try {
rs.next();
} catch (SQLException e) {
throw new RuntimeException(e);
}
} else {
already = false;
}
return new TableRow() {
public double getDouble(int column) {
try {
return rs.getDouble(column);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public double getDouble(String columnName) {
try {
return rs.getDouble(columnName);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public float getFloat(int column) {
try {
return rs.getFloat(column);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public float getFloat(String columnName) {
try {
return rs.getFloat(columnName);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public int getInt(int column) {
try {
return rs.getInt(column);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public int getInt(String columnName) {
try {
return rs.getInt(columnName);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public long getLong(int column) {
try {
return rs.getLong(column);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public long getLong(String columnName) {
try {
return rs.getLong(columnName);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public String getString(int column) {
try {
return rs.getString(column);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public String getString(String columnName) {
try {
return rs.getString(columnName);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public void setString(int column, String value) { immutable(); }
public void setString(String columnName, String value) { immutable(); }
public void setInt(int column, int value) { immutable(); }
public void setInt(String columnName, int value) { immutable(); }
public void setLong(int column, long value) { immutable(); }
public void setLong(String columnName, long value) { immutable(); }
public void setFloat(int column, float value) { immutable(); }
public void setFloat(String columnName, float value) { immutable(); }
public void setDouble(int column, double value) { immutable(); }
public void setDouble(String columnName, double value) { immutable(); }
private void immutable() {
throw new IllegalArgumentException("This TableRow cannot be modified.");
}
public int getColumnCount() {
try {
return rs.getMetaData().getColumnCount();
} catch (SQLException e) {
e.printStackTrace();
return -1;
}
}
public int getColumnType(String columnName) {
// unimplemented
}
public int getColumnType(int column) {
// unimplemented
}
};
}
public void remove() {
throw new IllegalArgumentException("remove() not supported");
}
};
}
*/
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
* @webref table:method
* @brief Get an integer value from the specified row and column
* @param row ID number of the row to reference
* @param column ID number of the column to reference
* @see Table#getFloat(int, int)
* @see Table#getString(int, int)
* @see Table#getStringColumn(String)
* @see Table#setInt(int, int, int)
* @see Table#setFloat(int, int, float)
* @see Table#setString(int, int, String)
*/
public int getInt(int row, int column) {
checkBounds(row, column);
if (columnTypes[column] == INT ||
columnTypes[column] == CATEGORY) {
int[] intData = (int[]) columns[column];
return intData[row];
}
String str = getString(row, column);
return (str == null || str.equals(missingString)) ?
missingInt : PApplet.parseInt(str, missingInt);
}
/**
* @param columnName title of the column to reference
*/
public int getInt(int row, String columnName) {
return getInt(row, getColumnIndex(columnName));
}
public void setMissingInt(int value) {
missingInt = value;
}
/**
* @webref table:method
* @brief Store an integer value in the specified row and column
* @param row ID number of the target row
* @param column ID number of the target column
* @param value value to assign
* @see Table#setFloat(int, int, float)
* @see Table#setString(int, int, String)
* @see Table#getInt(int, int)
* @see Table#getFloat(int, int)
* @see Table#getString(int, int)
* @see Table#getStringColumn(String)
*/
public void setInt(int row, int column, int value) {
if (columnTypes[column] == STRING) {
setString(row, column, String.valueOf(value));
} else {
ensureBounds(row, column);
if (columnTypes[column] != INT &&
columnTypes[column] != CATEGORY) {
throw new IllegalArgumentException("Column " + column + " is not an int column.");
}
int[] intData = (int[]) columns[column];
intData[row] = value;
}
}
/**
* @param columnName title of the target column
*/
public void setInt(int row, String columnName, int value) {
setInt(row, getColumnIndex(columnName), value);
}
public int[] getIntColumn(String name) {
int col = getColumnIndex(name);
return (col == -1) ? null : getIntColumn(col);
}
public int[] getIntColumn(int col) {
int[] outgoing = new int[rowCount];
for (int row = 0; row < rowCount; row++) {
outgoing[row] = getInt(row, col);
}
return outgoing;
}
public int[] getIntRow(int row) {
int[] outgoing = new int[columns.length];
for (int col = 0; col < columns.length; col++) {
outgoing[col] = getInt(row, col);
}
return outgoing;
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
public long getLong(int row, int column) {
checkBounds(row, column);
if (columnTypes[column] == LONG) {
long[] longData = (long[]) columns[column];
return longData[row];
}
String str = getString(row, column);
if (str == null || str.equals(missingString)) {
return missingLong;
}
try {
return Long.parseLong(str);
} catch (NumberFormatException nfe) {
return missingLong;
}
}
public long getLong(int row, String columnName) {
return getLong(row, getColumnIndex(columnName));
}
public void setMissingLong(long value) {
missingLong = value;
}
public void setLong(int row, int column, long value) {
if (columnTypes[column] == STRING) {
setString(row, column, String.valueOf(value));
} else {
ensureBounds(row, column);
if (columnTypes[column] != LONG) {
throw new IllegalArgumentException("Column " + column + " is not a 'long' column.");
}
long[] longData = (long[]) columns[column];
longData[row] = value;
}
}
public void setLong(int row, String columnName, long value) {
setLong(row, getColumnIndex(columnName), value);
}
public long[] getLongColumn(String name) {
int col = getColumnIndex(name);
return (col == -1) ? null : getLongColumn(col);
}
public long[] getLongColumn(int col) {
long[] outgoing = new long[rowCount];
for (int row = 0; row < rowCount; row++) {
outgoing[row] = getLong(row, col);
}
return outgoing;
}
public long[] getLongRow(int row) {
long[] outgoing = new long[columns.length];
for (int col = 0; col < columns.length; col++) {
outgoing[col] = getLong(row, col);
}
return outgoing;
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
* Get a float value from the specified row and column. If the value is null
* or not parseable as a float, the "missing" value is returned. By default,
* this is Float.NaN, but can be controlled with setMissingFloat().
*
* @webref table:method
* @brief Get a float value from the specified row and column
* @param row ID number of the row to reference
* @param column ID number of the column to reference
* @see Table#getInt(int, int)
* @see Table#getString(int, int)
* @see Table#getStringColumn(String)
* @see Table#setInt(int, int, int)
* @see Table#setFloat(int, int, float)
* @see Table#setString(int, int, String)
*/
public float getFloat(int row, int column) {
checkBounds(row, column);
if (columnTypes[column] == FLOAT) {
float[] floatData = (float[]) columns[column];
return floatData[row];
}
String str = getString(row, column);
if (str == null || str.equals(missingString)) {
return missingFloat;
}
return PApplet.parseFloat(str, missingFloat);
}
/**
* @param columnName title of the column to reference
*/
public float getFloat(int row, String columnName) {
return getFloat(row, getColumnIndex(columnName));
}
public void setMissingFloat(float value) {
missingFloat = value;
}
/**
* @webref table:method
* @brief Store a float value in the specified row and column
* @param row ID number of the target row
* @param column ID number of the target column
* @param value value to assign
* @see Table#setInt(int, int, int)
* @see Table#setString(int, int, String)
* @see Table#getInt(int, int)
* @see Table#getFloat(int, int)
* @see Table#getString(int, int)
* @see Table#getStringColumn(String)
*/
public void setFloat(int row, int column, float value) {
if (columnTypes[column] == STRING) {
setString(row, column, String.valueOf(value));
} else {
ensureBounds(row, column);
if (columnTypes[column] != FLOAT) {
throw new IllegalArgumentException("Column " + column + " is not a float column.");
}
float[] longData = (float[]) columns[column];
longData[row] = value;
}
}
/**
* @param columnName title of the target column
*/
public void setFloat(int row, String columnName, float value) {
setFloat(row, getColumnIndex(columnName), value);
}
public float[] getFloatColumn(String name) {
int col = getColumnIndex(name);
return (col == -1) ? null : getFloatColumn(col);
}
public float[] getFloatColumn(int col) {
float[] outgoing = new float[rowCount];
for (int row = 0; row < rowCount; row++) {
outgoing[row] = getFloat(row, col);
}
return outgoing;
}
public float[] getFloatRow(int row) {
float[] outgoing = new float[columns.length];
for (int col = 0; col < columns.length; col++) {
outgoing[col] = getFloat(row, col);
}
return outgoing;
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
public double getDouble(int row, int column) {
checkBounds(row, column);
if (columnTypes[column] == DOUBLE) {
double[] doubleData = (double[]) columns[column];
return doubleData[row];
}
String str = getString(row, column);
if (str == null || str.equals(missingString)) {
return missingDouble;
}
try {
return Double.parseDouble(str);
} catch (NumberFormatException nfe) {
return missingDouble;
}
}
public double getDouble(int row, String columnName) {
return getDouble(row, getColumnIndex(columnName));
}
public void setMissingDouble(double value) {
missingDouble = value;
}
public void setDouble(int row, int column, double value) {
if (columnTypes[column] == STRING) {
setString(row, column, String.valueOf(value));
} else {
ensureBounds(row, column);
if (columnTypes[column] != DOUBLE) {
throw new IllegalArgumentException("Column " + column + " is not a 'double' column.");
}
double[] doubleData = (double[]) columns[column];
doubleData[row] = value;
}
}
public void setDouble(int row, String columnName, double value) {
setDouble(row, getColumnIndex(columnName), value);
}
public double[] getDoubleColumn(String name) {
int col = getColumnIndex(name);
return (col == -1) ? null : getDoubleColumn(col);
}
public double[] getDoubleColumn(int col) {
double[] outgoing = new double[rowCount];
for (int row = 0; row < rowCount; row++) {
outgoing[row] = getDouble(row, col);
}
return outgoing;
}
public double[] getDoubleRow(int row) {
double[] outgoing = new double[columns.length];
for (int col = 0; col < columns.length; col++) {
outgoing[col] = getDouble(row, col);
}
return outgoing;
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
//public long getTimestamp(String rowName, int column) {
//return getTimestamp(getRowIndex(rowName), column);
//}
/**
* Returns the time in milliseconds by parsing a SQL Timestamp at this cell.
*/
// public long getTimestamp(int row, int column) {
// String str = get(row, column);
// java.sql.Timestamp timestamp = java.sql.Timestamp.valueOf(str);
// return timestamp.getTime();
// }
// public long getExcelTimestamp(int row, int column) {
// return parseExcelTimestamp(get(row, column));
// }
// static protected DateFormat excelDateFormat;
// static public long parseExcelTimestamp(String timestamp) {
// if (excelDateFormat == null) {
// excelDateFormat = new SimpleDateFormat("MM/dd/yy HH:mm");
// }
// try {
// return excelDateFormat.parse(timestamp).getTime();
// } catch (ParseException e) {
// e.printStackTrace();
// return -1;
// }
// }
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
// public void setObject(int row, int column, Object value) {
// if (value == null) {
// data[row][column] = null;
// } else if (value instanceof String) {
// set(row, column, (String) value);
// } else if (value instanceof Float) {
// setFloat(row, column, ((Float) value).floatValue());
// } else if (value instanceof Integer) {
// setInt(row, column, ((Integer) value).intValue());
// } else {
// set(row, column, value.toString());
// }
// }
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
* Get a String value from the table. If the row is longer than the table
*
* @webref table:method
* @brief Get an String value from the specified row and column
* @param row ID number of the row to reference
* @param column ID number of the column to reference
* @see Table#getInt(int, int)
* @see Table#getFloat(int, int)
* @see Table#getStringColumn(String)
* @see Table#setInt(int, int, int)
* @see Table#setFloat(int, int, float)
* @see Table#setString(int, int, String)
*/
public String getString(int row, int column) {
checkBounds(row, column);
if (columnTypes[column] == STRING) {
String[] stringData = (String[]) columns[column];
return stringData[row];
} else if (columnTypes[column] == CATEGORY) {
int cat = getInt(row, column);
if (cat == missingCategory) {
return missingString;
}
return columnCategories[column].key(cat);
} else if (columnTypes[column] == FLOAT) {
if (Float.isNaN(getFloat(row, column))) {
return null;
}
} else if (columnTypes[column] == DOUBLE) {
if (Double.isNaN(getFloat(row, column))) {
return null;
}
}
return String.valueOf(Array.get(columns[column], row));
}
/**
* @param columnName title of the column to reference
*/
public String getString(int row, String columnName) {
return getString(row, getColumnIndex(columnName));
}
/**
* Treat entries with this string as "missing". Also used for categorial.
*/
public void setMissingString(String value) {
missingString = value;
}
/**
* @webref table:method
* @brief Store a String value in the specified row and column
* @param row ID number of the target row
* @param column ID number of the target column
* @param value value to assign
* @see Table#setInt(int, int, int)
* @see Table#setFloat(int, int, float)
* @see Table#getInt(int, int)
* @see Table#getFloat(int, int)
* @see Table#getString(int, int)
* @see Table#getStringColumn(String)
*/
public void setString(int row, int column, String value) {
ensureBounds(row, column);
if (columnTypes[column] != STRING) {
throw new IllegalArgumentException("Column " + column + " is not a String column.");
}
String[] stringData = (String[]) columns[column];
stringData[row] = value;
}
/**
* @param columnName title of the target column
*/
public void setString(int row, String columnName, String value) {
int column = checkColumnIndex(columnName);
setString(row, column, value);
}
/**
* @webref table:method
* @brief Gets all values in the specified column
* @param columnName title of the column to search
* @see Table#getInt(int, int)
* @see Table#getFloat(int, int)
* @see Table#getString(int, int)
* @see Table#setInt(int, int, int)
* @see Table#setFloat(int, int, float)
* @see Table#setString(int, int, String)
*/
public String[] getStringColumn(String columnName) {
int col = getColumnIndex(columnName);
return (col == -1) ? null : getStringColumn(col);
}
/**
* @param column ID number of the column to search
*/
public String[] getStringColumn(int column) {
String[] outgoing = new String[rowCount];
for (int i = 0; i < rowCount; i++) {
outgoing[i] = getString(i, column);
}
return outgoing;
}
public String[] getStringRow(int row) {
String[] outgoing = new String[columns.length];
for (int col = 0; col < columns.length; col++) {
outgoing[col] = getString(row, col);
}
return outgoing;
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
* Return the row that contains the first String that matches.
* @param value the String to match
* @param column ID number of the column to search
*/
public int findRowIndex(String value, int column) {
checkColumn(column);
if (columnTypes[column] == STRING) {
String[] stringData = (String[]) columns[column];
if (value == null) {
for (int row = 0; row < rowCount; row++) {
if (stringData[row] == null) return row;
}
} else {
for (int row = 0; row < rowCount; row++) {
if (stringData[row] != null && stringData[row].equals(value)) {
return row;
}
}
}
} else { // less efficient, includes conversion as necessary
for (int row = 0; row < rowCount; row++) {
String str = getString(row, column);
if (str == null) {
if (value == null) {
return row;
}
} else if (str.equals(value)) {
return row;
}
}
}
return -1;
}
/**
* Return the row that contains the first String that matches.
* @param value the String to match
* @param columnName title of the column to search
*/
public int findRowIndex(String value, String columnName) {
return findRowIndex(value, getColumnIndex(columnName));
}
/**
* Return a list of rows that contain the String passed in. If there are no
* matches, a zero length array will be returned (not a null array).
* @param value the String to match
* @param column ID number of the column to search
*/
public int[] findRowIndices(String value, int column) {
int[] outgoing = new int[rowCount];
int count = 0;
checkColumn(column);
if (columnTypes[column] == STRING) {
String[] stringData = (String[]) columns[column];
if (value == null) {
for (int row = 0; row < rowCount; row++) {
if (stringData[row] == null) {
outgoing[count++] = row;
}
}
} else {
for (int row = 0; row < rowCount; row++) {
if (stringData[row] != null && stringData[row].equals(value)) {
outgoing[count++] = row;
}
}
}
} else { // less efficient, includes conversion as necessary
for (int row = 0; row < rowCount; row++) {
String str = getString(row, column);
if (str == null) {
if (value == null) {
outgoing[count++] = row;
}
} else if (str.equals(value)) {
outgoing[count++] = row;
}
}
}
return PApplet.subset(outgoing, 0, count);
}
/**
* Return a list of rows that contain the String passed in. If there are no
* matches, a zero length array will be returned (not a null array).
* @param value the String to match
* @param columnName title of the column to search
*/
public int[] findRowIndices(String value, String columnName) {
return findRowIndices(value, getColumnIndex(columnName));
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
* @webref table:method
* @brief Finds a row that contains the given value
* @param value the value to match
* @param column ID number of the column to search
* @see Table#getRow(int)
* @see Table#rows()
* @see Table#findRows(String, int)
* @see Table#matchRow(String, int)
* @see Table#matchRows(String, int)
*/
public TableRow findRow(String value, int column) {
int row = findRowIndex(value, column);
return (row == -1) ? null : new RowPointer(this, row);
}
/**
* @param columnName title of the column to search
*/
public TableRow findRow(String value, String columnName) {
return findRow(value, getColumnIndex(columnName));
}
/**
* @webref table:method
* @brief Finds multiple rows that contain the given value
* @param value the value to match
* @param column ID number of the column to search
* @see Table#getRow(int)
* @see Table#rows()
* @see Table#findRow(String, int)
* @see Table#matchRow(String, int)
* @see Table#matchRows(String, int)
*/
public Iterable findRows(final String value, final int column) {
return new Iterable() {
public Iterator iterator() {
return findRowIterator(value, column);
}
};
}
/**
* @param columnName title of the column to search
*/
public Iterable findRows(final String value, final String columnName) {
return findRows(value, getColumnIndex(columnName));
}
/**
* @brief Finds multiple rows that contain the given value
* @param value the value to match
* @param column ID number of the column to search
*/
public Iterator findRowIterator(String value, int column) {
return new RowIndexIterator(this, findRowIndices(value, column));
}
/**
* @param columnName title of the column to search
*/
public Iterator findRowIterator(String value, String columnName) {
return findRowIterator(value, getColumnIndex(columnName));
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
* Return the row that contains the first String that matches.
* @param regexp the String to match
* @param column ID number of the column to search
*/
public int matchRowIndex(String regexp, int column) {
checkColumn(column);
if (columnTypes[column] == STRING) {
String[] stringData = (String[]) columns[column];
for (int row = 0; row < rowCount; row++) {
if (stringData[row] != null &&
PApplet.match(stringData[row], regexp) != null) {
return row;
}
}
} else { // less efficient, includes conversion as necessary
for (int row = 0; row < rowCount; row++) {
String str = getString(row, column);
if (str != null &&
PApplet.match(str, regexp) != null) {
return row;
}
}
}
return -1;
}
/**
* Return the row that contains the first String that matches.
* @param what the String to match
* @param columnName title of the column to search
*/
public int matchRowIndex(String what, String columnName) {
return matchRowIndex(what, getColumnIndex(columnName));
}
/**
* Return a list of rows that contain the String passed in. If there are no
* matches, a zero length array will be returned (not a null array).
* @param regexp the String to match
* @param column ID number of the column to search
*/
public int[] matchRowIndices(String regexp, int column) {
int[] outgoing = new int[rowCount];
int count = 0;
checkColumn(column);
if (columnTypes[column] == STRING) {
String[] stringData = (String[]) columns[column];
for (int row = 0; row < rowCount; row++) {
if (stringData[row] != null &&
PApplet.match(stringData[row], regexp) != null) {
outgoing[count++] = row;
}
}
} else { // less efficient, includes conversion as necessary
for (int row = 0; row < rowCount; row++) {
String str = getString(row, column);
if (str != null &&
PApplet.match(str, regexp) != null) {
outgoing[count++] = row;
}
}
}
return PApplet.subset(outgoing, 0, count);
}
/**
* Return a list of rows that match the regex passed in. If there are no
* matches, a zero length array will be returned (not a null array).
* @param what the String to match
* @param columnName title of the column to search
*/
public int[] matchRowIndices(String what, String columnName) {
return matchRowIndices(what, getColumnIndex(columnName));
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
* @webref table:method
* @brief Finds a row that matches the given expression
* @param regexp the regular expression to match
* @param column ID number of the column to search
* @see Table#getRow(int)
* @see Table#rows()
* @see Table#findRow(String, int)
* @see Table#findRows(String, int)
* @see Table#matchRows(String, int)
*/
public TableRow matchRow(String regexp, int column) {
int row = matchRowIndex(regexp, column);
return (row == -1) ? null : new RowPointer(this, row);
}
/**
* @param columnName title of the column to search
*/
public TableRow matchRow(String regexp, String columnName) {
return matchRow(regexp, getColumnIndex(columnName));
}
/**
* @webref table:method
* @brief Finds multiple rows that match the given expression
* @param regexp the regular expression to match
* @param column ID number of the column to search
* @see Table#getRow(int)
* @see Table#rows()
* @see Table#findRow(String, int)
* @see Table#findRows(String, int)
* @see Table#matchRow(String, int)
*/
public Iterable matchRows(final String regexp, final int column) {
return new Iterable() {
public Iterator iterator() {
return matchRowIterator(regexp, column);
}
};
}
/**
* @param columnName title of the column to search
*/
public Iterable matchRows(String regexp, String columnName) {
return matchRows(regexp, getColumnIndex(columnName));
}
/**
* @webref table:method
* @brief Finds multiple rows that match the given expression
* @param value the regular expression to match
* @param column ID number of the column to search
*/
public Iterator matchRowIterator(String value, int column) {
return new RowIndexIterator(this, matchRowIndices(value, column));
}
/**
* @param columnName title of the column to search
*/
public Iterator matchRowIterator(String value, String columnName) {
return matchRowIterator(value, getColumnIndex(columnName));
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
* Replace a String with another. Set empty entries null by using
* replace("", null) or use replace(null, "") to go the other direction.
* If this is a typed table, only String columns will be modified.
* @param orig
* @param replacement
*/
public void replace(String orig, String replacement) {
for (int col = 0; col < columns.length; col++) {
replace(orig, replacement, col);
}
}
public void replace(String orig, String replacement, int col) {
if (columnTypes[col] == STRING) {
String[] stringData = (String[]) columns[col];
if (orig != null) {
for (int row = 0; row < rowCount; row++) {
if (orig.equals(stringData[row])) {
stringData[row] = replacement;
}
}
} else { // null is a special case (and faster anyway)
for (int row = 0; row < rowCount; row++) {
if (stringData[row] == null) {
stringData[row] = replacement;
}
}
}
}
}
public void replace(String orig, String replacement, String colName) {
replace(orig, replacement, getColumnIndex(colName));
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
public void replaceAll(String regex, String replacement) {
for (int col = 0; col < columns.length; col++) {
replaceAll(regex, replacement, col);
}
}
public void replaceAll(String regex, String replacement, int column) {
checkColumn(column);
if (columnTypes[column] == STRING) {
String[] stringData = (String[]) columns[column];
for (int row = 0; row < rowCount; row++) {
if (stringData[row] != null) {
stringData[row] = stringData[row].replaceAll(regex, replacement);
}
}
} else {
throw new IllegalArgumentException("replaceAll() can only be used on String columns");
}
}
/**
* Run String.replaceAll() on all entries in a column.
* Only works with columns that are already String values.
* @param regex the String to match
* @param columnName title of the column to search
*/
public void replaceAll(String regex, String replacement, String columnName) {
replaceAll(regex, replacement, getColumnIndex(columnName));
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
* Remove any of the specified characters from the entire table.
*
* @webref table:method
* @brief Removes characters from the table
* @param tokens a list of individual characters to be removed
* @see Table#trim()
*/
public void removeTokens(String tokens) {
for (int col = 0; col < getColumnCount(); col++) {
removeTokens(tokens, col);
}
}
/**
* Removed any of the specified characters from a column. For instance,
* the following code removes dollar signs and commas from column 2:
*
* table.removeTokens(",$", 2);
*
*
* @param column ID number of the column to process
*/
public void removeTokens(String tokens, int column) {
for (int row = 0; row < rowCount; row++) {
String s = getString(row, column);
if (s != null) {
char[] c = s.toCharArray();
int index = 0;
for (int j = 0; j < c.length; j++) {
if (tokens.indexOf(c[j]) == -1) {
if (index != j) {
c[index] = c[j];
}
index++;
}
}
if (index != c.length) {
setString(row, column, new String(c, 0, index));
}
}
}
}
/**
* @param columnName title of the column to process
*/
public void removeTokens(String tokens, String columnName) {
removeTokens(tokens, getColumnIndex(columnName));
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
* @webref table:method
* @brief Trims whitespace from values
* @see Table#removeTokens(String)
*/
public void trim() {
columnTitles = PApplet.trim(columnTitles);
for (int col = 0; col < getColumnCount(); col++) {
trim(col);
}
// remove empty columns
int lastColumn = getColumnCount() - 1;
//while (isEmptyColumn(lastColumn) && lastColumn >= 0) {
while (isEmptyArray(getStringColumn(lastColumn)) && lastColumn >= 0) {
lastColumn--;
}
setColumnCount(lastColumn + 1);
// trim() works from both sides
while (getColumnCount() > 0 && isEmptyArray(getStringColumn(0))) {
removeColumn(0);
}
// remove empty rows (starting from the end)
int lastRow = lastRowIndex();
//while (isEmptyRow(lastRow) && lastRow >= 0) {
while (isEmptyArray(getStringRow(lastRow)) && lastRow >= 0) {
lastRow--;
}
setRowCount(lastRow + 1);
while (getRowCount() > 0 && isEmptyArray(getStringRow(0))) {
removeRow(0);
}
}
protected boolean isEmptyArray(String[] contents) {
for (String entry : contents) {
if (entry != null && entry.length() > 0) {
return false;
}
}
return true;
}
/*
protected boolean isEmptyColumn(int column) {
String[] contents = getStringColumn(column);
for (String entry : contents) {
if (entry != null && entry.length() > 0) {
return false;
}
}
return true;
}
protected boolean isEmptyRow(int row) {
String[] contents = getStringRow(row);
for (String entry : contents) {
if (entry != null && entry.length() > 0) {
return false;
}
}
return true;
}
*/
/**
* @param column ID number of the column to trim
*/
public void trim(int column) {
if (columnTypes[column] == STRING) {
String[] stringData = (String[]) columns[column];
for (int row = 0; row < rowCount; row++) {
if (stringData[row] != null) {
stringData[row] = PApplet.trim(stringData[row]);
}
}
}
}
/**
* @param columnName title of the column to trim
*/
public void trim(String columnName) {
trim(getColumnIndex(columnName));
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/** Make sure this is a legit column, and if not, expand the table. */
protected void ensureColumn(int col) {
if (col >= columns.length) {
setColumnCount(col + 1);
}
}
/** Make sure this is a legit row, and if not, expand the table. */
protected void ensureRow(int row) {
if (row >= rowCount) {
setRowCount(row + 1);
}
}
/** Make sure this is a legit row and column. If not, expand the table. */
protected void ensureBounds(int row, int col) {
ensureRow(row);
ensureColumn(col);
}
/** Throw an error if this row doesn't exist. */
protected void checkRow(int row) {
if (row < 0 || row >= rowCount) {
throw new ArrayIndexOutOfBoundsException("Row " + row + " does not exist.");
}
}
/** Throw an error if this column doesn't exist. */
protected void checkColumn(int column) {
if (column < 0 || column >= columns.length) {
throw new ArrayIndexOutOfBoundsException("Column " + column + " does not exist.");
}
}
/** Throw an error if this entry is out of bounds. */
protected void checkBounds(int row, int column) {
checkRow(row);
checkColumn(column);
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
static class HashMapBlows {
HashMap dataToIndex = new HashMap<>();
ArrayList indexToData = new ArrayList<>();
HashMapBlows() { }
HashMapBlows(DataInputStream input) throws IOException {
read(input);
}
/** gets the index, and creates one if it doesn't already exist. */
int index(String key) {
Integer value = dataToIndex.get(key);
if (value != null) {
return value;
}
int v = dataToIndex.size();
dataToIndex.put(key, v);
indexToData.add(key);
return v;
}
String key(int index) {
return indexToData.get(index);
}
boolean hasCategory(int index) {
return index < size() && indexToData.get(index) != null;
}
void setCategory(int index, String name) {
while (indexToData.size() <= index) {
indexToData.add(null);
}
indexToData.set(index, name);
dataToIndex.put(name, index);
}
int size() {
return dataToIndex.size();
}
void write(DataOutputStream output) throws IOException {
output.writeInt(size());
for (String str : indexToData) {
output.writeUTF(str);
}
}
private void writeln(PrintWriter writer) throws IOException {
for (String str : indexToData) {
writer.println(str);
}
writer.flush();
writer.close();
}
void read(DataInputStream input) throws IOException {
int count = input.readInt();
//System.out.println("found " + count + " entries in category map");
dataToIndex = new HashMap<>(count);
for (int i = 0; i < count; i++) {
String str = input.readUTF();
//System.out.println(i + " " + str);
dataToIndex.put(str, i);
indexToData.add(str);
}
}
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
// class HashMapSucks extends HashMap {
//
// void increment(String what) {
// Integer value = get(what);
// if (value == null) {
// put(what, 1);
// } else {
// put(what, value + 1);
// }
// }
//
// void check(String what) {
// if (get(what) == null) {
// put(what, 0);
// }
// }
// }
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
* Sorts (orders) a table based on the values in a column.
*
* @webref table:method
* @brief Orders a table based on the values in a column
* @param columnName the name of the column to sort
* @see Table#trim()
*/
public void sort(String columnName) {
sort(getColumnIndex(columnName), false);
}
/**
* @param column the column ID, e.g. 0, 1, 2
*/
public void sort(int column) {
sort(column, false);
}
public void sortReverse(String columnName) {
sort(getColumnIndex(columnName), true);
}
public void sortReverse(int column) {
sort(column, true);
}
protected void sort(final int column, final boolean reverse) {
final int[] order = IntList.fromRange(getRowCount()).array();
Sort s = new Sort() {
@Override
public int size() {
return getRowCount();
}
@Override
public int compare(int index1, int index2) {
int a = reverse ? order[index2] : order[index1];
int b = reverse ? order[index1] : order[index2];
switch (getColumnType(column)) {
case INT:
return getInt(a, column) - getInt(b, column);
case LONG:
long diffl = getLong(a, column) - getLong(b, column);
return diffl == 0 ? 0 : (diffl < 0 ? -1 : 1);
case FLOAT:
float difff = getFloat(a, column) - getFloat(b, column);
return difff == 0 ? 0 : (difff < 0 ? -1 : 1);
case DOUBLE:
double diffd = getDouble(a, column) - getDouble(b, column);
return diffd == 0 ? 0 : (diffd < 0 ? -1 : 1);
case STRING:
String string1 = getString(a, column);
if (string1 == null) {
string1 = ""; // avoid NPE when cells are left empty
}
String string2 = getString(b, column);
if (string2 == null) {
string2 = "";
}
return string1.compareToIgnoreCase(string2);
case CATEGORY:
return getInt(a, column) - getInt(b, column);
default:
throw new IllegalArgumentException("Invalid column type: " + getColumnType(column));
}
}
@Override
public void swap(int a, int b) {
int temp = order[a];
order[a] = order[b];
order[b] = temp;
}
};
s.run();
//Object[] newColumns = new Object[getColumnCount()];
for (int col = 0; col < getColumnCount(); col++) {
switch (getColumnType(col)) {
case INT:
case CATEGORY:
int[] oldInt = (int[]) columns[col];
int[] newInt = new int[rowCount];
for (int row = 0; row < getRowCount(); row++) {
newInt[row] = oldInt[order[row]];
}
columns[col] = newInt;
break;
case LONG:
long[] oldLong = (long[]) columns[col];
long[] newLong = new long[rowCount];
for (int row = 0; row < getRowCount(); row++) {
newLong[row] = oldLong[order[row]];
}
columns[col] = newLong;
break;
case FLOAT:
float[] oldFloat = (float[]) columns[col];
float[] newFloat = new float[rowCount];
for (int row = 0; row < getRowCount(); row++) {
newFloat[row] = oldFloat[order[row]];
}
columns[col] = newFloat;
break;
case DOUBLE:
double[] oldDouble = (double[]) columns[col];
double[] newDouble = new double[rowCount];
for (int row = 0; row < getRowCount(); row++) {
newDouble[row] = oldDouble[order[row]];
}
columns[col] = newDouble;
break;
case STRING:
String[] oldString = (String[]) columns[col];
String[] newString = new String[rowCount];
for (int row = 0; row < getRowCount(); row++) {
newString[row] = oldString[order[row]];
}
columns[col] = newString;
break;
}
}
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
public String[] getUnique(String columnName) {
return getUnique(getColumnIndex(columnName));
}
public String[] getUnique(int column) {
StringList list = new StringList(getStringColumn(column));
return list.getUnique();
}
public IntDict getTally(String columnName) {
return getTally(getColumnIndex(columnName));
}
public IntDict getTally(int column) {
StringList list = new StringList(getStringColumn(column));
return list.getTally();
}
public IntDict getOrder(String columnName) {
return getOrder(getColumnIndex(columnName));
}
public IntDict getOrder(int column) {
StringList list = new StringList(getStringColumn(column));
return list.getOrder();
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
public IntList getIntList(String columnName) {
return new IntList(getIntColumn(columnName));
}
public IntList getIntList(int column) {
return new IntList(getIntColumn(column));
}
public FloatList getFloatList(String columnName) {
return new FloatList(getFloatColumn(columnName));
}
public FloatList getFloatList(int column) {
return new FloatList(getFloatColumn(column));
}
public StringList getStringList(String columnName) {
return new StringList(getStringColumn(columnName));
}
public StringList getStringList(int column) {
return new StringList(getStringColumn(column));
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
public IntDict getIntDict(String keyColumnName, String valueColumnName) {
return new IntDict(getStringColumn(keyColumnName),
getIntColumn(valueColumnName));
}
public IntDict getIntDict(int keyColumn, int valueColumn) {
return new IntDict(getStringColumn(keyColumn),
getIntColumn(valueColumn));
}
public FloatDict getFloatDict(String keyColumnName, String valueColumnName) {
return new FloatDict(getStringColumn(keyColumnName),
getFloatColumn(valueColumnName));
}
public FloatDict getFloatDict(int keyColumn, int valueColumn) {
return new FloatDict(getStringColumn(keyColumn),
getFloatColumn(valueColumn));
}
public StringDict getStringDict(String keyColumnName, String valueColumnName) {
return new StringDict(getStringColumn(keyColumnName),
getStringColumn(valueColumnName));
}
public StringDict getStringDict(int keyColumn, int valueColumn) {
return new StringDict(getStringColumn(keyColumn),
getStringColumn(valueColumn));
}
public Map getRowMap(String columnName) {
int col = getColumnIndex(columnName);
return (col == -1) ? null : getRowMap(col);
}
/**
* Return a mapping that connects the entry from a column back to the row
* from which it came. For instance:
*
* Table t = loadTable("country-data.tsv", "header");
* // use the contents of the 'country' column to index the table
* Map lookup = t.getRowMap("country");
* // get the row that has "us" in the "country" column:
* TableRow usRow = lookup.get("us");
* // get an entry from the 'population' column
* int population = usRow.getInt("population");
*
*/
public Map getRowMap(int column) {
Map outgoing = new HashMap<>();
for (int row = 0; row < getRowCount(); row++) {
String id = getString(row, column);
outgoing.put(id, new RowPointer(this, row));
}
// for (TableRow row : rows()) {
// String id = row.getString(column);
// outgoing.put(id, row);
// }
return outgoing;
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
// /**
// * Return an object that maps the String values in one column back to the
// * row from which they came. For instance, if the "name" of each row is
// * found in the first column, getColumnRowLookup(0) would return an object
// * that would map each name back to its row.
// */
// protected HashMap getRowLookup(int col) {
// HashMap outgoing = new HashMap();
// for (int row = 0; row < getRowCount(); row++) {
// outgoing.put(getString(row, col), row);
// }
// return outgoing;
// }
// incomplete, basically this is silly to write all this repetitive code when
// it can be implemented in ~3 lines of code...
// /**
// * Return an object that maps the data from one column to the data of found
// * in another column.
// */
// public HashMap,?> getLookup(int col1, int col2) {
// HashMap outgoing = null;
//
// switch (columnTypes[col1]) {
// case INT: {
// if (columnTypes[col2] == INT) {
// outgoing = new HashMap();
// for (int row = 0; row < getRowCount(); row++) {
// outgoing.put(getInt(row, col1), getInt(row, col2));
// }
// } else if (columnTypes[col2] == LONG) {
// outgoing = new HashMap();
// for (int row = 0; row < getRowCount(); row++) {
// outgoing.put(getInt(row, col1), getLong(row, col2));
// }
// } else if (columnTypes[col2] == FLOAT) {
// outgoing = new HashMap();
// for (int row = 0; row < getRowCount(); row++) {
// outgoing.put(getInt(row, col1), getFloat(row, col2));
// }
// } else if (columnTypes[col2] == DOUBLE) {
// outgoing = new HashMap();
// for (int row = 0; row < getRowCount(); row++) {
// outgoing.put(getInt(row, col1), getDouble(row, col2));
// }
// } else if (columnTypes[col2] == STRING) {
// outgoing = new HashMap();
// for (int row = 0; row < getRowCount(); row++) {
// outgoing.put(getInt(row, col1), get(row, col2));
// }
// }
// break;
// }
// }
// return outgoing;
// }
// public StringIntPairs getColumnRowLookup(int col) {
// StringIntPairs sc = new StringIntPairs();
// String[] column = getStringColumn(col);
// for (int i = 0; i < column.length; i++) {
// sc.set(column[i], i);
// }
// return sc;
// }
// public String[] getUniqueEntries(int column) {
//// HashMap indices = new HashMap();
//// for (int row = 0; row < rowCount; row++) {
//// indices.put(data[row][column], this); // 'this' is a dummy
//// }
// StringIntPairs sc = getStringCount(column);
// return sc.keys();
// }
//
//
// public StringIntPairs getStringCount(String columnName) {
// return getStringCount(getColumnIndex(columnName));
// }
//
//
// public StringIntPairs getStringCount(int column) {
// StringIntPairs outgoing = new StringIntPairs();
// for (int row = 0; row < rowCount; row++) {
// String entry = data[row][column];
// if (entry != null) {
// outgoing.increment(entry);
// }
// }
// return outgoing;
// }
//
//
// /**
// * Return an object that maps the String values in one column back to the
// * row from which they came. For instance, if the "name" of each row is
// * found in the first column, getColumnRowLookup(0) would return an object
// * that would map each name back to its row.
// */
// public StringIntPairs getColumnRowLookup(int col) {
// StringIntPairs sc = new StringIntPairs();
// String[] column = getStringColumn(col);
// for (int i = 0; i < column.length; i++) {
// sc.set(column[i], i);
// }
// return sc;
// }
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
// TODO naming/whether to include
protected Table createSubset(int[] rowSubset) {
Table newbie = new Table();
newbie.setColumnTitles(columnTitles); // also sets columns.length
newbie.columnTypes = columnTypes;
newbie.setRowCount(rowSubset.length);
for (int i = 0; i < rowSubset.length; i++) {
int row = rowSubset[i];
for (int col = 0; col < columns.length; col++) {
switch (columnTypes[col]) {
case STRING: newbie.setString(i, col, getString(row, col)); break;
case INT: newbie.setInt(i, col, getInt(row, col)); break;
case LONG: newbie.setLong(i, col, getLong(row, col)); break;
case FLOAT: newbie.setFloat(i, col, getFloat(row, col)); break;
case DOUBLE: newbie.setDouble(i, col, getDouble(row, col)); break;
}
}
}
return newbie;
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
* Searches the entire table for float values.
* Returns missing float (Float.NaN by default) if no valid numbers found.
*/
protected float getMaxFloat() {
boolean found = false;
float max = PConstants.MIN_FLOAT;
for (int row = 0; row < getRowCount(); row++) {
for (int col = 0; col < getColumnCount(); col++) {
float value = getFloat(row, col);
if (!Float.isNaN(value)) { // TODO no, this should be comparing to the missing value
if (!found) {
max = value;
found = true;
} else if (value > max) {
max = value;
}
}
}
}
return found ? max : missingFloat;
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
// converts a TSV or CSV file to binary.. do not use
protected void convertBasic(BufferedReader reader, boolean tsv,
File outputFile) throws IOException {
FileOutputStream fos = new FileOutputStream(outputFile);
BufferedOutputStream bos = new BufferedOutputStream(fos, 16384);
DataOutputStream output = new DataOutputStream(bos);
output.writeInt(0); // come back for row count
output.writeInt(getColumnCount());
if (columnTitles != null) {
output.writeBoolean(true);
for (String title : columnTitles) {
output.writeUTF(title);
}
} else {
output.writeBoolean(false);
}
for (int type : columnTypes) {
output.writeInt(type);
}
String line = null;
//setRowCount(1);
int prev = -1;
int row = 0;
while ((line = reader.readLine()) != null) {
convertRow(output, tsv ? PApplet.split(line, '\t') : splitLineCSV(line, reader));
row++;
if (row % 10000 == 0) {
if (row < rowCount) {
int pct = (100 * row) / rowCount;
if (pct != prev) {
System.out.println(pct + "%");
prev = pct;
}
}
// try {
// Thread.sleep(5);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
}
// shorten or lengthen based on what's left
// if (row != getRowCount()) {
// setRowCount(row);
// }
// has to come afterwards, since these tables get built out during the conversion
int col = 0;
for (HashMapBlows hmb : columnCategories) {
if (hmb == null) {
output.writeInt(0);
} else {
hmb.write(output);
hmb.writeln(PApplet.createWriter(new File(columnTitles[col] + ".categories")));
// output.writeInt(hmb.size());
// for (Map.Entry e : hmb.entrySet()) {
// output.writeUTF(e.getKey());
// output.writeInt(e.getValue());
// }
}
col++;
}
output.flush();
output.close();
// come back and write the row count
RandomAccessFile raf = new RandomAccessFile(outputFile, "rw");
raf.writeInt(rowCount);
raf.close();
}
protected void convertRow(DataOutputStream output, String[] pieces) throws IOException {
if (pieces.length > getColumnCount()) {
throw new IllegalArgumentException("Row with too many columns: " +
PApplet.join(pieces, ","));
}
// pieces.length may be less than columns.length, so loop over pieces
for (int col = 0; col < pieces.length; col++) {
switch (columnTypes[col]) {
case STRING:
output.writeUTF(pieces[col]);
break;
case INT:
output.writeInt(PApplet.parseInt(pieces[col], missingInt));
break;
case LONG:
try {
output.writeLong(Long.parseLong(pieces[col]));
} catch (NumberFormatException nfe) {
output.writeLong(missingLong);
}
break;
case FLOAT:
output.writeFloat(PApplet.parseFloat(pieces[col], missingFloat));
break;
case DOUBLE:
try {
output.writeDouble(Double.parseDouble(pieces[col]));
} catch (NumberFormatException nfe) {
output.writeDouble(missingDouble);
}
break;
case CATEGORY:
String peace = pieces[col];
if (peace.equals(missingString)) {
output.writeInt(missingCategory);
} else {
output.writeInt(columnCategories[col].index(peace));
}
break;
}
}
for (int col = pieces.length; col < getColumnCount(); col++) {
switch (columnTypes[col]) {
case STRING:
output.writeUTF("");
break;
case INT:
output.writeInt(missingInt);
break;
case LONG:
output.writeLong(missingLong);
break;
case FLOAT:
output.writeFloat(missingFloat);
break;
case DOUBLE:
output.writeDouble(missingDouble);
break;
case CATEGORY:
output.writeInt(missingCategory);
break;
}
}
}
/*
private void convertRowCol(DataOutputStream output, int row, int col, String piece) {
switch (columnTypes[col]) {
case STRING:
String[] stringData = (String[]) columns[col];
stringData[row] = piece;
break;
case INT:
int[] intData = (int[]) columns[col];
intData[row] = PApplet.parseInt(piece, missingInt);
break;
case LONG:
long[] longData = (long[]) columns[col];
try {
longData[row] = Long.parseLong(piece);
} catch (NumberFormatException nfe) {
longData[row] = missingLong;
}
break;
case FLOAT:
float[] floatData = (float[]) columns[col];
floatData[row] = PApplet.parseFloat(piece, missingFloat);
break;
case DOUBLE:
double[] doubleData = (double[]) columns[col];
try {
doubleData[row] = Double.parseDouble(piece);
} catch (NumberFormatException nfe) {
doubleData[row] = missingDouble;
}
break;
default:
throw new IllegalArgumentException("That's not a valid column type.");
}
}
*/
/** Make a copy of the current table */
public Table copy() {
return new Table(rows());
}
public void write(PrintWriter writer) {
writeTSV(writer);
}
public void print() {
writeTSV(new PrintWriter(System.out));
}
}