/*******************************************************************************
* Copyright (c) 2014 Open Door Logistics (www.opendoorlogistics.com)
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v3
* which accompanies this distribution, and is available at http://www.gnu.org/licenses/lgpl.txt
******************************************************************************/
package com.opendoorlogistics.core.tables.decorators.datastores;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.list.array.TLongArrayList;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.procedure.TLongProcedure;
import gnu.trove.set.TLongSet;
import gnu.trove.set.hash.TLongHashSet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;
import com.opendoorlogistics.api.Tables;
import com.opendoorlogistics.api.tables.ODLColumnType;
import com.opendoorlogistics.api.tables.ODLDatastore;
import com.opendoorlogistics.api.tables.ODLDatastoreAlterable;
import com.opendoorlogistics.api.tables.ODLListener;
import com.opendoorlogistics.api.tables.ODLTable;
import com.opendoorlogistics.api.tables.ODLTableAlterable;
import com.opendoorlogistics.api.tables.ODLTableDefinition;
import com.opendoorlogistics.api.tables.ODLTableReadOnly;
import com.opendoorlogistics.api.tables.TableFlags;
import com.opendoorlogistics.api.tables.TableQuery;
import com.opendoorlogistics.core.api.impl.ODLApiImpl;
import com.opendoorlogistics.core.tables.decorators.listeners.ListenerRedirector;
import com.opendoorlogistics.core.tables.decorators.tables.FlatDs2TableObject;
import com.opendoorlogistics.core.tables.utils.DatastoreCopier;
import com.opendoorlogistics.core.tables.utils.TableFlagUtils;
import com.opendoorlogistics.core.tables.utils.TableUtils;
/**
* A decorator which filters the original rows on one or more tables. Rows cannot be added or deleted to the original datastore whilst the filtering happens.
*
* @author Phil
*
*/
final public class RowFilterDecorator <T extends ODLTableReadOnly> extends AbstractDecorator<T> {
private final ODLDatastore<? extends T> src;
private final ArrayList<FilteredTable> tablesByIndx = new ArrayList<>();
private final TIntObjectHashMap<FilteredTable> tablesById = new TIntObjectHashMap<>();
private final ListenerRedirector listenerRedirector;
public int getRowCount(){
int ret=0;
for(FilteredTable table : tablesByIndx){
ret += table.size();
}
return ret;
}
private class FilteredTable {
final int tableId;
final private TLongArrayList arrayList = new TLongArrayList();
final private TLongHashSet hashSet = new TLongHashSet();
FilteredTable(int tableId) {;
this.tableId = tableId;
}
boolean contains(long rowId){
return hashSet.contains(rowId);
}
boolean add(long rowId) {
if (contains(rowId)) {
return false;
}
arrayList.add(rowId);
hashSet.add(rowId);
return true;
}
int size() {
return arrayList.size();
}
long getRowId(int indx) {
if(indx>=arrayList.size()){
return -1;
}
return arrayList.get(indx);
}
void removeAt(int indx) {
long id = arrayList.removeAt(indx);
hashSet.remove(id);
}
void clear(){
arrayList.clear();
hashSet.clear();
}
}
public RowFilterDecorator(ODLDatastore<? extends T> src, int... filterTableIds) {
this.src = src;
this.listenerRedirector = new ListenerRedirector(src, false);
class AddHelper{
void add(int tableid){
FilteredTable table = new FilteredTable(tableid);
tablesByIndx.add(table);
tablesById.put(table.tableId, table);
}
}
AddHelper helper = new AddHelper();
if(filterTableIds.length > 0){
// include subset
for(int tableid : filterTableIds){
helper.add(tableid);
}
}else{
// include all
for (int i = 0; i < src.getTableCount(); i++) {
helper.add(src.getTableAt(i).getImmutableId());
}
}
}
public boolean addRowToFilter(int tableId, long rowId) {
// check table exists
ODLTableReadOnly table = src.getTableByImmutableId(tableId);
if (table == null) {
return false;
}
// check row exists
if (table.containsRowId(rowId)==false) {
return false;
}
// add to filtered table
FilteredTable filteredTable = tablesById.get(tableId);
return filteredTable.add(rowId);
}
public static class UpdateCounter {
private int nbRowsAdded;
private int nbRowsDeleted;
private int nbTablesAdded;
private int nbTablesDeleted;
private UpdateCounter() {
}
public int getNbRowsAdded() {
return nbRowsAdded;
}
public int getNbRowsDeleted() {
return nbRowsDeleted;
}
public int getNbTablesAdded() {
return nbTablesAdded;
}
public int getNbTablesDeleted() {
return nbTablesDeleted;
}
public boolean getChanged(){
return (nbRowsAdded + nbRowsDeleted + nbTablesAdded + nbRowsDeleted)>0;
}
}
private void removeTable(int tableId) {
Iterator<FilteredTable> it = tablesByIndx.iterator();
while (it.hasNext()) {
if (it.next().tableId == tableId) {
it.remove();
}
}
tablesById.remove(tableId);
}
public void clearRows(){
for(FilteredTable table: tablesById.valueCollection()){
table.clear();
}
}
public UpdateCounter update(TLongSet globalRowIds, boolean allowTableDeletion) {
final UpdateCounter ret = new UpdateCounter();
// add everything
int nbTables = tablesByIndx.size();
globalRowIds.forEach(new TLongProcedure() {
@Override
public boolean execute(long globalRowId) {
if (addRowToFilter(TableUtils.getTableId(globalRowId), globalRowId)) {
ret.nbRowsAdded++;
}
return true;
}
});
ret.nbTablesAdded = tablesByIndx.size() - nbTables;
// remove everything that no longer exists
for (final FilteredTable table : new ArrayList<FilteredTable>(tablesByIndx)) {
// check if table still exists
if (src.getTableByImmutableId(table.tableId) != null) {
// get set of row ids for this table
final TLongHashSet rowIds = new TLongHashSet();
globalRowIds.forEach(new TLongProcedure() {
@Override
public boolean execute(long globalRowId) {
int tableId = TableUtils.getTableId(globalRowId);
if (tableId == table.tableId) {
rowIds.add(globalRowId);
}
return true;
}
});
// get the indices to be deleted
TIntArrayList toDeleteIndices = new TIntArrayList();
int n = table.size();
for(int row =0 ; row< n;row++){
if(rowIds.contains(table.getRowId(row))==false){
toDeleteIndices.add(row);
}
}
// delete them
for(int i = toDeleteIndices.size()-1;i>=0;i--){
table.removeAt(toDeleteIndices.get(i));
ret.nbRowsDeleted++;
}
}
else{
// remove all
ret.nbRowsDeleted += table.size();
table.clear();
}
if (allowTableDeletion && table.size() == 0) {
removeTable(table.tableId);
ret.nbTablesDeleted++;
}
}
return ret;
}
@Override
public T createTable(String tablename, int id) {
throwUnsupported();
return null;
}
@Override
public void deleteTableById(int tableId) {
throwUnsupported();
}
@Override
public boolean setTableName(int tableId, String newName) {
throwUnsupported();
return false;
}
@Override
public int getTableCount() {
return tablesByIndx.size();
}
@Override
public T getTableAt(int i) {
return getTableByImmutableId(tablesByIndx.get(i).tableId);
}
@Override
public T getTableByImmutableId(int tableId) {
if(tablesById.containsKey(tableId)){
return (T)new FlatDs2TableObject(this,tableId);
}
return null;
}
@Override
public void addListener(ODLListener tml, int... tableIds) {
listenerRedirector.addListener(tml, tableIds);
}
@Override
public void removeListener(ODLListener tml) {
listenerRedirector.removeListener(tml);
}
@Override
public void disableListeners() {
listenerRedirector.disableListeners();
}
@Override
public void enableListeners() {
listenerRedirector.enableListeners();
}
@Override
public void startTransaction() {
src.startTransaction();
}
@Override
public void endTransaction() {
src.endTransaction();
}
@Override
public boolean isInTransaction() {
return src.isInTransaction();
}
@Override
public long getFlags() {
return src.getFlags();
}
@Override
public void setFlags(long flags) {
throw new UnsupportedOperationException();
}
@Override
public int getRowCount(int tableId) {
return tablesById.get(tableId).size();
}
@Override
public Object getValueById(int tableId, long rowId, int columnIndex) {
ODLTableReadOnly srcTable = getSourceTable(tableId);
if(srcTable!= null){
return srcTable.getValueById(rowId, columnIndex);
}
return null;
}
@Override
public Object getValueAt(int tableId, int rowIndex, int columnIndex) {
FilteredTable filteredTable = tablesById.get(tableId);
long srcRowId = filteredTable.getRowId(rowIndex);
return getValueById(tableId, srcRowId, columnIndex);
}
private T getSourceTable(int tableId) {
FilteredTable filteredTable = tablesById.get(tableId);
T srcTable = (T)src.getTableByImmutableId(filteredTable.tableId);
return srcTable;
}
@Override
public ODLColumnType getColumnFieldType(int tableId, int col) {
return getSourceTable(tableId)!=null?getSourceTable(tableId).getColumnType(col):null;
}
@Override
public String getColumnName(int tableId, int col) {
return getSourceTable(tableId)!=null?getSourceTable(tableId).getColumnName(col):null;
}
@Override
public Object getColumnDefaultValue(int tableId, int col) {
return getSourceTable(tableId)!=null?getSourceTable(tableId).getColumnDefaultValue(col):null;
}
@Override
public int getColumnCount(int tableId) {
return getSourceTable(tableId)!=null?getSourceTable(tableId).getColumnCount():0;
}
@Override
public String getName(int tableId) {
return getSourceTable(tableId)!=null?getSourceTable(tableId).getName():null;
}
@Override
public long getFlags(int tableId) {
long ret=0;
if(getSourceTable(tableId)!=null){
ret = getSourceTable(tableId).getFlags();
}
// don't allow insert or move
ret = TableFlagUtils.removeFlags(ret, TableFlags.UI_MOVE_ALLOWED | TableFlags.UI_INSERT_ALLOWED);
return ret;
}
@Override
public long getColumnFlags(int tableId, int col) {
return getSourceTable(tableId)!=null?getSourceTable(tableId).getColumnFlags(col):0;
}
@Override
public void setValueAt(int tableId, Object aValue, int rowIndex, int columnIndex) {
FilteredTable filteredTable = tablesById.get(tableId);
long srcRowId = filteredTable.getRowId(rowIndex);
setValueById(tableId, aValue, srcRowId, columnIndex);
}
@Override
public void setValueById(int tableId, Object aValue, long rowId, int columnIndex) {
FilteredTable filteredTable = tablesById.get(tableId);
ODLTable srcTable =(ODLTable) src.getTableByImmutableId(filteredTable.tableId);
if(srcTable!=null){
srcTable.setValueById(aValue, rowId, columnIndex);
}
}
@Override
public int createEmptyRow(int tableId, long rowId) {
ODLTable srcTable =(ODLTable) getSourceTable(tableId);
int indx = srcTable.createEmptyRow(rowId);
rowId = srcTable.getRowId(indx);
FilteredTable filteredTable = tablesById.get(tableId);
filteredTable.add(rowId);
return filteredTable.size() - 1;
}
@Override
public void insertEmptyRow(int tableId, int insertAtRowNb, long rowId) {
throwUnsupported();
}
@Override
public void deleteRow(int tableId, int rowNumber) {
FilteredTable filteredTable = tablesById.get(tableId);
long rowId = filteredTable.getRowId(rowNumber);
filteredTable.removeAt(rowNumber);
ODLTable srcTable = (ODLTable)getSourceTable(tableId);
if(srcTable!=null){
TableUtils.deleteById(srcTable, rowId);
}
}
@Override
public void deleteCol(int tableId, int col) {
throwUnsupported();
}
@Override
public boolean insertCol(int tableId, int colId, int col, String name, ODLColumnType type, long flags, boolean allowDuplicateNames) {
throwUnsupported();
return false;
}
@Override
public int addColumn(int tableId, int colId,String name, ODLColumnType type, long flags) {
throwUnsupported();
return -1;
}
@Override
public void setFlags(int tableId, long flags) {
throwUnsupported();
}
@Override
public void setColumnFlags(int tableId, int col, long flags) {
throwUnsupported();
}
// @Override
// public int getRowIndexByGlobalId(int tableId, long globalId) {
// return getRowIndexByLocalId(tableId, Utils.getLocalRowId(globalId));
// }
@Override
public long getRowGlobalId(int tableId, int rowIndex) {
return tablesById.get(tableId).getRowId(rowIndex);
// return getSourceTable(tableId)!=null? getSourceTable(tableId).getRowGlobalIdByLocal(localRowId):-1;
}
@Override
public boolean containsRowId(int tableId, long rowId) {
return tablesById.get(tableId).contains(rowId);
}
@Override
public ODLDatastoreAlterable<T> deepCopyWithShallowValueCopy(boolean lazyCopy) {
throw new UnsupportedOperationException();
}
@Override
public String getColumnDescription(int tableId, int col) {
return getSourceTable(tableId)!=null?getSourceTable(tableId).getColumnDescription(col):null;
}
@Override
public void setColumnDescription(int tableId, int col, String description) {
if(getSourceTable(tableId)!=null){
getSourceTable(tableId).setColumnDescription(col, description);
}
}
@Override
public java.util.Set<String> getColumnTags(int tableId, int col) {
return getSourceTable(tableId)!=null?getSourceTable(tableId).getColumnTags(col):null;
}
@Override
public int getColumnImmutableId(int tableId, int col) {
return getSourceTable(tableId)!=null? getSourceTable(tableId).getColumnImmutableId(col):-1;
}
@Override
public java.util.Set<String> getTags(int tableId) {
return getSourceTable(tableId)!=null?getSourceTable(tableId).getTags():null;
}
@Override
public void setColumnTags(int tableId, int col, Set<String> tags) {
throwUnsupported();
}
@Override
public void setTags(int tableId, Set<String> tags) {
throwUnsupported();
}
@Override
public void setColumnDefaultValue(int tableId, int col, Object value) {
throwUnsupported();
}
private void throwUnsupported(){
throw new UnsupportedOperationException("Operation is not allowed when filtering data.");
}
@Override
public long[] find(int tableId, int col, Object value) {
FilteredTable table = tablesById.get(tableId);
if(table==null){
return null;
}
ODLTableReadOnly src = getSourceTable(tableId);
if(table.size() < 10 || src==null){
// may be more efficient not use the index as its unfiltered
return TableUtils.find(getTableByImmutableId(tableId), col, value);
}else{
// get unfiltered ids matching the value
long[] unfiltered = src.find(col, value);
// filter them
int n = unfiltered.length;
TLongArrayList ret = new TLongArrayList();
for(int i =0 ; i<n;i++){
if(table.contains(unfiltered[i])){
ret.add(unfiltered[i]);
}
}
return ret.toArray();
}
}
@Override
public ODLTableReadOnly query(int tableId, TableQuery query) {
FilteredTable filteredRowIds = tablesById.get(tableId);
if(filteredRowIds==null){
return null;
}
ODLTableReadOnly src = getSourceTable(tableId);
if(src==null){
return null;
}
// Do the query first as we assume it will be more efficient at paring down the data
ODLTableReadOnly queryResult = src.query(query);
if(queryResult==null){
return null;
}
// Now filter
ODLApiImpl api = new ODLApiImpl();
Tables tables = api.tables();
ODLDatastoreAlterable<? extends ODLTableAlterable > ds = tables.createAlterableDs();
ODLTableAlterable ret=(ODLTableAlterable)tables.copyTableDefinition(queryResult, ds);
int n = queryResult.getRowCount();
for(int row =0 ; row < n ; row++){
if(filteredRowIds.contains(queryResult.getRowId(row))){
tables.copyRow(queryResult, row, ret);
}
}
return ret;
}
@Override
public long getRowFlags(int tableId, long rowId) {
ODLTableReadOnly srcTable = getSourceTable(tableId);
if(srcTable!= null){
return srcTable.getRowFlags(rowId);
}
return 0;
}
@Override
public long getRowLastModifiedTimeMillisecs(int tableId, long rowId) {
ODLTableReadOnly srcTable = getSourceTable(tableId);
if(srcTable!= null){
return srcTable.getRowLastModifiedTimeMillsecs(rowId);
}
return 0;
}
@Override
public void setRowFlags(int tableId, long flags, long rowId) {
ODLTableReadOnly srcTable = getSourceTable(tableId);
if(srcTable!= null){
((ODLTable)srcTable).setRowFlags(flags,rowId);
}
}
@Override
public void rollbackTransaction() {
throwUnsupported();
}
@Override
public boolean isRollbackSupported() {
// cannot rollback a row filter as filtered row records held in this class will not be rolled back
return false;
}
@Override
public boolean getTableExists(int tableId) {
return src.getTableByImmutableId(tableId)!=null;
}
@Override
public ODLTableDefinition deepCopyWithShallowValueCopy(int tableId) {
throw new UnsupportedOperationException();
}
}