All Downloads are FREE. Search and download functionalities are using the official Maven repository.

xdev.ui.ManyToMany Maven / Gradle / Ivy

/*
 * XDEV Application Framework - XDEV Application Framework
 * Copyright © 2003 XDEV Software (https://xdev.software)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 */
package xdev.ui;


import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import xdev.db.DBConnection;
import xdev.db.DBException;
import xdev.db.DBUtils;
import xdev.db.WriteRequest;
import xdev.db.WriteResult;
import xdev.db.sql.DELETE;
import xdev.db.sql.INSERT;
import xdev.db.sql.SELECT;
import xdev.db.sql.UPDATE;
import xdev.db.sql.WHERE;
import xdev.util.IntList;
import xdev.vt.EntityRelationship;
import xdev.vt.EntityRelationship.Entity;
import xdev.vt.EntityRelationships;
import xdev.vt.KeyValues;
import xdev.vt.TableColumnLink;
import xdev.vt.VirtualTable;
import xdev.vt.VirtualTable.ColumnSelector;
import xdev.vt.VirtualTable.VirtualTableRow;
import xdev.vt.VirtualTableColumn;
import xdev.vt.VirtualTableException;
import xdev.vt.VirtualTables;


/**
 * Utility class with methods to manipulate many to many relations.
 * 
 * 
 * @author XDEV Software (TF, FH)
 * @since 3.0
 */
public final class ManyToMany
{
	private ManyToMany()
	{
	}
	
	
	
	static class State
	{
		/*
		 * original
		 */
		final VirtualTableRow	masterRecord;
		final VirtualTable		nTable;
		final VirtualTable		nmTable;
		/*
		 * clone
		 */
		final VirtualTable		mTable;
		
