/*
* Copyright 2016 (C) Tom Parker <thpr@users.sourceforge.net>
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library 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 Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package pcgen.cdom.format.table;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import pcgen.base.util.FormatManager;
import pcgen.cdom.base.Loadable;
/**
* A DataTable represents tabular data (stored in columns and rows). Each column
* has a name and a specific Format for the data.
*
* This class makes best-effort based on an assumed good behavior of other
* classes. This assumes that all columns are added before any rows. Also, if
* the format of a column is altered after rows are added, then the behavior of
* DataTable is not defined.
*/
public class DataTable implements Loadable
{
/**
* The source URI for this DataTable
*/
private URI sourceURI;
/**
* The name of this DataTable.
*/
private String name;
/**
* The columns definitions for this DataTable.
*/
private ArrayList<TableColumn> columns = new ArrayList<>();
/**
* The contents of the DataTable.
*/
private ArrayList<Object[]> dataByRow = new ArrayList<>();
/**
* Appends a new TableColumn to the DataTable.
*
* This may only be done BEFORE addRow is called. Once addRow is called,
* this method will return an IllegalStateException.
*
* The provided TableColumn is value-semantic. It is captured and stored by
* this DataTable and changes (such as changing the TableColumn name) will
* alter the contents of this DataTable.
*
* @param column
* The new TableColumn to be added to the DataTable
*/
public void addColumn(TableColumn column)
{
if (!dataByRow.isEmpty())
{
throw new IllegalStateException(
"Column may not be added after rows are added");
}
if (columns.contains(Objects.requireNonNull(column)))
{
throw new IllegalArgumentException(
"Column may not be duplicate: " + column);
}
columns.add(column);
}
/**
* Appends a new row of data to this DataTable.
*
* Note that the provided row must conform to the format of data as defined
* by the columns in this DataTable.
*
* The provided List is reference-semantic. The contents of the List are
* stored, but the list itself is not stored. Thus any modification of the
* list after this method completes will not alter the contents of the
* DataTable.
*
* @param rowData
* The List of objects in a row to be added to this DataTable.
*/
public void addRow(List<Object> rowData)
{
Object[] data = rowData.toArray();
for (int i = 0; i < columns.size(); i++)
{
TableColumn column = columns.get(i);
Object object = data[i];
if (!column.getFormatManager().getManagedClass()
.isAssignableFrom(object.getClass()))
{
throw new IllegalArgumentException("Item " + i
+ " in provided row was incorrect format, found: "
+ object.getClass() + " but requried "
+ column.getFormatManager().getManagedClass());
}
}
dataByRow.add(data);
}
/**
* Sets the name for this DataTable.
*
* @param name
* The name to be given to this DataTable
*/
@Override
public void setName(String name)
{
this.name = name;
}
/**
* Returns the name of this DataTable.
*
* @return The name of this DataTable
*/
public String getName()
{
return name;
}
/**
* Returns the count of the number of rows in this DataTable.
*
* @return The count of the number of rows in this DataTable
*/
public int getRowCount()
{
return dataByRow.size();
}
/**
* Returns the count of the number of columns in this DataTable.
*
* @return The count of the number of columns in this DataTable
*/
public int getColumnCount()
{
return columns.size();
}
/**
* Returns true if the given String is the name of a column in this
* DataTable.
*
* @param columnName
* The name to be checked to see if it is a column in this
* DataTable.
* @return true if the given String is the name of a column in this
* DataTable
*/
public boolean isColumn(String columnName)
{
return getColumnIndex(columnName) != -1;
}
/**
* Returns true if the given object is the lookup value (value in the first
* column) of a row in this DataTable.
*
* @param lookupValue
* The value to be checked to see if it is a lookup value in a
* row of this DataTable.
* @return true if the given object is the lookup value (value in the first
* column) of a row in this DataTable
*/
public boolean hasRow(Object lookupValue)
{
return getRow(lookupValue) != null;
}
/**
* Returns the FormatManager for the given column (zero indexed).
*
* @param i
* The column number for which the FormatManager should be
* returned
* @return The format for the given column (zero indexed)
*/
public FormatManager<?> getFormat(int i)
{
return columns.get(i).getFormatManager();
}
/**
* Returns the FormatManager for the column with the given name.
*
* @param columnName
* The name of the column for which the FormatManager should be
* returned
* @return The FormatManager for the column with the given name
*/
public FormatManager<?> getFormat(String columnName)
{
for (TableColumn column : columns)
{
if (column.getName().equals(columnName))
{
return column.getFormatManager();
}
}
throw new IllegalArgumentException(
"Column Name must exist in the DataTable");
}
/**
* Returns the value in the column of the given name and in the given row
* (zero indexed).
*
* @param string
* The name of the column for which the value should be returned
* @param i
* The index of the row for which the value should be returned
* (zero indexed)
* @return The value in the column of the given name and in the given row
* (zero indexed).
*/
public Object get(String string, int i)
{
int columnNumber = getColumnIndex(string);
if (columnNumber == -1)
{
throw new IllegalArgumentException(
"Cannot find column named: " + string);
}
return dataByRow.get(i)[columnNumber];
}
private int getColumnIndex(String string)
{
int columnNumber = 0;
for (TableColumn column : columns)
{
if (column.getName().equals(string))
{
return columnNumber;
}
columnNumber++;
}
return -1;
}
/**
* Returns the value in this DataTable for the row with the lookup value
* equal (via .equals()) to the given key and from the column with the given
* name.
*
* Note that the returned value is value-semantic. Therefore any changes
* made to that object will change the object stored in this DataTable.
*
* @param key
* The key used (via .equals()) to determine the row from which
* the value will be retrieved
* @param resultingColumn
* The name of the column from which the value should be
* retrieved
* @return The value in this DataTable for the row with the lookup value
* equal to the given key and from the column with the given name.
*/
public Object lookupExact(Object key, String resultingColumn)
{
int resultingColumnNumber = getColumnIndex(resultingColumn);
if (resultingColumnNumber == -1)
{
throw new IllegalArgumentException(
"Cannot find column named: " + resultingColumn);
}
Object[] row = getRow(key);
if (row == null)
{
throw new IllegalArgumentException(
"Cannot find row for: " + key + " in first column");
}
return row[resultingColumnNumber];
}
@SuppressWarnings("PMD.ReturnEmptyArrayRatherThanNull")
private Object[] getRow(Object lookupValue)
{
for (Object[] row : dataByRow)
{
if (lookupValue.equals(row[0]))
{
return row;
}
}
return null;
}
/**
* Trims this DataTable to reduce memory usage. Similar to trim() in
* java.util.ArrayList.
*/
public void trim()
{
columns.trimToSize();
dataByRow.trimToSize();
}
@Override
public String getKeyName()
{
return name;
}
@Override
public String getDisplayName()
{
return name;
}
@Override
public String getLSTformat()
{
return name;
}
@Override
public URI getSourceURI()
{
return sourceURI;
}
@Override
public void setSourceURI(URI source)
{
sourceURI = source;
}
@Override
public boolean isInternal()
{
return false;
}
@Override
public boolean isType(String type)
{
return false;
}
}