/*******************************************************************************
* 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.TLongArrayList;
import gnu.trove.set.hash.TLongHashSet;
import java.util.List;
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.tables.FlatDs2TableObject;
import com.opendoorlogistics.core.tables.utils.DatastoreComparer;
import com.opendoorlogistics.core.tables.utils.TableFlagUtils;
import com.opendoorlogistics.core.utils.Long2Ints;
final public class UnionDecorator<T extends ODLTableDefinition> extends AbstractDecorator<T> {
private final List<ODLDatastore<? extends T>> stores;
private final int length;
private static class UnsupportedInUnion extends UnsupportedOperationException{
public UnsupportedInUnion() {
super("Operation is unsupported for a union table / datastore.");
}
}
public UnionDecorator( List<ODLDatastore<? extends T>> datastores){
if(datastores.size() < 2){
throw new RuntimeException("A union must have at least two datastores.");
}
this.stores = datastores;
this.length = stores.size();
for(int i =1 ; i< length ; i++){
if(!DatastoreComparer.isSameStructure(stores.get(0), stores.get(i), DatastoreComparer.CHECK_IMMUTABLE_TABLE_IDS)){
throw new RuntimeException("Datastores input into union do not have same structure.");
}
}
}
/**
* Translate a row index in a union table into the datastore
* index and row index of the non-union table.
* @param tableId
* @param rowIndex
* @return
*/
private long dsRowIndx(int tableId, int rowIndex){
for(int dsIndex = 0; dsIndex<length;dsIndex++){
ODLTableReadOnly table = (ODLTableReadOnly)stores.get(dsIndex).getTableByImmutableId(tableId);
if(table!=null){
int nr =table.getRowCount();
if(rowIndex < nr){
return Long2Ints.get(dsIndex, rowIndex);
}
rowIndex -= nr;
}
}
return -1;
}
private static int dsIndx(long dsIndxRowIndx){
return Long2Ints.getFirst(dsIndxRowIndx);
}
private static int rowIndx(long dsIndxRowIndx){
return Long2Ints.getSecond(dsIndxRowIndx);
}
private int dsIndexWithRowId(int tableId, long rowId) {
for(int i =0 ; i< length ; i++){
ODLDatastore<? extends T> store=stores.get(i);
ODLTableReadOnly table = (ODLTableReadOnly)store.getTableByImmutableId(tableId);
if(table!=null){
if(table.containsRowId(rowId)){
return i;
}
}
}
return -1;
}
@Override
public T createTable(String tablename, int id) {
throw new UnsupportedInUnion();
}
@Override
public void deleteTableById(int tableId) {
throw new UnsupportedInUnion();
}
@Override
public boolean setTableName(int tableId, String newName) {
throw new UnsupportedInUnion();
}
@Override
public long getFlags() {
return stores.get(0).getFlags();
}
@Override
public void setFlags(long flags) {
throw new UnsupportedInUnion();
}
@Override
public ODLDatastoreAlterable<T> deepCopyWithShallowValueCopy(boolean lazyCopy) {
throw new UnsupportedInUnion();
}
@Override
public int getTableCount() {
return stores.get(0).getTableCount();
}
@SuppressWarnings("unchecked")
@Override
public T getTableAt(int tableIndex) {
T ret =null;
if(tableIndex < stores.get(0).getTableCount()){
int id = stores.get(0).getTableAt(tableIndex).getImmutableId();
ret= (T)new FlatDs2TableObject(this,id);
}
return ret;
}
@Override
public void addListener(ODLListener tml, int... tableIds) {
// listener support probably un-needed as script execution framework takes
// care of notifying when underlying data has changed
}
@Override
public void removeListener(ODLListener tml) {
// TODO Auto-generated method stub
}
@Override
public void disableListeners() {
// TODO Auto-generated method stub
}
@Override
public void enableListeners() {
// TODO Auto-generated method stub
}
@Override
public void startTransaction() {
new MultiDsTransactions<T>().startTransaction(stores);
}
@Override
public void endTransaction() {
new MultiDsTransactions<T>().endTransaction(stores);
}
@Override
public void rollbackTransaction() {
new MultiDsTransactions<T>().rollbackTransaction(stores);
}
@Override
public boolean isInTransaction() {
return new MultiDsTransactions<T>().isInTransaction(stores);
}
@Override
public int getRowCount(int tableId) {
int sum=0;
for(ODLDatastore<? extends T> ds: stores){
ODLTableReadOnly table = (ODLTableReadOnly)ds.getTableByImmutableId(tableId);
if(table!=null){
sum += table.getRowCount();
}
}
return sum;
}
@Override
public Object getValueAt(int tableId, int rowIndex, int columnIndex) {
long dsRow = dsRowIndx(tableId, rowIndex);
if(dsRow!=-1){
return readOnly(dsRow, tableId).getValueAt(rowIndx(dsRow), columnIndex);
}
return null;
}
@Override
public Object getValueById(int tableId, long rowId, int columnIndex) {
int dsIndx = dsIndexWithRowId(tableId, rowId);
if(dsIndx!=-1){
return ((ODLTableReadOnly)stores.get(dsIndx).getTableByImmutableId(tableId)).getValueById(rowId, columnIndex);
}
return null;
}
@Override
public ODLColumnType getColumnFieldType(int tableId, int col) {
return stores.get(0).getTableByImmutableId(tableId).getColumnType(col);
}
@Override
public String getColumnName(int tableId, int col) {
return stores.get(0).getTableByImmutableId(tableId).getColumnName(col);
}
@Override
public Object getColumnDefaultValue(int tableId, int col) {
return stores.get(0).getTableByImmutableId(tableId).getColumnDefaultValue(col);
}
@Override
public int getColumnCount(int tableId) {
return stores.get(0).getTableByImmutableId(tableId).getColumnCount();
}
@Override
public String getName(int tableId) {
return stores.get(0).getTableByImmutableId(tableId).getName();
}
@Override
public long getFlags(int tableId) {
// Remove all edit flags as they are ambiguous - particularly as the same physical row (in the external datastore)
// can appear multiple times in a union.
long ret= stores.get(0).getTableByImmutableId(tableId).getFlags();
ret = TableFlagUtils.removeFlags(ret, TableFlags.UI_INSERT_ALLOWED | TableFlags.UI_MOVE_ALLOWED| TableFlags.UI_DELETE_ALLOWED|TableFlags.UI_SET_ALLOWED);
return ret;
}
@Override
public long getColumnFlags(int tableId, int col) {
return stores.get(0).getTableByImmutableId(tableId).getColumnFlags(col);
}
@Override
public int getColumnImmutableId(int tableId, int col) {
return stores.get(0).getTableByImmutableId(tableId).getColumnImmutableId(col);
}
@Override
public boolean containsRowId(int tableId, long rowId) {
return dsIndexWithRowId(tableId, rowId)!=-1;
}
@Override
public Set<String> getColumnTags(int tableId, int col) {
return stores.get(0).getTableByImmutableId(tableId).getColumnTags(col);
}
@Override
public Set<String> getTags(int tableId) {
return stores.get(0).getTableByImmutableId(tableId).getTags();
}
@Override
public String getColumnDescription(int tableId, int col) {
return stores.get(0).getTableByImmutableId(tableId).getColumnDescription(col);
}
@Override
public void setValueAt(int tableId, Object aValue, int rowIndex, int columnIndex) {
long dsRow = dsRowIndx(tableId, rowIndex);
if(dsRow!=-1){
writable(dsRow, tableId).setValueAt(aValue,rowIndx(dsRow), columnIndex);
}
}
private ODLTable writable(long dsRow, int tableId) {
return (ODLTable)stores.get((dsIndx(dsRow))).getTableByImmutableId(tableId);
}
@Override
public void setValueById(int tableId, Object aValue, long rowId, int columnIndex) {
int dsIndx = dsIndexWithRowId(tableId, rowId);
if(dsIndx!=-1){
((ODLTable)stores.get(dsIndx).getTableByImmutableId(tableId)).setValueById(aValue,rowId, columnIndex);
}
}
@Override
public int createEmptyRow(int tableId, long rowId) {
throw new UnsupportedInUnion();
}
@Override
public void insertEmptyRow(int tableId, int insertAtRowNb, long rowId) {
throw new UnsupportedInUnion();
}
@Override
public void deleteRow(int tableId, int rowIndex) {
long dsRow = dsRowIndx(tableId, rowIndex);
if(dsRow!=-1){
writable(dsRow, tableId).deleteRow(rowIndx(dsRow));
}
}
@Override
public void deleteCol(int tableId, int col) {
throw new UnsupportedInUnion();
}
@Override
public boolean insertCol(int tableId, int id, int col, String name, ODLColumnType type, long flags, boolean allowDuplicateNames) {
throw new UnsupportedInUnion();
}
@Override
public int addColumn(int tableId, int columnid, String name, ODLColumnType type, long flags) {
throw new UnsupportedInUnion();
}
@Override
public void setFlags(int tableId, long flags) {
throw new UnsupportedInUnion();
}
@Override
public void setColumnFlags(int tableId, int col, long flags) {
throw new UnsupportedInUnion();
}
@Override
public void setColumnDefaultValue(int tableId, int col, Object value) {
throw new UnsupportedInUnion();
}
@Override
public void setColumnTags(int tableId, int col, Set<String> tags) {
throw new UnsupportedInUnion();
}
@Override
public void setTags(int tableId, Set<String> tags) {
throw new UnsupportedInUnion();
}
@Override
public void setColumnDescription(int tableId, int col, String description) {
throw new UnsupportedInUnion();
}
private ODLTableReadOnly readOnly(long dsRow, int tableId) {
return (ODLTableReadOnly)stores.get((dsIndx(dsRow))).getTableByImmutableId(tableId);
}
@Override
public long getRowGlobalId(int tableId, int rowIndex) {
long dsRow = dsRowIndx(tableId, rowIndex);
if(dsRow!=-1){
return readOnly(dsRow, tableId).getRowId(rowIndx(dsRow));
}
return -1;
}
@Override
public long getRowFlags(int tableId, long rowId) {
int dsIndx = dsIndexWithRowId(tableId, rowId);
if(dsIndx!=-1){
return ((ODLTableReadOnly)stores.get(dsIndx).getTableByImmutableId(tableId)).getRowFlags(rowId);
}
return 0;
}
@Override
public void setRowFlags(int tableId, long flags, long rowId) {
int dsIndx = dsIndexWithRowId(tableId, rowId);
if(dsIndx!=-1){
((ODLTable)stores.get(dsIndx).getTableByImmutableId(tableId)).setRowFlags(flags,rowId);
}
}
@Override
public boolean isRollbackSupported() {
return false;
}
@Override
public boolean getTableExists(int tableId) {
return true;
}
@Override
public ODLTableDefinition deepCopyWithShallowValueCopy(int tableId) {
throw new UnsupportedOperationException();
}
@Override
public long getRowLastModifiedTimeMillisecs(int tableId, long rowId) {
int dsIndx = dsIndexWithRowId(tableId, rowId);
if(dsIndx!=-1){
return ((ODLTableReadOnly)stores.get(dsIndx).getTableByImmutableId(tableId)).getRowLastModifiedTimeMillsecs(rowId);
}
return 0;
}
@Override
public long[] find(int tableId, int col, Object value) {
TLongArrayList ret = new TLongArrayList();
TLongHashSet hashset = new TLongHashSet();
for(int dsIndex = 0; dsIndex<length;dsIndex++){
ODLTableReadOnly table = (ODLTableReadOnly)stores.get(dsIndex).getTableByImmutableId(tableId);
if(table!=null){
long[] result = table.find(col, value);
int n = result.length;
for(int i=0;i<n;i++){
long id = result[i];
if(hashset.contains(id)==false){
hashset.add(id);
ret.add(id);
}
}
}
}
return ret.toArray();
}
@Override
public ODLTableReadOnly query(int tableId, TableQuery query) {
// Create empty return table
ODLApiImpl api = new ODLApiImpl();
Tables tables = api.tables();
ODLDatastoreAlterable<? extends ODLTableAlterable > ds = tables.createAlterableDs();
ODLTableDefinition outDfn = getTableByImmutableId(tableId);
ODLTableAlterable ret=(ODLTableAlterable)tables.copyTableDefinition(outDfn, ds);
// Fill with results
for(int dsIndex = 0; dsIndex<length;dsIndex++){
ODLTableReadOnly srcTable = (ODLTableReadOnly)stores.get(dsIndex).getTableByImmutableId(tableId);
if(srcTable!=null){
ODLTableReadOnly queryResult = srcTable.query(query);
if(queryResult!=null){
int n = queryResult.getRowCount();
for(int i =0 ; i < n ; i++){
tables.copyRow(queryResult, i, ret);
}
}
}
}
return ret;
}
}