package ls.graph;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import ls.graph.script.AlgorithmListener;
import ls.graph.script.GraphScriptEngine;
import ls.graph.script.GraphUIListener;
import ls.graph.script.ScriptableGraph;
import ls.graph.serialize.DefaultGraphReader;
import ls.graph.serialize.GraphReader;
import ls.graph.ui.GraphInternalFrame;
import ls.graph.ui.MainWindow;
import ls.graph.ui.dialogs.ScriptIncludesDialog;
import ls.util.Utils;
import edu.uci.ics.jung.graph.DirectedEdge;
import edu.uci.ics.jung.graph.DirectedGraph;
import edu.uci.ics.jung.graph.Edge;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.Vertex;
import edu.uci.ics.jung.graph.impl.DirectedSparseEdge;
import edu.uci.ics.jung.graph.impl.DirectedSparseGraph;
import edu.uci.ics.jung.graph.impl.DirectedSparseVertex;
import edu.uci.ics.jung.random.generators.GraphGenerator;
import edu.uci.ics.jung.random.generators.SimpleRandomGenerator;
/**
* A controller class.
* Its main function is to register listeners and pass messages (events).
* This class also holds the global configuration for the entire GraphScript workbench
*
* @author Lior
*
*/
public class Controller
{
/**
* Program initalization properties
*/
private static Properties initProps = new Properties();
// private ScriptableGraph graph = null;
private List<GraphController> graphControllers = new LinkedList<GraphController>();
private List<ControlListener> controlListeners = new LinkedList<ControlListener>();
private List<AlgorithmListener> algorithmListeners = new LinkedList<AlgorithmListener>();
private List<GraphUIListener> globalGraphUIListeners = new LinkedList<GraphUIListener>();
/**
* The main UI window
*/
private MainWindow mainWindow;
static
{
BufferedReader br = null;
try
{
File f = new File("graphscript.ini");
br = new BufferedReader(new FileReader(f));
initProps.load(br);
}
catch (IOException exn)
{
Utils.err("while loading ini file: " + exn.getMessage());
}
finally
{
try { if (br != null) br.close(); }
catch (IOException exn) { Utils.exn(exn); }
}
}
/**
* Return the currently selected graph, if any
* @return The graph object in the currently selected frame
*/
public Graph selectedGraph()
{
if (this.mainWindow == null) return null;
GraphInternalFrame gif = this.mainWindow.selectedGraphFrame();
if (gif == null) return null;
return gif.controller().graph();
}
/**
* Add a graph UI listener
* @param gul The listener to add
* @see GraphUIListener
*/
public void addGlobalGraphUIListener(GraphUIListener gul)
{
if (gul != null)
this.globalGraphUIListeners.add(gul);
}
/**
* Add an algorithm listener
* @param al The listener to add
* @see AlgorithmListener
*/
public void addAlgorithmListener(AlgorithmListener al)
{
if (al != null)
this.algorithmListeners.add(al);
}
/**
* Start the GraphScript workbench
*
*/
private void start()
{
String defScript = getDefaultScriptText();
mainWindow = new MainWindow(this,defScript);
initGraph();
mainWindow.setExtendedState(JFrame.MAXIMIZED_BOTH);
mainWindow.setVisible(true);
}
/**
* Add the new graph to the graphs
* A new window with a new graph will appear
* @param g The new graph loaded
*/
public void newGraph(ScriptableGraph g)
{
if (g == null)
{
Utils.err("Controller#newGraph: Graph is null");
return;
}
GraphInternalFrame newFrame = mainWindow.newGraphWindow(g);
GraphController gc = new GraphController(newFrame,this);
this.graphControllers.add(gc);
this.addControlListener(gc);
for (AlgorithmListener l : this.algorithmListeners)
gc.addAlgorithmListener(l);
int frameCount = this.graphControllers.size();
newFrame.setLocation(frameCount * DY_FRAME_LOCATION, frameCount * DX_FRAME_LOCATION);
newFrame.setTitle("Graph " + frameCount);
gc.frame().setVisible(true);
}
private static final int DX_FRAME_LOCATION = 10;
private static final int DY_FRAME_LOCATION = 10;
/**
* Add a control listener
* @param l The listener to add
* @see ControlListener
*/
public void addControlListener(ControlListener l)
{
this.controlListeners.add(l);
}
/**
* Raise a "graph changed" event for the given graph
* @param sg The changed graph
*/
void fireGraphChanged(ScriptableGraph sg)
{
for (ControlListener l : controlListeners)
l.graphChanged(sg);
}
/**
* Raise a "script loaded" event for the given script file
* @param f The script file loaded
* @param code The script code loaded from the file
*/
public void fireScriptLoaded(File f, String code)
{
if (f != null && code != null)
for (ControlListener l : controlListeners)
{
l.scriptLoaded(f, code);
}
}
/**
* Raise a "graph loaded" event
* @param f The file storing the graph information
*/
public void fireGraphLoaded(File f)
{
if (f != null)
for (ControlListener l : controlListeners)
l.graphLoaded(f);
}
/**
* Raise a "graph close" (the graph window is closed) event.
* @param sg The graph that is unloaded.
*/
void fireGraphUnloaded(ScriptableGraph sg)
{
if (sg != null)
for (ControlListener l : controlListeners)
l.graphUnloaded(sg);
}
/**
* Get the default script to load, if provided by the initial parameters
* @return the default script test
*/
private String getDefaultScriptText()
{
String defFilename = getProperty("DEFAULT_SCRIPT",null);
if (defFilename != null)
{
File f = new File(defFilename);
if (!f.isAbsolute())
{
String dir = getProperty("SCRIPTS_DIR",null);
if (dir == null)
return null;
else
dir = System.getProperty("user.dir") + dir;
f = new File(dir + File.separator + defFilename);
}
BufferedReader br = null;
StringBuffer sb = new StringBuffer();
try
{
br = new BufferedReader(new FileReader(f));
String line = br.readLine();
while (line != null)
{
sb.append(line).append('\n');
line = br.readLine();
}
}
catch (IOException exn)
{
Utils.exn(exn);
return null;
}
finally
{
try {if (br != null) br.close(); }
catch (IOException exn) { Utils.exn(exn); }
}
return sb.toString();
}
return null;
}
/**
* Set a given property to the inital properties collection
* @param key The property key
* @param value The value of the property
*/
public static void setProperty(String key,String value)
{
initProps.setProperty(key, value);
}
/**
* Get the value associated with the given key
* @param key The key to search
* @return The value associated with the given key
*/
public static String getProperty(String key)
{
String val = initProps.getProperty(key);
return val;
}
/**
* Get a list of all values for the given key
* @param key The key to look for
* @return All the values for this key
*/
public static List<String> getPropertyValues(String key)
{
String sValues = getProperty(key,"");
if ("".equals(sValues) || sValues == null)
return Arrays.asList(new String[] {}); //return empty list
return Arrays.asList(sValues.split(","));
}
/**
* Get the property value associated with the given key.
* @param key The key to look for
* @param defVal The default value to return in case there's no value for the given key
* @return The value for the given key of the default value supplied
*/
public static String getProperty(String key, String defVal)
{
String ret = getProperty(key);
return ret == null ? defVal : ret;
}
/**
* Get an icon based on the Controller class path
* @param path The relative path to the icon
* @return The new image icon instance
* @see Utils#getIcon(Class, String)
*/
public static ImageIcon getIcon(String path)
{
return Utils.getIcon(Controller.class, path);
}
/**
* Entry point to the program
* @param args
*/
public static void main(String[] args)
{
Controller m = new Controller();
m.start();
}
/**
* Create a new random graph. Initialize and display it
* @param nodeCount How many nodes to include in the graph
* @param edgeCount How many edges to create
* @param directed A flag indicating whether to create a directed graph or not
* @return The newly created graph
* @throws Exception
*/
public ScriptableGraph createNewRandomGraph(int nodeCount, int edgeCount,boolean directed)
throws Exception
{
GraphGenerator gGen = new SimpleRandomGenerator(nodeCount,edgeCount);
Graph g = (Graph)gGen.generateGraph();
ScriptableGraph sg = null;
if (directed)
{
DirectedGraph dg = convertToDirected(g);
sg = new ScriptableGraph(dg);
}
else
sg = new ScriptableGraph(g);
return sg;
}
/**
* Convert a given graph to a directed graph.
* @param g The graph to convert, must not be null
* @return A new instance of a directed graph
*/
@SuppressWarnings("unchecked")
private DirectedGraph convertToDirected(Graph g)
{
if (g == null) return null;
DirectedGraph dg = new DirectedSparseGraph();
Map<Vertex,DirectedSparseVertex> vMap = new HashMap<Vertex,DirectedSparseVertex>();
for (Vertex v : ((Set<Vertex>)g.getVertices()))
{
DirectedSparseVertex dv = new DirectedSparseVertex();
dv.importUserData(v);
vMap.put(v, dv);
dg.addVertex(dv);
}
for (Edge e : ((Set<Edge>)g.getEdges()))
{
Vertex v = (Vertex)(e.getEndpoints().getFirst());
DirectedSparseVertex dv1 = vMap.get(v);
v = (Vertex)(e.getEndpoints().getSecond());
DirectedSparseVertex dv2 = vMap.get(v);
if (dv1 == null || dv2 == null)
{
Utils.err("While converting to a directed graph - one of the vertices is missing");
continue;
}
else
{
DirectedEdge de = new DirectedSparseEdge(dv1,dv2);
dg.addEdge(de);
}
}
return dg;
}
/**
* Create a new graph from the given text representation of the graph
* @param gt The text representation of the graph
*/
public void setGraphFromText(String gt)
{
BufferedReader br = new BufferedReader(new StringReader(gt));
GraphReader gr = new DefaultGraphReader();
try
{
ScriptableGraph sg = gr.read(br);
newGraph(sg);
}
catch (IOException exn)
{
Utils.exn(exn);
}
}
public static void msg(String m)
{
Utils.msg("SYSTEM: " + m);
}
/**
* Initialize a random graph based on the initial properties
*
*/
private void initGraph()
{
int nc,ec;
try
{ nc = Integer.parseInt(getProperty("DEF_GRAPH_NODE_COUNT","5")); }
catch (NumberFormatException exn) {Utils.exn(exn); nc = 5;}
try
{ ec = Integer.parseInt(getProperty("DEF_GRAPH_EDGE_COUNT","8")); }
catch (NumberFormatException exn) {Utils.exn(exn); ec = 8;}
try
{
ScriptableGraph sg = createNewRandomGraph(nc,ec,false);
newGraph(sg);
}
catch (Exception exn)
{
Utils.exn(exn);
}
}
/**
* Get the currently selected Graph Script Engine
* @return The script engine of the current selected graph
*/
public GraphScriptEngine getCurrentGraphScriptEngine()
{
if (this.mainWindow == null) return null;
GraphInternalFrame gif = this.mainWindow.selectedGraphFrame();
if (gif == null) return null;
return gif.controller().scriptEngine();
}
private List<File> scriptIncludeFiles = null;
/**
* Set the given list as the list of script files to include when executing a script
* @param includes The list of include files
* @see #scriptIncludeFiles
*/
public void setScriptIncludes(List<File> includes)
{
if (this.scriptIncludeFiles == null)
this.scriptIncludeFiles = new ArrayList<File>(includes);
else
{
this.scriptIncludeFiles.clear();
this.scriptIncludeFiles.addAll(includes);
}
}
/**
* Retrieve the list of all script include files as set in the {@link ScriptIncludesDialog script settings dialog}
* @return The list of script include files
*/
public List<File> getScriptIncludeFiles()
{
if (scriptIncludeFiles == null)
return Collections.emptyList();
else
return scriptIncludeFiles;
}
}