		EntityRelationship		nToNMrelation;
		
		
		State(VirtualTableRow masterRecord, VirtualTable nmTable) throws MasterDetailException,
				VirtualTableException
		{
			this.masterRecord = masterRecord;
			this.nTable = masterRecord.getVirtualTable();
			this.nmTable = nmTable;
			
			Map map = new Hashtable();
			
			for(EntityRelationship relation : EntityRelationships.getModel().getRelations(
					nmTable.getName()))
			{
				if(relation.isLoop())
				{
					continue;
				}
				
				Entity entity = relation.getOther(relation.getReferrer(nmTable.getName()));
				if(!entity.refersTo(nTable.getName()))
				{
					map.put(entity.getTableName(),relation);
					nToNMrelation = relation;
				}
				
				if(map.size() > 1)
				{
					break;
				}
			}
			
			if(map.size() != 1)
			{
				throw new MasterDetailException(nTable,nmTable,
						"No unambiguous detail table found for '" + nTable.getName() + "' -> '"
								+ nmTable.getName() + "' <- ?");
			}
			
			String mTableName = map.keySet().iterator().next();
			VirtualTable mTable = VirtualTables.getVirtualTable(mTableName);
			if(mTable == null)
			{
				VirtualTableException.throwVirtualTableNotFound(mTableName);
			}
			
			this.mTable = mTable.clone(false);
		}
		
		
		KeyValues getForeignKeyValues()
		{
			return MasterDetail.getForeignKeyValues(masterRecord,nmTable);
		}
		
		
		int[] fillUpNMTable(VirtualTable nmTable, VirtualTable nmClone) throws DBException,
				VirtualTableException
		{
			IntList indices = new IntList();
			
			nmTable.clear();
			
			Map, VirtualTableColumn> columnMap = new Hashtable();
			VirtualTableColumn[] nmRelationColumns = null;
			VirtualTableColumn[] mRelationColumns = null;
			
			for(VirtualTableColumn nmColumn : nmClone)
			{
				VirtualTableColumn mColumn = null;
				
				if(!nmColumn.isPersistent())
				{
					TableColumnLink link = nmColumn.getTableColumnLink();
					if(link != null && mTable.getName().equals(link.getLinkedTable()))
					{
						mColumn = mTable.getColumn(link.getLinkedColumn());
					}
				}
				
				if(mColumn != null)
				{
					columnMap.put(mColumn,nmColumn);
				}
			}
			
			Entity nmEntity = nToNMrelation.getReferrer(nmClone.getName());
			if(nmEntity != null)
			{
				Entity mEntity = nToNMrelation.getOther(nmEntity);
				
				nmRelationColumns = nmClone.getColumns(nmEntity.getColumnNames());
				mRelationColumns = mTable.getColumns(mEntity.getColumnNames());
				
				for(int i = 0; i < mRelationColumns.length; i++)
				{
					columnMap.put(mRelationColumns[i],nmRelationColumns[i]);
				}
			}
			
			mTable.queryAndFill(mTable.getDefaultSelect());
			
			int cc;
			if(mRelationColumns != null && (cc = mRelationColumns.length) > 0)
			{
				int nmc = nmClone.getRowCount();
				
				int rc = mTable.getRowCount();
				Object[] mValues = new Object[cc];
				for(int r = 0; r < rc; r++)
				{
					for(int c = 0; c < cc; c++)
					{
						mValues[c] = mRelationColumns[c].getValueAt(r);
					}
					for(int nmr = 0; nmr < nmc; nmr++)
					{
						boolean equal = true;
						for(int c = 0; c < cc; c++)
						{
							Object nmValue = nmRelationColumns[c].getValueAt(nmr);
							if(!VirtualTable.equals(mValues[c],nmValue,
									nmRelationColumns[c].getType()))
							{
								equal = false;
								break;
							}
						}
						if(equal)
						{
							indices.add(r);
						}
					}
					
					Map map = new HashMap();
					for(VirtualTableColumn mColumn : columnMap.keySet())
					{
						VirtualTableColumn nmColumn = columnMap.get(mColumn);
						map.put(nmColumn.getName(),mColumn.getValueAt(r));
					}
					nmTable.addRow(map,false);
				}
			}
			
			return indices.toArray();
		}
	}
	
	
	static void getChanges(VirtualTable oldState, List newRows,
			List changed, List deleted)
	{
		int[] columnIndices = oldState.getColumnIndices(new ColumnSelector()
		{
			@Override
			public boolean select(VirtualTableColumn column)
			{
				return column.isPersistent() && !column.isAutoIncrement();
			}
		});
		
		int oc = oldState.getRowCount();
		for(int oi = 0; oi < oc; oi++)
		{
			VirtualTableRow oldRow = oldState.getRow(oi);
			Map values = new HashMap();
			for(int col : columnIndices)
			{
				values.put(oldState.getColumnName(col),oldRow.get(col));
			}
			KeyValues kv = new KeyValues(oldState,values);
			
			VirtualTableRow newRow = null;
			int nc = newRows.size();
			for(int ni = 0; ni < nc; ni++)
			{
				VirtualTableRow row = newRows.get(ni);
				if(kv.equals(row))
				{
					newRow = row;
					newRows.remove(ni);
					break;
				}
			}
			
			if(newRow == null)
			{
				deleted.add(oldRow);
			}
			else if(!equals(oldRow,newRow,columnIndices))
			{
				changed.add(newRow);
			}
		}
	}
	
	
	private static boolean equals(VirtualTableRow row1, VirtualTableRow row2, int[] indices)
	{
		for(int index : indices)
		{
			if(!VirtualTable.equals(row1.get(index),row2.get(index),row1.getVirtualTable()
					.getColumnAt(index).getType()))
			{
				return false;
			}
		}
		
		return true;
	}
	
	
	static void synchronize(VirtualTable table, List added,
			List changed, List deleted, DBConnection connection)
			throws DBException
	{
		VirtualTableColumn[] pk = table.getPrimaryKeyColumns();
		if(pk.length == 0)
		{
			throw new VirtualTableException(table,"Cannot synchronize: No primary key defined");
		}
		
		int[] pkIndices = table.getColumnIndices(pk);
		
		int[] colIndices = table.getPersistentColumnIndices();
		VirtualTableColumn[] columns = table.getColumnsAt(colIndices);
		
		Map statements = new LinkedHashMap();
		
		if(added != null)
		{
			for(VirtualTableRow row : added)
			{
				INSERT insert = new INSERT().INTO(table);
				List values = new ArrayList();
				
				for(int c = 0; c < columns.length; c++)
				{
					VirtualTableColumn column = columns[c];
					
					if(!column.isAutoIncrement())
					{
						insert.assign(column,"?");
						values.add(row.get(colIndices[c]));
					}
				}
				
				statements.put(new WriteRequest(insert,true,values.toArray()),row);
			}
		}
		
		if(changed != null)
		{
			for(VirtualTableRow row : changed)
			{
				UPDATE update = new UPDATE(table);
				List values = new ArrayList();
				
				for(int c = 0; c < columns.length; c++)
				{
					VirtualTableColumn cd = columns[c];
					update.SET(cd.toSqlField(),"?");
					values.add(row.get(colIndices[c]));
				}
				
				WHERE where = new WHERE();
				for(int pi = 0; pi < pk.length; pi++)
				{
					where.and(pk[pi].eq("?"));
					values.add(row.get(pkIndices[pi]));
				}
				update.WHERE(where);
				
				statements.put(new WriteRequest(update,false,values.toArray()),null);
			}
		}
		
		if(deleted != null)
		{
			for(VirtualTableRow row : deleted)
			{
				DELETE delete = new DELETE().FROM(table);
				List values = new ArrayList();
				
				WHERE where = new WHERE();
				for(int pi = 0; pi < pk.length; pi++)
				{
					where.and(pk[pi].eq("?"));
					values.add(row.get(pkIndices[pi]));
				}
				delete.WHERE(where);
				
				statements.put(new WriteRequest(delete,false,values.toArray()),null);
			}
		}
		
		boolean closeConnection = false;
		if(connection == null)
		{
			connection = DBUtils.getCurrentDataSource().openConnection();
			closeConnection = true;
		}
		
		try
		{
			for(WriteRequest statement : statements.keySet())
			{
				WriteResult result = statement.execute(connection);
				try
				{
					VirtualTableRow row = statements.get(statement);
					if(row != null && result.hasGeneratedKeys())
					{
						row.updateKeys(result.getGeneratedKeys());
					}
				}
				finally
				{
					result.close();
				}
			}
		}
		finally
		{
			if(closeConnection)
			{
				connection.close();
			}
		}
	}
	
	
	/**
	 * Adds a connection of specified detail row to a master row.
	 * 

* If the detail row that is already connected to the master, this row is * ignored. *

*

* This method writes changes back to database immediately. *

* * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterRow * a {@link VirtualTableRow} to which details are to be added * @param detailRow * a {@link VirtualTableRow} to be added. * @throws DBException * if database operation fails. */ public static void addDetail(VirtualTable masterDetailVT, VirtualTableRow masterRow, VirtualTableRow detailRow) throws DBException { addDetails(masterDetailVT,masterRow,new VirtualTableRow[]{detailRow}); } /** * Adds a connection of specified detail row to a master row. *

* If the detail row that is already connected to the master, this row is * ignored. *

*

* This method writes changes back to database immediately. *

* * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterRow * a {@link VirtualTableRow} to which details are to be added * @param detailRow * a {@link VirtualTableRow} to be added. * @param connection * a {@link DBConnection} instance for accessing the database * @throws DBException * if database operation fails. */ public static void addDetail(VirtualTable masterDetailVT, VirtualTableRow masterRow, VirtualTableRow detailRow, DBConnection connection) throws DBException { addDetails(masterDetailVT,masterRow,new VirtualTableRow[]{detailRow},connection); } /** * Adds a connection of specified detail rows within a VirtualTable to a * master row. *

* If the details contain rows that are already connected to the master, * those rows are ignored. *

*

* This method writes changes back to database immediately. *

* * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterVT * a {@link VirtualTable} containing a {@link VirtualTableRow} to * which details are to be added * @param detailVT * a {@link VirtualTable} containing {@link VirtualTableRow}s * that are to be added to the master row. * @throws DBException * if database operation fails. */ public static void addDetails(VirtualTable masterDetailVT, VirtualTable masterVT, VirtualTable detailVT) throws DBException { if(masterVT.getRowCount() != 1) { throw new IllegalStateException( "The masterVT has to contain exactly one record, but contains " + masterVT.getRowCount()); } VirtualTableRow masterRow = masterVT.getRow(0); VirtualTableRow[] rows = rowsFromVT(detailVT); addDetails(masterDetailVT,masterRow,rows); } /** * Adds a connection of specified detail rows within a VirtualTable to a * master row. *

* If the details contain rows that are already connected to the master, * those rows are ignored. *

*

* This method writes changes back to database immediately. *

* * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterVT * a {@link VirtualTable} containing a {@link VirtualTableRow} to * which details are to be added * @param detailVT * a {@link VirtualTable} containing {@link VirtualTableRow}s * that are to be added to the master row. * @param connection * a {@link DBConnection} instance for accessing the database * @throws DBException * if database operation fails. */ public static void addDetails(VirtualTable masterDetailVT, VirtualTable masterVT, VirtualTable detailVT, DBConnection connection) throws DBException { if(masterVT.getRowCount() != 1) { throw new IllegalStateException( "The masterVT has to contain exactly one record, but contains " + masterVT.getRowCount()); } VirtualTableRow masterRow = masterVT.getRow(0); VirtualTableRow[] rows = rowsFromVT(detailVT); addDetails(masterDetailVT,masterRow,rows,connection); } /** * Convenience method for * {@link #addDetails(VirtualTable, VirtualTableRow, VirtualTableRow[])}. *

* The masterVT has to contain exactly one row. *

*

* This method writes changes back to database immediately. *

* * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterRow * a {@link VirtualTableRow} to which details are to be added * @param detailVT * a {@link VirtualTable} containing {@link VirtualTableRow}s * that are to be added to the master row. * @throws DBException * if database operation fails. */ public static void addDetails(VirtualTable masterDetailVT, VirtualTableRow masterRow, VirtualTable detailVT) throws DBException { VirtualTableRow[] rows = rowsFromVT(detailVT); addDetails(masterDetailVT,masterRow,rows); } /** * Convenience method for * {@link #addDetails(VirtualTable, VirtualTableRow, VirtualTableRow[], DBConnection)} * . . *

* The masterVT has to contain exactly one row. *

*

* This method writes changes back to database immediately. *

* * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterRow * a {@link VirtualTableRow} to which details are to be added * @param detailVT * a {@link VirtualTable} containing {@link VirtualTableRow}s * that are to be added to the master row. * @param connection * a {@link DBConnection} instance for accessing the database * @throws DBException * if database operation fails. */ public static void addDetails(VirtualTable masterDetailVT, VirtualTableRow masterRow, VirtualTable detailVT, DBConnection connection) throws DBException { VirtualTableRow[] rows = rowsFromVT(detailVT); addDetails(masterDetailVT,masterRow,rows,connection); } /** * Adds a connection of a specified list of detail rows to a master row. *

* If the details contain rows that are already connected to the master, * those rows are ignored. *

*

* This method writes changes back to database immediately. *

* * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterRow * a {@link VirtualTableRow} to which details are to be added * @param detailRows * a list of {@link VirtualTableRow}s that are to be added to the * master row. * @throws DBException * if database operation fails. */ public static void addDetails(VirtualTable masterDetailVT, VirtualTableRow masterRow, VirtualTableRow[] detailRows) throws DBException { DBConnection connection = masterDetailVT.getDataSource().openConnection(); addDetails(masterDetailVT,masterRow,detailRows,connection); } /** * Adds a connection of a specified list of detail rows to a master row. *

* If the details contain rows that are already connected to the master, * those rows are ignored. *

*

* This method writes changes back to database immediately. *

* * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterRow * a {@link VirtualTableRow} to which details are to be added * @param detailRows * an array of {@link VirtualTableRow}s that are to be added to * the master row. * @param connection * a {@link DBConnection} instance for accessing the database * @throws DBException * if database operation fails. */ public static void addDetails(VirtualTable masterDetailVT, VirtualTableRow masterRow, VirtualTableRow[] detailRows, DBConnection connection) throws DBException { KeyValues masterKeyValues = MasterDetail.getForeignKeyValues(masterRow,masterDetailVT); List rowsToAdd = new ArrayList(); for(VirtualTableRow detailRow : detailRows) { if(detailRow != null) { KeyValues detailKeyValues = MasterDetail.getForeignKeyValues(detailRow, masterDetailVT); boolean alreadyExists = false; VirtualTable masterRowsFromDB = getMasterRowsFromDB(masterDetailVT,masterKeyValues); for(int i = 0; i < masterRowsFromDB.getRowCount(); i++) { VirtualTableRow existingRow = masterRowsFromDB.getRow(i); if(rowHasKeyValues(masterKeyValues,existingRow) && rowHasKeyValues(detailKeyValues,existingRow)) { alreadyExists = true; break; } } if(!alreadyExists) { VirtualTableRow nmRow = masterDetailVT.createRow(); setKeyValues(masterKeyValues,nmRow); setKeyValues(detailKeyValues,nmRow); rowsToAdd.add(nmRow); } } } ManyToMany.synchronize(masterDetailVT,rowsToAdd,Collections.EMPTY_LIST, Collections.EMPTY_LIST,connection); } /** * Removes all connections of a specified master row related to the * masterDetailVT. *

* This method writes changes back to database immediately. *

* * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterRow * a {@link VirtualTableRow} on which all details are to be * removed * @throws DBException * if database operation fails. */ public static void removeAllDetails(VirtualTable masterDetailVT, VirtualTableRow masterRow) throws DBException { removeDetails(masterDetailVT,masterRow,null,true); } /** * Removes all connections of a specified master row related to the * masterDetailVT. *

* This method writes changes back to database immediately. *

* * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterRow * a {@link VirtualTableRow} on which all details are to be * removed * @param connection * a {@link DBConnection} instance for accessing the database * @throws DBException * if database operation fails. */ public static void removeAllDetails(VirtualTable masterDetailVT, VirtualTableRow masterRow, DBConnection connection) throws DBException { removeDetails(masterDetailVT,masterRow,null,true,connection); } /** * Removes all connections of a specified master row related to the * masterDetailVT. * * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterVT * a {@link VirtualTable} with a {@link VirtualTableRow} on which * all details are to be removed * @throws DBException * if database operation fails. */ public static void removeAllDetails(VirtualTable masterDetailVT, VirtualTable masterVT) throws DBException { if(masterVT.getRowCount() != 1) { throw new IllegalStateException( "The masterVT has to contain exactly one record, but contains " + masterVT.getRowCount()); } VirtualTableRow masterRow = masterVT.getRow(0); removeDetails(masterDetailVT,masterRow,null,true); } /** * Removes all connections of a specified master row related to the * masterDetailVT. * * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterVT * a {@link VirtualTable} with a {@link VirtualTableRow} on which * all details are to be removed * @param connection * a {@link DBConnection} instance for accessing the database * @throws DBException * if database operation fails. */ public static void removeAllDetails(VirtualTable masterDetailVT, VirtualTable masterVT, DBConnection connection) throws DBException { if(masterVT.getRowCount() != 1) { throw new IllegalStateException( "The masterVT has to contain exactly one record, but contains " + masterVT.getRowCount()); } VirtualTableRow masterRow = masterVT.getRow(0); removeDetails(masterDetailVT,masterRow,null,true,connection); } /** * Removes the connection of a specified detail row to its master row. *

* If the details contain rows that are not connected to the master, this * method will silently ignore those rows. *

*

* This method writes changes back to database immediately. *

* * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterRow * a {@link VirtualTableRow} on which details are to be removed * @param detailRow * a {@link VirtualTableRow} on which details are to be removed * @throws DBException * if database operation fails. */ public static void removeDetail(VirtualTable masterDetailVT, VirtualTableRow masterRow, VirtualTableRow detailRow) throws DBException { removeDetails(masterDetailVT,masterRow,new VirtualTableRow[]{detailRow}); } /** * Removes the connection of a specified detail row to its master row. *

* If the details contain rows that are not connected to the master, this * method will silently ignore those rows. *

*

* This method writes changes back to database immediately. *

* * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterRow * a {@link VirtualTableRow} on which details are to be removed * @param detailRow * a {@link VirtualTableRow} on which details are to be removed * @param connection * a {@link DBConnection} instance for accessing the database * @throws DBException * if database operation fails. */ public static void removeDetail(VirtualTable masterDetailVT, VirtualTableRow masterRow, VirtualTableRow detailRow, DBConnection connection) throws DBException { removeDetails(masterDetailVT,masterRow,new VirtualTableRow[]{detailRow},connection); } /** * Removes the connection of specified detail rows within a VirtualTable to * its master row. *

* If the details contain rows that are not connected to the master, this * method will silently ignore those rows. *

*

* This method writes changes back to database immediately. *

* * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterRow * a {@link VirtualTableRow} on which details are to be removed * @param detailVT * a {@link VirtualTable} containing the {@link VirtualTableRow}s * to be removed. * @throws DBException * if database operation fails. */ public static void removeDetails(VirtualTable masterDetailVT, VirtualTableRow masterRow, VirtualTable detailVT) throws DBException { VirtualTableRow[] rows = rowsFromVT(detailVT); removeDetails(masterDetailVT,masterRow,rows); } /** * Gets an array of all rows from a {@link VirtualTable} * * @param vt * a {@link VirtualTable} * @return a array containing {@link VirtualTableRow}s */ private static VirtualTableRow[] rowsFromVT(VirtualTable vt) { int rowCount = vt.getRowCount(); VirtualTableRow[] rows = new VirtualTableRow[rowCount]; for(int i = 0; i < rowCount; i++) { rows[i] = vt.getRow(i); } return rows; } /** * Removes the connection of specified detail rows within a VirtualTable to * its master row. *

* If the details contain rows that are not connected to the master, this * method will silently ignore those rows. *

*

* This method writes changes back to database immediately. *

* * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterRow * a {@link VirtualTableRow} on which details are to be removed * @param detailVT * a {@link VirtualTable} containing the {@link VirtualTableRow}s * to be removed. * @param connection * a {@link DBConnection} instance for accessing the database * @throws DBException * if database operation fails. */ public static void removeDetails(VirtualTable masterDetailVT, VirtualTableRow masterRow, VirtualTable detailVT, DBConnection connection) throws DBException { VirtualTableRow[] rows = rowsFromVT(detailVT); removeDetails(masterDetailVT,masterRow,rows,connection); } /** * Convenience method for * {@link #removeDetails(VirtualTable, VirtualTableRow, VirtualTable)}. *

* The masterVT has to contain exactly one row. *

*

* This method writes changes back to database immediately. *

* * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterVT * a {@link VirtualTable} containing the {@link VirtualTableRow} * on which details are to be removed * @param detailVT * a {@link VirtualTable} containing the {@link VirtualTableRow}s * to be removed. * @throws DBException * if database operation fails. */ public static void removeDetails(VirtualTable masterDetailVT, VirtualTable masterVT, VirtualTable detailVT) throws DBException { if(masterVT.getRowCount() != 1) { throw new IllegalStateException( "The masterVT has to contain exactly one record, but contains " + masterVT.getRowCount()); } VirtualTableRow masterRow = masterVT.getRow(0); VirtualTableRow[] rows = rowsFromVT(detailVT); removeDetails(masterDetailVT,masterRow,rows); } /** * Convenience method for * {@link #removeDetails(VirtualTable, VirtualTableRow, VirtualTableRow[], DBConnection)} * . *

* The masterVT has to contain exactly one row. *

*

* This method writes changes back to database immediately. *

* * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterVT * a {@link VirtualTable} containing the {@link VirtualTableRow} * on which details are to be removed * @param detailVT * a {@link VirtualTable} containing the {@link VirtualTableRow}s * to be removed. * @param connection * a {@link DBConnection} instance for accessing the database * @throws DBException * if database operation fails. */ public static void removeDetails(VirtualTable masterDetailVT, VirtualTable masterVT, VirtualTable detailVT, DBConnection connection) throws DBException { if(masterVT.getRowCount() != 1) { throw new IllegalStateException( "The masterVT has to contain exactly one record, but contains " + masterVT.getRowCount()); } VirtualTableRow masterRow = masterVT.getRow(0); VirtualTableRow[] rows = rowsFromVT(detailVT); removeDetails(masterDetailVT,masterRow,rows,connection); } /** * Removes the connection of a specified list of detail rows to its master * row. *

* If the details contain rows that are not connected to the master, this * method will silently ignore those rows. *

*

* This method writes changes back to database immediately. *

* * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterRow * a {@link VirtualTableRow} on which details are to be removed * @param detailRows * a list of {@link VirtualTableRow}s that are to be removed from * the master row. * @throws DBException * if database operation fails. */ public static void removeDetails(VirtualTable masterDetailVT, VirtualTableRow masterRow, VirtualTableRow[] detailRows) throws DBException { removeDetails(masterDetailVT,masterRow,detailRows,false); } /** * Removes the connection of a specified list of detail rows to its master * row. *

* If the details contain rows that are not connected to the master, this * method will silently ignore those rows. *

*

* This method writes changes back to database immediately. *

* * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterRow * a {@link VirtualTableRow} on which details are to be removed * @param detailRows * a list of {@link VirtualTableRow}s that are to be removed from * the master row. * @param connection * a {@link DBConnection} instance for accessing the database * @throws DBException * if database operation fails. */ public static void removeDetails(VirtualTable masterDetailVT, VirtualTableRow masterRow, VirtualTableRow[] detailRows, DBConnection connection) throws DBException { removeDetails(masterDetailVT,masterRow,detailRows,false,connection); } /** * Removes the connection of a specified list of detail rows to its master * row. *

* If the details contain rows that are not connected to the master, this * method will silently ignore those rows. *

*

* This method writes changes back to database immediately. *

* * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterRow * a {@link VirtualTableRow} on which details are to be removed * @param detailRows * a list of {@link VirtualTableRow}s that are to be removed from * the master row. * @param deleteAllDetails * if set to {@code true} all details rows of the master get * removed * @throws DBException * if database operation fails. */ private static void removeDetails(VirtualTable masterDetailVT, VirtualTableRow masterRow, VirtualTableRow[] detailRows, boolean deleteAllDetails) throws DBException { DBConnection connection = masterDetailVT.getDataSource().openConnection(); removeDetails(masterDetailVT,masterRow,detailRows,deleteAllDetails,connection); } /** * Removes the connection of a specified list of detail rows to its master * row. *

* If the details contain rows that are not connected to the master, this * method will silently ignore those rows. *

*

* This method writes changes back to database immediately. *

* * @param masterDetailVT * the intermediary {@link VirtualTable} connecting master and * details. * @param masterRow * a {@link VirtualTableRow} on which details are to be removed * @param detailRows * a list of {@link VirtualTableRow}s that are to be removed from * the master row. * @param deleteAllDetails * if set to {@code true} all details rows of the master get * removed * @param connection * a {@link DBConnection} instance for accessing the database * @throws DBException * if database operation fails. */ private static void removeDetails(VirtualTable masterDetailVT, VirtualTableRow masterRow, VirtualTableRow[] detailRows, boolean deleteAllDetails, DBConnection connection) throws DBException { KeyValues masterKeyValues = MasterDetail.getForeignKeyValues(masterRow,masterDetailVT); VirtualTable masterRowsFromDB = getMasterRowsFromDB(masterDetailVT,masterKeyValues); List rowsToDelete = new ArrayList(); for(int i = 0; i < masterRowsFromDB.getRowCount(); i++) { VirtualTableRow row = masterRowsFromDB.getRow(i); boolean rowToBeDeleted = false; if(rowHasKeyValues(masterKeyValues,row)) { if(!deleteAllDetails) { for(VirtualTableRow detailRow : detailRows) { if(detailRow != null) { KeyValues detailKeyValues = MasterDetail.getForeignKeyValues(detailRow, masterRowsFromDB); if(rowHasKeyValues(detailKeyValues,row)) { rowToBeDeleted = true; break; } } } } else { rowToBeDeleted = true; } } if(rowToBeDeleted) { rowsToDelete.add(row); } } ManyToMany.synchronize(masterDetailVT,Collections.EMPTY_LIST,Collections.EMPTY_LIST, rowsToDelete,connection); } /** * Executes a query on the masterdetail table for querying the current rows * related to a specified master row. * * @param masterDetailVT * the masterdetail vt (not changed by this method call) * @param masterKeyValues * an instance of {@link KeyValues} for uniquely identifying the * master row. * @return a instance of {@link VirtualTable} containing the rows related to * the master row. * @throws DBException * if query fails. */ private static VirtualTable getMasterRowsFromDB(VirtualTable masterDetailVT, KeyValues masterKeyValues) throws DBException { VirtualTable masterDetailVTFromDB = masterDetailVT.clone(); SELECT select = masterDetailVTFromDB.getDefaultSelect(); WHERE where = new WHERE(); List values = new ArrayList(); masterKeyValues.appendCondition(where,values); Object[] params = values.toArray(new Object[values.size()]); select.WHERE(where); masterDetailVTFromDB.queryAndFill(select,params); return masterDetailVTFromDB; } /** * Checks if a {@link VirtualTableRow} matches the specified * {@link KeyValues}. * * @param keyValues * the {@link KeyValues} to match against. * @param nmRow * a {@link VirtualTableRow} to be checked. * @return {@code true} if row matches keyValues */ private static boolean rowHasKeyValues(KeyValues keyValues, VirtualTableRow nmRow) { boolean haveSame = true; for(String columnName : keyValues.getColumnNames()) { Object detailValue = keyValues.getValue(columnName); VirtualTable nmTable = nmRow.getVirtualTable(); Object nmColumnValue = nmRow.get(nmTable.getColumn(columnName)); if(!detailValue.equals(nmColumnValue)) { haveSame = false; break; } } return haveSame; } /** * Sets the specified {@link KeyValues} in a {@link VirtualTableRow}. * * @param keyValues * the {@link KeyValues} to be set * @param nmRow * the target {@link VirtualTableRow} to set the * {@link KeyValues} on. */ private static void setKeyValues(KeyValues keyValues, VirtualTableRow nmRow) { for(String columnName : keyValues.getColumnNames()) { Object value = keyValues.getValue(columnName); nmRow.set(columnName,value); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy