/*
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.Comparator;
import com.servoy.j2db.persistence.ITransactable;
import com.servoy.j2db.query.AbstractBaseQuery.PlaceHolderSetter;
import com.servoy.j2db.query.Placeholder;
import com.servoy.j2db.query.TablePlaceholderKey;
import com.servoy.j2db.util.IDelegate;
import com.servoy.j2db.util.SortedList;
import com.servoy.j2db.util.Utils;
/**
* Data set optimized for PKs.
*
* @author rgansevles
*
*/
public class PKDataSet implements IDataSet, IDelegate<IDataSet>
{
public static final Comparator<Object[]> PK_COMPARATOR = new Comparator<Object[]>()
{
public int compare(Object[] o1, Object[] o2)
{
if (o1 == o2) return 0;
if (o2 == null) return 1;
if (o1 == null) return -1;
if (o1.length != o2.length)
{
return o1.length - o2.length;
}
for (int i = 0; i < o1.length; i++)
{
Object el1 = o1[i];
Object el2 = o2[i];
if (el1 != el2)
{
if (el2 == null) return 1;
if (el1 == null) return -1;
int cmp;
if (el1 instanceof Long && el2 instanceof Long)
{
long l1 = ((Long)el1).longValue();
long l2 = ((Long)el2).longValue();
cmp = l1 < l2 ? 1 : l1 > l2 ? -1 : 0;
}
if (el1 instanceof Number && el2 instanceof Number)
{
double d1 = ((Number)el1).doubleValue();
double d2 = ((Number)el2).doubleValue();
cmp = d1 < d2 ? 1 : d1 > d2 ? -1 : 0;
}
else if (el1 instanceof Comparable< ? > && el1.getClass().isAssignableFrom(el2.getClass()))
{
cmp = ((Comparable)el1).compareTo(el2);
}
else if (el2 instanceof Comparable< ? > && el2.getClass().isAssignableFrom(el1.getClass()))
{
cmp = -1 * ((Comparable)el2).compareTo(el1);
}
else
{
cmp = el1.toString().compareTo(el2.toString());
}
if (cmp != 0) return cmp;
}
}
// the same ?
return RowManager.createPKHashKey(o1).compareTo(RowManager.createPKHashKey(o2));
}
};
private final IDataSet pks;
private transient SortedList<Object[]> sortedPKs; // cache of pks for fast lookup, used for matching the the next chunk in FoundSet with the current set.
private transient PksAndRecordsHolder pksAndRecordsHolder;
private transient ITransactable transactionListener;
public PKDataSet(IDataSet pks)
{
if (pks == null)
{
throw new NullPointerException();
}
this.pks = pks;
}
/**
* @param pksAndRecordsHolder the pksAndRecordsHolder to set
*/
public void setPksAndRecordsHolder(PksAndRecordsHolder pksAndRecordsHolder)
{
this.pksAndRecordsHolder = pksAndRecordsHolder;
}
@Override
public PKDataSet clone()
{
return new PKDataSet(pks.clone());
}
public boolean addColumn(int columnIndex, String columnName, int columnType)
{
sortedPKs = null;
return pks.addColumn(columnIndex, columnName, columnType);
}
public void setColumnName(int columnIndex, String columnName)
{
pks.setColumnName(columnIndex, columnName);
}
public void addRow(int index, Object[] pk)
{
pksToBeUpdated();
if (sortedPKs != null)
{
sortedPKs.add(pk);
}
pks.addRow(index, pk);
// pk is added, update the dynamic pk values holder.
if (pksAndRecordsHolder != null && pksAndRecordsHolder.hasDynamicPlaceholder() && pksAndRecordsHolder.getFoundSet() != null &&
!pksAndRecordsHolder.getFoundSet().isInFindMode())
{
DynamicPkValuesArray dynArray = getDynamicPkValuesArray();
if (dynArray != null)
{
dynArray.getPKs().addRow(pk); // index does not matter
}
}
}
public void addRow(Object[] pk)
{
addRow(pks.getRowCount(), pk);
}
public void clearHadMoreRows()
{
sortedPKs = null; // no longer needed
pks.clearHadMoreRows();
}
public int getColumnCount()
{
return pks.getColumnCount();
}
public String[] getColumnNames()
{
return pks.getColumnNames();
}
public int[] getColumnTypes()
{
return pks.getColumnTypes();
}
public Object[] getRow(int row)
{
return pks.getRow(row);
}
public int getRowCount()
{
return pks.getRowCount();
}
public boolean hadMoreRows()
{
return pks.hadMoreRows();
}
public boolean removeColumn(int columnIndex)
{
sortedPKs = null;
return pks.removeColumn(columnIndex);
}
private void pksToBeUpdated()
{
// When a dynamic pk condition is used (placeholder SQLGenerator.PLACEHOLDER_FOUNDSET_PKS), and we are in a transaction, the
// pk set from before the transaction is saved and restored on rollback, so that after rollback, deleted records are still found.
if (transactionListener == null && pksAndRecordsHolder != null && pksAndRecordsHolder.hasDynamicPlaceholder())
{
// check if I am within a transaction
GlobalTransaction globalTransaction = pksAndRecordsHolder.getFoundSet().getFoundSetManager().getGlobalTransaction();
if (globalTransaction != null)
{
// add a listener for rollback to restore pks on rollback so that query returns deleted records
DynamicPkValuesArray dynArray = getDynamicPkValuesArray();
if (dynArray != null)
{
final DynamicPkValuesArray pksBeforeTransaction = dynArray.clone();
globalTransaction.addTransactionEndListener(transactionListener = new ITransactable()
{
public void processPostRollBack()
{
// restore old pks from before transaction, so that pks deleted in the transaction are still found
if (pksAndRecordsHolder.hasDynamicPlaceholder())
{
// query still has the pk set condition, set the condition back to the pk set from before the transaction
pksAndRecordsHolder.getQuerySelectForReading().acceptVisitor(
new PlaceHolderSetter(new TablePlaceholderKey(pksAndRecordsHolder.getQuerySelectForReading().getTable(),
SQLGenerator.PLACEHOLDER_FOUNDSET_PKS), pksBeforeTransaction));
}
transactionListener = null;
}
public void processPostCommit()
{
transactionListener = null;
}
});
}
}
}
}
private DynamicPkValuesArray getDynamicPkValuesArray()
{
if (pksAndRecordsHolder != null && pksAndRecordsHolder.hasDynamicPlaceholder() && pksAndRecordsHolder.getFoundSet() != null &&
!pksAndRecordsHolder.getFoundSet().isInFindMode())
{
Placeholder placeholder = pksAndRecordsHolder.getQuerySelectForReading().getPlaceholder(
new TablePlaceholderKey(pksAndRecordsHolder.getQuerySelectForReading().getTable(), SQLGenerator.PLACEHOLDER_FOUNDSET_PKS));
Object value = placeholder.getValue();
if (value instanceof DynamicPkValuesArray)
{
return (DynamicPkValuesArray)value;
}
}
return null;
}
public void removeRow(int index)
{
pksToBeUpdated();
Object[] pk = pks.getRow(index);
if (pk != null && sortedPKs != null)
{
sortedPKs.remove(pk);
}
pks.removeRow(index);
// pk is removed, update the dynamic pk values holder.
if (pk != null && pksAndRecordsHolder != null && pksAndRecordsHolder.hasDynamicPlaceholder() && pksAndRecordsHolder.getFoundSet() != null &&
!pksAndRecordsHolder.getFoundSet().isInFindMode())
{
DynamicPkValuesArray dynArray = getDynamicPkValuesArray();
if (dynArray != null)
{
IDataSet ds = dynArray.getPKs();
for (int i = ds.getRowCount() - 1; i >= 0; i--)
{
if (Utils.equalObjects(pk, ds.getRow(i)))
{
ds.removeRow(i);
}
}
}
}
}
public void setRow(int index, Object[] pk)
{
setRow(index, pk, true);
}
public void setRow(int index, Object[] pk, boolean updateDynamicPKCondition)
{
if (updateDynamicPKCondition) pksToBeUpdated();
if (sortedPKs != null)
{
Object[] orgPk = pks.getRow(index);
if (orgPk != null)
{
sortedPKs.remove(orgPk);
}
if (pk != null)
{
sortedPKs.add(pk);
}
}
Object[] oldpk = pks.getRow(index);
pks.setRow(index, pk);
// // pk is updated, update the dynamic pk values holder if needed.
if (updateDynamicPKCondition && pk != null && pksAndRecordsHolder != null && pksAndRecordsHolder.hasDynamicPlaceholder() &&
pksAndRecordsHolder.getFoundSet() != null && !pksAndRecordsHolder.getFoundSet().isInFindMode())
{
DynamicPkValuesArray dynArray = getDynamicPkValuesArray();
if (dynArray != null)
{
if (oldpk != null)
{
// updated pk, remove old one
IDataSet ds = dynArray.getPKs();
for (int i = ds.getRowCount() - 1; i >= 0; i--)
{
if (Utils.equalObjects(oldpk, ds.getRow(i)))
{
ds.removeRow(i);
}
}
}
dynArray.getPKs().addRow(pk); // index does not matter
}
}
}
public void sort(int column, boolean ascending)
{
pks.sort(column, ascending);
}
public void sort(Comparator<Object[]> rowComparator)
{
pks.sort(rowComparator);
}
public IDataSet getDelegate()
{
return pks;
}
public boolean hasPKCache()
{
return sortedPKs != null;
}
public void createPKCache()
{
if (sortedPKs == null)
{
sortedPKs = new SortedList<Object[]>(PK_COMPARATOR, pks.getRowCount());
for (int i = 0; i < pks.getRowCount(); i++)
{
sortedPKs.add(pks.getRow(i));
}
}
}
public boolean containsPk(Object[] pk)
{
if (sortedPKs == null)
{
createPKCache();
}
return sortedPKs.contains(pk);
}
}