/*
* Copyright (C) 2014 Alec Dhuse
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package co.foldingmap.data;
import co.foldingmap.Logger;
import java.util.ArrayList;
/**
*
* @author Alec
*/
public class TabularData {
private ArrayList<ArrayList<DataCell>> columns;
private ArrayList<String> headerCells, headerNames;
public TabularData() {
columns = new ArrayList<ArrayList<DataCell>>();
headerCells = new ArrayList<String>();
headerNames = new ArrayList<String>();
}
public TabularData(ArrayList<ArrayList<DataCell>> columns, ArrayList<String> headerNames, ArrayList<String> headerCells) {
this.columns = columns;
this.headerCells = headerCells;
this.headerNames = headerNames;
}
/**
* Add a new cell to the end of a column.
*
* @param row
* @param cell
*/
public void addCell(int row, DataCell cell) {
ArrayList<DataCell> col;
try {
col = columns.get(row);
col.add(cell);
} catch (Exception e) {
Logger.log(Logger.ERR, "Error in TabularData.addCell - " + e);
}
}
/**
* Adds a column with a given name at a given index.
*
* @param index
* @param headerName
*/
public void addColumn(int index, String headerName) {
columns.add(index, new ArrayList<DataCell>());
if (!headerNames.contains(headerName))
headerNames.add(index, headerName);
}
/**
* Adds a new header name, if the name dose not already exist as a header.
* If the name already exists, it will not be added.
*
* @param headerName
*/
public void addHeader(String headerName) {
if (!headerNames.contains(headerName)) {
headerNames.add(headerName);
}
}
/**
* Returns if a given ArrayList of strings are all numbers.
*
* @param list
* @return
*/
public static boolean areOnlyNumbers(ArrayList<DataCell> list) {
boolean onlyNumbers;
float number;
onlyNumbers = false;
for (DataCell currentElement: list) {
try {
if (!currentElement.toString().equals("")) {
number = currentElement.getFloatValue();
if (number != Float.NaN)
onlyNumbers = true;
} else {
//skip blank cells
}
} catch (Exception e) {
return false;
}
}
return onlyNumbers;
}
/**
* Checks to see if a given column contains any coordinate information.
*
* @param column
* @return
*/
public boolean columnContainsCoordinates(int column) {
ArrayList<DataCell> cells;
boolean hasCS;
double trueFalseRatio;
int falseCount, trueCount;
cells = columns.get(column);
falseCount = 1;
hasCS = false;
trueCount = 0;
for (DataCell cell: cells) {
if (cell.trim().indexOf(" ") > 0) {
//check to see if cell contains a coordinate string
if (cell.getCoordinate() != null) {
trueCount++;
} else {
falseCount++;
}
}
}
trueFalseRatio = (trueCount / falseCount);
if (trueFalseRatio > 0.75) {
hasCS = true;
} else {
hasCS = false;
}
return hasCS;
}
/**
* Checks to see if a given column contains a Coordinate String.
*
* @param column
* @return
*/
public boolean containsCoordinateString(int column) {
ArrayList<DataCell> cells;
boolean hasCS;
double trueFalseRatio;
int falseCount, trueCount;
cells = columns.get(column);
falseCount = 1;
hasCS = false;
trueCount = 0;
for (DataCell cell: cells) {
if (cell.trim().indexOf(" ") > 0) {
//check to see if cell contains a coordinate string
if (cell.getCoordinate() != null) {
trueCount++;
} else {
falseCount++;
}
}
}
trueFalseRatio = (trueCount / falseCount);
if (trueFalseRatio > 0.75) {
hasCS = true;
} else {
hasCS = false;
}
return hasCS;
}
protected void findHeaders(ArrayList<ArrayList<DataCell>> rows) {
ArrayList<DataCell> firstRow, SecondRow;
int cellNumber, filledCellCount;
String cellText;
try {
cellNumber = 0;
filledCellCount = 0;
firstRow = rows.get(0);
SecondRow = rows.get(1);
/*
* Check to see if only one cell in the first row has text.
* This should indicate that the first row is only a label.
*/
for (DataCell cell: firstRow) {
if (!cell.equals(""))
filledCellCount++;
}
if (filledCellCount > 1) {
for (DataCell cell: firstRow) {
if (!cell.equals("")) {
if (!headerNames.contains(cell.toString())) {
headerNames.add(cell.toString());
columns.add(new ArrayList<DataCell>());
}
} else {
/* The cell is blank, check the next row.
* But only if the first cell is blank,
* otherwise assume it is data and not header.
*/
cellText = SecondRow.get(0).toString();
if (cellText.equals("")) {
//first cell of the second row is empty, assume more header info
if (cellNumber < SecondRow.size()) {
cellText = SecondRow.get(cellNumber).toString();
} else {
cellText = "";
}
if (!headerNames.contains(cell.toString())) {
headerNames.add(cellText);
columns.add(new ArrayList<DataCell>());
}
} else {
if (!headerNames.contains(cell.toString())) {
headerNames.add(" ");
columns.add(new ArrayList<DataCell>());
}
}
}
cellNumber++;
}
}
} catch (Exception e) {
Logger.log(Logger.ERR, "Error in TabularDataFile.findHeaders(ArrayList<ArrayList>) - " + e);
}
}
/**
* This function attempts to locate any coordinate information
*
* @return
*/
public ArrayList<Boolean> findLocationData() {
ArrayList<Boolean> columnContainsCoordinate;
boolean containsCoordinate;
double falseCount, trueCount, trueFalseRatio;
String headerName;
columnContainsCoordinate = new ArrayList<Boolean>();
for (int i = 0; i < columns.size(); i++) {
ArrayList<DataCell> c = columns.get(i);
containsCoordinate = false;
falseCount = 1;
trueCount = 0;
if (i < headerNames.size()) {
headerName = headerNames.get(i);
} else {
headerName = "unknown";
}
if (headerName.equalsIgnoreCase("Altitude") || headerName.equalsIgnoreCase("Alt")) {
for (DataCell cell: c) {
if (cell.getFloatValue() != Float.NaN) {
trueCount++;
} else {
falseCount++;
}
}
trueFalseRatio = (trueCount / falseCount);
if (trueFalseRatio > 0.75) {
containsCoordinate = true;
} else {
containsCoordinate = false;
}
} else {
for (DataCell cell: c) {
/** check to see if cell has spaces, if it does perhaps
* it is a coordinate String
*/
if (cell.trim().indexOf(" ") > 0) {
//check to see if cell contains a coordinate string
if (cell.getCoordinate() != null) {
trueCount++;
} else {
falseCount++;
}
} else {
//does not contain spaces, check to see if it is a coordinate
if (cell.getCoordinate() != null) {
trueCount++;
} else {
if (cell.containsCoordinateInformation()) {
trueCount++;
} else {
falseCount++;
}
}
}
}
trueFalseRatio = (trueCount / falseCount);
if (trueFalseRatio > 0.75) {
containsCoordinate = true;
} else {
containsCoordinate = false;
}
} //end altitude check
columnContainsCoordinate.add(containsCoordinate);
}
return columnContainsCoordinate;
}
/**
* Returns a the column at the given index.
*
* @param index
* @return
*/
public ArrayList<DataCell> getColumn(int index) {
return columns.get(index);
}
/**
* Returns the Header Name at a given index.
*
* @param index
* @return
*/
public String getHeaderName(int index) {
return headerNames.get(index);
}
/**
* Returns the Header names of the data.
*
* @return
*/
public ArrayList<String> getHeaderNames() {
return headerNames;
}
/**
* Returns the row at the given index.
*
* @param index
* @return
*/
public ArrayList<DataCell> getRow(int index) {
ArrayList<DataCell> row;
row = new ArrayList<DataCell>();
for (ArrayList<DataCell> c: columns) {
if (index < c.size()) {
row.add(c.get(index));
} else {
row.add(new DataCell(""));
}
}
return row;
}
/**
* Returns a count of the number of times a given string appears in a
* column of this data table.
*
* @param instance
* @param column
* @return
*/
public int getNumberOfInstances(String instance, int column) {
ArrayList<DataCell> currentColumn;
int count = 0;
currentColumn = columns.get(column);
for (DataCell cell: currentColumn) {
if (cell.toString().equalsIgnoreCase(instance))
count++;
}
return count;
}
/**
* Returns the column count.
*
* @return
*/
public int getNumberOfColumns() {
return this.columns.size();
}
/**
* Returns the number of headers in this TabularData.
*
* @return
*/
public int getNumberOfHeaders() {
return this.headerNames.size();
}
/**
* Returns the row count.
* Columns may contain different number of rows, this method will return
* the the row count for the column with the most rows.
*
* @return
*/
public int getNumberOfRows() {
int numberOfRows = 0;
for (ArrayList<DataCell> c: columns) {
if (numberOfRows < c.size())
numberOfRows = c.size();
}
return numberOfRows;
}
/**
* Returns a TabularData class that is a subset where a column
* contains a given value.
*
* @param compareColumnHeader
* @param columnValue
* @return
*/
public TabularData getSubSet(String compareColumnHeader, String columnValue) {
ArrayList<ArrayList<DataCell>> newColumns;
ArrayList<DataCell> currentRow;
int compareColumn;
String cellString;
newColumns = new ArrayList<ArrayList<DataCell>>();
compareColumn = -1;
for (int i = 0; i < this.headerNames.size(); i++) {
newColumns.add(new ArrayList<DataCell>());
}
//find the column the that matches the compair column
for (int i = 0; i < this.headerNames.size(); i++) {
String currentHeader = headerNames.get(i);
if (currentHeader.equalsIgnoreCase(compareColumnHeader)) {
compareColumn = i;
break;
}
}
if (compareColumn >= 0) {
for (int i = 0; i < getNumberOfRows(); i++) {
currentRow = getRow(i);
cellString = currentRow.get(compareColumn).toString().trim();
cellString = cellString.replaceAll("[\\u00A0]", "");
if (cellString.equalsIgnoreCase(columnValue)) {
/**
* If the column we are matching, in fact matches the value
* we are looking for then add this row to the subset.
*/
for (int j = 0; j < this.headerNames.size(); j++) {
newColumns.get(j).add(currentRow.get(j));
}
}
}
}
return new TabularData(newColumns, this.headerNames, this.headerCells);
}
/**
* Returns one instance of each data value in this column.
*
* @return
*/
public ArrayList<DataCell> getUniqueColumnItems(int columnIndex) {
ArrayList<DataCell> searchColumn, uniqueColumnItems;
uniqueColumnItems = new ArrayList<DataCell>();
searchColumn = columns.get(columnIndex);
for (DataCell currentCell: searchColumn) {
if (!uniqueColumnItems.contains(currentCell))
uniqueColumnItems.add(currentCell);
}
return uniqueColumnItems;
}
/**
* Returns one instance of each data value in this column.
*
* @param headerName
* @return
*/
public ArrayList<DataCell> getUniqueColumnItems(String headerName) {
int columnIndex = -1;
for (int i = 0; i < this.headerNames.size(); i++) {
String currentHeader = headerNames.get(i);
if (currentHeader.equalsIgnoreCase(headerName)) {
columnIndex = i;
break;
}
}
if (columnIndex >= 0) {
return getUniqueColumnItems(columnIndex);
} else {
return new ArrayList<DataCell>();
}
}
/**
* Loads this file with DataCells.
*
* @param rows
*/
public void loadData(ArrayList<ArrayList<DataCell>> rows) {
ArrayList<DataCell> currentColumn, currentRow;
int rowDataStartIndex;
DataCell currentCell;
findHeaders(rows);
currentRow = rows.get(1);
//find where the headers end, and the data starts.
if (currentRow.get(0).equals("")) {
rowDataStartIndex = 2;
} else {
rowDataStartIndex = 1;
}
for (int currentRowIndex = rowDataStartIndex; currentRowIndex < rows.size(); currentRowIndex++) {
currentRow = rows.get(currentRowIndex);
for (int currentCellIndex = 0; currentCellIndex < currentRow.size(); currentCellIndex++) {
currentCell = currentRow.get(currentCellIndex);
if (currentCellIndex < columns.size()) {
currentColumn = columns.get(currentCellIndex);
currentColumn.add(currentCell);
} else {
currentColumn = new ArrayList<DataCell>();
currentColumn.add(currentCell);
columns.add(currentColumn);
}
}
}
}
/**
* Rename a specific header in the data.
*
* @param index
* @param newName
*/
public void renameHeader(int index, String newName) {
this.headerNames.remove(index);
this.headerNames.add(index, newName);
}
}