package uk.ac.rhul.cs.cl1.ui.cytoscape;
import giny.model.Edge;
import giny.model.Node;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Iterator;
import java.util.WeakHashMap;
import uk.ac.rhul.cs.utils.ObjectUtils;
import uk.ac.rhul.cs.utils.Pair;
import uk.ac.rhul.cs.utils.UniqueIDGenerator;
import cytoscape.CyNetwork;
import cytoscape.Cytoscape;
import cytoscape.data.CyAttributes;
/**
* Class holding ClusterONE's own representations of Cytoscape networks.
*
* This class encapsulates a weak hash map mapping {@link CyNetwork} instances to
* {@link Graph} instances.
*
* Whenever a Cytoscape network is processed by ClusterONE, it will check whether
* a corresponding ClusterONE representation already exists in this hash map. If
* so, the network representation is not created again. The hash map does not stop
* Java from freeing the memory associated to the Cytoscape network when the network
* is discarded by Cytoscape.
*
* A desirable behaviour would be that a network listener is registered on all
* Cytoscape networks that have a corresponding entry in the cache so the cached
* entry would be invalidated immediately when the network is modified. Unfortunately
* this does not seem possible with Cytoscape at the moment (2.6.3), therefore the
* cache entry is invalidated manually before starting a whole clustering process
* on a network, but not when the local cluster of a given node is explored in
* the interactive mode.
*/
public class CyNetworkCache implements PropertyChangeListener {
/** Internal weak hash map used as a storage area */
WeakHashMap<CyNetwork, Pair<String, Graph> > storage =
new WeakHashMap<CyNetwork, Pair<String, Graph> >();
/**
* Constructor
*/
public CyNetworkCache() {
Cytoscape.getDesktop().getSwingPropertyChangeSupport()
.addPropertyChangeListener(Cytoscape.NETWORK_MODIFIED, this);
Cytoscape.getDesktop().getSwingPropertyChangeSupport()
.addPropertyChangeListener(Cytoscape.NETWORK_DESTROYED, this);
Cytoscape.getSwingPropertyChangeSupport().addPropertyChangeListener(this);
}
/**
* Returns the ClusterONE representation of the given {@link CyNetwork}.
*
* If the {@link CyNetwork} was not seen before by this instance, a network
* listener will be registered on the {@link CyNetwork} to ensure that the
* cache entry is invalidated when the {@link CyNetwork} changes.
*
* @param network the network being converted
* @param weightAttr name of the attribute that will be used for edge
* weights. null means that the network is unweighted.
* @throws NonNumericAttributeException when a non-numeric weight attribute
* was used
*/
public Graph convertCyNetworkToGraph(CyNetwork network, String weightAttr)
throws NonNumericAttributeException {
Pair<String, Graph> attrNameAndGraph = storage.get(network);
if (attrNameAndGraph != null &&
ObjectUtils.equals(weightAttr, attrNameAndGraph.getLeft()))
return attrNameAndGraph.getRight();
Graph graph = new Graph();
UniqueIDGenerator<Node> nodeIdGen = new UniqueIDGenerator<Node>(graph);
CyAttributes edgeAttrs = Cytoscape.getEdgeAttributes();
Double weight;
/* Import all the edges into our graph */
try {
Iterator<?> it = network.edgesIterator();
while (it.hasNext()) {
Edge edge = (Edge)it.next();
int src = nodeIdGen.get(edge.getSource());
int dest = nodeIdGen.get(edge.getTarget());
if (src == dest)
continue;
weight = (weightAttr == null) ? null :
(Double)edgeAttrs.getAttribute(edge.getIdentifier(), weightAttr);
if (weight == null)
weight = 1.0;
graph.createEdge(src, dest, weight);
}
} catch (ClassCastException ex) {
throw new NonNumericAttributeException(weightAttr);
}
graph.setNodeMapping(nodeIdGen.getReversedList());
this.storage.put(network, Pair.create(weightAttr, graph));
return graph;
}
/**
* Returns the ClusterONE representation of the given {@link CyNetwork}.
*
* If the {@link CyNetwork} was not seen before by this instance, a network
* listener will be registered on the {@link CyNetwork} to ensure that the
* cache entry is invalidated when the {@link CyNetwork} changes.
*
* The network will be created using the currently selected edge weight
* attribute from the control panel. If the control panel is hidden,
* the result is null.
*
* @param network the network being converted
* @throws NonNumericAttributeException when a non-numeric weight attribute
* was used
*/
public Graph convertCyNetworkToGraph(CyNetwork network)
throws NonNumericAttributeException {
ControlPanel panel = ControlPanel.getShownInstance();
if (panel == null)
return convertCyNetworkToGraph(network, null);
return convertCyNetworkToGraph(network, panel.getWeightAttributeName());
}
/**
* Invalidates the cached representation of the given {@link CyNetwork}
*/
public void invalidate(CyNetwork network) {
this.storage.remove(network);
}
/**
* Called when a network is changed or destroyed. Invalidates the network
* from the cache.
*/
public void propertyChange(PropertyChangeEvent e) {
if (e.getPropertyName().equals(Cytoscape.NETWORK_MODIFIED)) {
this.invalidate((CyNetwork)e.getNewValue());
} else if (e.getPropertyName().equals(Cytoscape.NETWORK_DESTROYED)) {
String value = (String)e.getNewValue();
for (CyNetwork network: storage.keySet()) {
if (network.getTitle().equals(value))
this.invalidate(network);
}
}
}
}