/******************************************************************************* * 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.utils; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.TreeMap; import com.lowagie.text.Table; import com.opendoorlogistics.api.tables.ODLColumnType; import com.opendoorlogistics.api.tables.ODLDatastore; import com.opendoorlogistics.api.tables.ODLDatastoreAlterable; import com.opendoorlogistics.api.tables.ODLTable; import com.opendoorlogistics.api.tables.ODLTableAlterable; import com.opendoorlogistics.api.tables.ODLTableDefinition; import com.opendoorlogistics.api.tables.ODLTableDefinitionAlterable; import com.opendoorlogistics.api.tables.ODLTableReadOnly; import com.opendoorlogistics.api.tables.TableFlags; import com.opendoorlogistics.core.tables.*; import com.opendoorlogistics.core.tables.decorators.rows.ODLRowReadOnlyImpl; import com.opendoorlogistics.core.tables.memory.ODLDatastoreImpl; final public class DatastoreCopier { // public static boolean modifyColumnWithoutTransaction(int index, int newIndx, String newName, ODLColumnType newType, long newFlags, // ODLTableDefinitionAlterable tableDfn) { // // int found = TableUtils.findColumnIndx(tableDfn, newName, true); // if(found!=-1 && found!=index){ // return false; // } // // // insert the new column first as this can be rejected // String description = tableDfn.getColumnDescription(index); // Set<String> tags = tableDfn.getColumnTags(index); // Object defaultValue = tableDfn.getColumnDefaultValue(index); // if(tableDfn.insertColumn(-1,newIndx, newName, newType, newFlags,true)){ // // // also copy description and tags // tableDfn.setColumnDescription(newIndx, description); // if(tags!=null){ // tableDfn.setColumnTags(newIndx, tags); // } // if(defaultValue!=null){ // tableDfn.setColumnDefaultValue(newIndx, defaultValue); // } // // // update the source index // if(index >= newIndx){ // index++; // } // // // copy all values across to the new column // if(ODLTable.class.isInstance(tableDfn)){ // ODLTable table = (ODLTable)tableDfn; // int nr = table.getRowCount(); // for(int row =0 ; row < nr ; row++){ // DatastoreCopier.copyCell(table, row, index, table, row, newIndx); // } // } // // // remove the old column // tableDfn.deleteColumn(index); // // return true; // } // // return false; // } public static boolean modifyColumnWithoutTransaction(int index, int newIndx, String newName, ODLColumnType newType, long newFlags, ODLTableDefinitionAlterable tableDfn) { // ensure name is OK int found = TableUtils.findColumnIndx(tableDfn, newName, true); if(found!=-1 && found!=index){ return false; } // copy all column details String description = tableDfn.getColumnDescription(index); Set<String> tags = tableDfn.getColumnTags(index); Object defaultValue = tableDfn.getColumnDefaultValue(index); int id = tableDfn.getColumnImmutableId(index); // save all values for this column LinkedList<Object> savedValues = new LinkedList<>(); boolean isTable = ODLTable.class.isInstance(tableDfn); if(isTable){ ODLTableReadOnly table = (ODLTableReadOnly)tableDfn; int nr = table.getRowCount(); for(int row=0;row<nr;row++){ savedValues.add(table.getValueAt(row, index)); } } // remove the old column tableDfn.deleteColumn(index); // re-add the column if(tableDfn.insertColumn(id,newIndx, newName, newType, newFlags,true)){ // copy description, tags and default value tableDfn.setColumnDescription(newIndx, description); if(tags!=null){ tableDfn.setColumnTags(newIndx, tags); } if(defaultValue!=null){ tableDfn.setColumnDefaultValue(newIndx, defaultValue); } // copy all values if(isTable){ ODLTable table = (ODLTable)tableDfn; int row=0; for(Object val:savedValues){ table.setValueAt(val, row, newIndx); row++; } } }else{ throw new RuntimeException(); } // // insert the new column first as this can be rejected // if(tableDfn.insertColumn(-1,newIndx, newName, newType, newFlags,true)){ // // // also copy description and tags // tableDfn.setColumnDescription(newIndx, description); // if(tags!=null){ // tableDfn.setColumnTags(newIndx, tags); // } // if(defaultValue!=null){ // tableDfn.setColumnDefaultValue(newIndx, defaultValue); // } // // // update the source index // if(index >= newIndx){ // index++; // } // // // copy all values across to the new column // if(ODLTable.class.isInstance(tableDfn)){ // ODLTable table = (ODLTable)tableDfn; // int nr = table.getRowCount(); // for(int row =0 ; row < nr ; row++){ // DatastoreCopier.copyCell(table, row, index, table, row, newIndx); // } // } // // // remove the old column // tableDfn.deleteColumn(index); // // return true; // } return true; } public static void copyStructure(ODLDatastore<? extends ODLTableDefinition> copyThis, ODLDatastoreAlterable<? extends ODLTableAlterable> copyTo ){ if(copyTo.getTableCount()>0){ throw notEmptyException(); } for(int i = 0 ; i < copyThis.getTableCount() ; i++){ if(copyTableDefinition(copyThis.getTableAt(i), copyTo)==null){ throw new RuntimeException("Failed to create table " + copyThis.getTableAt(i).getName()); } } } public static void copyData(ODLDatastore<? extends ODLTableReadOnly> copyThis, ODLDatastoreAlterable<? extends ODLTable> copyTo ){ if(copyThis.getTableCount()!=copyTo.getTableCount()){ throw unequalStructureException(); } for(int i = 0 ; i < copyThis.getTableCount() ; i++){ ODLTableReadOnly tFrom = copyThis.getTableAt(i); ODLTable tTo = copyTo.getTableAt(i); copyData(tFrom, tTo); } } public static ODLTableAlterable copyTableIntoSameDatastore(ODLDatastoreAlterable<? extends ODLTableAlterable> ds, int tableId, String copyName){ boolean transaction = ds.isInTransaction(); if(!transaction){ ds.startTransaction(); } ODLTableAlterable copy = ds.createTable(copyName, -1); if(copy!=null){ ODLTableReadOnly original = ds.getTableByImmutableId(tableId); copyTableDefinition(original, copy); copyData(original, copy); } if(!transaction){ ds.endTransaction(); } return copy; } public static ODLTableAlterable copyTable(ODLTableReadOnly copyThis){ ODLDatastoreAlterable<ODLTableAlterable> ds = ODLDatastoreImpl.alterableFactory.create(); ODLTableAlterable ret = ds.createTable("tmp", -1); DatastoreCopier.copyTableDefinition(copyThis, ret); DatastoreCopier.copyData(copyThis, ret); return ret; } public static ODLTableAlterable copyTable(ODLTableReadOnly copyThis, ODLDatastoreAlterable<? extends ODLTableAlterable> copyInto, String newName){ // is id already used? int id = copyThis.getImmutableId(); if(copyInto.getTableByImmutableId(id)!=null){ id = -1; } // is table name already used? if(TableUtils.findTable(copyInto,newName, true)!=null){ return null; } ODLTableAlterable ret = (ODLTableAlterable)copyTableDefinition(copyThis, copyInto, newName); DatastoreCopier.copyData(copyThis, ret); return ret; } /** * Copy the table into the datastore. The same id is used if available. * If the name is already used then the copy fails. * @param copyThis * @param copyInto * @return */ public static ODLTableAlterable copyTable(ODLTableReadOnly copyThis, ODLDatastoreAlterable<? extends ODLTableAlterable> copyInto){ return copyTable(copyThis, copyInto, copyThis.getName()); } public static void copyData(ODLTableReadOnly tFrom, ODLTable tTo){ copyData(tFrom, tTo, true); } public static void copyData(ODLTableReadOnly tFrom, ODLTable tTo, boolean copyRowFlags){ if(!DatastoreComparer.isSameStructure(tFrom, tTo, 0)){ throw unequalStructureException(); } int nr = tFrom.getRowCount(); for(int srcRow =0 ; srcRow < nr ; srcRow++){ insertRow(tFrom, srcRow, tTo, tTo.getRowCount(),copyRowFlags); } } /** * Sync the To table to the contents of the From table, disregarding * any difference in row order. Tables must have the same structure. * @param syncFrom * @param toSync */ public static void unorderedSync(ODLTableReadOnly syncFrom, ODLTable toSync){ if(!DatastoreComparer.isSameStructure(syncFrom, toSync, 0)){ throw unequalStructureException(); } // rows are compared in the map based on their values and two rows could have the same values, // so we have to use a multi-map TreeMap<ODLRowReadOnly, ArrayList<ODLRowReadOnly>> map = new TreeMap<>(TableUtils.createRowComparatorUsingColumnType()); // read sync from int nsr = syncFrom.getRowCount(); for(int i =0 ; i< nsr ; i++){ ODLRowReadOnly row = new ODLRowReadOnlyImpl(syncFrom, i); ArrayList<ODLRowReadOnly> list = map.get(row); if(list==null){ list = new ArrayList<>(); map.put(row, list); } list.add(row); } // loop over sync to in reverse order for(int i = toSync.getRowCount()-1 ; i>=0 ; i--){ ODLRowReadOnly current= new ODLRowReadOnlyImpl(toSync, i); List<ODLRowReadOnly> exists =map.get(current); if(exists!=null){ // keep row and remove one from syncfrom collection exists.remove(exists.size()-1); if(exists.size()==0){ map.remove(current); } }else{ toSync.deleteRow(i); } } // everything still remaining in toSync can stay; anything remaining in multimap must be added // fill in everything that needs adding for(List<ODLRowReadOnly> list : map.values()){ for(ODLRowReadOnly row : list){ int rowIndx = toSync.createEmptyRow(-1); int nc = row.getColumnCount(); for(int col = 0 ; col < nc ; col++){ toSync.setValueAt(row.get(col), rowIndx, col); } } } } public static void insertRow(ODLTableReadOnly tFrom,int fromRow, ODLTable tTo, int toRow){ insertRow(tFrom, fromRow, tTo, toRow, true); } public static void insertRow(ODLTableReadOnly tFrom,int fromRow, ODLTable tTo, int toRow, boolean copyRowFlags){ // use original id if available long id = tFrom.getRowId(fromRow); // copy row flags but strip selection state and linked excel flags long flags =0; if(copyRowFlags){ flags =removeLinkedExcelFlags(tFrom.getRowFlags(id)); flags &= ~TableFlags.FLAG_ROW_SELECTED_IN_MAP; } if(tTo.containsRowId(id)){ id = -1; } tTo.insertEmptyRow(toRow,id); int nc = tFrom.getColumnCount(); for(int col =0 ; col < nc; col++){ copyCell(tFrom, fromRow,col, tTo, toRow, col); } // Set flags if(copyRowFlags){ id = tTo.getRowId(toRow); tTo.setRowFlags(id, flags); } } public static void copyRowById(ODLTableReadOnly tFrom,long fromRowId, ODLTable tTo){ int row = tTo.createEmptyRow(-1); int nc = tFrom.getColumnCount(); for(int col =0 ; col < nc; col++){ tTo.setValueAt(tFrom.getValueById(fromRowId, col), row, col); } } public static void copyCell(ODLTableReadOnly from, int fromRow,int fromCol, ODLTable to, int toRow, int toCol) { to.setValueAt(from.getValueAt(fromRow, fromCol), toRow, toCol); } public static ODLDatastoreAlterable<ODLTableAlterable> copyAll(ODLDatastore<? extends ODLTableReadOnly> copyThis){ ODLDatastoreAlterable<ODLTableAlterable> ret = ODLDatastoreImpl.alterableFactory.create(); copyAll(copyThis, ret); return ret; } public static void copyAll(ODLDatastore<? extends ODLTableReadOnly> copyThis, ODLDatastoreAlterable<? extends ODLTableAlterable> copyTo){ copyTo.setFlags(copyThis.getFlags()); for(int i =0 ; i < copyThis.getTableCount() ; i++){ ODLTableAlterable table = (ODLTableAlterable)copyTableDefinition(copyThis.getTableAt(i), copyTo); copyData(copyThis.getTableAt(i), table); } } public static boolean copyTableDefinitions(ODLDatastore<? extends ODLTableDefinition> copyThese, ODLDatastoreAlterable<? extends ODLTableDefinitionAlterable> copyTo){ return copyTableDefinitions(new Iterable<ODLTableDefinition>() { @Override public Iterator<ODLTableDefinition> iterator() { int n = copyThese.getTableCount(); return new Iterator<ODLTableDefinition>() { int index=-1; @Override public ODLTableDefinition next() { return copyThese.getTableAt(++index); } @Override public boolean hasNext() { return (index+1)< n; } }; } }, copyTo); } public static boolean copyTableDefinitions(Iterable<? extends ODLTableDefinition> copyThese, ODLDatastoreAlterable<? extends ODLTableDefinitionAlterable> copyTo){ for(ODLTableDefinition table : copyThese){ ODLTableDefinitionAlterable copyTable = copyTo.createTable(table.getName(), -1); if(copyTable==null){ return false; } copyTableDefinition(table, copyTable); } return true; } public static ODLTableDefinitionAlterable copyTableDefinition(ODLTableDefinition copyThis, ODLDatastoreAlterable<? extends ODLTableDefinitionAlterable> copyTo, String newName){ return copyTableDefinition(copyThis, copyTo, newName, -1); } public static ODLTableDefinitionAlterable copyTableDefinition(ODLTableDefinition copyThis, ODLDatastoreAlterable<? extends ODLTableDefinitionAlterable> copyTo, String newName, int id){ // reuse the original id if we can if(id==-1){ id = copyThis.getImmutableId(); } // only use id if unique if(id!=-1 && copyTo.getTableByImmutableId(id)!=null){ id = -1; } if(copyTo.createTable(newName,id)==null){ return null; } ODLTableDefinitionAlterable table = copyTo.getTableAt(copyTo.getTableCount()-1); copyTableDefinition(copyThis, table); return table; } public static ODLTableDefinitionAlterable copyTableDefinition(ODLTableDefinition copyThis, ODLDatastoreAlterable<? extends ODLTableDefinitionAlterable> copyTo){ return copyTableDefinition(copyThis, copyTo, copyThis.getName()); } public static boolean copyTableDefinition(ODLTableDefinition copyThis, ODLTableDefinitionAlterable copyTo) { return copyTableDefinition(copyThis, copyTo, true); } public static boolean copyTableDefinition(ODLTableDefinition copyThis, ODLTableDefinitionAlterable copyTo, boolean copyColumnIds) { copyTo.setFlags(removeLinkedExcelFlags(copyThis.getFlags())); copyTo.setTags(copyThis.getTags()); boolean allAdded= true; for(int fromCol =0 ;fromCol < copyThis.getColumnCount() ; fromCol++){ if(!copyColumnDefinition(copyThis, copyTo, fromCol, copyColumnIds)){ allAdded = false; } } return allAdded; } public static boolean copyColumnDefinition(ODLTableDefinition copyThis, ODLTableDefinitionAlterable copyTo, int fromCol, boolean copyColumnIds) { return copyColumnDefinition(copyThis, copyTo, fromCol, -1, copyColumnIds); } public static boolean copyColumnDefinition(ODLTableDefinition copyThis, ODLTableDefinitionAlterable copyTo, int fromCol,int toCol, boolean copyColumnIds) { int id = copyColumnIds? copyThis.getColumnImmutableId(fromCol) : -1; // check id not already used if(id!=-1){ int n = copyTo.getColumnCount(); for(int i=0 ; i<n;i++){ if(copyTo.getColumnImmutableId(i)==id){ id=-1; break; } } } // remove all flags indicating this data was from a linked excel (as its now a copy) long flags = removeLinkedExcelFlags(copyThis.getColumnFlags(fromCol)); boolean copied; if(toCol==-1){ copied = copyTo.addColumn(id,copyThis.getColumnName(fromCol), copyThis.getColumnType(fromCol), flags)!=-1; toCol = copyTo.getColumnCount()-1; }else{ copied = copyTo.insertColumn(id, toCol, copyThis.getColumnName(fromCol), copyThis.getColumnType(fromCol), flags,false); } if(copied){ copyTo.setColumnDescription(toCol, copyThis.getColumnDescription(fromCol)); copyTo.setColumnTags(toCol, copyThis.getColumnTags(fromCol)); copyTo.setColumnDefaultValue(toCol, copyThis.getColumnDefaultValue(fromCol)); } return copied; } private static long removeLinkedExcelFlags(long flags) { flags &= ~ TableFlags.ALL_LINKED_EXCEL_FLAGS; return flags; } private static RuntimeException notEmptyException() { return new RuntimeException("Copy to database not empty"); } private static RuntimeException unequalStructureException() { return new RuntimeException("Copy from and copy to databases have different structure"); } /** * Modify the datastore if needed to create the input tables and fields * @param schema * @param ds */ public static void enforceSchema(ODLDatastore<? extends ODLTableDefinition> schema, ODLDatastoreAlterable<? extends ODLTableDefinitionAlterable> ds, boolean changeFieldTypes){ // check for table int nt = schema.getTableCount(); for(int i =0 ; i < nt ; i++){ ODLTableDefinition src = schema.getTableAt(i); ODLTableDefinitionAlterable dest = TableUtils.findTable(ds, src.getName()); if(dest==null){ copyTableDefinition(src, ds); }else{ enforceSchema(src, dest, changeFieldTypes); } } } public static void enforceSchema(ODLTableDefinition schema,ODLTableDefinitionAlterable target, boolean changeFieldTypes){ int nf = schema.getColumnCount(); for(int srcCol =0 ; srcCol < nf ; srcCol++){ String name = schema.getColumnName(srcCol); int targetCol = TableUtils.findColumnIndx(target, name); if(targetCol==-1){ copyColumnDefinition(schema, target, srcCol, false); } else if(changeFieldTypes && schema.getColumnType(srcCol)!=target.getColumnType(targetCol)){ modifyColumnWithoutTransaction(targetCol, targetCol, target.getColumnName(targetCol), schema.getColumnType(srcCol), target.getColumnFlags(targetCol), target); } } } /** * Merge the source datastore into the destination. * Fields and tables are added as needed. * @param source * @param destination */ public static void mergeAll(ODLDatastore<? extends ODLTableReadOnly> source, ODLDatastoreAlterable<? extends ODLTableAlterable> destination){ // ensure all tables and fields exist enforceSchema(source, destination, false); // loop over all source tables int nt = source.getTableCount(); for(int srcTableIndex =0 ; srcTableIndex < nt ; srcTableIndex++){ // get the destination table ODLTableReadOnly srcTable = source.getTableAt(srcTableIndex); ODLTable destTable = TableUtils.findTable(destination, srcTable.getName()); // get the destination column indices int nc = srcTable.getColumnCount(); int [] destCols = new int[nc]; for(int srcCol=0; srcCol < nc ; srcCol++){ destCols[srcCol] = TableUtils.findColumnIndx(destTable, srcTable.getColumnName(srcCol)); } // loop over all source rows int nr = srcTable.getRowCount(); for(int srcRow = 0 ; srcRow <nr ; srcRow++){ // create destination row int destRow = destTable.createEmptyRow(-1); // copy column values for(int srcCol=0; srcCol < nc ; srcCol++){ copyCell(srcTable, srcRow, srcCol, destTable, destRow, destCols[srcCol]); } } } } }