blob: e1e58199e32a7cbf0c0e95aee85bb97f26b8cb21 [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.Collections;
import java.util.Comparator;
import java.util.List;
import javax.swing.event.TreeModelEvent;
import javax.swing.tree.TreePath;
/**
* A partition represents a set of items which can be exclusively classified
* into one or more distinct subsets along with the algorithm to perform this
* subset operation.
*
* @author Tom Oinn
*
* @param <ItemType>
* all items in the underlying set of which this is a subset are
* instances of this type or can be cast to it according to
* conventional java language rules.
* @param <PartitionValueType>
* the partition value type used by the parent partition algorithm to
* create this partition object. As an example, if this partition
* object represented all those entries with a particular host
* institution this would be a String or possibly URL.
* @param <ChildPartitionValueType>
* the partition value type used by this partition's partition
* algorithm to create sub-partitions, it's used in the signature
* here because ordering of children is done based on these values.
* Any child partition will have a getPartitionValue return type
* cast-able to this type.
*/
public class Partition<ItemType extends Comparable, PartitionValueType, ChildPartitionValueType> {
// A comparator operating over the value type of the child partitions and
// used to order them as created or to re-order on a change of this property
private Comparator<ChildPartitionValueType> childPartitionOrder = null;
// Back reference to the root partition of which this is a direct or
// indirect sub-partition. If this *is* the root partition this points to
// self
protected RootPartition<ItemType> root;
// A subset of the parent's member set containing items which have been
// allocated to this partition by the parent's partition algorithm. The
// partition is specified by the partitionValue field.
private List<ItemType> members;
// The parent partition of which this is a subset
private Partition<ItemType, ?, PartitionValueType> parent;
// Specification of the partition in terms of the parent's partitioning
// algorithm
private PartitionValueType partitionValue;
// List of partitioning algorithms to be applied to this subset to create
// further partitions, the algorithm at index 0 is the one used for this
// partition, all others are passed in to the constructors for
// sub-partitions
protected List<PartitionAlgorithm<?>> partitionAlgorithms;
// An initially empty list of sub-partitions created by the head element of
// the partition algorithm list
protected List<Partition<ItemType, ChildPartitionValueType, ?>> children;
// Path from this node back to the root, initialised on first access and
// cached
private List<Partition<ItemType, ?, ?>> partitionPath = null;
// For leaf partitions this is equal to the number of items in the member
// set, for all other partitions it is the sum of the item count of all
// child partitions
protected int itemCount = 0;
/**
* Construct a new Partition, this is used by the RootPartition and by this
* class to construct the recursively sub-divided partition structure based
* on the rules encapsulated by the partition algorithm list.
*
* @param parent
* parent partition of which this is a subset
* @param pa
* partition algorithm list, with the algorithm used to create
* child partitions of this one at position 0, if this list is
* empty this is a leaf partition.
* @param root
* reference to the RootPartition acting as the externally
* visible front-end to this structure
* @param pv
* the value which the parent's partition algorithm has assigned
* to this partition. This must be interpreted in the context of
* the parent's first partition algorithm for display and other
* purposes
*/
protected Partition(Partition<ItemType, ?, PartitionValueType> parent,
List<PartitionAlgorithm<?>> pa, RootPartition<ItemType> root,
PartitionValueType pv) {
this.root = root;
this.members = new ArrayList<ItemType>();
this.parent = parent;
this.partitionValue = pv;
this.partitionAlgorithms = pa;
this.children = new ArrayList<Partition<ItemType, ChildPartitionValueType, ?>>();
}
/**
* Return the number of items below this node in the partition tree; in the
* case of leaf partitions this is the number of items in the member set,
* for non-leaf partitions it is the sum of the item count for all immediate
* child partitions.
*/
public int getItemCount() {
return this.itemCount;
}
/**
* Sub-partitions of this partition are ordered based on a comparator over
* the child partition value type.
*
* @return a comparator over child partition value types, or null if no
* comparator has been specified (by default this returns null)
*/
public Comparator<ChildPartitionValueType> getChildPartitionOrder() {
return this.childPartitionOrder;
}
/**
* Set a comparator for child partition ordering - if the supplied
* comparator is different to the current one this will also trigger a
* re-order of all child partitions and corresponding events in the root
* partition's tree view. In the current implementation this is the very
* broad 'tree structure changed' event for this node in the tree view.
*
* @param order
* a new comparator to order child partitions
*/
public void setChildPartitionOrder(Comparator<ChildPartitionValueType> order) {
if (!order.equals(childPartitionOrder)) {
childPartitionOrder = order;
sortChildPartitions();
}
}
/**
* Return the parent partition of which this is a sub-partition, or null if
* this is the root partition.
*/
public Partition<ItemType, ?, PartitionValueType> getParent() {
return this.parent;
}
/**
* The parent partition created this partition based on a particular value
* of a property of the members of the sub-partition. This returns that
* value, and is the result returned from the parent partition's first
* partition algorithm when run on all members of this partition or its
* direct or indirect sub-partitions.
*/
public PartitionValueType getPartitionValue() {
return this.partitionValue;
}
@Override
public String toString() {
if (getParent() != null) {
// query type
String string = this.getParent().getPartitionAlgorithms().get(0)
.toString();
// result of query
String string2 = this.partitionValue.toString();
return string2 + " (" + getItemCount() + ")";
} else {
// This is for a root partition, loop through its children to return
// the correct number when running a new query
int items = 0;
for (Partition child : children) {
items = items + child.getItemCount();
}
String queryType = getPartitionAlgorithms().get(0).toString();
// return "Activities which match query = " + getItemCount();
return "Available activities (" + items + ")";
//+ ", query by "
// + queryType;
}
}
/**
* Return a list of Partition objects from the root (at index 0) to this
* node at the final position in the list. Computes the first time then
* caches, as it should be impossible for this to be modified without
* recreation of the entire structure from scratch.
*/
public synchronized List<Partition<ItemType, ?, ?>> getPartitionPath() {
if (partitionPath == null) {
List<Partition<ItemType, ?, ?>> al = new ArrayList<Partition<ItemType, ?, ?>>();
Partition<ItemType, ?, ?> activePartition = this;
al.add(activePartition);
while (activePartition.getParent() != null) {
al.add(0, activePartition.getParent());
activePartition = activePartition.getParent();
}
partitionPath = al;
}
return partitionPath;
}
/**
* If this is a leaf partition, defined as one with an empty list of
* partition algorithms, then this method returns the set of all items which
* have been classified as belonging to this leaf partition. For non-leaf
* partitions it will return an empty set.
*/
public final List<ItemType> getMembers() {
return Collections.unmodifiableList(this.members);
}
/**
* The list of partition algorithms applicable to this node (at index 0) and
* subsequent downstream sub-partitions of it. If this is empty then the
* partition is a leaf partition.
*/
public final List<PartitionAlgorithm<?>> getPartitionAlgorithms() {
return Collections.unmodifiableList(partitionAlgorithms);
}
/**
* Sub-partitions of this partition defined by the partition algorithm at
* index 0 of the list. If this is a leaf partition this will always be
* empty.
*/
public final List<Partition<ItemType, ChildPartitionValueType, ?>> getChildren() {
return Collections.unmodifiableList(children);
}
/**
* Inject an item into this partition, if there are partition algorithms in
* the partition algorithm list (i.e. this is not a leaf) this will
* recursively call the same method on child partitions or create new
* partitions where there are none that match the value from the partition
* algorithm. If this is a leaf partition the item is added to the member
* set. The list is sorted when adding an item and it is inserted in the
* appropriate place
*
* @param item
* the item to add to the partition structure.
*/
@SuppressWarnings("unchecked")
protected synchronized void addItem(ItemType item) {
if (partitionAlgorithms.isEmpty()) {
// itemCount = 0;
// Allocate directly to member set, no further partitioning
members.add(item);
Collections.sort(members);
int indexOf = members.indexOf(item);
// root.treeNodesInserted(new TreeModelEvent(this, getTreePath(),
// new int[] { members.size() - 1 }, new Object[] { item }));
root.treeNodesInserted(new TreeModelEvent(this, getTreePath(),
new int[] { indexOf }, new Object[] { item }));
// Increment item count for all partitions in the partition path
for (Partition<ItemType, ?, ?> p : getPartitionPath()) {
synchronized (p) {
p.itemCount++;
root.treeNodesChanged(new TreeModelEvent(this, p
.getTreePath()));
}
}
// Cache the storage of this item to this partition in the root
// partition for more efficient removal if required (saves having to
// search the entire partition tree, although that wouldn't be too
// painful if it was required this is faster at the cost of a few
// bytes of memory)
root.itemStoredAt(item, this);
// TODO - when the tree model covers items on the leaf nodes we'll
// want to message it here as well.
} else {
PartitionAlgorithm<ChildPartitionValueType> pa;
pa = (PartitionAlgorithm<ChildPartitionValueType>) partitionAlgorithms
.get(0);
ChildPartitionValueType pvalue = pa.allocate(item, root
.getPropertyExtractorRegistry());
// FIXME not sure how to do this since you seem to have to add the
// items to the partition if you want to then search them again,
// maybe you need a non-showing partition or something?
// //if it is a failed search then don't bother adding to the
// partition
// if (pvalue.toString().equalsIgnoreCase("No match")) {
// return;
// }
// See if there's a partition with this value already in the child
// partition list
for (Partition<ItemType, ChildPartitionValueType, ?> potentialChild : children) {
if (potentialChild.getPartitionValue().equals(pvalue)) {
potentialChild.addItem(item);
return;
}
}
// If not we have to create a new sub-partition
List<PartitionAlgorithm<?>> tail = new ArrayList<PartitionAlgorithm<?>>();
for (int i = 1; i < partitionAlgorithms.size(); i++) {
tail.add(partitionAlgorithms.get(i));
}
Partition<ItemType, ChildPartitionValueType, ?> newPartition = new Partition(
this, tail, this.root, pvalue);
// Insert the new partition in the correct place according to the
// comparator currently installed, or at the end if none exists or
// the list is empty
if (childPartitionOrder == null || children.isEmpty()) {
children.add(newPartition);
root.treeNodesInserted(new TreeModelEvent(this, getTreePath(),
new int[] { children.indexOf(newPartition) },
new Object[] { newPartition }));
} else {
boolean foundIndex = false;
for (int i = 0; i < children.size(); i++) {
ChildPartitionValueType existingPartitionValue = children
.get(i).getPartitionValue();
if (childPartitionOrder.compare(pvalue,
existingPartitionValue) < 0) {
children.add(i, newPartition);
root.treeNodesInserted(new TreeModelEvent(this,
getTreePath(), new int[] { i },
new Object[] { newPartition }));
if (i != 0) {
root.treeStructureChanged(new TreeModelEvent(this,
getTreePath()));
}
foundIndex = true;
break;
}
}
if (!foundIndex) {
// Fallen off the end of the array without finding something
// with greater index than the new partition so we add it at
// the
// end (by definition this is the largest value according to
// the
// comparator)
children.add(newPartition);
root.treeNodesInserted(new TreeModelEvent(this,
getTreePath(), new int[] { children
.indexOf(newPartition) },
new Object[] { newPartition }));
}
}
// Add the item to the new partition to trigger creation of any
// sub-partitions required
newPartition.addItem(item);
}
}
/**
* Remove an item from the member set
*
* @param item
* the item to remove
*/
protected void removeMember(ItemType item) {
this.members.remove(item);
}
/**
* Re-order the child partitions based on the comparator, if no comparator
* has been defined this method does nothing. Tree structure changed
* messages are fired from this node in the tree view if the comparator is
* defined even if no nodes have been changed (lazy but not too much of an
* issue I suspect)
*/
protected synchronized final void sortChildPartitions() {
if (this.childPartitionOrder == null) {
// Can't do anything unless the comparator is set appropriately
return;
}
Comparator<Partition<ItemType, ChildPartitionValueType, ?>> comparator = new Comparator<Partition<ItemType, ChildPartitionValueType, ?>>() {
public int compare(
Partition<ItemType, ChildPartitionValueType, ?> o1,
Partition<ItemType, ChildPartitionValueType, ?> o2) {
// FIXME is this really safe to do? It's fairly specific to our
// case. Doesn't seem very generic
if (o1.getPartitionValue().toString().equalsIgnoreCase(
"no value")) {
// No value so put it to the end
return 1;
}
return childPartitionOrder.compare(o1.getPartitionValue(), o2
.getPartitionValue());
}
};
Collections.<Partition<ItemType, ChildPartitionValueType, ?>> sort(
children, comparator);
// Message the root that the node structure under this node has changed
// (this is a bit lazy and we could almost certainly be more clever here
// as the nodes have been removed and added to re-order them)
root.treeStructureChanged(new TreeModelEvent(this, getTreePath()));
}
/**
* Return a TreePath object with this node as the final entry in the path
*/
protected final synchronized TreePath getTreePath() {
return new TreePath(getPartitionPath().toArray());
}
// public void sortItems() {
// System.out.println("sorting the items");
// synchronized (members) {
// List<ItemType> oldOrder = new ArrayList<ItemType>(members);
// Collections.sort(oldOrder);
//
// for (ItemType item : oldOrder) {
// removeMember(item);
// }
// for (ItemType item : oldOrder) {
// addItem(item);
// }
// }
//
// }
}