package uk.ac.rhul.cs.cl1.ui.cytoscape3;
import java.util.WeakHashMap;
import org.cytoscape.model.CyEdge;
import org.cytoscape.model.CyNetwork;
import org.cytoscape.model.CyNode;
import org.cytoscape.model.CyRow;
import org.cytoscape.model.events.NetworkAboutToBeDestroyedEvent;
import org.cytoscape.model.events.NetworkAboutToBeDestroyedListener;
import uk.ac.rhul.cs.utils.ObjectUtils;
import uk.ac.rhul.cs.utils.Pair;
import uk.ac.rhul.cs.utils.UniqueIDGenerator;
/**
* 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.
*/
public class CyNetworkCache implements NetworkAboutToBeDestroyedListener {
/** Internal weak hash map used as a storage area */
WeakHashMap<CyNetwork, Pair<String, Graph> > storage =
new WeakHashMap<CyNetwork, Pair<String, Graph> >();
/** The ClusterONE plugin app within Cytoscape that owns this cache */
private ClusterONECytoscapeApp app;
// --------------------------------------------------------------------
// Constructors
// --------------------------------------------------------------------
public CyNetworkCache(ClusterONECytoscapeApp app) {
this.app = app;
app.registerService(this, NetworkAboutToBeDestroyedListener.class);
// TODO: need to listen for events when new nodes/edges are added to a network,
// nodes/edges are removed from a network, or attributes are modified
}
// --------------------------------------------------------------------
// Properties
// --------------------------------------------------------------------
// --------------------------------------------------------------------
// Query methods
// --------------------------------------------------------------------
// --------------------------------------------------------------------
// Manipulation methods
// --------------------------------------------------------------------
/**
* 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(network);
UniqueIDGenerator<CyNode> nodeIdGen = new UniqueIDGenerator<CyNode>(graph);
Double weight;
/* Import all the edges into our graph */
try {
for (CyEdge edge: network.getEdgeList()) {
int src = nodeIdGen.get(edge.getSource());
int dest = nodeIdGen.get(edge.getTarget());
if (src == dest)
continue;
if (weightAttr == null) {
weight = null;
} else {
CyRow row = network.getRow(edge);
weight = row.get(weightAttr, Double.class, 1.0);
}
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}.
*
* 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 = app.getControlPanel();
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) {
if (network != null) {
this.storage.remove(network);
}
}
// --------------------------------------------------------------------
// Event handlers
// --------------------------------------------------------------------
/**
* Called when a network is destroyed in Cytoscape.
*/
public void handleEvent(NetworkAboutToBeDestroyedEvent event) {
invalidate(event.getNetwork());
}
// --------------------------------------------------------------------
// Private methods
// --------------------------------------------------------------------
}