/*
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.concurrent.atomic.AtomicInteger;
import com.servoy.j2db.query.AbstractBaseQuery;
import com.servoy.j2db.query.QuerySelect;
import com.servoy.j2db.query.TablePlaceholderKey;
import com.servoy.j2db.util.SafeArrayList;
import com.servoy.j2db.util.Utils;
/**
* @author jcompagner
*
*/
public class PksAndRecordsHolder
{
private transient SafeArrayList<IRecordInternal> cachedRecords = new SafeArrayList<IRecordInternal>(5); //row -> State Note:is based on the 'pks'
private PKDataSet pks; //the primary keys so far
private AtomicInteger dbIndexLastPk; // mutable integer wrapper, use wrapper in stead of primitive so that shallow copy can update same data
private QuerySelect querySelect; // the query the pks were based on
private final int chunkSize;
private final FoundSet foundSet;
private boolean hasDynamicPlaceholder;
private PksAndRecordsHolder(FoundSet foundSet, SafeArrayList<IRecordInternal> cachedRecords, IDataSet pks, AtomicInteger dbIndexLastPk,
QuerySelect querySelect, int chunkSize, boolean hasDynamicPlaceholder)
{
this(foundSet, chunkSize);
this.cachedRecords = cachedRecords;
this.pks = pks == null || pks instanceof PKDataSet ? (PKDataSet)pks : new PKDataSet(pks);
if (this.pks != null)
{
this.pks.setPksAndRecordsHolder(this);
}
this.dbIndexLastPk = dbIndexLastPk;
this.querySelect = querySelect;
this.hasDynamicPlaceholder = hasDynamicPlaceholder;
}
public PksAndRecordsHolder(FoundSet foundSet, int chunkSize)
{
this.foundSet = foundSet;
this.chunkSize = chunkSize;
}
public synchronized PksAndRecordsHolder shallowCopy()
{
return new PksAndRecordsHolder(foundSet, cachedRecords, pks, dbIndexLastPk, querySelect, chunkSize, hasDynamicPlaceholder);
}
public synchronized SafeArrayList<IRecordInternal> setPks(IDataSet bufferedDataSet, int dbIndexLastPk)
{
return setPksAndQuery(bufferedDataSet, dbIndexLastPk, this.querySelect, false);
}
public synchronized SafeArrayList<IRecordInternal> setPksAndQuery(IDataSet bufferedDataSet, int dbIndexLastPk, QuerySelect querySelect)
{
return setPksAndQuery(bufferedDataSet, dbIndexLastPk, querySelect, false);
}
/**
* @param bufferedDataSet
* @param querySelect
*/
public synchronized SafeArrayList<IRecordInternal> setPksAndQuery(IDataSet bufferedDataSet, int dbIndexLastPk, QuerySelect querySelect, boolean reuse)
{
pks = bufferedDataSet == null || bufferedDataSet instanceof PKDataSet ? (PKDataSet)bufferedDataSet : new PKDataSet(bufferedDataSet);
this.dbIndexLastPk = new AtomicInteger(dbIndexLastPk);
this.querySelect = querySelect;
this.hasDynamicPlaceholder = checkForDynamicPlaceholder(querySelect);
if (this.pks != null) this.pks.setPksAndRecordsHolder(this);
if (reuse)
{
cachedRecords = reUseStatesBasedOnNewPrimaryKeys();
}
else
{
cachedRecords = new SafeArrayList<IRecordInternal>((pks != null ? pks.getRowCount() : 0) + 5);//(re)new
}
return cachedRecords;
}
public synchronized SafeArrayList<IRecordInternal> getCachedRecords()
{
return cachedRecords;
}
public synchronized PKDataSet getPks()
{
return pks;
}
public synchronized PKDataSet getPksClone()
{
if (pks == null) return null;
PKDataSet clone = pks.clone();
clone.setPksAndRecordsHolder(null); // does not belong to this holder anymore
return clone;
}
public FoundSet getFoundSet()
{
return foundSet;
}
public int getDbIndexLastPk()
{
return dbIndexLastPk.get();
}
public void setDbIndexLastPk(int dbIndexLastPk)
{
this.dbIndexLastPk.set(dbIndexLastPk);
}
/**
* Get the querySelect for reading only, make no change to the query!
*/
public synchronized QuerySelect getQuerySelectForReading()
{
return querySelect;
}
/**
* Get a clone of the querySelect
*/
public synchronized QuerySelect getQuerySelectForModification()
{
return AbstractBaseQuery.deepClone(querySelect);
}
private SafeArrayList<IRecordInternal> reUseStatesBasedOnNewPrimaryKeys()
{
SafeArrayList<IRecordInternal> retval = new SafeArrayList<IRecordInternal>((pks != null ? pks.getRowCount() : 0) + 5);//(re)new
if (cachedRecords.size() > 3 * chunkSize)
{
return retval;//sub optimal just re query when needed
}
if (pks != null)
{
for (int i = 0; i < pks.getRowCount(); i++)
{
Object[] rawpk = pks.getRow(i);
String calcPKHashKey = RowManager.createPKHashKey(rawpk);
for (int j = cachedRecords.size() - 1; j >= 0; j--)//loop reverse more likely it is found earlier
{
IRecordInternal state = cachedRecords.get(j);
if (state == null) continue;
if (calcPKHashKey.equals(state.getPKHashKey()))
{
retval.set(i, state);
cachedRecords.set(j, null);
break;
}
}
}
}
return retval;
}
private static boolean checkForDynamicPlaceholder(QuerySelect querySelect)
{
return querySelect != null &&
querySelect.getPlaceholder(new TablePlaceholderKey(querySelect.getTable(), SQLGenerator.PLACEHOLDER_FOUNDSET_PKS)) != null;
}
public boolean hasDynamicPlaceholder()
{
return hasDynamicPlaceholder;
}
/**
* Sort entries in PKs as in sortedPKs.
*
* @param sortedPKs
*/
public synchronized void reorder(IDataSet sortedPKs)
{
if (pks == null || sortedPKs == null)
{
return;
}
int dsIndex = 0;
// for each pk in sortedPKs
for (int sorted = 0; sorted < sortedPKs.getRowCount(); sorted++)
{
Object[] sortedPK = sortedPKs.getRow(sorted);
if (sortedPK != null && sortedPK.length > 0)
{
// search for the pk in pks, starting from what is already sorted
boolean equal = false;
int i;
Object[] pk = null;
for (i = dsIndex; !equal && sortedPK != null && i < pks.getRowCount(); i++)
{
pk = pks.getRow(i);
// compare rows using Utils.equalObjects (possibly values from JS and DB have to be compared)
equal = pk != null && pk.length == sortedPK.length;
for (int r = 0; equal && r < pk.length; r++)
{
equal = Utils.equalObjects(pk[r], sortedPK[r]);
}
}
if (equal)
{
// equal pk found
if (i - 1 != dsIndex)
{
// flip pk(i) and pk(dsindex)
Object[] tmppk = pks.getRow(dsIndex);
pks.setRow(dsIndex, pk, false);
pks.setRow(i - 1, tmppk, false);
IRecordInternal tmprec = cachedRecords.get(dsIndex);
cachedRecords.set(dsIndex, cachedRecords.get(i - 1));
cachedRecords.set(i - 1, tmprec);
}
// else pk was on right position already
// we are sorted up to dsIndex
dsIndex++;
}
// else pk was not found in pks
}
}
}
public synchronized void rowPkUpdated(String oldPkHash, Row row)
{
for (int i = 0; pks != null && i < pks.getRowCount(); i++)
{
if (oldPkHash.equals(RowManager.createPKHashKey(pks.getRow(i))))
{
pks.setRow(i, row.getPK());
return;
}
}
}
}