Files
secondo/Javagui/viewer/update2/RelationTableModel.java
2026-01-23 17:03:45 +08:00

741 lines
16 KiB
Java

//This file is part of SECONDO.
//Copyright (C) 2004, University in Hagen, Department of Computer Science,
//Database Systems for New Applications.
//SECONDO is free software; you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation; either version 2 of the License, or
//(at your option) any later version.
//SECONDO 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 General Public License for more details.
//You should have received a copy of the GNU General Public License
//along with SECONDO; if not, write to the Free Software
//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package viewer.update2;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import gui.idmanager.*;
import gui.SecondoObject;
import sj.lang.ListExpr;
import tools.Reporter;
import viewer.update2.*;
/**
* This class provides table data displayed in the RelationPanel.
*/
public class RelationTableModel extends AbstractTableModel
{
private Relation relation;
private String relationName;
private String[] columnNames = {"TupleId", "Name", "Value"};
private List<String> attributeNames;
private int tupleSize;
private int[] maxContentLengths;
private int state;
private boolean editable;
// contains changes in chronological order
private List<Change> changes;
// contains tuple ids for deletion in chronological order
private List<String> deletions;
// hits from latest search, ordered by tow and startindex
private List<SearchHit> hitList;
private int currHit;
public static final int COL_TUPLEID = 0;
public static final int COL_ATTRNAME = 1;
public static final int COL_ATTRVALUE = 2;
/**
* Constructor.
*/
public RelationTableModel(Relation pRelation, boolean pEditable) throws InvalidRelationException
{
if(pRelation==null || !pRelation.isInitialized())
{
throw(new InvalidRelationException());
}
this.relation = pRelation;
this.relationName = pRelation.getName();
this.tupleSize = pRelation.getTupleSize();
this.attributeNames = pRelation.getAttributeNames();
this.changes = new ArrayList<Change>();
this.deletions = new ArrayList<String>();
this.hitList = new ArrayList<SearchHit>();
this.state = pEditable? States.LOADED : States.LOADED_READ_ONLY;
Reporter.debug(this.toString());
}
/**
* Append specified change to change list for this relation.
*/
public void addChange(Change pChange)
{
if (!this.isReadOnly())
{
//Reporter.debug("RelationTableModel.addChange :" + pChange.toString());
this.changes.add(pChange);
}
}
/**
* Marks the tuple corresponding to the specified row index for deletion.
* Return true if it was not yet marked else false.
*/
public boolean addDeletion(int pRow)
{
if (!this.isReadOnly() && !isRowSeparator(pRow) )
{
String tupleid = (String)this.getValueAt(pRow, this.COL_TUPLEID);
if (!this.isRowDeleted(pRow))
{
this.deletions.add(tupleid);
//Reporter.debug("RelationTableModel.addDeletion: marked tuple " + tupleid);
return true;
}
//else Reporter.debug("RelationTableModel.addDeletion: already marked tuple " + tupleid);
}
return false;
}
/**
* Inserts a hit in the hitlist, if not already exists.
*/
public void addHit(SearchHit pHit)
{
if (!this.hitList.contains(pHit))
{
this.hitList.add(pHit);
Collections.sort(hitList);
}
}
/**
* Appends a tuple specified by the ListExpression
*/
public void addTuple(ListExpr pTuple)
{
try
{
Tuple tuple = this.relation.createEmptyTuple();
tuple.readValueFromLE(pTuple);
this.relation.addTuple(tuple);
fireTableRowsInserted(0, this.getRowsPerTuple());
}
catch (InvalidRelationException e)
{
Reporter.debug(e);
Reporter.writeError("RelationTableModel.addTuple: invalid LE is " + pTuple.toString());
}
}
/**
* Removes all changes. Returns true if any changes existed.
*/
public boolean clearChanges()
{
if (this.changes != null && !this.changes.isEmpty())
{
this.changes.clear();
}
return false;
}
/**
* Removes all deletions. Returns true if any deletions existed.
*/
public boolean clearDeletions()
{
if (this.hasDeletions())
{
this.deletions.clear();
return true;
}
return false;
}
/*
* Returns all changes in chronological order.
*/
public List<Change> getChanges()
{
return this.changes;
}
/**
* Returns a list of changes, that contain newest value and original value of each changed cell.
*/
public List<Change> getChangesForReset()
{
List<Change> result = new ArrayList<Change>();
Map<Integer, HashMap<String, Change>> updateTuples = new HashMap<Integer, HashMap<String, Change>>();
for (Integer i : updateTuples.keySet())
{
for (String s : updateTuples.get(i).keySet())
{
result.add(updateTuples.get(i).get(s));
}
}
return result;
}
/*
* Returns only valid (=last change of attribute) changes mapped by tuple index and attribute name.
* Changes include original values.
*/
public Map<Integer, HashMap<String, Change>> getChangesForUpdate()
{
Map<Integer, HashMap<String, Change>> result = new HashMap<Integer, HashMap<String, Change>>();
Integer tid;
String attrName;
HashMap<String, Change> mapTuple;
for (Change ch : this.changes)
{
tid = ch.getTupleIndex();
attrName = ch.getAttributeName();
mapTuple = result.get(tid);
if (mapTuple == null)
{
mapTuple = new HashMap<String, Change>();
}
mapTuple.put(attrName, ch);
if (!mapTuple.isEmpty())
{
Change chOld = mapTuple.get(attrName);
if (chOld == null && ch.changesSameObject(chOld))
{
ch.setOldValue(chOld.getOldValue());
}
mapTuple.put(attrName, ch);
}
result.put(tid, mapTuple);
}
// only debugging
for (Integer i : result.keySet())
{
for (String s : result.get(i).keySet())
{
//Reporter.debug("RelationPanel.getUpdateTuples result is :" + result.get(i).get(s).toString());
}
}
return result;
}
/**
* Method of interface AbstractTableModel.
*
*/
public Class getColumnClass(int pCol)
{
return getValueAt(0, pCol).getClass();
}
/**
* Returns a List of String representations of all the values in the specified column.
*/
public List<String> getColumnContent(int pCol)
{
List<String> result = new ArrayList<String>();
for (int i = 0; i < this.relation.getTupleCount() * this.getRowsPerTuple(); i++)
{
Object o = this.getValueAt(i, pCol);
if (o== null)
{
Reporter.debug("RelationPanel.getColumnContent: null object at tupleindex=" + i + ", columnindex=" + pCol);
}
result.add(o.toString());
}
return result;
}
/*
* Method of interface AbstractTableModel.
*/
public int getColumnCount()
{
return columnNames.length;
}
/*
* Method of interface AbstractTableModel.
*/
public String getColumnName(int pCol)
{
return columnNames[pCol];
}
/*
* Method of interface AbstractTableModel.
*/
public String[] getColumnNames()
{
return columnNames;
}
public int getCurrentHitIndex()
{
return this.currHit;
}
public List<String> getDeletions()
{
return this.deletions;
}
/**
* Returns SearchHit with specified index in this relation.
*/
public SearchHit getHit(int pIndex)
{
if (pIndex >= 0 && this.hitList != null && pIndex < this.hitList.size())
{
return this.hitList.get(pIndex);
}
return null;
}
/**
* Returns number of all search hits
*/
public int getHitCount()
{
if (this.hitList != null)
{
return this.hitList.size();
}
return 0;
}
/**
* Returns all SearchHits for the specified row.
*/
public List<SearchHit> getHits(int pRow)
{
List<SearchHit> result = new ArrayList<SearchHit>();
if (this.hasSearchHits())
{
for (SearchHit hit : this.hitList)
{
if (hit.getRowIndex() == pRow)
{
result.add(hit);
}
}
}
return result;
}
/**
* Returns newest change.
*/
public Change getLastChange()
{
if (this.hasChanges())
{
return this.changes.get(this.changes.size()-1);
}
return null;
}
/**
* Returns the relation name.
*/
public String getRelationName()
{
return this.relationName;
}
public int getRow(String pTupleId, String pAttributeName)
{
int tupleIndex = this.relation.getTupleIndex(pTupleId);
int attributeIndex = this.relation.getAttributeNames().indexOf(pAttributeName);
int result = tupleIndex*this.getRowsPerTuple() + attributeIndex +1;
return result;
}
/*
* Method of interface AbstractTableModel.
*/
public int getRowCount()
{
// add (empty) extra row as separator between tuples
return this.relation.getTupleCount() * this.getRowsPerTuple();
}
public int getRowsPerTuple()
{
return (this.tupleSize+1);
}
/**
* Returns state.
* Used in TableCellEditor/-Renderer.
* @see viewer.update2.States
*/
public int getState()
{
return this.state;
}
/*
* Method of interface AbstractTableModel.
* Returns String representation of Tuple ID for column 1,
* String representation of attribute name for column 2,
* formatted String representation of attribute value for column 3.
*/
public Object getValueAt(int pRow, int pCol)
{
String result = " ";
// if this is not a Separator Row
if(!this.isRowSeparator(pRow))
{
// get Tuple value
int tupleIndex = this.rowToTupleIndex(pRow);
int attrIndex = this.rowToAttributeIndex(pRow);
Tuple tuple = this.relation.getTupleAt(tupleIndex);
// get value
switch (pCol)
{
case COL_TUPLEID:
result = tuple.getID();
break;
case COL_ATTRNAME:
result = this.attributeNames.get(attrIndex);
break;
case COL_ATTRVALUE:
result = tuple.getValueAt(attrIndex);
break;
default:
result = "Fehler";
}
}
//Reporter.debug("RelationTableModel.getValueAt(" + pRow + ", " + pCol + ": " + result);
return result;
}
public boolean hasChanges()
{
return (this.changes != null && !this.changes.isEmpty());
}
public boolean hasDeletions()
{
return (this.deletions != null && !this.deletions.isEmpty());
}
public boolean hasSearchHits()
{
return (this.hitList != null && !this.hitList.isEmpty());
}
/*
* Method of interface AbstractTableModel.
* Don't need to implement this method unless your table's
* editable.
*/
public boolean isCellEditable(int pRow, int pCol)
{
if (this.state == States.UPDATE || this.state == States.INSERT)
{
int attrIndex = this.rowToAttributeIndex(pRow);
if (attrIndex < 0)
{
return false;
}
String attrName = this.relation.getAttributeNames().get(attrIndex);
if (attrIndex != this.relation.getTypeInfo().getTidIndex()
&& !this.relation.isAttributeReadOnly(attrName))
{
return (pCol == COL_ATTRVALUE && !this.isRowSeparator(pRow));
}
}
return false;
}
/**
* Returns true if there are uncommitted changes for specified table cell.
*/
public boolean isCellChanged(int pRow, int pCol)
{
for (Change ch : this.changes)
{
if (ch.getRowIndex() == pRow)
{
return true;
}
}
return false;
}
/**
* Returns true if relation is read-only (no edit function)
*/
public boolean isReadOnly()
{
//Reporter.debug("RelationTableModel.isReadOnly: " + (this.state == States.LOADED_READ_ONLY));
return (this.state == States.LOADED_READ_ONLY);
}
/**
* Returns true if the tuple this row belongs to has been marked for deletion.
*/
public boolean isRowDeleted(int pRow)
{
if (!this.isRowSeparator(pRow))
{
String tupleid = (String)this.getValueAt(pRow, this.COL_TUPLEID);
if (tupleid != null && (tupleid.length() > 0) && this.deletions.contains(tupleid))
{
return true;
}
}
return false;
}
/**
* Returns true if rowIndex specifies a separator row (empty row between tuples).
*/
private boolean isRowSeparator(int pRow)
{
return (pRow % this.getRowsPerTuple() == 0);
}
/**
* Removes change at specified index from change history list.
*/
public Change removeChange(int pIndex)
{
//Reporter.debug("RelationTableModel.removeChange :" + pIndex);
Change result = null;
if (this.hasChanges() && pIndex < this.changes.size())
{
return (Change)this.changes.remove(this.changes.size()-1);
}
return result;
}
/**
* Removes specified change and returns TRUE if it existed.
*/
public boolean removeChange(Change pChange)
{
//Reporter.debug("RelationTableModel.removeChange :" + pChange.toString());
boolean result = false;
if (this.hasChanges())
{
result = this.changes.remove(pChange);
}
return result;
}
/**
* Removes specified SearchHit and returns TRUE if it existed.
*/
public boolean removeHit(SearchHit pHit)
{
//Reporter.debug("RelationTableModel.removeHit :" + pHit.toString());
boolean result = false;
if (this.hasSearchHits())
{
int delIndex = this.hitList.indexOf(pHit);
result = this.hitList.remove(pHit);
if (this.currHit > delIndex || this.currHit >= this.getHitCount())
{
this.currHit--;
}
}
return result;
}
/**
* Removes last deletion from deletion history list, if any.
* Returns TRUE if deletion existed.
*/
public boolean removeLastDeletion()
{
if (this.hasDeletions())
{
this.deletions.remove(this.deletions.size()-1);
return true;
}
return false;
}
/**
* Removes Tuple from Relation and notifies table of deleted rows.
*/
public void removeTuple(String pTupleId)
{
this.relation.removeTupleByID(pTupleId);
fireTableRowsDeleted(0, this.getRowsPerTuple()-1);
}
public int rowToTupleIndex(int pRow)
{
return (pRow / this.getRowsPerTuple());
}
public int rowToAttributeIndex(int pRow)
{
return ((pRow % this.getRowsPerTuple()) - 1);
}
/**
* Builds map for searchhits by row index.
* Resets index for currently displayed searchhit.
*/
public void setSearchHits(List<SearchHit> pHitlist)
{
if (pHitlist == null || pHitlist.isEmpty())
{
this.hitList.clear();
this.currHit = -1;
}
else
{
this.hitList = pHitlist;
this.currHit = 0;
}
}
/**
* Sets the hit index if valid.
*/
public void setCurrentHitIndex(int pIndex)
{
if (pIndex >= 0 && pIndex < this.hitList.size())
{
this.currHit = pIndex;
}
}
/*
* Method of interface AbstractTableModel.
* Change table data.
*/
@Override
public void setValueAt(Object pValue, int pRow, int pCol)
{
//Reporter.debug("RelationTableModel.setValueAt: " + pRow + ", " + pCol);
if(!this.isReadOnly() && isCellEditable(pRow, pCol))
{
int tupleIndex = this.rowToTupleIndex(pRow);
int attrIndex = this.rowToAttributeIndex(pRow);
this.relation.setValueAt(tupleIndex, attrIndex, (String)pValue);
fireTableCellUpdated(pRow, pCol);
}
}
/**
* Sets the state.
* Does not change state if model was initialized with States.LOADED_READ_ONLY.
* @see viewer.update2.States
*/
public void setState(int pState)
{
if (!this.isReadOnly())
{
this.state = pState;
}
}
public String toString()
{
StringBuffer sb = new StringBuffer("[RelationTableModel]: ");
sb.append(", relationName: ").append(relationName);
sb.append(", tupleCount: ").append(this.relation.getTupleCount());
sb.append(", tupleSize: ").append(tupleSize);
sb.append(this.relation.toString());
return sb.toString();
}
}