package ch.rakudave.jnetmap.model;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import ch.rakudave.jnetmap.controller.Scheduler;
import ch.rakudave.jnetmap.controller.command.Command;
import ch.rakudave.jnetmap.controller.command.CommandHistory;
import ch.rakudave.jnetmap.model.MapEvent.Type;
import ch.rakudave.jnetmap.model.IF.LogicalIF;
import ch.rakudave.jnetmap.model.IF.NetworkIF;
import ch.rakudave.jnetmap.model.IF.PhysicalIF;
import ch.rakudave.jnetmap.model.IF.TransparentIF;
import ch.rakudave.jnetmap.model.device.Device;
import ch.rakudave.jnetmap.model.device.DeviceEvent;
import ch.rakudave.jnetmap.model.device.DeviceListener;
import ch.rakudave.jnetmap.plugins.Notifier;
import ch.rakudave.jnetmap.util.Crypto;
import ch.rakudave.jnetmap.util.IO;
import ch.rakudave.jnetmap.util.Lang;
import ch.rakudave.jnetmap.util.Settings;
import ch.rakudave.jnetmap.util.SkippingCollectionConverter;
import ch.rakudave.jnetmap.util.XStreamHelper;
import ch.rakudave.jnetmap.util.logging.Logger;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamConverter;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.algorithms.layout.StaticLayout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.MultiGraph;
import edu.uci.ics.jung.graph.SparseMultigraph;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.graph.util.Pair;
/**
* @author rakudave
*/
@XStreamAlias("Map")
public class Map implements MultiGraph<Device, Connection>, Graph<Device, Connection>, DeviceListener {
private Map _this = this;
@XStreamOmitField
private File mapFile;
@XStreamOmitField
private String password = "";
@XStreamOmitField
private CommandHistory history;
private int updateInterval = 5;
private Graph<Device, Connection> graph;
private Layout<Device, Connection> layout;
@XStreamOmitField
private List<MapListener> mapListeners;
@XStreamConverter(SkippingCollectionConverter.class)
private List<Notifier> statusListeners;
public Map() {
history = new CommandHistory(Settings.getInt("commandhistory.size", 20));
graph = new SparseMultigraph<Device, Connection>();
layout = new StaticLayout<Device, Connection>(this);
mapListeners = new LinkedList<MapListener>();
statusListeners = new LinkedList<Notifier>();
}
public boolean addMapListener(MapListener listener) {
if (listener == null) return false;
if (mapListeners == null) mapListeners = new LinkedList<MapListener>();
return mapListeners.add(listener);
}
public boolean removeMapListener(MapListener listener) {
if (listener == null || mapListeners == null) return false;
return mapListeners.remove(listener);
}
public boolean addStatusListener(Notifier listener) {
if (listener == null) return false;
if (statusListeners == null) statusListeners = new LinkedList<Notifier>();
return statusListeners.add(listener);
}
public boolean removeStatusListener(Notifier listener) {
if (listener == null || statusListeners == null) return false;
return statusListeners.remove(listener);
}
public List<Notifier> getStatusListeners() {
return statusListeners;
}
private void notifyListeners(final MapEvent e) {
Logger.trace("Received MapEvent "+e);
for (MapListener l : mapListeners) {
try {
l.mapChanged(e);
} catch (Exception ex) {
Logger.error("Unable to notify MapListener "+l, ex);
}
}
if (e.getType() == Type.DEVICE_EVENT) {
Logger.trace("Received DeviceEvent "+e);
Scheduler.execute(new Runnable() {
@Override
public void run() {
DeviceEvent ev = (DeviceEvent) e.getSubject();
if (ev.getType() == DeviceEvent.Type.STATUS_CHANGED
|| ev.getType() == DeviceEvent.Type.INTERFACE_STATUS_CHANGED) {
for (Notifier l : statusListeners) {
try {
l.statusChanged(ev, _this);
} catch (Exception e2) {
Logger.error("Unable to notify StatusListener "+l, e2);
}
}
}
}
});
}
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Map other = (Map) obj;
if (mapFile == null || other.mapFile == null) {
return false;
} else {
return mapFile.equals(other.mapFile);
}
}
public String getFileName() {
return (mapFile != null)?mapFile.getName().replace(".jnm", ""):Lang.getNoHTML("map.newmap");
}
public String getFilePath() {
return (mapFile != null)?mapFile.getAbsolutePath():Lang.getNoHTML("map.unsaved");
}
public Layout<Device, Connection> getGraphLayout() {
return layout;
}
/**
* @return the history
*/
public CommandHistory getHistory() {
if (history == null) history = new CommandHistory(Settings.getInt("commandhistory.size", 20));
return history;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((graph == null) ? 0 : graph.hashCode());
result = prime * result + ((mapFile == null) ? 0 : mapFile.hashCode());
return result;
}
public boolean isSaved() {
return getHistory().isSaved();
}
public boolean save() {
XStream xs = XStreamHelper.getXStream();
try {
String xml = xs.toXML(this);
if (password != null && !password.isEmpty()) {
xml = Crypto.encrypt(xml, password);
}
IO.copy(new ByteArrayInputStream(xml.getBytes("UTF-8")), new FileOutputStream(mapFile));
getHistory().setSaved(true);
return true;
} catch (Exception e) {
Logger.error("unable to save map to " + mapFile, e);
return false;
}
}
public boolean setFile(File f) {
if (f != null && f.exists() && f.canWrite()) {
mapFile = f.getAbsoluteFile();
return true;
} else {
return false;
}
}
public void setLayout(Layout<Device, Connection> layout) {
if (layout != null) this.layout = layout;
}
// Delegate-Methods
/**
* @param arg0
* @param arg1
* @param arg2
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#addEdge(java.lang.Object, java.util.Collection, edu.uci.ics.jung.graph.util.EdgeType)
*/
public boolean addEdge(final Connection arg0, final Collection<? extends Device> arg1, final EdgeType arg2) {
return addEdge(arg0, arg1); //ignore EdgeType, is always UNDIRECTED
}
/**
* @param arg0
* @param arg1
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#addEdge(java.lang.Object, java.util.Collection)
*/
public boolean addEdge(final Connection arg0, final Collection<? extends Device> arg1) {
return (Boolean) getHistory().execute(new Command() {
@Override
public Object undo() {
boolean b = graph.removeEdge(arg0);
notifyListeners(new MapEvent(_this, Type.EDGE_REMOVED, arg0));
return b;
}
@Override
public Object redo() {
boolean b = graph.addEdge(arg0, arg1);
notifyListeners(new MapEvent(_this, Type.EDGE_ADDED, arg0));
return b;
}
});
}
/**
* @param arg0
* @param arg1
* @param arg2
* @param arg3
* @return
* @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object, edu.uci.ics.jung.graph.util.EdgeType)
*/
public boolean addEdge(final Connection arg0, final Device arg1, final Device arg2, EdgeType arg3) {
return addEdge(arg0, arg1, arg2); // ignore EdgeType, is always UNDIRECTED
}
/**
* @param arg0
* @param arg1
* @param arg2
* @return
* @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object)
*/
public boolean addEdge(final Connection arg0, final Device arg1, final Device arg2) {
return (Boolean) getHistory().execute(new Command() {
@Override
public Object undo() {
arg1.removeInterface(arg1.getInterfaceFor(arg0));
arg2.removeInterface(arg2.getInterfaceFor(arg0));
boolean b = graph.removeEdge(arg0);
notifyListeners(new MapEvent(_this, Type.EDGE_REMOVED, arg0));
return b;
}
@Override
public Object redo() {
addInterfaceIfMissing(arg0, arg1, arg2);
addInterfaceIfMissing(arg0, arg2, arg1);
boolean b = graph.addEdge(arg0, arg1, arg2);
notifyListeners(new MapEvent(_this, Type.EDGE_ADDED, arg0));
return b;
}
});
}
private void addInterfaceIfMissing(Connection arg0, Device arg1, Device arg2) {
if (arg1.getInterfaceFor(arg0) == null) {
if (arg1.equals(arg2)) {
arg1.addInterface(new LogicalIF(arg1, arg0, ""));
} else {
String t = arg1.getType().toString() + "";
NetworkIF nif;
if (t.contains("Switch") || t.contains("Hub") || t.contains("Wireless")) {
nif = new TransparentIF(arg1, arg0, arg2.getInterfaceFor(arg0));
} else {
nif = new PhysicalIF(arg1, arg0, "");
}
arg1.addInterface(nif);
NetworkIF counterpart = arg2.getInterfaceFor(arg0);
if (counterpart != null && counterpart instanceof TransparentIF) {
((TransparentIF) counterpart).setCounterpart(nif);
}
}
}
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#addVertex(java.lang.Object)
*/
public boolean addVertex(final Device arg0) {
return (Boolean) getHistory().execute(new Command() {
@Override
public Object undo() {
boolean b = graph.removeVertex(arg0);
notifyListeners(new MapEvent(_this, Type.VERTEX_REMOVED, arg0));
arg0.removeDeviceListener(_this);
return b;
}
@Override
public Object redo() {
boolean b = graph.addVertex(arg0);
notifyListeners(new MapEvent(_this, Type.VERTEX_ADDED, arg0));
arg0.addDeviceListener(_this);
return b;
}
});
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#containsEdge(java.lang.Object)
*/
public boolean containsEdge(Connection arg0) {
return graph.containsEdge(arg0);
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#containsVertex(java.lang.Object)
*/
public boolean containsVertex(Device arg0) {
return graph.containsVertex(arg0);
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#degree(java.lang.Object)
*/
public int degree(Device arg0) {
return graph.degree(arg0);
}
/**
* @param arg0
* @param arg1
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#findEdge(java.lang.Object, java.lang.Object)
*/
public Connection findEdge(Device arg0, Device arg1) {
return graph.findEdge(arg0, arg1);
}
/**
* @param arg0
* @param arg1
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#findEdgeSet(java.lang.Object, java.lang.Object)
*/
public Collection<Connection> findEdgeSet(Device arg0, Device arg1) {
return graph.findEdgeSet(arg0, arg1);
}
/**
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#getDefaultEdgeType()
*/
public EdgeType getDefaultEdgeType() {
return graph.getDefaultEdgeType();
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Graph#getDest(java.lang.Object)
*/
public Device getDest(Connection arg0) {
return graph.getDest(arg0);
}
/**
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#getEdgeCount()
*/
public int getEdgeCount() {
return graph.getEdgeCount();
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#getEdgeCount(edu.uci.ics.jung.graph.util.EdgeType)
*/
public int getEdgeCount(EdgeType arg0) {
return graph.getEdgeCount(arg0);
}
/**
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#getEdges()
*/
public Collection<Connection> getEdges() {
return graph.getEdges();
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#getEdges(edu.uci.ics.jung.graph.util.EdgeType)
*/
public Collection<Connection> getEdges(EdgeType arg0) {
return graph.getEdges(arg0);
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#getEdgeType(java.lang.Object)
*/
public EdgeType getEdgeType(Connection arg0) {
return graph.getEdgeType(arg0);
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Graph#getEndpoints(java.lang.Object)
*/
public Pair<Device> getEndpoints(Connection arg0) {
return graph.getEndpoints(arg0);
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#getIncidentCount(java.lang.Object)
*/
public int getIncidentCount(Connection arg0) {
return graph.getIncidentCount(arg0);
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#getIncidentEdges(java.lang.Object)
*/
public Collection<Connection> getIncidentEdges(Device arg0) {
return graph.getIncidentEdges(arg0);
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#getIncidentVertices(java.lang.Object)
*/
public Collection<Device> getIncidentVertices(Connection arg0) {
return graph.getIncidentVertices(arg0);
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Graph#getInEdges(java.lang.Object)
*/
public Collection<Connection> getInEdges(Device arg0) {
return graph.getInEdges(arg0);
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#getNeighborCount(java.lang.Object)
*/
public int getNeighborCount(Device arg0) {
return graph.getNeighborCount(arg0);
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#getNeighbors(java.lang.Object)
*/
public Collection<Device> getNeighbors(Device arg0) {
return graph.getNeighbors(arg0);
}
/**
* @param arg0
* @param arg1
* @return
* @see edu.uci.ics.jung.graph.Graph#getOpposite(java.lang.Object, java.lang.Object)
*/
public Device getOpposite(Device arg0, Connection arg1) {
return graph.getOpposite(arg0, arg1);
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Graph#getOutEdges(java.lang.Object)
*/
public Collection<Connection> getOutEdges(Device arg0) {
return graph.getOutEdges(arg0);
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Graph#getPredecessorCount(java.lang.Object)
*/
public int getPredecessorCount(Device arg0) {
return graph.getPredecessorCount(arg0);
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Graph#getPredecessors(java.lang.Object)
*/
public Collection<Device> getPredecessors(Device arg0) {
return graph.getPredecessors(arg0);
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Graph#getSource(java.lang.Object)
*/
public Device getSource(Connection arg0) {
return graph.getSource(arg0);
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Graph#getSuccessorCount(java.lang.Object)
*/
public int getSuccessorCount(Device arg0) {
return graph.getSuccessorCount(arg0);
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Graph#getSuccessors(java.lang.Object)
*/
public Collection<Device> getSuccessors(Device arg0) {
return graph.getSuccessors(arg0);
}
/**
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#getVertexCount()
*/
public int getVertexCount() {
return graph.getVertexCount();
}
/**
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#getVertices()
*/
public Collection<Device> getVertices() {
return graph.getVertices();
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Graph#inDegree(java.lang.Object)
*/
public int inDegree(Device arg0) {
return graph.inDegree(arg0);
}
/**
* @param arg0
* @param arg1
* @return
* @see edu.uci.ics.jung.graph.Graph#isDest(java.lang.Object, java.lang.Object)
*/
public boolean isDest(Device arg0, Connection arg1) {
return graph.isDest(arg0, arg1);
}
/**
* @param arg0
* @param arg1
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#isIncident(java.lang.Object, java.lang.Object)
*/
public boolean isIncident(Device arg0, Connection arg1) {
return graph.isIncident(arg0, arg1);
}
/**
* @param arg0
* @param arg1
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#isNeighbor(java.lang.Object, java.lang.Object)
*/
public boolean isNeighbor(Device arg0, Device arg1) {
return graph.isNeighbor(arg0, arg1);
}
/**
* @param arg0
* @param arg1
* @return
* @see edu.uci.ics.jung.graph.Graph#isPredecessor(java.lang.Object, java.lang.Object)
*/
public boolean isPredecessor(Device arg0, Device arg1) {
return graph.isPredecessor(arg0, arg1);
}
/**
* @param arg0
* @param arg1
* @return
* @see edu.uci.ics.jung.graph.Graph#isSource(java.lang.Object, java.lang.Object)
*/
public boolean isSource(Device arg0, Connection arg1) {
return graph.isSource(arg0, arg1);
}
/**
* @param arg0
* @param arg1
* @return
* @see edu.uci.ics.jung.graph.Graph#isSuccessor(java.lang.Object, java.lang.Object)
*/
public boolean isSuccessor(Device arg0, Device arg1) {
return graph.isSuccessor(arg0, arg1);
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Graph#outDegree(java.lang.Object)
*/
public int outDegree(Device arg0) {
return graph.outDegree(arg0);
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#removeEdge(java.lang.Object)
*/
public boolean removeEdge(final Connection arg0) {
return (Boolean) getHistory().execute(new Command() {
private Pair<Device> arg1 = getEndpoints(arg0);
@Override
public Object undo() {
if (arg1.getFirst() != null) arg1.getFirst().addInterface(arg1.getFirst().getInterfaceFor(arg0));
if (arg1.getSecond() != null) arg1.getSecond().addInterface(arg1.getSecond().getInterfaceFor(arg0));
boolean b = graph.addEdge(arg0, arg1);
notifyListeners(new MapEvent(_this, Type.EDGE_ADDED, arg0));
return b;
}
@Override
public Object redo() {
if (arg1.getFirst() != null) arg1.getFirst().removeInterface(arg1.getFirst().getInterfaceFor(arg0));
if (arg1.getSecond() != null) arg1.getSecond().removeInterface(arg1.getSecond().getInterfaceFor(arg0));
boolean b = graph.removeEdge(arg0);
notifyListeners(new MapEvent(_this, Type.EDGE_REMOVED, arg0));
return b;
}
});
}
/**
* @param arg0
* @return
* @see edu.uci.ics.jung.graph.Hypergraph#removeVertex(java.lang.Object)
*/
public boolean removeVertex(final Device arg0) {
return (Boolean) getHistory().execute(new Command() {
HashMap<Pair<Device>, Connection> connectors = new HashMap<Pair<Device>, Connection>();
HashMap<Connection, NetworkIF> oppositeIFs = new HashMap<Connection, NetworkIF>();
@Override
public Object undo() {
boolean b = graph.addVertex(arg0);
for (Pair<Device> p : connectors.keySet()) {
Connection c = connectors.get(p);
graph.addEdge(c, p);
Device other = (p.getFirst().equals(arg0))?p.getSecond():p.getFirst();
other.addInterface(oppositeIFs.get(c));
}
notifyListeners(new MapEvent(_this, Type.VERTEX_ADDED, arg0));
arg0.addDeviceListener(_this);
return b;
}
@Override
public Object redo() {
for (Connection c : graph.getOutEdges(arg0)) {
Pair<Device> pair = graph.getEndpoints(c);
connectors.put(pair, c);
Device other = (pair.getFirst().equals(arg0))?pair.getSecond():pair.getFirst();
NetworkIF nif = other.getInterfaceFor(c);
oppositeIFs.put(c, nif);
other.removeInterface(nif);
}
boolean b = graph.removeVertex(arg0);
notifyListeners(new MapEvent(_this, Type.VERTEX_REMOVED, arg0));
arg0.removeDeviceListener(_this);
return b;
}
});
}
@Override
public void deviceChanged(DeviceEvent e) {
notifyListeners(new MapEvent(this, Type.DEVICE_EVENT, e));
}
public void setPassword(String password) {
if (password == null) return;
this.password = password;
}
public String getPassword() {
return password;
}
public void setUpdateInterval(int updateInterval) {
if (updateInterval <= 0) return;
this.updateInterval = updateInterval;
}
public int getUpdateInterval() {
return updateInterval;
}
public void refreshView() {
notifyListeners(new MapEvent(this, Type.REFRESH, null));
}
}