/*******************************************************************************
* 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.concurrency;
import gnu.trove.list.array.TLongArrayList;
import java.util.BitSet;
import com.opendoorlogistics.api.tables.ODLDatastoreUndoable;
import com.opendoorlogistics.api.tables.ODLTable;
import com.opendoorlogistics.api.tables.ODLTableAlterable;
import com.opendoorlogistics.core.tables.utils.DatastoreComparer;
import com.opendoorlogistics.core.tables.utils.DatastoreCopier;
import com.opendoorlogistics.core.tables.utils.TableUtils;
final public class MergeBranchedDatastore {
public static boolean merge(WriteRecorderDecorator<? extends ODLTableAlterable> mergeFrom, ODLDatastoreUndoable<? extends ODLTableAlterable> mergeInto) {
boolean ok = true;
mergeInto.startTransaction();
try {
// loop over each table
int nt = mergeFrom.getTableCount();
for (int i = 0; i < nt && ok; i++) {
ODLTableAlterable source = mergeFrom.getTableAt(i);
if (mergeFrom.isCreatedTable(source.getImmutableId())) {
// simple.. copy across
ok = DatastoreCopier.copyTable(source, mergeInto) != null;
} else {
// more complex... need to merge the two tables
ODLTable destination = mergeInto.getTableByImmutableId(source.getImmutableId());
ok = destination != null && DatastoreComparer.isSameStructure(source, destination, 0);
if (ok) {
mergeTables(mergeFrom, source, destination);
}
}
}
} catch (Throwable e) {
ok = false;
}
if (ok) {
mergeInto.endTransaction();
} else {
mergeInto.rollbackTransaction();
}
return ok;
}
private enum MergeTableResult{
NO_WRITES_DONE,
WRITES_DONE
}
private static MergeTableResult mergeTables(WriteRecorderDecorator<? extends ODLTableAlterable> mergeFromDs, ODLTableAlterable source, ODLTable destination) {
// We only support a couple of simple merging cases...
// 1. Simple appending of rows
// 2. Setting of original rows
// 3. Deleting original rows
// ... and combinations of these
// A WriteRecorderDecorator should not allow inserting of rows, only appending
// so only these cases should be possible.
MergeTableResult result = MergeTableResult.NO_WRITES_DONE;
// set any values for non-appended rows (rows that were already present)
int srcTableId = source.getImmutableId();
for (long rowId : mergeFromDs.getWrittenRowIds()) {
int tableId = TableUtils.getTableId(rowId);
if (tableId == srcTableId) {
result = MergeTableResult.WRITES_DONE;
if (mergeFromDs.isAppendedRow(rowId) == false) {
// check row has not been deleted from the destination table or source table
if (destination.containsRowId(rowId) && source.containsRowId(rowId)) {
BitSet bs = mergeFromDs.getWrittenCols(rowId);
int ncol = source.getColumnCount();
for (int col = 0; col < ncol; col++) {
if (bs.get(col)) {
destination.setValueById(source.getValueById(rowId, col), rowId, col);
}
}
}
}
}
}
// delete any rows deleted from the original datastore.
// If they still exist in the source table, they must have re-added and hence
// will be appended later...
TLongArrayList rowIdsToDelete = new TLongArrayList();
for (long rowId : mergeFromDs.getDeletedOriginalRowIds()) {
int tableID = TableUtils.getTableId(rowId);
if (tableID == srcTableId) {
result = MergeTableResult.WRITES_DONE;
if (destination.containsRowId(rowId)) {
rowIdsToDelete.add(rowId);
}
}
}
TableUtils.deleteById(destination, rowIdsToDelete.toArray());
// append into the destination all rows appended in the source
int nr = source.getRowCount();
for (int srcRow = 0; srcRow < nr; srcRow++) {
long rowId = source.getRowId(srcRow);
if (mergeFromDs.isAppendedRow(rowId)) {
result = MergeTableResult.WRITES_DONE;
// append, using correct rowid if still available in the destination
if (destination.containsRowId(rowId)) {
rowId = -1;
}
int destRow = destination.createEmptyRow(rowId);
int nc = source.getColumnCount();
for (int col = 0; col < nc; col++) {
destination.setValueAt(source.getValueAt(srcRow, col), destRow, col);
}
}
}
return result;
}
}