/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.dataprocessing;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.servoy.j2db.query.ColumnType;
import com.servoy.j2db.util.SafeArrayList;
import com.servoy.j2db.util.Utils;
/**
* Default implementation of the {@link IDataSet} interface.
*
* ContentList like result, can be serialized between server and client
*
* @author jblok
*/
public class BufferedDataSet implements ISerializableDataSet
{
public static final long serialVersionUID = -6878367385657220897L;
private List<Object[]> rows; //which contains RowData with column data
private boolean hadMore;
private String[] columnNames;
private ColumnType[] columnTypes;
public BufferedDataSet()
{
rows = new SafeArrayList<Object[]>(0);
}
/**
* Create a non db bind dataset
*
* @param columnNames the array with all the column names
* @param rows a list with Object arrays containing column data in same length/order as columnames array
*/
public BufferedDataSet(String[] columnNames, List<Object[]> rows)
{
this(columnNames, null, rows);
}
public BufferedDataSet(String[] columnNames, int[] columnTypes)
{
this(columnNames, columnTypes, new SafeArrayList<Object[]>(0));
}
public BufferedDataSet(String[] columnNames, int[] columnTypes, List<Object[]> rows)
{
this.columnNames = columnNames;
this.rows = rows;
hadMore = false;
setColumnTypes(columnTypes);
}
/* package scope so it does not end up in javadoc */
BufferedDataSet(String[] columnNames, ColumnType[] columnTypes, List<Object[]> rows, boolean hadMore)
{
this.columnNames = columnNames;
this.columnTypes = columnTypes;
this.rows = rows;
this.hadMore = hadMore;
}
protected BufferedDataSet(IDataSet set)//make copy
{
this(set, null);
}
protected BufferedDataSet(IDataSet set, int[] columns)//make copy, with only indicated columns
{
if (set != null)
{
int rowCount = set.getRowCount();
rows = new SafeArrayList<Object[]>(rowCount + 5);
for (int r = 0; r < rowCount; r++)
{
Object[] array = set.getRow(r);
Object[] data = null;
if (array != null)
{
if (columns == null)
{
data = new Object[array.length];
for (int c = 0; c < array.length; c++)
{
data[c] = array[c];
}
}
else if (columns.length > 0)
{
data = new Object[columns.length];
for (int c = 0; c < columns.length; c++)
{
data[c] = array[columns[c]];
}
}
}
if (data != null) rows.add(data);
}
hadMore = set.hadMoreRows();
}
else
{
rows = new SafeArrayList<Object[]>(0);
hadMore = false;
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#clone()
*/
@Override
public IDataSet clone()
{
BufferedDataSet set = new BufferedDataSet();
set.columnNames = columnNames != null ? columnNames.clone() : null;
set.columnTypes = columnTypes != null ? columnTypes.clone() : null;
set.rows = rows != null ? new SafeArrayList<Object[]>(new ArrayList<Object[]>(rows)) : null;
set.hadMore = hadMore;
return set;
}
/**
* Get the number of rows in this dataset
*/
public int getRowCount()
{
if (rows == null)
{
return 0;
}
else
{
return rows.size();
}
}
/**
* Get a specified row
*
* @param row the row to get
* @return the row data
*/
public Object[] getRow(int row)
{
return rows.get(row);
}
/*
* private int numberOfColumns = 0; public int getColumnCount() { return numberOfColumns; }
*/
public void removeRow(int index)
{
if (index == -1)
{
rows.clear();
}
else
{
rows.remove(index);
}
}
public void setRow(int index, Object[] array)
{
rows.set(index, array);
}
public void addRow(Object[] array)
{
rows.add(array);
}
public void addRow(int index, Object[] array)
{
rows.add(index, array);
}
public int getColumnCount()
{
if (columnNames != null)
{
return columnNames.length;
}
//fallback to first row
if (getRowCount() > 0)
{
return getRow(0).length;
}
return 0;
}
public boolean isColumnUnique(int column)
{
Set<Object> set = new HashSet<Object>();
for (int i = 0; i < getRowCount(); i++)
{
Object obj = getRow(i)[column];
if (obj != null && !set.add(obj))
{
return false;
}
}
return true;
}
public boolean hadMoreRows()
{
return hadMore;
}
public void clearHadMoreRows()
{
hadMore = false;
}
public String[] getColumnNames()
{
if (columnNames == null)
{
int count = getColumnCount();
columnNames = new String[count];
for (int i = 0; i < columnNames.length; i++)
{
columnNames[i] = "column" + i; //$NON-NLS-1$
}
}
return columnNames;
}
/*
* Setter for json deserialisation
*/
public void setColumnNames(String[] columnNames)
{
this.columnNames = columnNames;
}
public int[] getColumnTypes()
{
if (columnTypes == null)
{
return null;
}
int[] tps = new int[columnTypes.length];
for (int i = 0; i < columnTypes.length; i++)
{
tps[i] = columnTypes[i].getSqlType();
}
return tps;
}
/**
* @return the column types
*/
/* package scope so it does not end up in javadoc */
ColumnType[] getColumnTypeInfo()
{
return columnTypes == null ? null : columnTypes.clone();
}
/*
* Setter for json deserialisation
*/
public void setColumnTypes(int[] columnTypes)
{
if (columnTypes == null)
{
this.columnTypes = null;
}
else
{
this.columnTypes = new ColumnType[columnTypes.length];
for (int i = 0; i < columnTypes.length; i++)
{
this.columnTypes[i] = ColumnType.getInstance(columnTypes[i], Integer.MAX_VALUE, 0);
}
}
}
/*
* Getter for json serialisation
*/
public List<Object[]> getRows()
{
return new ArrayList<Object[]>(rows);
}
/*
* Setter for json deserialisation
*/
public void setRows(List<Object[]> rows)
{
this.rows = new SafeArrayList<Object[]>(rows);
}
public void sort(int column, boolean ascending)
{
sort(new ArrayComparator(column, ascending));
}
public void sort(Comparator<Object[]> rowComparator)
{
Object[][] array = this.rows.toArray(new Object[this.rows.size()][]);//strange construct due to type interference
Arrays.sort(array, rowComparator);
this.rows = new SafeArrayList<Object[]>(Arrays.asList(array));
}
public boolean addColumn(int columnIndex, String columnName, int columnType)
{
int size = getColumnCount();
int index = (columnIndex == -1) ? size : columnIndex;
if (index < 0 || index > size || Utils.stringIsEmpty(columnName))
{
return false;
}
String[] newColumns = Utils.arrayInsert(getColumnNames(), new String[] { columnName }, index, 1);
columnNames = newColumns;
if (size == 0 || columnTypes != null)
{
ColumnType[] newColumnTypes = Utils.arrayInsert(columnTypes, new ColumnType[] { ColumnType.getInstance(columnType, Integer.MAX_VALUE, 0) }, index,
1);
columnTypes = newColumnTypes;
}
updateRowsWhenColumnAdded(index);
return true;
}
public void setColumnName(int columnIndex, String columnName)
{
if (columnIndex >= 0 && columnIndex < getColumnCount() && !Utils.stringIsEmpty(columnName))
{
getColumnNames()[columnIndex] = columnName;
}
}
public boolean removeColumn(int columnIndex)
{
int size = getColumnCount();
if (columnIndex < 0 || columnIndex >= size)
{
return false;
}
String[] oldColumns = getColumnNames();
String[] newColumns = new String[size - 1];
ColumnType[] oldColumnTypes = columnTypes;
ColumnType[] newColumnTypes = oldColumnTypes == null || size == 1 ? null : new ColumnType[size - 1];
for (int i = 0; i < columnIndex; i++)
{
newColumns[i] = oldColumns[i];
if (newColumnTypes != null) newColumnTypes[i] = oldColumnTypes[i];
}
for (int i = columnIndex; i < size - 1; i++)
{
newColumns[i] = oldColumns[i + 1];
if (newColumnTypes != null) newColumnTypes[i] = oldColumnTypes[i + 1];
}
columnNames = newColumns;
columnTypes = newColumnTypes;
updateRowsWhenColumnRemoved(columnIndex);
return true;
}
private void updateRowsWhenColumnAdded(int columnIndex)
{
int size = getColumnCount();
if (columnIndex > size || columnIndex < 0) return;
int x = 0;
for (Object[] row : rows)
{
Object[] currentRow = new Object[size];
for (int i = 0; i < columnIndex; i++)
{
currentRow[i] = row[i];
}
currentRow[columnIndex] = null;
for (int i = columnIndex + 1; i < size; i++)
{
currentRow[i] = row[i - 1];
}
rows.set(x++, currentRow);
}
}
private void updateRowsWhenColumnRemoved(int columnIndex)
{
int size = getColumnCount();
if (columnIndex >= size + 1 || columnIndex < 0) return; //shouldn't happen
int x = 0;
for (Object[] row : rows)
{
Object[] currentRow = new Object[size];
for (int i = 0; i < columnIndex; i++)
{
currentRow[i] = row[i];
}
for (int i = columnIndex + 1; i < size + 1; i++)
{
currentRow[i - 1] = row[i];
}
rows.set(x++, currentRow);
}
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("BufferedDataSet "); //$NON-NLS-1$
if (columnNames != null && columnNames.length > 0)
{
sb.append('{');
sb.append("Columnnames "); //$NON-NLS-1$
sb.append(Arrays.toString(columnNames));
sb.append("} "); //$NON-NLS-1$
}
int rowCount = getRowCount() > 100 ? 100 : getRowCount();
for (int i = 0; i < rowCount; i++)
{
sb.append("\nrow_"); //$NON-NLS-1$
sb.append(i + 1);
sb.append('=');
sb.append(Arrays.toString(getRow(i)));
sb.append(' ');
}
return sb.toString();
}
private static class ArrayComparator implements Comparator<Object[]>
{
private final int column;
private final boolean acending;
ArrayComparator(int col, boolean acending)
{
this.column = col;
this.acending = acending;
}
public int compare(Object[] o1, Object[] o2)
{
int retval = compareAsc(o1, o2);
if (!acending)
{
retval = retval * -1;
}
return retval;
}
public int compareAsc(Object[] o1, Object[] o2)
{
Object value1 = o1[column];
Object value2 = o2[column];
if (value1 == null && value2 == null) return 0;
if (value1 == null) return 1;
if (value2 == null) return -1;
if (value1 instanceof String)
{
return ((String)value1).compareToIgnoreCase((String)value2);
}
else if ((value1 instanceof Number) && (value2 instanceof Number))
{
Double d1 = new Double(((Number)value1).doubleValue());
Double d2 = new Double(((Number)value2).doubleValue());
return d1.compareTo(d2);
}
else if (value1 instanceof Comparable)
{
return ((Comparable<Object>)value1).compareTo(value2);
}
return 0;
}
}
}