blob: 64edfd8ab10728a94db844ad65044a99f8c8890e [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2007 The University of Manchester
*
* Modifications to the initial code base are copyright of their
* respective authors, or their employers as appropriate.
*
* 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 2.1 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
******************************************************************************/
package net.sf.taverna.t2.partition;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
/**
* Subclass of Partition acting as the public access point for the partition
* structure. Exposes a TreeModel for use with a UI.
*
* @author Tom Oinn
*
* @param <ItemType>
* all items added to this partition must cast to this type
*/
public class RootPartition<ItemType extends Comparable> extends
Partition<ItemType, Object, Object> implements TreeModel {
// Used to track where we ended up putting items with the addOrUpdateItem
// method, this makes checking for duplicate items, reclassification and
// removal of partitions much faster at the expense of a few bytes of memory
private Map<ItemType, Partition<ItemType, ?, ?>> itemToLeafPartition;
private PropertyExtractorRegistry propertyExtractorRegistry;
private final SetModelChangeListener<ItemType> setChangeListener = new SetModelChangeListener<ItemType>() {
private List<Query<?>> queryList = new ArrayList<Query<?>>();
public void itemsWereAdded(Set<ItemType> newItems) {
for (ItemType item : newItems) {
addOrUpdateItem(item);
}
}
@SuppressWarnings("unchecked")
public void itemsWereRemoved(Set<Object> itemsRemoved) {
for (Object item : itemsRemoved) {
try {
removeItem((ItemType) item);
} catch (ClassCastException cce) {
// Obviously wasn't the right type of item but that means it
// couldn't have been added in the first place so we can
// safely ignore this.
}
}
}
public void addQuery(Query<?> query) {
queryList.add(query);
}
public List<Query<?>> getQueries() {
return queryList;
}
};
/**
* Build a new empty root partition with the specified list of partition
* algorithm implementations used to recursively allocate new data items to
* the sub-partitions below this one on addition
*
* @param pa
*/
public RootPartition(List<PartitionAlgorithm<?>> pa,
PropertyExtractorRegistry per) {
super(null, pa, null, null);
this.root = this;
this.propertyExtractorRegistry = per;
this.itemToLeafPartition = new HashMap<ItemType, Partition<ItemType, ?, ?>>();
}
/**
* The root partition comes with a convenience implementation of
* SetModelChangeListener which can be used to attach it to a compliant
* instance of SetModel (assuming the item types match). This allows the
* SetModel to act as the backing data store for the partition - as Query
* and the various subset / set union operators also implement this it
* provides a relatively simple mechanism to link multiple sets of data to
* this partition.
*/
public SetModelChangeListener<ItemType> getSetModelChangeListener() {
return this.setChangeListener;
}
/**
* Alter the list of partition algorithms which drive the construction of
* the partitions. Calling this effectively forces a complete rebuild of the
* tree structure which is an expensive operation so be careful when you use
* it.
*
* @param pa
* a new list of PartitionAlgorithmSPI instances to use as the
* basis for the partition structure.
*/
public synchronized void setPartitionAlgorithmList(
List<PartitionAlgorithm<?>> pa) {
if (pa.equals(this.partitionAlgorithms)) {
// At the least this checks for reference equality, although I'm not
// sure it does much more than that. TODO - replace this with a
// smarter check to see whether the list has really changed, doing a
// full re-build is expensive.
return;
}
// First create a copy of the keyset containing all the items we've
// added to this point.
Set<ItemType> itemsToAdd = new HashSet<ItemType>(itemToLeafPartition
.keySet());
this.partitionAlgorithms = pa;
this.children.clear();
this.itemToLeafPartition.clear();
treeStructureChanged(new TreeModelEvent(this, getTreePath()));
for (ItemType item : itemsToAdd) {
addOrUpdateItem(item);
}
sortChildPartitions();
}
/**
* Add a new item to the partition structure. If the item already exists
* this is interpreted as a request to reclassify according to properties
* which may have changed. This is not the same as a reclassification due to
* modification of the partition algorithm list, and just refers to the
* specified item. If the item exists already and its classification is
* changed the model will be notified with a removal event from the previous
* location and the item will be added as usual immediately afterwards.
*/
public synchronized void addOrUpdateItem(ItemType item) {
// First check whether the item is already stored
if (itemToLeafPartition.containsKey(item)) {
// request to reclassify item.
List<Partition<ItemType, ?, ?>> partitions = itemToLeafPartition
.get(item).getPartitionPath();
// partitions[i].getPartitionValue is the result of running
// getPartitionAlgorithms[i-1] on the item, we run through the array
// until either we hit the end (no reclassification) or the item
// classifies differently in which case we remove and re-add it.
for (int i = 1; i < partitions.size(); i++) {
PartitionAlgorithm<?> pa = getPartitionAlgorithms().get(
i - 1);
Object existingValue = partitions.get(i).getPartitionValue();
Object reclassifiedValue = pa.allocate(item,
getPropertyExtractorRegistry());
if (existingValue.equals(reclassifiedValue) == false) {
// Items classify differently, remove it
removeItem(item);
// ...and add it back again, forcing reclassification
super.addItem(item);
return;
}
}
// return as the item wasn't changed.
return;
} else {
// Value wasn't already in the map so we just add it as usual
super.addItem(item);
}
}
/**
* Remove an item from the partition structure, if this leaves any
* partitions with zero item count they are removed as well to keep things
* tidy.
*
* @param item
* the item to remove from the partition structure. If this isn't
* present in the structure this method does nothing along the
* lines of the collections API.
*/
public synchronized void removeItem(ItemType item) {
Partition<ItemType, ?, ?> partition = itemToLeafPartition.get(item);
if (partition != null) {
// Remove the item from the leaf partition
int previousIndex = partition.getMembers().indexOf(item);
TreePath pathToPartition = partition.getTreePath();
//partition.removeMember(item);
for (Partition<ItemType, ?, ?> parentPathElement : partition
.getPartitionPath()) {
// Notify path to root that the number of members
// has changed and it should update the renderers of
// any attached trees
treeNodesChanged(new TreeModelEvent(this, parentPathElement
.getTreePath()));
}
partition.removeMember(item);
treeNodesRemoved(new TreeModelEvent(this, pathToPartition,
new int[] { previousIndex }, new Object[] { item }));
// Traverse up the partition path and decrement the item count. If
// any item count becomes zero we mark this as a partition to
// remove, then at the end we remove the highest level one (so we
// only have to send a single delete event to the tree view)
for (Partition<ItemType, ?, ?> p : partition.getPartitionPath()) {
synchronized (p) {
p.itemCount--;
if (p.getItemCount() == 0 && p != this) {
// Can remove this node, all nodes after this will by
// definition have item count of zero. The exception is
// if this is the root node, in which case we just
// ignore it and move on to the next child. This avoids
// deleting the root, which is generally not a smart
// thing to do to a tree.
Partition<ItemType, ?, ?> parent = p.getParent();
int indexInParent = getIndexOfChild(parent, p);
parent.children.remove(indexInParent);
treeNodesRemoved(new TreeModelEvent(this, parent
.getTreePath(), new int[] { indexInParent },
new Object[] { p }));
break;
}
}
}
itemToLeafPartition.remove(item);
}
treeStructureChanged(new TreeModelEvent(this,getTreePath()));
}
/**
* Called by a leaf Partition when it has stored an item in its member set,
* used to keep track of where items have been stored to make removal more
* efficient.
*/
void itemStoredAt(ItemType item, Partition<ItemType, ?, ?> partition) {
itemToLeafPartition.put(item, partition);
}
// ---------------------//
// TreeModel interfaces //
// ---------------------//
private List<TreeModelListener> treeListeners = new ArrayList<TreeModelListener>();
@SuppressWarnings("unchecked")
public synchronized Object getChild(Object parent, int index) {
if (parent instanceof Partition) {
Partition<ItemType, ?, ?> p = (Partition<ItemType, ?, ?>) parent;
if (p.getMembers().isEmpty() == false) {
if (index < 0 || index >= p.getMembers().size()) {
return null;
} else {
return p.getMembers().get(index);
}
} else {
if (index < 0 || index >= p.getChildren().size()) {
return null;
} else {
return p.getChildren().get(index);
}
}
}
return null;
}
@SuppressWarnings("unchecked")
public synchronized int getChildCount(Object parent) {
if (parent instanceof Partition) {
Partition<ItemType, ?, ?> p = (Partition<ItemType, ?, ?>) parent;
if (p.getMembers().isEmpty() == false) {
return p.getMembers().size();
}
return p.getChildren().size();
}
return 0;
}
@SuppressWarnings("unchecked")
public synchronized int getIndexOfChild(Object parent, Object child) {
if (parent != null && child != null && parent instanceof Partition
&& child instanceof Partition) {
Partition<ItemType, ?, ?> p = (Partition<ItemType, ?, ?>) parent;
Partition<ItemType, ?, ?> c = (Partition<ItemType, ?, ?>) child;
if (p.root == c.root && p.root == this) {
// Parent and child must both be members of this tree structure
return p.getChildren().indexOf(child);
}
} else if (parent != null && child != null
&& parent instanceof Partition) {
Partition<ItemType, ?, ?> p = (Partition<ItemType, ?, ?>) parent;
return p.getMembers().indexOf(child);
}
return -1;
}
public Object getRoot() {
// The root partition is also the root of the tree model
return this;
}
public boolean isLeaf(Object node) {
// No leaves at the moment as we're only considering partitions which
// are by definition not leaves (the items within the last partition are
// but at the moment we're not including them in the tree model)
return (!(node instanceof Partition));
}
public void removeTreeModelListener(TreeModelListener l) {
treeListeners.remove(l);
}
public void addTreeModelListener(TreeModelListener l) {
if (treeListeners.contains(l) == false) {
treeListeners.add(l);
}
}
public void valueForPathChanged(TreePath path, Object newValue) {
// Ignore this, the tree values are never changed by the user in this
// implementation so we don't have to do anything
}
// -------------------------------------------------------//
// Tree event forwarding helper methods used by Partition //
// -------------------------------------------------------//
void treeNodesChanged(final TreeModelEvent e) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
for (TreeModelListener listener : new ArrayList<TreeModelListener>(
treeListeners)) {
listener.treeNodesChanged(e);
}
}
});
}
void treeNodesInserted(final TreeModelEvent e) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
for (TreeModelListener listener : new ArrayList<TreeModelListener>(
treeListeners)) {
listener.treeNodesInserted(e);
}
}
});
}
void treeNodesRemoved(final TreeModelEvent e) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
for (TreeModelListener listener : new ArrayList<TreeModelListener>(
treeListeners)) {
listener.treeNodesRemoved(e);
}
}
});
}
void treeStructureChanged(final TreeModelEvent e) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
for (TreeModelListener listener : new ArrayList<TreeModelListener>(
treeListeners)) {
listener.treeStructureChanged(e);
}
}
});
}
public PropertyExtractorRegistry getPropertyExtractorRegistry() {
return this.propertyExtractorRegistry;
}
}