The most recent version of ReactICS GUI.

This commit is contained in:
Marcin Piątkowski
2025-05-19 23:25:12 +02:00
parent fafe8dbe07
commit b6d34723f2
13 changed files with 3936 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: pl.umk.mat.martinp.reactics.ReacticsGUI

View File

@@ -0,0 +1,666 @@
package pl.umk.mat.martinp.reactics;
import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.algorithms.layout.StaticLayout;
import edu.uci.ics.jung.algorithms.layout.util.Relaxer;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.Context;
import edu.uci.ics.jung.graph.util.Pair;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
import edu.uci.ics.jung.visualization.decorators.EdgeShape;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.picking.PickedState;
import edu.uci.ics.jung.visualization.renderers.Renderer;
import org.apache.commons.collections15.Transformer;
import org.w3c.dom.Document;
import javax.swing.*;
import javax.swing.border.EtchedBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.io.PrintWriter;
import java.util.*;
public class ContextAutomatonEditor extends JPanel {
private enum EditorState {STATE, EDGE, DELETE, CLEAR, NONE}
private static final long serialVersionUID = 1L;
//---------------------------------------------------------------
// Visual configuration settings
//---------------------------------------------------------------
private static final Color pickedNodeColor = Color.yellow;
//private static final Color pickedEdgeColor = Color.red;
private static final Color pickedEdgeColor = Color.black;
private static final Color edgeColor = Color.black;
private static final Color emptyEdgeColor = Color.lightGray;
private static final Font nodeLabelFont = new Font("Helvetica", Font.BOLD, 20);
private static final Font edgeLabelFont = new Font("Helvetica", Font.BOLD, 15);
private static final Font toolTipFont = new Font("Helvetica", Font.PLAIN, 14);
private static final float[] dash = {10.0f, 10.0f};
private static final Stroke basicEdgeStroke = new BasicStroke(3.0f,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND);
private static final Stroke emptyEdgeStroke = new BasicStroke(3.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 10.0f, dash, 0.f);
private static final Stroke basicEdgeArrow = new BasicStroke(3.0f);
private static final Stroke emptyEdgeArrow = new BasicStroke(3.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 10.0f, dash, 10.0f);
private static Dimension initialEditorSize;
private VisualizationViewer<CAState, CAEdge> graphViewer;
private Layout<CAState, CAEdge> gLayout;
DefaultModalGraphMouse graphMouse;
private ContextAutomatonGraph graph;
// Context menus for transitions, places and edges
private JPopupMenu stateContextMenu;
private JPopupMenu edgeContextMenu;
private TransitionEditor transitionEditor = null;
private EditorState edtState;
private Vector<EditorModeButton> modeButtons;
private CAState lastPickedNode = null;
public ContextAutomatonEditor(Dimension size) {
this.setLayout(new BorderLayout());
initialEditorSize = size;
edtState = EditorState.NONE;
graph = new ContextAutomatonGraph();
createGraphEditor();
createModeButtonsPanel();
createContextMenus();
}
public boolean isModified() { return graph.isModified(); }
public void clearModificationStatus() { graph.clearModificationStatus(); }
public String toRSSLString() {
return graph.toRSSLString();
}
public void exportToXML(PrintWriter output) {
updateNodesLocations();
graph.exportToXML(output);
}
public void loadFromXML(Document input) throws ContextAutomatonStructureError {
clear();
graph.loadFromXML(input);
}
public void clear() {
graph.clear();
for (EditorModeButton emb : modeButtons)
emb.setSelected(false);
graphViewer.repaint();
}
public void lockNodesPositions(boolean lock) {
if (lock)
graphMouse.setMode(ModalGraphMouse.Mode.TRANSFORMING);
else
graphMouse.setMode(ModalGraphMouse.Mode.PICKING);
}
public Set<String> getReactantsSet() { return graph.getReactantsSet(); }
//--------------------------------------------------------------------------
// Creates visualization viewer for context automaton graph display/edition
//--------------------------------------------------------------------------
private void createGraphEditor() {
gLayout = new StaticLayout<CAState, CAEdge>(graph,
new Transformer<CAState, Point2D>() {
public Point2D transform(CAState node) {
return node.getLocation();
}
});
graphViewer = new VisualizationViewer<CAState, CAEdge>(gLayout, initialEditorSize) {
public JToolTip createToolTip() {
JToolTip tooltip = super.createToolTip();
tooltip.setFont(toolTipFont);
return tooltip;
}
};
// Nodes labels
graphViewer.getRenderContext().setVertexLabelTransformer(
new ToStringLabeller<CAState>());
graphViewer.getRenderer().getVertexLabelRenderer()
.setPosition(Renderer.VertexLabel.Position.CNTR);
graphViewer.getRenderContext().setVertexFontTransformer(
new Transformer<CAState,Font>() {
public Font transform(CAState v) {
return nodeLabelFont;
}
});
// Nodes colour, background, etc.
graphViewer.getRenderContext().setVertexIconTransformer(new Transformer<CAState,Icon>() {
public Icon transform(final CAState v) {
Color nodeColor = null;
if(graphViewer.getPickedVertexState().isPicked(v))
nodeColor = pickedNodeColor;
else
nodeColor = v.getFillColor();
return (v.isInitial() ? new SquareIcon(nodeColor, v.getWidth()) : new CircleIcon(nodeColor, v.getWidth()));
}});
// Edge color, shape and weight
graphViewer.getRenderContext().setEdgeDrawPaintTransformer(new Transformer<CAEdge, Paint>() {
public Paint transform(CAEdge e) {
return edgeColor;
}
});
graphViewer.getRenderContext().setEdgeStrokeTransformer(new Transformer<CAEdge, Stroke>() {
public Stroke transform(CAEdge e) {
return basicEdgeStroke;
}
});
graphViewer.getRenderContext().setEdgeArrowStrokeTransformer(new Transformer<CAEdge, Stroke>() {
public Stroke transform(CAEdge e) {
return basicEdgeArrow;
}
});
graphViewer.getRenderContext().setArrowFillPaintTransformer(new Transformer<CAEdge, Paint>() {
public Paint transform(CAEdge caEdge) {
if (caEdge.hasTransition())
return edgeColor;
else
return emptyEdgeColor;
}
});
graphViewer.getRenderContext().setEdgeShapeTransformer(new ConditionalEdgeShape<>(1.5f));
// Edge labels
graphViewer.getRenderContext().setEdgeLabelTransformer(new ToStringLabeller<CAEdge>());
graphViewer.getRenderContext().getEdgeLabelRenderer().setRotateEdgeLabels(true);
graphViewer.getRenderContext().setLabelOffset(20);
graphViewer.getRenderContext().setEdgeFontTransformer(new Transformer<CAEdge, Font>() {
public Font transform(CAEdge CAEdge) {
return edgeLabelFont;
}
});
// For interactive graph editing
graphMouse = new DefaultModalGraphMouse();
graphViewer.setGraphMouse(graphMouse);
graphMouse.setMode(ModalGraphMouse.Mode.PICKING);
graphViewer.setVertexToolTipTransformer(new Transformer<CAState, String>() {
public String transform(CAState caState) {
return caState.getTooltipText();
}
});
graphViewer.setEdgeToolTipTransformer(new Transformer<CAEdge, String>() {
public String transform(CAEdge caEdge) {
return caEdge.getTooltipText();
}
});
graphViewer.setBackground(Color.white);
graphViewer.setBorder(new EtchedBorder(EtchedBorder.LOWERED));
graphViewer.addMouseListener(new EditorMouseListener());
add(graphViewer,BorderLayout.CENTER);
}
//---------------------------------------------------------------
// Create panel with buttons alowing editing mode choice
//---------------------------------------------------------------
private void createModeButtonsPanel() {
modeButtons = new Vector<EditorModeButton>();
modeButtons.add(new EditorModeButton("STATE",new CircleIcon(CAState.baseFillColor, 15), EditorState.STATE, 0));
modeButtons.add(new EditorModeButton("EDGE", EditorState.EDGE, 1));
modeButtons.add(new EditorModeButton("DELETE", EditorState.DELETE, 2));
modeButtons.add(new EditorModeButton("CLEAR", EditorState.CLEAR, 3));
ModeButtonsListener mbListener = new ModeButtonsListener();
for(int i=0;i<modeButtons.size();++i)
modeButtons.elementAt(i).addActionListener(mbListener);
JPanel btnPanel = new JPanel();
btnPanel.setLayout(new FlowLayout());
for(int i=0;i<modeButtons.size();++i)
btnPanel.add(modeButtons.elementAt(i));
JCheckBox posLockCBox = new JCheckBox("Lock relative nodes positions");
posLockCBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
lockNodesPositions(posLockCBox.isSelected());
}
});
btnPanel.add(posLockCBox);
add(btnPanel,BorderLayout.NORTH);
}
//---------------------------------------------------------------
// Create context menus for transitions, places and edges
//---------------------------------------------------------------
private void createContextMenus() {
stateContextMenu = new JPopupMenu();
JMenuItem stateInitialItem = new JMenuItem("Set as initial");
stateInitialItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
PickedState<CAState> pickedVertexState = graphViewer.getPickedVertexState();
Set<CAState> pickedNodes = pickedVertexState.getPicked();
Iterator<CAState> it = pickedNodes.iterator();
if(!it.hasNext()) {
return;
}
CAState editedNode = it.next();
pickedVertexState.clear();
graph.setInitialState(editedNode);
}
});
JMenuItem setLabelItem = new JMenuItem("Set label");
setLabelItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
PickedState<CAState> pickedVertexState = graphViewer.getPickedVertexState();
Set<CAState> pickedNodes = pickedVertexState.getPicked();
Iterator<CAState> it = pickedNodes.iterator();
if(!it.hasNext()) {
return;
}
CAState state = it.next();
pickedVertexState.clear();
showStateLabelEditDialog(state);
}
});
stateContextMenu.add(stateInitialItem);
stateContextMenu.add(setLabelItem);
stateContextMenu.setVisible(false);
edgeContextMenu = new JPopupMenu();
JMenuItem edgeLabelEditItem = new JMenuItem("Nondeterministic transitions");
edgeLabelEditItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
PickedState<CAEdge> pickedEdgeState = graphViewer.getPickedEdgeState();
Set<CAEdge> pickedEdges = pickedEdgeState.getPicked();
Iterator<CAEdge> it = pickedEdges.iterator();
if(!it.hasNext()) {
return;
}
CAEdge editedEdge = it.next();
pickedEdgeState.clear();
showEdgeLabelEditDialog(editedEdge);
}
});
edgeContextMenu.add(edgeLabelEditItem);
edgeContextMenu.setVisible(false);
transitionEditor = new TransitionEditor();
}
private void showStateLabelEditDialog(CAState state) {
String idRegex = "[a-zA-Z][a-zA-Z_0-9:-]*";
boolean idOk = false;
do {
String newLabel = JOptionPane.showInputDialog(null, "State label", state.getLabel());
if (newLabel == null)
return;
newLabel = newLabel.trim();
if (!newLabel.matches(idRegex)) {
JOptionPane.showMessageDialog(this,
"<html>State label should start with a letter and may contain only:<br/>" +
"letters, numbers, colons (:), underscores (_) and hyphens (-)</html>",
"Error", JOptionPane.ERROR_MESSAGE);
}
else {
//TODO: State description should be unique
idOk = true;
state.setLabel(newLabel);
}
}
while (!idOk);
graph.markAsModified();
this.repaint();
}
//---------------------------------------------------------------
// Creates a simple dialog window for editing edge labels
//---------------------------------------------------------------
private void showEdgeLabelEditDialog(CAEdge edge) {
Pair<CAState> endPoints = graph.getEndpoints(edge);
transitionEditor.showTransitionEditDialog(this, edge.getTransitions(), endPoints.getFirst().getLabel(), endPoints.getSecond().getLabel());
graph.markAsModified();
graphViewer.repaint();
}
/** Updates coordinates stored in each graph node (eg. after interactive graph modification).
* Called before each storing net structure to the file. */
private void updateNodesLocations() {
Collection<CAState> nodes = graph.getVertices();
for(CAState n : nodes) {
n.updateLocation(gLayout.transform(n));
}
}
/** For better vertex placement */
private void relax() {
Layout<CAState, CAEdge> layout = graphViewer.getGraphLayout();
layout.initialize();
layout.setSize(graphViewer.getSize());
Relaxer relaxer = graphViewer.getModel().getRelaxer();
if (relaxer != null) {
relaxer.stop();
relaxer.prerelax();
relaxer.relax();
}
}
//---------------------------------------------------------------------------------------------
static class EditorModeButton extends JToggleButton {
private EditorState mode;
int seqNum;
public EditorModeButton(String label, EditorState mode) {
super(label);
this.mode = mode;
seqNum = 0;
}
public EditorModeButton(String label, EditorState mode, int seqNum) {
super(label);
this.mode = mode;
this.seqNum = seqNum;
}
public EditorModeButton(String label, Icon icon, EditorState mode, int seqNum) {
super(label, icon);
this.mode = mode;
this.seqNum = seqNum;
}
public EditorState getMode() {
return mode;
}
public int getSeqNum() {
return seqNum;
}
}
//---------------------------------------------------------------------------------------------
class ModeButtonsListener implements ActionListener {
public void actionPerformed(ActionEvent changeEvent) {
EditorModeButton emb = (EditorModeButton) changeEvent.getSource();
if(emb.isSelected()) {
int num = emb.getSeqNum();
for(int i=0;i<modeButtons.size();++i)
if(i != num || emb.mode == EditorState.CLEAR)
modeButtons.elementAt(i).setSelected(false);
edtState = emb.getMode();
}
else {
edtState = EditorState.NONE;
}
if (edtState == EditorState.CLEAR) {
graph.clear();
repaint();
edtState = EditorState.NONE;
}
}
};
//---------------------------------------------------------------------------------------------
// Mouse event handler. To allow interactive net editing
//---------------------------------------------------------------------------------------------
class EditorMouseListener extends MouseAdapter {
public void mouseClicked(MouseEvent me) {
int btnClicked = me.getButton();
if(btnClicked == MouseEvent.BUTTON1)
leftButtonClicked(me);
else if(btnClicked == MouseEvent.BUTTON3)
rightButtonClicked(me);
}
private void leftButtonClicked(MouseEvent me) {
Point clickPt = me.getPoint();
GraphElementAccessor<CAState, CAEdge> pickSupport = null;
Layout<CAState, CAEdge> layout = null;
CAState node = null;
CAEdge edge = null;
switch(edtState) {
case STATE:
try {
// Transform screen coordinates to layout coordinates.
// Necessary for possible automaton structure viewer scaling.
Point2D nodeLocation = graphViewer.getRenderContext().getMultiLayerTransformer().inverseTransform(clickPt);
graph.addState(new CAState(nodeLocation));
} catch (ContextAutomatonStructureError e) {
JOptionPane.showMessageDialog(null,
e.getMessage(),
"Error", JOptionPane.ERROR_MESSAGE);
}
break;
case EDGE:
PickedState<CAState> pickedVertexState = graphViewer.getPickedVertexState();
pickSupport = graphViewer.getPickSupport();
layout = graphViewer.getGraphLayout();
node = pickSupport.getVertex(layout, clickPt.getX(), clickPt.getY());
if (node != null) {
if (lastPickedNode == null) {
lastPickedNode = node;
} else {
graph.addEdge(lastPickedNode, node);
lastPickedNode = null;
pickedVertexState.pick(node,false);
}
} else {
lastPickedNode = null;
}
break;
case DELETE:
pickSupport = graphViewer.getPickSupport();
layout = graphViewer.getGraphLayout();
node = pickSupport.getVertex(layout, clickPt.getX(), clickPt.getY());
edge = pickSupport.getEdge(layout, clickPt.getX(), clickPt.getY());
if (node != null) {
graph.removeState(node);
}
else if (edge != null) {
graph.removeEdge(edge);
}
break;
case NONE:
default:
break;
}
me.consume();
}
private void rightButtonClicked(MouseEvent me) {
GraphElementAccessor<CAState, CAEdge> pickSupport = graphViewer.getPickSupport();
Layout<CAState, CAEdge> layout = graphViewer.getGraphLayout();
CAState node = pickSupport.getVertex(layout, me.getX(), me.getY());
CAEdge edge = pickSupport.getEdge(layout, me.getX(), me.getY());
if (node != null) {
PickedState<CAState> pickedVertexState = graphViewer.getPickedVertexState();
pickedVertexState.clear();
pickedVertexState.pick(node,true);
stateContextMenu.show(graphViewer, me.getX(), me.getY());
}
else if (edge != null) {
PickedState<CAEdge> pickedEdgeState = graphViewer.getPickedEdgeState();
pickedEdgeState.clear();
pickedEdgeState.pick(edge, true);
edgeContextMenu.show(graphViewer, me.getX(), me.getY());
}
me.consume();
}
};
//---------------------------------------------------------------------------------------------
// For drawing the initial state
//---------------------------------------------------------------------------------------------
static class SquareIcon implements Icon {
private Color fillColor;
private int width;
public SquareIcon(Color fColor, int width) {
fillColor = fColor;
this.width = width;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
g.setColor(fillColor);
g.fillRect(x, y, width, width);
g.setColor(Color.black);
g.drawRect(x, y, width, width);
}
public int getIconWidth() {
return width;
}
public int getIconHeight() {
return width;
}
}
//---------------------------------------------------------------------------------------------
// For drawing all non-initial states
//---------------------------------------------------------------------------------------------
static class CircleIcon implements Icon {
private Color fillColor;
private int radius;
public CircleIcon(Color fColor, int radius) {
fillColor = fColor;
this.radius = radius;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
g.setColor(fillColor);
g.fillOval(x, y, radius, radius);
g.setColor(Color.black);
g.drawOval(x, y, radius, radius);
}
public int getIconWidth() {
return radius;
}
public int getIconHeight() {
return radius;
}
}
}
class ConditionalEdgeShape<V, E> implements Transformer<Context<Graph<V, E>, E>, Shape> {
private final EdgeShape.Loop<V, E> loopShape;
private final Transformer<Context<Graph<V, E>, E>, Shape> defaultShape;
private final float loopScale;
public ConditionalEdgeShape(float loopScale) {
this.loopShape = new EdgeShape.Loop<>();
this.defaultShape = new EdgeShape.QuadCurve<>(); // or Line<>
this.loopScale = loopScale;
}
public Shape transform(Context<Graph<V, E>, E> context) {
Graph<V, E> graph = context.graph;
E edge = context.element;
V src = graph.getSource(edge);
V dst = graph.getDest(edge);
if (src.equals(dst)) {
// Scale the self-loop
Shape baseLoop = loopShape.transform(context);
Rectangle bounds = baseLoop.getBounds();
double centerX = bounds.getCenterX();
double centerY = bounds.getCenterY();
AffineTransform at = new AffineTransform();
at.translate(centerX, centerY);
at.scale(loopScale, loopScale);
at.translate(-centerX, -centerY);
return at.createTransformedShape(baseLoop);
} else {
return defaultShape.transform(context);
}
}
}

View File

@@ -0,0 +1,435 @@
package pl.umk.mat.martinp.reactics;
import edu.uci.ics.jung.graph.DirectedSparseGraph;
import edu.uci.ics.jung.graph.util.Pair;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.awt.*;
import java.awt.geom.Point2D;
import java.io.PrintWriter;
import java.util.*;
class ContextAutomatonGraph extends DirectedSparseGraph<CAState, CAEdge> {
private static final long serialVersionUID = 1L;
HashMap<Integer, CAState> states = new HashMap<>();
private CAState initialState = null;
boolean modified = false;
public ContextAutomatonGraph() {
super();
}
public boolean isModified() { return modified; }
public void markAsModified() { modified = true; }
public void clearModificationStatus() { modified = false; }
String toRSSLString() {
StringBuilder rsslString = new StringBuilder("context-automaton {\n");
rsslString.append(" states { ");
Collection<CAState> allStates = states.values();
Iterator<CAState> it = allStates.iterator();
if (it.hasNext()) {
rsslString.append(it.next().getLabel());
}
while (it.hasNext()) {
rsslString.append(", ").append(it.next().getLabel());
}
rsslString.append(" };\n");
rsslString.append(" init-state { ").append(initialState != null ? initialState.getLabel() : "").append(" };\n");
rsslString.append(" transitions {\n");
for (CAEdge edge : this.getEdges()) {
for (Transition tr : edge.getTransitions()) {
rsslString.append(" { ").append(tr.context).append(" }: ");
rsslString.append(this.getSource(edge).label).append(" -> ").append(this.getDest(edge).label);
String guard = tr.guard;
if (guard.length() > 0) {
rsslString.append(" : ").append(guard);
}
rsslString.append(";\n");
}
}
rsslString.append(" };\n");
rsslString.append("};\n");
return rsslString.toString();
}
public void exportToXML(PrintWriter output) {
output.println(" <context-automaton>");
for (CAState state : states.values()) {
Point2D pos = state.getLocation();
output.print(" <state");
output.print(" id=\"" + state.getId() + "\"");
output.print(" name=\"" + state.getLabel() + "\"");
output.print(" x=\"" + pos.getX() + "\"");
output.print(" y=\"" + pos.getY() + "\"");
if (state.isInitial())
output.print(" initial=\"true\"");
output.println("/>");
}
for (CAEdge edge : edges.keySet()) {
Pair<CAState> endPts = this.getEndpoints(edge);
output.print(" <edge");
output.print(" from=\"" + endPts.getFirst().getLabel() + "\"");
output.print(" to=\"" + endPts.getSecond().getLabel() + "\"");
output.println(">");
for (Transition tr : edge.getTransitions()) {
output.print(" <transition");
output.print(" context=\"" + tr.context + "\"");
output.print(" guard=\"" + tr.guard + "\"");
output.println("/>");
}
output.println(" </edge>");
}
output.println(" </context-automaton>");
}
/**
* Reads the context automaton structure from the XML file.
*
* @param input An XML file containing a valid description of a context automaton.
* @throws ContextAutomatonStructureError In the case of <b>inputFile</b> structure error, eg. \
* more than one context-automaton section, more than one \
* initial state, repeating state identifiers, \
* edge between non-existing nodes, etc.
*/
public void loadFromXML(Document input) throws ContextAutomatonStructureError {
NodeList caSections = input.getElementsByTagName("context-automaton");
if (caSections.getLength() == 0 || caSections.getLength() > 1) {
throw new ContextAutomatonStructureError("An XML file should contain a single context automaton description.");
}
//--------------------------------------------------------------------------------------------------------------
// Retrieve the states
//--------------------------------------------------------------------------------------------------------------
HashMap<String, CAState> states = new HashMap<String, CAState>();
NodeList stateList = input.getElementsByTagName("state");
boolean hasInitialState = false;
for (int idx = 0; idx < stateList.getLength(); ++idx) {
Node stateNode = stateList.item(idx);
if (stateNode.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
Element stateElement = (Element) stateNode;
int id = Integer.parseInt(stateElement.getAttribute("id"));
double x = Double.parseDouble(stateElement.getAttribute("x"));
double y = Double.parseDouble(stateElement.getAttribute("y"));
String name = stateElement.getAttribute("name");
String initial = stateElement.getAttribute("initial");
if (states.containsKey(name)) {
throw new ContextAutomatonStructureError("State name <" + name + "> already used.");
}
CAState newState = new CAState(id, name, new Point2D.Double(x, y));
this.addState(newState);
states.put(name, newState);
if (initial.equals("true")) {
if (hasInitialState)
throw new ContextAutomatonStructureError("There should not be more than one initial state.");
hasInitialState = true;
this.setInitialState(newState);
}
}
//--------------------------------------------------------------------------------------------------------------
// Retrieve the edges
//--------------------------------------------------------------------------------------------------------------
NodeList edgeList = input.getElementsByTagName("edge");
for (int idx = 0; idx < edgeList.getLength(); ++idx) {
Node edgeNode = edgeList.item(idx);
if (edgeNode.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
Element edgeElement = (Element) edgeNode;
String from = edgeElement.getAttribute("from");
String to = edgeElement.getAttribute("to");
// Retrieve all child nodes describing nondeterministic transitions related to this edge
NodeList childNodeList = edgeNode.getChildNodes();
Vector<Transition> transitionList = new Vector<Transition>();
for (int cIdx=0; cIdx < childNodeList.getLength(); ++ cIdx) {
Node childNode = childNodeList.item(cIdx);
if (childNode.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
Element transElement = (Element) childNode;
transitionList.add(new Transition(transElement.getAttribute("context"), transElement.getAttribute("guard")));
}
CAState stateFrom = states.get(from);
CAState stateTo = states.get(to);
if (stateFrom == null || stateTo == null)
throw new ContextAutomatonStructureError("An edge may be created only between existing nodes.");
this.addEdge(stateFrom, stateTo, transitionList);
}
}
public Set<String> getReactantsSet() {
Set<String> reactants = new HashSet<String>();
for (CAEdge edge : getEdges()) {
for (Transition trans : edge.getTransitions()) {
int start = trans.context.indexOf("{");
int end = trans.context.indexOf("}");
if (start != -1 && end != -1 && start < end) {
reactants.addAll(Arrays.asList(trans.context.substring(start + 1, end).split("\\s*,\\s*")));
}
}
}
return reactants;
}
public void setInitialState(CAState state) {
if (initialState != null)
initialState.setInitial(false);
state.setInitial(true);
initialState = state;
modified = true;
}
public CAState getInitialState() {
return initialState;
}
/**
* Clears the entire automaton structure structure
*/
public void clear() {
for (CAState st : states.values())
removeVertex(st);
states.clear();
CAState.resetIdCounter();
modified = true;
}
public boolean addEdge(CAState from, CAState to) {
modified = true;
return this.addEdge(new CAEdge(), from, to);
}
public void addEdge(CAState from, CAState to, Collection<Transition> transitionList) {
CAEdge edge = this.findEdge(from, to);
if (edge == null)
this.addEdge(new CAEdge(transitionList), from, to);
else
edge.addTransitions(transitionList);
modified = true;
}
public void addState(CAState state) throws ContextAutomatonStructureError {
int stateId = state.getId();
if (states.get(stateId) != null)
throw new ContextAutomatonStructureError("State id " + stateId + " already in use.");
states.put(stateId, state);
this.addVertex(state);
modified = true;
}
public boolean removeState(CAState state) {
if (states.get(state.getId()) != null) {
states.remove(state.getId());
}
modified = true;
// Remove the state with all connected edges
return this.removeVertex(state);
}
}
//-------------------------------------------------------------------------------------------------
// Single node of the context automaton graph
//-------------------------------------------------------------------------------------------------
class CAState {
public final static Color baseFillColor = Color.lightGray;
public final static Color selectedFillColor = Color.lightGray;
public final static Color initStateFillColor = new Color(102, 178, 255);
private static final int nodeWidth = 30;
private static final int nodeHeight = 30;
protected static int nextId = 1;
/** Unique auto incremented identifier of the node */
protected int id;
/** User defined label of the node displayed as a tool tip and used in RSSL and XML files */
protected String label;
protected Point2D location;
private boolean isInitial = false;
/** A state created from the description stored in a file. */
public CAState(int id, String label, Point2D location) {
this.id = id;
this.label = label;
this.location = location;
// To avoid duplicate node identifiers
if (id >= nextId)
nextId = id + 1;
}
/** A state created by clicking in graphical component. */
public CAState(Point2D location) {
this.id = nextId;
this.label = "q" + id;
this.location = location;
++nextId;
}
public Point2D getLocation() { return location; }
public void updateLocation(Point2D location) { this.location = location; }
public boolean isInitial() { return isInitial; }
public void setInitial(boolean status) { isInitial = status; }
public int getWidth() { return nodeWidth; }
public int getHeight() { return nodeHeight; }
public String getLabel() { return label; }
public void setLabel(String newLabel) { label = newLabel; }
public int getId() { return id; }
static void resetIdCounter() { nextId = 1; }
public String toString() { return Integer.toString(id); }
public String getTooltipText() {
return "<html>"+
"<b>State</b> " + id + "<br/><hr>" + getLabel() + //"<hr>"+
"</html>";
}
Color getFillColor() {
return isInitial ? initStateFillColor : baseFillColor;
}
}
//-------------------------------------------------------------------------------------------------
// Single edge of the context automaton graph.
// Each edge can represent a number of single transitions between the same pair of automaton states.
//-------------------------------------------------------------------------------------------------
class CAEdge {
protected static int nextId = 0;
protected int id;
// The list of all nondeterministic transitions represented by this edge
private Vector<Transition> transitions = new Vector<Transition>();
public boolean hasTransition() { return transitions.size() > 0; }
public CAEdge() {
this.id = nextId++;
}
public CAEdge(Collection<Transition> transitionList) {
this.transitions.addAll(transitionList);
}
Vector<Transition> getTransitions() {
return transitions;
}
public int getId() {
return id;
}
public String toString() {
// Update this if you need any label displayed together with graph edge
return "";
}
public String getTooltipText() {
StringBuilder tooTip = new StringBuilder("<html><hr/><center><b>Context <font color=\"red\">:</font> Guard</b></center><hr/>");
for (Transition tr : transitions) {
tooTip.append(tr.context).append(" <b><font color=\"red\">:</font></b> ").append(tr.guard).append("<br/><hr/>");
}
tooTip.append("</html>");
return tooTip.toString();
}
public void addTransitions(Collection<Transition> transitionList) {
transitions.addAll(transitionList);
}
}
//-------------------------------------------------------------------------------------------------
// Single transition. To be included in a multiedge.
//-------------------------------------------------------------------------------------------------
class Transition {
String context;
String guard;
public Transition(String ctx, String grd) {
context = ctx;
guard = grd;
}
}
class ContextAutomatonStructureError extends FileStructureError {
public ContextAutomatonStructureError(String message) {
super(message);
}
}

View File

@@ -0,0 +1,13 @@
package pl.umk.mat.martinp.reactics;
import javax.swing.*;
import java.awt.*;
public class CumulatedReactionsViewer extends JPanel {
public CumulatedReactionsViewer() {
this.setLayout(new BorderLayout());
this.add(new JLabel("Cumulate Reactions Viewer", SwingConstants.CENTER), BorderLayout.CENTER);
}
}

View File

@@ -0,0 +1,406 @@
package pl.umk.mat.martinp.reactics;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.EtchedBorder;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.*;
import java.awt.*;
import java.io.*;
import java.util.Collection;
import java.util.Vector;
public class FormulaEditor extends JPanel {
private FormulaTableModel ftModel;
private JTable formTable;
private Vector<Formula> formulaList;
boolean modified = false;
static final Color TableHeaderBackgroundColor = Color.gray;
static final Color TableHeaderFontColor = Color.white;
static final Color BaseFormulaBackgroundColor = Color.white;
static final Color TrueFormulaBackgroundColor = new Color(138, 242, 183);
static final Color FalseFormulaBackgroundColor = new Color(240, 141, 141);
static final Color InvalidFormulaBackgroundColor = new Color(124, 124, 124);
static final Border emptyBorder = BorderFactory.createEmptyBorder(1, 1, 1, 1);
static final Border selectedBorder = BorderFactory.createLineBorder(Color.RED, 2);
public FormulaEditor() {
formulaList = new Vector<Formula>();
// Create the table for displaying formulas. Prevent default selection modes.
ftModel = new FormulaTableModel();
formTable = new JTable(ftModel, new FormulaColumnModel());
formTable.setCellSelectionEnabled(false);
formTable.setRowSelectionAllowed(true);
Font headerFont = formTable.getTableHeader().getFont();
Font cellFont = formTable.getFont();
JTableHeader formHeader = formTable.getTableHeader();
formTable.setFont(new Font("Monospaced", Font.PLAIN, cellFont.getSize() + 2));
formHeader.setFont(new Font(headerFont.getFamily(), Font.BOLD, headerFont.getSize() + 2));
formHeader.setBackground(TableHeaderBackgroundColor);
formHeader.setForeground(TableHeaderFontColor);
formHeader.setReorderingAllowed(false);
formHeader.setResizingAllowed(true);
formHeader.setBorder(new EtchedBorder(EtchedBorder.LOWERED));
FormulaTableCellRenderer renderer = new FormulaTableCellRenderer();
formTable.setDefaultRenderer(Object.class, renderer);
formTable.createDefaultColumnsFromModel();
setLayout(new BorderLayout());
add(formTable, BorderLayout.CENTER);
}
public boolean isModified() { return modified; }
public void clearModificationStatus() { modified = false; }
public String toRSSLString() {
StringBuilder rsslString = new StringBuilder();
for (Formula f: formulaList)
rsslString.append(f.toRSSLString()).append("\n");
return rsslString.toString();
}
public void exportToXML(PrintWriter output) {
output.println(" <formulas>");
for (Formula f : formulaList)
output.println(" " + f.toXMLString());
output.println(" </formulas>");
}
public void loadFromXML(Document input) {
formulaList.clear();
NodeList formulaTags = input.getElementsByTagName("formula");
for (int idx = 0; idx < formulaTags.getLength(); ++idx) {
Node formulaNode = formulaTags.item(idx);
if (formulaNode.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
Element formulaElement = (Element) formulaNode;
String label = formulaElement.getAttribute("label");
String formula = formulaElement.getTextContent();
formulaList.add(new Formula(label, formula));
}
ftModel.fireTableDataChanged();
}
private boolean showFormulaEditDialog(Formula ff) {
final int Select = 0;
final int Approve = 1;
JTextField labelInput = new JTextField(30);
JTextField formulaInput = new JTextField(30);
String labelStr = "";
String formulaStr = "";
labelStr = ff.label;
formulaStr = ff.formula;
labelInput.setToolTipText("<html>A unique label of the formula</html>");
formulaInput.setToolTipText("<html>Formula to be evaluated</html>");
JPanel myPanel = new JPanel(new GridBagLayout());
GridBagConstraints cs = new GridBagConstraints();
cs.insets = new Insets(5, 5, 5, 5);
cs.anchor = GridBagConstraints.WEST;
cs.gridx = 0;
cs.gridy = 0;
myPanel.add(new JLabel("Label"), cs);
cs.gridx = 1;
cs.gridy = 0;
myPanel.add(labelInput, cs);
cs.gridx = 0;
cs.gridy = 1;
myPanel.add(new JLabel("Formula"), cs);
cs.gridx = 1;
cs.gridy = 1;
myPanel.add(formulaInput, cs);
int opStatus = Select;
do {
labelInput.setText(labelStr);
formulaInput.setText(formulaStr);
int result = JOptionPane.showConfirmDialog(null, myPanel,
"Formula details", JOptionPane.OK_CANCEL_OPTION);
if (result != JOptionPane.OK_OPTION) {
return false;
}
labelStr = labelInput.getText();
formulaStr = formulaInput.getText();
//TODO: Validate the data entered to the text fields
ff.label = labelStr;
ff.formula = formulaStr;
opStatus = Approve;
}
while(opStatus == Select);
return true;
}
public void addFormula() {
Formula ff = new Formula();
if (showFormulaEditDialog(ff)) {
formulaList.add(ff);
modified = true;
ftModel.fireTableDataChanged();
}
}
// If more than one formula is selected throw an exception only the first can be edited
public void editFormula() {
int[] selected = formTable.getSelectedRows();
if (selected.length != 1) {
JOptionPane.showMessageDialog(this, "Select a single formula to edit.", "Select formula", JOptionPane.WARNING_MESSAGE);
return;
}
Formula fEdt = formulaList.get(formTable.convertRowIndexToModel(selected[0]));
if (fEdt == null)
return;
showFormulaEditDialog(fEdt);
fEdt.status = Formula.FormulaStatus.None;
modified = true;
ftModel.fireTableDataChanged();
}
public void removeFormulas() {
int[] viewRows = formTable.getSelectedRows();
// Remove formulas starting from the last to avoid indexes updates
for (int vIdx = viewRows.length - 1; vIdx>=0; --vIdx) {
int mr = formTable.convertRowIndexToModel(viewRows[vIdx]);
formulaList.remove(mr);
}
modified = true;
ftModel.fireTableDataChanged();
}
public Collection<Formula> getSelectedFormulas() {
Vector<Formula> selected = new Vector<Formula>();
int[] viewRows = formTable.getSelectedRows();
for (int vr : viewRows) {
int mr = formTable.convertRowIndexToModel(vr);
selected.add(formulaList.get(mr));
}
return selected;
}
// Needed to repaint JTable. Called after content modification by an external object.
public void refreshStatus() {
ftModel.fireTableDataChanged();
}
public void resetFormulasStatus() {
for (Formula ff : formulaList) {
ff.status = Formula.FormulaStatus.None;
ff.mcTime = ff.totalTime = ff.memory = "-";
}
ftModel.fireTableDataChanged();
}
static class FormulaColumnModel extends DefaultTableColumnModel {
int colno = 0;
public void addColumn(TableColumn tc) {
switch (colno) {
case 0 -> {
tc.setMinWidth(60);
tc.setMaxWidth(Integer.MAX_VALUE / 10);
tc.setPreferredWidth(100);
}
case 1 -> {
tc.setMinWidth(100);
tc.setMaxWidth(Integer.MAX_VALUE);
}
case 2, 3, 4, 5 -> {
tc.setMinWidth(50);
tc.setPreferredWidth(50);
tc.setMaxWidth(Integer.MAX_VALUE / 10);
}
}
super.addColumn(tc);
++colno;
}
}
class FormulaTableModel extends AbstractTableModel implements TableModelListener {
private final String[] columnNames = {"Label", "Formula", "Status", "MC Time (s)", "Total Time (s)", "Memory (MB)"};
public String getColumnName(int colIdx) {
return columnNames[colIdx];
}
public int getRowCount() {
return formulaList.size();
}
public int getColumnCount() {
return columnNames.length;
}
public Object getValueAt(int row, int column) {
Formula ff = formulaList.get(row);
return switch (column) {
case 0 -> ff.label;
case 1 -> ff.formula;
case 2 -> ff.status;
case 3 -> ff.mcTime;
case 4 -> ff.totalTime;
case 5 -> ff.memory;
default -> "";
};
}
public void setValueAt(Object value, int row, int column) {
// fireTableDataChanged();
}
public boolean isCellEditable(int rowIndex, int columnIndex) { return false; }
public Class getColumnClass(int column) {
return getValueAt(0, column).getClass();
}
public void tableChanged(TableModelEvent tableModelEvent) { }
}
// To properly display the content of the table containing formulas
class FormulaTableCellRenderer extends JLabel implements TableCellRenderer {
public FormulaTableCellRenderer() {
// Allows background to be visible
setOpaque(true);
}
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
String statusStr = "";
Formula ff = formulaList.get(row);
setForeground(Color.black);
if (isSelected) {
setBorder(selectedBorder);
}
else {
setBorder(emptyBorder);
}
// Set cell background and formula evaluation status
if (column == 2) {
switch (ff.status) {
case None -> {
statusStr = "?";
setBackground(BaseFormulaBackgroundColor);
}
case True -> {
statusStr = "True";
setBackground(TrueFormulaBackgroundColor);
}
case False -> {
statusStr = "False";
setBackground(FalseFormulaBackgroundColor);
}
case Invalid -> {
statusStr = "Invalid";
setBackground(InvalidFormulaBackgroundColor);
}
}
setText(statusStr);
}
else {
setText((String) value);
setBackground(BaseFormulaBackgroundColor);
}
if (column > 2) {
setHorizontalAlignment(SwingConstants.RIGHT);
} else {
setHorizontalAlignment(SwingConstants.LEFT);
}
return this;
}
}
}
class Formula {
public enum FormulaStatus {None, True, False, Invalid};
String label;
String formula;
FormulaStatus status;
String mcTime;
String totalTime;
String memory;
public Formula() {
this("", "");
}
public Formula(String label, String formula) {
this.label = label;
this.formula = formula;
this.status = FormulaStatus.None;
this.mcTime = this.totalTime = this.memory = "-";
}
public String toRSSLString() {
return "rsctlk-property { " + label + " : " + formula + " };";
}
public String toXMLString() {
return "<formula label=\"" + label +"\">" +
formula.replaceAll("<", "&lt;").replaceAll(">", "&gt;") +
"</formula>";
}
}

View File

@@ -0,0 +1,123 @@
package pl.umk.mat.martinp.reactics;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
public class HelpWindow extends JFrame {
private static final long serialVersionUID = 327019113533268901L;
private static final String startDocument = "index.html";
private static final String docPath = "/help/";
private static HelpWindow instance = null;
private static JTextPane helpPane = new JTextPane();;
public static synchronized HelpWindow getInstance() {
if (instance == null)
instance = new HelpWindow();
return instance;
}
private HelpWindow() {
super("ReactICS GUI Help");
getContentPane().setLayout(new BorderLayout());
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BorderLayout());
EtchedBorder panelBorder = new EtchedBorder(EtchedBorder.LOWERED);
mainPanel.setBorder(panelBorder);
helpPane.setEditable(false);
helpPane.addHyperlinkListener(new HyperlinkListener() {
public void hyperlinkUpdate(HyperlinkEvent hle) {
if (hle.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
String target = hle.getDescription();
if (target.startsWith("#")) {
helpPane.scrollToReference(target.substring(1));
}
}
}
});
try {
URL helpUrl = this.getClass().getResource(docPath + startDocument);
helpPane.setPage(helpUrl);
}
catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
JOptionPane.showMessageDialog(null, "Can not load help content",
"Help", JOptionPane.ERROR_MESSAGE);
}
JScrollPane helpScroller = new JScrollPane();
JViewport helpVp = new JViewport();
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
helpVp.setPreferredSize(new Dimension(screenSize.width-200, screenSize.height-200));
helpVp.add(helpPane);
helpScroller.setViewport(helpVp);
mainPanel.add(helpScroller, BorderLayout.CENTER);
getContentPane().add(mainPanel, BorderLayout.CENTER);
pack();
}
public void show(Component parent) {
setLocationRelativeTo(parent);
setVisible(true);
}
}
class AboutWindow extends JFrame {
private static AboutWindow instance = null;
public static synchronized AboutWindow getInstance() {
if (instance == null)
instance = new AboutWindow();
return instance;
}
private AboutWindow() {
super("About ...");
getContentPane().setLayout(new BorderLayout());
JPanel infoPanel = new JPanel();
infoPanel.add(new JLabel("<html><h1>" + ReacticsGUI.ApplicationName + "</h1>"
+ "<h2>version " + ReacticsGUI.ApplicationVersion + " (" + ReacticsGUI.ApplicationReleaseDate + ")</h2>"
+ "<h3>Model Checker for Reaction Systems</h3>"
+ "<h3>ReactICS Research Team (CC BY 4.0)</h3>"
+ "<h3><a href=\"https://reactics.org\">https://reactics.org</a></h3></html>"));
getContentPane().add(infoPanel, BorderLayout.CENTER);
JButton closeButton = new JButton("Close");
closeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
setVisible(false);
}
});
JPanel ctrlPanel = new JPanel();
ctrlPanel.add(closeButton);
ctrlPanel.setAlignmentX(SwingConstants.RIGHT);
getContentPane().add(ctrlPanel, BorderLayout.SOUTH);
setSize(new Dimension(400, 250));
setResizable(false);
this.setAlwaysOnTop(true);
this.setUndecorated(true);
}
public void show(Component parent) {
setLocationRelativeTo(parent);
setVisible(true);
}
}

View File

@@ -0,0 +1,478 @@
package pl.umk.mat.martinp.reactics;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.AbstractTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.PrintWriter;
import java.util.*;
//-------------------------------------------------------------------------------------------------
// Graphical component for editing a single process (list of reactions)
//-------------------------------------------------------------------------------------------------
class ProcessEditor extends JPanel {
private JTable reactionList;
private RSTableModel reactionsModel;
private Vector<Reaction> reactions;
private final int id;
private String label;
private boolean modified = false;
private JCheckBox selectBox;
private TitledBorder outBorder;
private final Color borderColor = new Color(24, 104, 92);;
private final Color selectedBorderColor = new Color(150, 10, 10);
private static final int borderThickness = 4;
private static int nextId = 1;
public ProcessEditor() {
id = nextId;
++nextId;
label = new String("Process_" + id);
init();
}
ProcessEditor(String name) {
id = nextId;
++nextId;
label = name;
init();
}
ProcessEditor(ProcessEditor procEdt) {
id = nextId;
++nextId;
label = "Copy_of_" + procEdt.label;
init();
reactions.addAll(procEdt.reactions);
reactionsModel.fireTableDataChanged();
modified = procEdt.modified;
}
public boolean isSelected() {
return selectBox.isSelected();
}
public boolean isModified() { return modified; }
public void clearModificationStatus() { modified = false; }
public String getLabel() {
return label;
}
public void addReaction(Reaction rr) {
reactions.add(rr);
reactionsModel.fireTableDataChanged();
}
private void init() {
// Create border for the component
outBorder = BorderFactory.createTitledBorder(label);
Font font = outBorder.getTitleFont();
Font newFont = new Font (font.getFamily(), Font.BOLD, font.getSize() + 2);
outBorder.setTitleFont(new Font (font.getFamily(), Font.BOLD, font.getSize() + 2));
outBorder.setTitleColor(borderColor);
outBorder.setBorder(new LineBorder(borderColor, borderThickness, true));
setBorder(outBorder);
// Create reactions list component
reactions = new Vector<Reaction>();
reactionsModel = new RSTableModel();
reactionList = new JTable(reactionsModel);
Font cellFont = reactionList.getFont();
Font headerFont = reactionList.getTableHeader().getFont();
reactionList.setFont(new Font("Monospaced", 0, cellFont.getSize() + 2));
reactionList.getTableHeader().setFont(new Font(headerFont.getFamily(), Font.BOLD, headerFont.getSize() + 2));
reactionList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
JPanel buttonPanel = new JPanel();
JButton addButton = new JButton("Add reaction");
addButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) { addReaction(); }
});
buttonPanel.add(addButton);
JButton edtButton = new JButton("Edit reaction");
edtButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
editReaction();
}
});
buttonPanel.add(edtButton);
JButton rmButton = new JButton("Remove reaction");
rmButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
removeReactions();
}
});
buttonPanel.add(rmButton);
JButton optButton = new JButton("Options");
optButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
editOptions();
}
});
buttonPanel.add(optButton);
selectBox = new JCheckBox("Select");
selectBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
toggleSelection();
}
});
buttonPanel.add(selectBox);
add(buttonPanel);
add(new JScrollPane(reactionList));
setPreferredSize(new Dimension(-1, 200));
}
private boolean showReactionEditDialog(Reaction rr) {
final int Select = 0;
final int Approve = 1;
JTextField reactantsInput = new JTextField(30);
JTextField inhibitorsInput = new JTextField(30);
JTextField productsInput = new JTextField(30);
String reactantsStr = "";
String inhibitorsStr = "";
String productsStr = "";
Iterator<?> it = rr.reactants.iterator();
StringBuilder dataStr = new StringBuilder();
while (it.hasNext())
dataStr.append(it.next()).append(" ");
reactantsStr = dataStr.toString();
it = rr.inhibitors.iterator();
dataStr.setLength(0);
while (it.hasNext())
dataStr.append(it.next()).append(" ");
inhibitorsStr = dataStr.toString();
it = rr.products.iterator();
dataStr.setLength(0);
while (it.hasNext())
dataStr.append(it.next()).append(" ");
productsStr = dataStr.toString();
reactantsInput.setToolTipText("<html>Space separated list of reactants</html>");
inhibitorsInput.setToolTipText("<html>Space separated list of inhibitors</html>");
productsInput.setToolTipText("<html>Space separated list of products</html>");
JPanel myPanel = new JPanel(new GridBagLayout());
GridBagConstraints cs = new GridBagConstraints();
cs.insets = new Insets(5, 5, 5, 5);
cs.anchor = GridBagConstraints.WEST;
cs.gridx = 0;
cs.gridy = 0;
myPanel.add(new JLabel("Reactants"), cs);
cs.gridx = 1;
cs.gridy = 0;
myPanel.add(reactantsInput, cs);
cs.gridx = 0;
cs.gridy = 1;
myPanel.add(new JLabel("Inhibitors"), cs);
cs.gridx = 1;
cs.gridy = 1;
myPanel.add(inhibitorsInput, cs);
cs.gridx = 0;
cs.gridy = 2;
myPanel.add(new JLabel("Products"), cs);
cs.gridx = 1;
cs.gridy = 2;
myPanel.add(productsInput, cs);
int opStatus = Select;
do {
reactantsInput.setText(reactantsStr);
inhibitorsInput.setText(inhibitorsStr);
productsInput.setText(productsStr);
int result = JOptionPane.showConfirmDialog(null, myPanel,
"Reaction details", JOptionPane.OK_CANCEL_OPTION);
if (result != JOptionPane.OK_OPTION) {
return false;
}
reactantsStr = reactantsInput.getText();
inhibitorsStr = inhibitorsInput.getText();
productsStr = productsInput.getText();
HashSet<String> reactantsSet = new HashSet<String>(Arrays.asList(reactantsStr.trim().split("\\s+")));
HashSet<String> inhibitorsSet = new HashSet<String>(Arrays.asList(inhibitorsStr.trim().split("\\s+")));
HashSet<String> productsSet = new HashSet<String>(Arrays.asList(productsStr.trim().split("\\s+")));
// Verify that reactant and inhibitor sets have empty intersection
boolean intersection = false;
for (String entity : inhibitorsSet) {
if (reactantsSet.contains(entity)) {
intersection = true;
break;
}
}
if (intersection) {
JOptionPane.showMessageDialog(this, "Reactant and inhibitor sets have to be disjoint.", "Error", JOptionPane.ERROR_MESSAGE);
continue;
}
//TODO: Validate entities names against a regular expression
rr.reactants.clear();
rr.reactants.addAll(reactantsSet);
rr.inhibitors.clear();
rr.inhibitors.addAll(inhibitorsSet);
rr.products.clear();
rr.products.addAll(productsSet);
opStatus = Approve;
}
while(opStatus == Select);
return true;
}
public Set<String> getReactantsSet() {
HashSet<String> rset = new HashSet<String>();
for (Reaction rr : reactions)
rset.addAll(rr.getReactantsSet());
return rset;
}
public void exportToXML(PrintWriter output) {
output.println(" <process name=\"" + this.label + "\">");
for (Reaction rr : reactions)
rr.exportToXML(output);
output.println(" </process>");
}
public String toRSSLString() {
StringBuilder rsslString = new StringBuilder(" " + this.label + " {\n");
for (Reaction rr : reactions) {
rsslString.append(rr.toRSSLString());
}
rsslString.append(" };\n");
return rsslString.toString();
}
private void editOptions() {
String idRegex = "[a-zA-Z][a-zA-Z_0-9:-]*";
boolean idOk = false;
do {
String newLabel = JOptionPane.showInputDialog(this, "Process name", label);
if (newLabel == null)
return;
newLabel = newLabel.trim();
if (!newLabel.matches(idRegex)) {
JOptionPane.showMessageDialog(this,
"<html>Process name should start with a letter and may contain only:<br/>" +
"letters, numbers, colons (:), underscores (_) and hyphens (-)</html>",
"Error", JOptionPane.ERROR_MESSAGE);
}
else {
idOk = true;
label = newLabel;
outBorder.setTitle(label);
}
}
while (!idOk);
modified = true;
this.repaint();
}
// Add new reaction
private void addReaction() {
Reaction rr = new Reaction();
if (!showReactionEditDialog(rr))
return;
reactions.add(rr);
modified = true;
reactionsModel.fireTableDataChanged();
}
// Edit details of an existing reaction
private void editReaction() {
int rIdx = reactionList.getSelectedRow();
if (rIdx == -1) {
JOptionPane.showMessageDialog(this, "Select the reaction to edit.", "Select reaction", JOptionPane.WARNING_MESSAGE);
return;
}
Reaction rr = new Reaction(reactions.get(rIdx));
reactionList.clearSelection();
if (!showReactionEditDialog(rr)) {
return;
}
reactions.setElementAt(rr, rIdx);
modified = true;
reactionsModel.fireTableDataChanged();
}
// Remove an existing reaction
private void removeReactions() {
int rIdx = reactionList.getSelectedRow();
if (rIdx == -1) {
JOptionPane.showMessageDialog(this, "Select the reaction to be removed.", "Select reaction", JOptionPane.WARNING_MESSAGE);
return;
}
reactions.remove(rIdx);
reactionList.clearSelection();
reactionsModel.fireTableDataChanged();
modified = true;
this.repaint();
}
private void toggleSelection() {
if (selectBox.isSelected()) {
outBorder.setTitleColor(selectedBorderColor);
outBorder.setBorder(new LineBorder(selectedBorderColor, borderThickness, true));
}
else {
outBorder.setTitleColor(borderColor);
outBorder.setBorder(new LineBorder(borderColor, borderThickness, true));
}
this.repaint();
}
class RSTableModel extends AbstractTableModel {
private String columnNames[] = {"Reactants", "Inhibitors", "Results"};
private Vector<String>[] data;
public String getColumnName(int colIdx) {
return columnNames[colIdx];
}
public int getRowCount() {
return reactions.size();
}
public int getColumnCount() {
return columnNames.length;
}
public Object getValueAt(int row, int column) {
Reaction rr = reactions.get(row);
return switch (column) {
case 0 -> rr.reactants;
case 1 -> rr.inhibitors;
case 2 -> rr.products;
default -> "";
};
}
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
}
}
//-------------------------------------------------------------------------------------------------
// Object representing a single reaction
//-------------------------------------------------------------------------------------------------
class Reaction {
LinkedList<String> reactants;
LinkedList<String> inhibitors;
LinkedList<String> products;
public Reaction() {
reactants = new LinkedList<String>();
inhibitors = new LinkedList<String>();
products = new LinkedList<String>();
}
public Reaction(Reaction rr) {
reactants = new LinkedList<String>(rr.reactants);
inhibitors = new LinkedList<String>(rr.inhibitors);
products = new LinkedList<String>(rr.products);
}
public Set<String> getReactantsSet() {
Set<String> rset = new HashSet<String>();
rset.addAll(reactants);
rset.addAll(inhibitors);
rset.addAll(products);
return rset;
}
public String toRSSLString() {
return " {" + "{" + reactants.toString().replace("[", "").replace("]", "") + "}, " +
"{" + inhibitors.toString().replace("[", "").replace("]", "") + "} -> " +
"{" + products.toString().replace("[", "").replace("]", "") + "}" +
"};\n";
}
public void exportToXML(PrintWriter output) {
output.println(" <reaction>");
for (String str : reactants)
output.println(" <reactant>" + str + "</reactant>");
for (String str : inhibitors)
output.println(" <inhibitor>" + str + "</inhibitor>");
for (String str : products)
output.println(" <product>" + str + "</product>");
output.println(" </reaction>");
}
}

View File

@@ -0,0 +1,42 @@
package pl.umk.mat.martinp.reactics;
import javax.swing.*;
import javax.swing.border.LineBorder;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.util.*;
public class ReactantSetPanel extends JPanel {
private final int borderThickness = 2;
JTextArea textArea = new JTextArea();
public ReactantSetPanel() {
textArea.setEditable(false);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(false);
Font textFont = textArea.getFont();
textArea.setFont(new Font(textFont.getFamily(), Font.BOLD, textFont.getSize()+2));
this.setPreferredSize(new Dimension(-1, 75));
this.setLayout(new BorderLayout());
this.add(new JScrollPane(textArea), BorderLayout.CENTER);
TitledBorder outBorder = BorderFactory.createTitledBorder("Reactants");
Font borderFont = outBorder.getTitleFont();
outBorder.setTitleFont(new Font (borderFont.getFamily(), Font.BOLD, borderFont.getSize()+2));
outBorder.setBorder(new LineBorder(Color.black, borderThickness, true));
setBorder(outBorder);
}
public void updateReactants(Set<String> reactantsSet) {
textArea.setText("");
textArea.setText(Arrays.toString(reactantsSet.toArray()));
}
public void clear() {
textArea.setText("");
}
}

View File

@@ -0,0 +1,711 @@
/** Main application window */
package pl.umk.mat.martinp.reactics;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.TreeSet;
public class ReacticsGUI extends JFrame {
public final static String ApplicationName = "ReactICS GUI";
public final static String ApplicationVersion = "0.1";
public final static String ApplicationReleaseDate = "2025";
private static final String defaultConfigFileName = "reactics.conf";
private JTabbedPane modulePane;
private ReactantSetPanel reactantSetPanel;
private ReactionSystemEditor reactionSystem;
private ContextAutomatonEditor contextAutomaton;
private TransitionSystemViewer transitionSystem;
private TransitionSystemViewer compressedTransitionSystem;
private FormulaEditor formulaEditor;
private FileSelector fileSelector;
private ReacticsRuntime reactics;
public ReacticsGUI() {
super(ApplicationName);
try {
reactics = ReacticsRuntime.getInstance();
}
catch (ReacticsRuntimeException rre) {
JOptionPane.showMessageDialog(this,
rre.getMessage(),
"Reactics Runtime Error", JOptionPane.ERROR_MESSAGE);
}
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
setSize(new Dimension(screenSize.width-200, screenSize.height-200));
createMenuBar();
createContentPane();
fileSelector = FileSelector.getInstance();
try {
reactics.loadConfig(defaultConfigFileName);
}
catch (ConfigReadingError cre) {
System.err.println("[ReactICS] Error reading configuration: " + cre.getMessage());
JOptionPane.showMessageDialog(this,
"Could not read configuration\n" + cre.getMessage(),
"Reactics Runtime Error", JOptionPane.ERROR_MESSAGE);
}
catch (InvalidRuntimeException ire) {
System.err.println("[ReactICS] ReactICS runtime cannot be executed" + ire.getMessage());
JOptionPane.showMessageDialog(this,
"Invalid ReactICS runtime patn\n" + ire.getMessage() +
"\nYou may proceed with editing reaction system structure," +
"however not model checking will be possible.",
"Reactics Runtime Error", JOptionPane.ERROR_MESSAGE);
}
}
private void createMenuBar() {
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
menuBar.add(fileMenu);
JMenuItem xmlLoadItem = new JMenuItem("Load from XML file");
xmlLoadItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
loadFromXML();
}
});
fileMenu.add(xmlLoadItem);
JMenuItem xmlsaveItem = new JMenuItem("Save to XML file");
xmlsaveItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
saveAsXML();
}
});
fileMenu.add(xmlsaveItem);
JMenuItem rsslImportItem = new JMenuItem("Import from RSSL file");
rsslImportItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
importFromRSSL();
}
});
rsslImportItem.setEnabled(false);
fileMenu.add(rsslImportItem);
JMenuItem rsslExportItem = new JMenuItem("Save to RSSL file");
rsslExportItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
exportToRSSLFile();
}
});
fileMenu.add(rsslExportItem);
JMenuItem isplExportItem = new JMenuItem("Export to ISPL file");
isplExportItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) { exportToISPLFile(); }
});
fileMenu.add(isplExportItem);
JMenuItem exitItem = new JMenuItem("Exit");
exitItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
System.exit(0);
}
});
fileMenu.addSeparator();
fileMenu.add(exitItem);
JMenu helpMenu = new JMenu("Help");
menuBar.add(helpMenu);
JMenuItem helpItem = new JMenuItem("Help");
helpItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) { showHelpWindow(); }
});
helpMenu.add(helpItem);
JMenuItem aboutItem = new JMenuItem("About");
aboutItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) { showAboutWindow(); }
});
helpMenu.add(aboutItem);
setJMenuBar(menuBar);
}
private void createContentPane() {
modulePane = new JTabbedPane();
modulePane.setTabPlacement(JTabbedPane.NORTH);
//-----------------------------------------------------------------------------------------
// Reaction System Editor
//-----------------------------------------------------------------------------------------
reactionSystem = new ReactionSystemEditor();
modulePane.addTab("Reaction System", reactionSystem);
//-----------------------------------------------------------------------------------------
// Context Automaton Editor
//-----------------------------------------------------------------------------------------
contextAutomaton = new ContextAutomatonEditor(getSize());
modulePane.addTab("Context Automaton", contextAutomaton);
//-----------------------------------------------------------------------------------------
// Transition System Viewer
//-----------------------------------------------------------------------------------------
JPanel transitionSystemPanel = new JPanel(new BorderLayout());
transitionSystem = new TransitionSystemViewer(getSize(), false);
transitionSystemPanel.add(transitionSystem, BorderLayout.CENTER);
JPanel tsCtrlPanel = new JPanel();
JButton tsUpdateBtn = new JButton("Update transition system");
tsUpdateBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) { updateTransitionSystem(); }
});
tsCtrlPanel.add(tsUpdateBtn);
JCheckBox tsLockCBox = new JCheckBox("Lock relative nodes positions");
tsLockCBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
transitionSystem.lockNodesPositions(tsLockCBox.isSelected());
}
});
tsCtrlPanel.add(tsLockCBox);
transitionSystemPanel.add(tsCtrlPanel, BorderLayout.NORTH);
modulePane.addTab("Transition System", transitionSystemPanel);
//-----------------------------------------------------------------------------------------
// Compressed Transition System Viewer
//-----------------------------------------------------------------------------------------
JPanel compressedTransitionSystemPanel = new JPanel(new BorderLayout());
compressedTransitionSystem = new TransitionSystemViewer(getSize(), true);
compressedTransitionSystemPanel.add(compressedTransitionSystem, BorderLayout.CENTER);
JPanel ctsCtrlPanel = new JPanel();
JButton ctsUpdateBtn = new JButton("Update transition system");
ctsUpdateBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) { updateTransitionSystem(); }
});
ctsCtrlPanel.add(ctsUpdateBtn);
JCheckBox ctsLockCBox = new JCheckBox("Lock relative nodes positions");
ctsLockCBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
compressedTransitionSystem.lockNodesPositions(ctsLockCBox.isSelected());
}
});
ctsCtrlPanel.add(ctsLockCBox);
compressedTransitionSystemPanel.add(ctsCtrlPanel, BorderLayout.NORTH);
modulePane.addTab("Compressed Transition System", compressedTransitionSystemPanel);
//-----------------------------------------------------------------------------------------
// Formula Editor
//-----------------------------------------------------------------------------------------
JPanel formulaEditorPanel = new JPanel();
formulaEditorPanel.setLayout(new BorderLayout());
formulaEditor = new FormulaEditor();
formulaEditorPanel.add(new JScrollPane(formulaEditor), BorderLayout.CENTER);
JPanel formulaCtrlPanel = new JPanel();
JButton addButton = new JButton("Add formula");
addButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) { formulaEditor.addFormula(); }
});
formulaCtrlPanel.add(addButton);
JButton edtButton = new JButton("Edit formula");
edtButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
formulaEditor.editFormula();
}
});
formulaCtrlPanel.add(edtButton);
JButton rmButton = new JButton("Remove formulas");
rmButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
formulaEditor.removeFormulas();
}
});
formulaCtrlPanel.add(rmButton);
JButton evalButton = new JButton("Evaluate");
evalButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
evaluateFormulas();
}
});
formulaCtrlPanel.add(evalButton);
JButton resetButton = new JButton("Reset");
resetButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
formulaEditor.resetFormulasStatus();
}
});
formulaCtrlPanel.add(resetButton);
formulaEditorPanel.add(formulaCtrlPanel, BorderLayout.NORTH);
//-----------------------------------------------------------------------------------------
// Split the main window into two parts. The upper one contains details of the reaction
// system. The bottom part contains the list of formulas for model checking.
//-----------------------------------------------------------------------------------------
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, modulePane, formulaEditorPanel);
splitPane.setResizeWeight(0.75);
// splitPane.setOneTouchExpandable(true);
splitPane.setDividerSize(8);
getContentPane().setLayout(new BorderLayout());
getContentPane().add(splitPane, BorderLayout.CENTER);
//-----------------------------------------------------------------------------------------
// Update button + Reactant Set Viewer
//-----------------------------------------------------------------------------------------
JPanel topPanel = new JPanel();
topPanel.setLayout(new BorderLayout());
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.Y_AXIS));
JButton updateButton = new JButton("<html><center>Update<br>Reaction System</center></html>");
updateButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
updateReactionSystem();
}
});
buttonPanel.add(updateButton);
topPanel.add(buttonPanel, BorderLayout.WEST);
reactantSetPanel = new ReactantSetPanel();
topPanel.add(reactantSetPanel, BorderLayout.CENTER);
getContentPane().add(topPanel, BorderLayout.NORTH);
}
private boolean isModified() {
return reactionSystem.isModified() || contextAutomaton.isModified() || formulaEditor.isModified();
}
private void clearModificationStatus() {
reactionSystem.clearModificationStatus();
contextAutomaton.clearModificationStatus();
formulaEditor.clearModificationStatus();
}
private void updateReactionSystem() {
System.out.println("[ReactICS] Update reaction system structure");
Path rsslFile = reactics.getRSSLFile();
try {
Files.writeString(rsslFile, "");
PrintWriter rsslStream = new PrintWriter(Files.newBufferedWriter(rsslFile));
printRSStructure(rsslStream);
rsslStream.close();
}
catch (IOException ie) {
System.err.println("[ReactICS] Could not read transition system structure: " + ie.getMessage());
JOptionPane.showMessageDialog(this,
"Could not read transition system structure\n" + ie.getMessage(),
"Error", JOptionPane.ERROR_MESSAGE);
}
updateReactantSet();
transitionSystem.clear();
compressedTransitionSystem.clear();
formulaEditor.resetFormulasStatus();
clearModificationStatus();
}
private void updateReactantSet() {
TreeSet<String> rset = new TreeSet<String>();
rset.addAll(reactionSystem.getReactantsSet());
rset.addAll(contextAutomaton.getReactantsSet());
rset.remove("");
reactantSetPanel.updateReactants(rset);
}
private void updateTransitionSystem() {
if (isModified()) {
updateReactionSystem();
}
else if (transitionSystem.isComputed()) {
// Skip re-computing transition system structure
return;
}
try {
String tsStructure = reactics.getTransitionSystemStructure();
transitionSystem.updateTransitionSystemStructure(tsStructure);
compressedTransitionSystem.updateTransitionSystemStructure(tsStructure);
}
catch (IOException ioe) {
System.out.println("IOException: " + ioe.getMessage());
JOptionPane.showMessageDialog(this,
"Could not write reaction system structure to a temporary file\n" + ioe.getMessage(),
"Error", JOptionPane.ERROR_MESSAGE);
}
catch (ReacticsRuntimeException rre) {
JOptionPane.showMessageDialog(this,
rre.getMessage(),
"Reactics Runtime Error", JOptionPane.ERROR_MESSAGE);
}
}
private void evaluateFormulas() {
if (isModified())
updateReactionSystem();
Collection<Formula> formulas = formulaEditor.getSelectedFormulas();
for (Formula ff : formulas) {
try {
System.out.println("[ReactICS] Evaluate formula: " + ff.label);
ReacticsRuntime.EvalResult result = reactics.evaluateFormula(ff.label);
ff.status = result.result();
ff.mcTime = result.mcTime() + " s";
ff.totalTime = result.totalTime() + " s";
ff.memory = result.memory() + " MB";
formulaEditor.refreshStatus();
}
catch(IOException ioe) {
System.err.println("[ReactICS] Error: " + ioe.getMessage());
JOptionPane.showMessageDialog(this, ioe.getMessage(),
"Error", JOptionPane.ERROR_MESSAGE);
}
catch(ReacticsRuntimeException rre) {
System.err.println("[ReactICS] Runtime error: " + rre.getMessage());
JOptionPane.showMessageDialog(this,
rre.getMessage(),
"Reactics Runtime Error", JOptionPane.ERROR_MESSAGE);
}
}
}
private void saveAsXML() {
File outFile = fileSelector.selectOutputFile(this, new File(System.getProperty("user.dir")),
new FileNameExtensionFilter("Reaction System (XML)", "xml"));
if(outFile == null)
return;
System.out.println("[ReactICS] Save to XML file: " + outFile.getName());
try {
PrintWriter xmlOut = new PrintWriter(new FileWriter(outFile));
xmlOut.println("<xml version=\"1.0\">");
reactionSystem.exportToXML(xmlOut);
contextAutomaton.exportToXML(xmlOut);
formulaEditor.exportToXML(xmlOut);
xmlOut.println("</xml>");
xmlOut.flush();
xmlOut.close();
}
catch(IOException ioe) {
JOptionPane.showMessageDialog(this, ioe.getMessage(),
"Error", JOptionPane.ERROR_MESSAGE);
}
}
private void loadFromXML() {
FileNameExtensionFilter rpnFilter = new FileNameExtensionFilter("Reaction System (XML)", "xml");
JFileChooser fc = new JFileChooser();
fc.addChoosableFileFilter(rpnFilter);
fc.setAcceptAllFileFilterUsed(true);
fc.setCurrentDirectory(new File(System.getProperty("user.dir")));
if(fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
File xmlFile = fc.getSelectedFile();
System.out.println("[ReactICS] Load from XML file: " + xmlFile.getName());
reactionSystem.clear();
contextAutomaton.clear();
reactantSetPanel.clear();
transitionSystem.clear();
compressedTransitionSystem.clear();
try {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(xmlFile);
doc.getDocumentElement().normalize();
reactionSystem.loadFromXML(doc);
contextAutomaton.loadFromXML(doc);
formulaEditor.loadFromXML(doc);
updateReactionSystem();
}
catch(IOException ioe) {
System.err.println("[ReactICS] Error: " + ioe.getMessage());
JOptionPane.showMessageDialog(this, "Error reading file: " + xmlFile.getName() + "\n" + ioe.getMessage(),
"Input error", JOptionPane.ERROR_MESSAGE);
reactionSystem.clear();
contextAutomaton.clear();
reactantSetPanel.clear();
}
catch(ParserConfigurationException pce) {
System.err.println("[ReactICS] Parse Configuration Error: " + pce.getMessage());
JOptionPane.showMessageDialog(this, pce.getMessage(),
"Parse Configuration Error", JOptionPane.ERROR_MESSAGE);
reactionSystem.clear();
contextAutomaton.clear();
reactantSetPanel.clear();
}
catch(SAXException se) {
System.err.println("[ReactICS] SAXException: " + se.getMessage());
JOptionPane.showMessageDialog(this, se.getMessage(),
"Parse error", JOptionPane.ERROR_MESSAGE);
reactionSystem.clear();
contextAutomaton.clear();
reactantSetPanel.clear();
}
catch(FileStructureError err) {
System.err.println("[ReactICS]: File Structure Error: " + err.getMessage());
JOptionPane.showMessageDialog(this, err.getMessage(),
"XML file structure error", JOptionPane.ERROR_MESSAGE);
reactionSystem.clear();
contextAutomaton.clear();
reactantSetPanel.clear();
}
catch(Exception e) {
System.out.println("[ReactICS] Error: " + e.getMessage());
JOptionPane.showMessageDialog(this, "Unexpected error reading file: " + xmlFile.getName() +
" " + e.getMessage(),
"Input error", JOptionPane.ERROR_MESSAGE);
reactionSystem.clear();
contextAutomaton.clear();
reactantSetPanel.clear();
}
setTitle("ReactICS GUI : " + xmlFile.getName());
}
modulePane.setSelectedIndex(0);
clearModificationStatus();
}
private void exportToRSSLFile() {
File outFile = fileSelector.selectOutputFile(this, new File(System.getProperty("user.dir")),
new FileNameExtensionFilter("Reaction System Specification Language (RSSL)", "rs"));
if(outFile == null)
return;
System.out.println("[ReactICS] Export to RSSL file: " + outFile.getName());
try {
PrintWriter rsslOut = new PrintWriter(new FileWriter(outFile));
printRSStructure(rsslOut);
rsslOut.close();
}
catch(IOException ioe) {
System.err.println("[ReactICS] Error: " + ioe.getMessage());
JOptionPane.showMessageDialog(this, ioe.getMessage(),
"Error", JOptionPane.ERROR_MESSAGE);
}
}
private void importFromRSSL() {
//TODO: Import reaction system structure from RSSL file
}
private void exportToISPLFile() {
updateReactionSystem();
File outFile = fileSelector.selectOutputFile(this, new File(System.getProperty("user.dir")),
new FileNameExtensionFilter("Interpreted System Programming Language (ISPL)", "ispl"));
if (outFile == null)
return;
System.out.println("[ReactICS] Export to ISPL file: " + outFile.getAbsolutePath());
try {
if (!outFile.exists() && !outFile.createNewFile()) {
System.err.println("[ReactICS] Error: The file "+ outFile.getName() + " could not be created.");
JOptionPane.showMessageDialog(this,
"The file " + outFile.getName() + " could not be created.",
"Error", JOptionPane.ERROR_MESSAGE);
return;
}
if (!outFile.canWrite()){
System.err.println("[ReactICS] Error: Cannot write to the file " + outFile.getName());
JOptionPane.showMessageDialog(this,
"Cannot write to the file " + outFile.getName(),
"Error", JOptionPane.ERROR_MESSAGE);
return;
}
reactics.exportToISPLFile(outFile);
}
catch(IOException ioe) {
System.err.println("[ReactICS] Error: " + ioe.getMessage());
JOptionPane.showMessageDialog(this, ioe.getMessage(),
"Error", JOptionPane.ERROR_MESSAGE);
}
catch(ReacticsRuntimeException rre) {
System.err.println("[ReactICS] Runtime error: " + rre.getMessage());
JOptionPane.showMessageDialog(this,
rre.getMessage(),
"ReactICS Runtime Error", JOptionPane.ERROR_MESSAGE);
}
}
private void printRSStructure(PrintWriter outStream) {
outStream.println("options { use-context-automaton; make-progressive; };\n");
outStream.println(reactionSystem.toRSSLString());
outStream.println(contextAutomaton.toRSSLString());
outStream.println(formulaEditor.toRSSLString());
}
private void showAboutWindow() {
AboutWindow awin = AboutWindow.getInstance();
awin.show(this);
}
private void showHelpWindow() {
HelpWindow hwin = HelpWindow.getInstance();
hwin.show(this);
}
public static void main(String args[]) {
ReacticsGUI wnd = new ReacticsGUI();
wnd.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
wnd.setLocationRelativeTo(null);
wnd.setVisible(true);
}
}
//-------------------------------------------------------------------------------------------------
// Class responsible for selecting a file to load / save the reaction system structure
//-------------------------------------------------------------------------------------------------
class FileSelector {
private enum Status {SELECT, APPROVE, CANCEL}
private static FileSelector instance = null;
public static synchronized FileSelector getInstance() {
if (instance == null)
instance = new FileSelector();
return instance;
}
private FileSelector() { }
public File selecInputFile(Component parent, File rootDir, FileNameExtensionFilter filter) {
Status opStatus = Status.SELECT;
JFileChooser fc = new JFileChooser();
fc.addChoosableFileFilter(filter);
fc.setAcceptAllFileFilterUsed(true);
fc.setCurrentDirectory(rootDir);
File outFile = null;
do {
if (fc.showOpenDialog(parent) != JFileChooser.APPROVE_OPTION) {
opStatus = Status.CANCEL;
break;
}
outFile = fc.getSelectedFile();
opStatus = Status.APPROVE;
if(outFile.exists()) {
if (JOptionPane.showConfirmDialog(parent,
"The file " + outFile.getName() + " already exists.\n" +
"Do you wish to overwrite it?", "Warning",
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.NO_OPTION) {
opStatus = Status.SELECT;
}
}
} while(opStatus == Status.SELECT);
if(opStatus != Status.APPROVE)
outFile = null;
return outFile;
}
public File selectOutputFile(Component parent, File rootDir, FileNameExtensionFilter filter) {
Status opStatus = Status.SELECT;
JFileChooser fc = new JFileChooser();
fc.addChoosableFileFilter(filter);
fc.setAcceptAllFileFilterUsed(true);
fc.setCurrentDirectory(rootDir);
File outFile = null;
do {
if (fc.showSaveDialog(parent) != JFileChooser.APPROVE_OPTION) {
opStatus = Status.CANCEL;
break;
}
outFile = fc.getSelectedFile();
opStatus = Status.APPROVE;
if(outFile.exists()) {
if (JOptionPane.showConfirmDialog(parent,
"The file " + outFile.getName() + " already exists.\n" +
"Do you wish to overwrite it?", "Warning",
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.NO_OPTION) {
opStatus = Status.SELECT;
}
}
} while(opStatus == Status.SELECT);
if(opStatus != Status.APPROVE)
outFile = null;
return outFile;
}
}
/**
* General exception class for any error related to input XML file
*/
class FileStructureError extends Exception {
public FileStructureError(String message) {
super(message);
}
}

View File

@@ -0,0 +1,175 @@
package pl.umk.mat.martinp.reactics;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
public class ReacticsRuntime {
private static ReacticsRuntime instance = null;
private String rctPath = "./";
private String rctRuntime = "reactics";
private Path rsslFile;
public static synchronized ReacticsRuntime getInstance() throws ReacticsRuntimeException {
if (instance == null)
instance = new ReacticsRuntime();
return instance;
}
private ReacticsRuntime() throws ReacticsRuntimeException {
try {
rsslFile = Files.createTempFile("reaction_system_", ".rs");
rsslFile.toFile().deleteOnExit();
System.out.println("[ReactICS] Created temporary file: " + rsslFile.toAbsolutePath());
}
catch (IOException e) {
System.err.println("[ReactICS] Problem creating temporary file: + e.getMessage()");
throw new ReacticsRuntimeException("Problem creating temporary file:\n" + e.getMessage());
}
}
public Path getRSSLFile() {
return rsslFile;
}
public void loadConfig(String configFileName) throws ConfigReadingError, InvalidRuntimeException {
System.out.println("[ReactICS] Loading configuration from: " + configFileName);
try {
BufferedReader inputStream = new BufferedReader(new FileReader(configFileName));
String line;
while ((line = inputStream.readLine()) != null) {
if (line.startsWith("runtime")) {
line = line.replaceAll("\\s", "");
rctRuntime = line.substring(line.indexOf(":") + 1);
}
else if (line.startsWith("path")) {
line = line.replaceAll("\\s", "");
rctPath = line.substring(line.indexOf(":") + 1);
}
}
}
catch (IOException e) {
throw new ConfigReadingError(e.getMessage());
}
File rctRntFile = new File(rctPath + rctRuntime);
if (! (rctRntFile.exists() && rctRntFile.canExecute()) ) {
throw new InvalidRuntimeException(rctPath + rctRuntime + " cannot be executed");
}
System.out.println("[ReactICS] Using runtime: " + rctPath + rctRuntime);
}
public EvalResult evaluateFormula(String fLabel) throws IOException, ReacticsRuntimeException {
// Drop "-B" option if verification time should not be measured
Process proc = Runtime.getRuntime().exec(rctPath + rctRuntime + " -B -c " + fLabel + " " + rsslFile.toAbsolutePath(),
null, new File(rctPath));
BufferedReader formResultStream = new BufferedReader(new InputStreamReader(proc.getInputStream()));
String line;
Formula.FormulaStatus result = Formula.FormulaStatus.None;
String mcTime = "";
String totalTime = "";
String memory = "";
while ((line = formResultStream.readLine()) != null) {
if (line.contains("holds")) {
result = Formula.FormulaStatus.True;
}
else if (line.contains("not hold")) {
result = Formula.FormulaStatus.False;
}
else if (line.startsWith("STAT")) {
String[] tokens = line.split(";");
mcTime = tokens[2];
totalTime = tokens[5];
memory = tokens[4];
}
//TODO: Handle incorrect formulas
}
StringBuilder errMsg = new StringBuilder();
BufferedReader procErrStr = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
while ((line = procErrStr.readLine()) != null) {
errMsg.append(line).append("\n");
}
if (!errMsg.isEmpty())
throw new ReacticsRuntimeException(errMsg.toString());
return new EvalResult(result, mcTime, totalTime, memory);
}
/** Complete Transition System. Context automaton state included. */
public String getTransitionSystemStructure() throws IOException, ReacticsRuntimeException {
Process proc = Runtime.getRuntime().exec(rctPath + rctRuntime + " -t -X " + rsslFile.toAbsolutePath(),
null, new File(rctPath));
String line;
StringBuilder tsString = new StringBuilder();
BufferedReader procOutStr = new BufferedReader(new InputStreamReader(proc.getInputStream()));
while ((line = procOutStr.readLine()) != null) {
tsString.append(line).append("\n");
}
StringBuilder errMsg = new StringBuilder();
BufferedReader procErrStr = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
while ((line = procErrStr.readLine()) != null) {
errMsg.append(line).append("\n");
}
if (!errMsg.isEmpty())
throw new ReacticsRuntimeException(errMsg.toString());
return tsString.toString();
}
public void exportToISPLFile(File outFile) throws IOException, ReacticsRuntimeException {
Process proc = Runtime.getRuntime().exec(rctPath + rctRuntime + " -e " + rsslFile.toAbsolutePath() + " > " + outFile.getAbsolutePath(),
null, new File(rctPath));
String line;
BufferedReader procOutStr = new BufferedReader(new InputStreamReader(proc.getInputStream()));
PrintWriter isplOutStr = new PrintWriter(new FileWriter(outFile));
while ((line = procOutStr.readLine()) != null) {
isplOutStr.println(line);
}
isplOutStr.close();
StringBuilder errMsg = new StringBuilder();
BufferedReader procErrStr = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
while ((line = procErrStr.readLine()) != null) {
errMsg.append(line).append("\n");
}
if (!errMsg.isEmpty())
throw new ReacticsRuntimeException(errMsg.toString());
}
record EvalResult(Formula.FormulaStatus result, String mcTime, String totalTime, String memory) {}
}
class ConfigReadingError extends Exception {
public ConfigReadingError(String message) {
super(message);
}
}
class InvalidRuntimeException extends Exception {
public InvalidRuntimeException(String description) {
super(description);
}
}
class ReacticsRuntimeException extends Exception {
public ReacticsRuntimeException(String description) {
super(description);
}
}

View File

@@ -0,0 +1,335 @@
/** A component for editing the reaction system details */
package pl.umk.mat.martinp.reactics;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.Vector;
//-------------------------------------------------------------------------------------------------
/** A graphical component allowing edition and display of a distributed reaction system.
* A distributed reaction system contains a number of processes.
* Each process contains a list of reactions.
*/
public class ReactionSystemEditor extends JPanel {
private Vector<ProcessEditor> processes = new Vector<ProcessEditor>();
private JPanel processPanel = new JPanel();
private boolean modified = false;
public ReactionSystemEditor() {
processPanel.setLayout(new BoxLayout(processPanel, BoxLayout.Y_AXIS));
final Dimension buttonSize = new Dimension(150, 25);
JButton addButton = new JButton("New process");
addButton.setMaximumSize(buttonSize);
addButton.setPreferredSize(buttonSize);
addButton.setMaximumSize(buttonSize);
addButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
createProcess();
}
});
JButton rmButton = new JButton("Remove");
rmButton.setMaximumSize(buttonSize);
rmButton.setPreferredSize(buttonSize);
rmButton.setMaximumSize(buttonSize);
rmButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
removeProcesses();
}
});
JButton copyButton = new JButton("Copy");
copyButton.setMaximumSize(buttonSize);
copyButton.setPreferredSize(buttonSize);
copyButton.setMaximumSize(buttonSize);
copyButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
copyProcesses();
}
});
JButton upButton = new JButton("Move up");
upButton.setMaximumSize(buttonSize);
upButton.setPreferredSize(buttonSize);
upButton.setMaximumSize(buttonSize);
upButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
moveSelectedUp();
}
});
JButton downButton = new JButton("Move down");
downButton.setMaximumSize(buttonSize);
downButton.setPreferredSize(buttonSize);
downButton.setMaximumSize(buttonSize);
downButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
moveSelectedDown();
}
});
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.Y_AXIS));
buttonPanel.add(addButton);
buttonPanel.add(rmButton);
buttonPanel.add(copyButton);
buttonPanel.add(upButton);
buttonPanel.add(downButton);
this.setLayout(new BorderLayout());
this.add(new JScrollPane(processPanel), BorderLayout.CENTER);
this.add(buttonPanel, BorderLayout.WEST);
createProcess();
}
public boolean isModified() {
boolean procModified = false;
for (ProcessEditor pe : processes)
procModified |= pe.isModified();
return modified | procModified;
}
public void clearModificationStatus() {
for (ProcessEditor pe : processes)
pe.clearModificationStatus();
modified = false;
}
public Set<String> getReactantsSet() {
HashSet<String> rset = new HashSet<String>();
for (ProcessEditor pe : processes)
rset.addAll(pe.getReactantsSet());
return rset;
}
public void exportToXML(PrintWriter output) {
output.println(" <reaction-system>");
for (ProcessEditor process : processes)
process.exportToXML(output);
output.println(" </reaction-system>");
}
public void loadFromXML(Document input) throws ReactionSystemStructureError {
NodeList rsSextions = input.getElementsByTagName("reaction-system");
if (rsSextions.getLength() == 0 || rsSextions.getLength() > 1) {
throw new ReactionSystemStructureError("An XML file should contain a single reaction system description.");
}
NodeList processList = rsSextions.item(0).getChildNodes();
for (int idx=0; idx<processList.getLength(); ++idx) {
Node processNode = processList.item(idx);
if (processNode.getNodeType() != Node.ELEMENT_NODE)
continue;
String procName = processNode.getAttributes().getNamedItem("name").getNodeValue();
ProcessEditor processEditor = new ProcessEditor(procName);
//----------------------------------------------------------------------------------------------------------
// Reactins details
//----------------------------------------------------------------------------------------------------------
NodeList reactionList = processNode.getChildNodes();
for (int rIdx=0; rIdx<reactionList.getLength(); ++rIdx) {
Node reactionNode = reactionList.item(rIdx);
if (reactionNode.getNodeType() != Node.ELEMENT_NODE)
continue;
Reaction reaction = new Reaction();
NodeList substrateNodes = reactionNode.getChildNodes();
for (int sIdx=0; sIdx<substrateNodes.getLength(); ++sIdx) {
Node sNode = substrateNodes.item(sIdx);
if (sNode.getNodeType() != Node.ELEMENT_NODE)
continue;
String nodeName = sNode.getNodeName();
String nodeContent = sNode.getTextContent();
switch (nodeName) {
case "reactant":
reaction.reactants.add(nodeContent);
break;
case "inhibitor":
reaction.inhibitors.add(nodeContent);
break;
case "product":
reaction.products.add(nodeContent);
break;
default:
throw new ReactionSystemStructureError("Unexpected node type: " + nodeName);
}
}
processEditor.addReaction(reaction);
}
processes.add(processEditor);
processPanel.add(processEditor);
}
revalidate();
}
public String toRSSLString() {
StringBuilder rsString = new StringBuilder("reactions {\n");
for (ProcessEditor pe : processes) {
rsString.append(pe.toRSSLString());
}
rsString.append("};\n");
return rsString.toString();
}
public void clear() {
processPanel.removeAll();
processes.clear();
}
private void createProcess() {
ProcessEditor newProcess = new ProcessEditor();
processes.add(newProcess);
processPanel.add(newProcess);
modified = true;
revalidate();
}
private void createProcess(String label) {
ProcessEditor newProcess = new ProcessEditor(label);
processes.add(newProcess);
processPanel.add(newProcess);
modified = true;
revalidate();
}
private void removeProcesses() {
Vector<ProcessEditor> toRemove = new Vector<ProcessEditor>();
for (ProcessEditor edt : processes) {
if (edt.isSelected()) {
toRemove.add(edt);
}
}
if (toRemove.size() == processes.size()) {
JOptionPane.showMessageDialog(this, "There should be at least a single process in the system.",
"Warning", JOptionPane.WARNING_MESSAGE);
return;
}
for (ProcessEditor edt : toRemove) {
processPanel.remove(edt);
processes.remove(edt);
}
modified = true;
revalidate();
}
private void copyProcesses() {
Vector<ProcessEditor> toBeCopied = new Vector<ProcessEditor>();
for (ProcessEditor edt : processes) {
if (edt.isSelected()) {
toBeCopied.add(edt);
}
}
for (ProcessEditor edt : toBeCopied) {
ProcessEditor newEdt = new ProcessEditor(edt);
processPanel.add(newEdt);
processes.add(newEdt);
}
modified = true;
revalidate();
}
private void moveSelectedUp() {
Vector<Integer> toBeMovedIds = new Vector<Integer>();
for (int i=0; i<processes.size(); ++i) {
if (processes.elementAt(i).isSelected())
toBeMovedIds.add(i);
}
for (int pos : toBeMovedIds) {
if (pos == 0 || processes.elementAt(pos-1).isSelected())
continue;
Collections.swap(processes, pos, pos-1);
}
processPanel.removeAll();
for (ProcessEditor edt : processes) {
processPanel.add(edt);
}
modified = true;
revalidate();
}
private void moveSelectedDown() {
Vector<Integer> toBeMovedIds = new Vector<Integer>();
for (int i=processes.size()-1; i>=0; --i) {
if (processes.elementAt(i).isSelected())
toBeMovedIds.add(i);
}
for (int pos : toBeMovedIds) {
if (pos == processes.size()-1 || processes.elementAt(pos+1).isSelected())
continue;
Collections.swap(processes, pos, pos+1);
}
processPanel.removeAll();
for (ProcessEditor edt : processes) {
processPanel.add(edt);
}
modified = true;
revalidate();
}
}
class ReactionSystemStructureError extends FileStructureError {
public ReactionSystemStructureError(String message) {
super(message);
}
}

View File

@@ -0,0 +1,208 @@
package pl.umk.mat.martinp.reactics;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
class TransitionEditor extends JDialog {
private JTable transitionsList;
private TransitionTableModel transitionsModel;
private Vector<Transition> transitions;
public TransitionEditor() {
super();
// Create reactions list component
transitionsModel = new TransitionTableModel();
transitionsList = new JTable(transitionsModel);
Font cellFont = transitionsList.getFont();
Font headerFont = transitionsList.getTableHeader().getFont();
transitionsList.setFont(new Font("Monospaced", 0, cellFont.getSize() + 2));
transitionsList.getTableHeader().setFont(new Font(headerFont.getFamily(), Font.BOLD, headerFont.getSize() + 2));
transitionsList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
getContentPane().setLayout(new BorderLayout());
JButton addButton = new JButton("Add transition");
addButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) { addTransition(); }
});
JButton edtButton = new JButton("Edit transition");
edtButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) { editTransition(); }
});
JButton rmButton = new JButton("Remove transition");
rmButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) { removeTransitions(); }
});
JButton closeButton = new JButton("Close");
closeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) { setVisible(false); }
});
JPanel buttonPanel = new JPanel();
buttonPanel.add(addButton);
buttonPanel.add(edtButton);
buttonPanel.add(rmButton);
buttonPanel.add(closeButton);
getContentPane().add(buttonPanel, BorderLayout.NORTH);
getContentPane().add(new JScrollPane(transitionsList), BorderLayout.CENTER);
setPreferredSize(new Dimension(-1, 200));
setSize(new Dimension(600, 200));
setTitle("Transitions");
}
public void showTransitionEditDialog(Component parent, Collection<Transition> trans, String from, String to) {
setTitle("Transitions for (" + from + " -> " + to + ")");
transitions = (Vector<Transition>) trans;
transitionsModel.fireTableDataChanged();
setLocationRelativeTo(parent);
setVisible(true);
}
private void addTransition() {
Transition tr = new Transition("", "");
if (!showTransitionEditDialog(tr))
return;
transitions.add(tr);
transitionsModel.fireTableDataChanged();
}
private void editTransition() {
int[] selected = transitionsList.getSelectedRows();
if (selected.length != 1) {
JOptionPane.showMessageDialog(this, "Select a single transition to edit.", "Select transition", JOptionPane.WARNING_MESSAGE);
return;
}
Transition tr = transitions.get(selected[0]);
transitionsList.clearSelection();
if (!showTransitionEditDialog(tr)) {
return;
}
transitions.setElementAt(tr, selected[0]);
transitionsModel.fireTableDataChanged();
}
private void removeTransitions() {
int[] selected = transitionsList.getSelectedRows();
for (int trIdx : selected) {
transitions.remove(trIdx);
transitionsList.clearSelection();
}
transitionsModel.fireTableDataChanged();
}
private boolean showTransitionEditDialog(Transition tr) {
final int Select = 0;
final int Approve = 1;
JTextField contextInput = new JTextField(30);
JTextField guardInput = new JTextField(30);
String contextStr = tr.context;
String guardStr = tr.guard;
contextInput.setToolTipText("<html>Additional entities for processes</html>");
guardInput.setToolTipText("<html>Initial conditions for the transition in rsCTL format.</html>");
JPanel myPanel = new JPanel(new GridBagLayout());
GridBagConstraints cs = new GridBagConstraints();
cs.insets = new Insets(5, 5, 5, 5);
cs.anchor = GridBagConstraints.WEST;
cs.gridx = 0;
cs.gridy = 0;
myPanel.add(new JLabel("Context"), cs);
cs.gridx = 1;
cs.gridy = 0;
myPanel.add(contextInput, cs);
cs.gridx = 0;
cs.gridy = 1;
myPanel.add(new JLabel("Guards"), cs);
cs.gridx = 1;
cs.gridy = 1;
myPanel.add(guardInput, cs);
int opStatus = Select;
do {
contextInput.setText(contextStr);
guardInput.setText(guardStr);
int result = JOptionPane.showConfirmDialog(null, myPanel,
"Reaction details", JOptionPane.OK_CANCEL_OPTION);
if (result != JOptionPane.OK_OPTION) {
return false;
}
contextStr = contextInput.getText();
guardStr = guardInput.getText();
//TODO: Validate the data entered to the text fields
tr.context = contextStr;
tr.guard = guardStr;
opStatus = Approve;
}
while(opStatus == Select);
return true;
}
class TransitionTableModel extends AbstractTableModel {
private final String[] columnNames = {"Context", "Guard"};
public String getColumnName(int colIdx) {
return columnNames[colIdx];
}
public int getRowCount() {
return transitions.size();
}
public int getColumnCount() {
return columnNames.length;
}
public Object getValueAt(int row, int column) {
Transition tr = transitions.get(row);
return switch (column) {
case 0 -> tr.context;
case 1 -> tr.guard;
default -> "";
};
}
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
}
}

View File

@@ -0,0 +1,341 @@
package pl.umk.mat.martinp.reactics;
import edu.uci.ics.jung.algorithms.layout.*;
import edu.uci.ics.jung.algorithms.layout.util.Relaxer;
import edu.uci.ics.jung.graph.DirectedSparseGraph;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.renderers.Renderer;
import org.apache.commons.collections15.Transformer;
import javax.swing.JPanel;
import javax.swing.JToolTip;
import javax.swing.Icon;
import javax.swing.border.EtchedBorder;
import java.awt.*;
import java.util.HashMap;
public class TransitionSystemViewer extends JPanel {
//---------------------------------------------------------------
// Visual configuration settings
//---------------------------------------------------------------
private static final Color pickedNodeColor = Color.yellow;
private static final Color edgeColor = Color.black;
private static final Font nodeLabelFont = new Font("Helvetica", Font.BOLD, 20);
private static final Font toolTipFont = new Font("Helvetica", Font.PLAIN, 14);
private static final Stroke basicEdgeArrow = new BasicStroke(3.0f);
private static Dimension initialEditorSize;
private VisualizationViewer<TSState, TSTransition> graphViewer;
private Layout<TSState, TSTransition> graphLayout;
DefaultModalGraphMouse graphMouse;
//---------------------------------------------------------------
// Transition system graph
//---------------------------------------------------------------
private DirectedSparseGraph<TSState, TSTransition> tsGraph;
private boolean isCompressed;
// Is transition system already computed?
// Used to avoid unnecessary computation of the same graph.
private boolean computed = false;
// compressed == true => context automaton state is not taken into account.
public TransitionSystemViewer(Dimension size, boolean compressed) {
this.setLayout(new BorderLayout());
tsGraph = new DirectedSparseGraph<TSState, TSTransition>();
this.isCompressed = compressed;
initialEditorSize = size;
createGraphViever();
lockNodesPositions(false);
}
// Is transition system graph already computed?
boolean isComputed() { return computed; }
private void createGraphViever() {
graphLayout = new FRLayout<TSState, TSTransition>(tsGraph);
graphViewer = new VisualizationViewer<TSState, TSTransition>(graphLayout, initialEditorSize) {
public JToolTip createToolTip() {
JToolTip tooltip = super.createToolTip();
tooltip.setFont(toolTipFont);
return tooltip;
}
};
// Nodes labels
graphViewer.getRenderContext().setVertexLabelTransformer(
new ToStringLabeller<TSState>());
graphViewer.getRenderer().getVertexLabelRenderer()
.setPosition(Renderer.VertexLabel.Position.CNTR);
graphViewer.getRenderContext().setVertexFontTransformer(
new Transformer<TSState,Font>() {
public Font transform(TSState st) {
return nodeLabelFont;
}
});
// Nodes colour, background, etc.
graphViewer.getRenderContext().setVertexIconTransformer(new Transformer<TSState,Icon>() {
public Icon transform(final TSState v) {
Color nodeColor = null;
if(graphViewer.getPickedVertexState().isPicked(v))
nodeColor = pickedNodeColor;
else
nodeColor = v.getFillColor();
return (v.isInitial() ? new SquareIcon(nodeColor, v.getWidth()) : new CircleIcon(nodeColor, v.getWidth()));
}});
graphViewer.getRenderContext().setEdgeShapeTransformer(new ConditionalEdgeShape<>(1.5f));
// Edges
graphViewer.getRenderContext().setEdgeArrowStrokeTransformer(new Transformer<TSTransition, Stroke>() {
public Stroke transform(TSTransition tr) {
return basicEdgeArrow;
}
});
// For interactive graph editing
graphMouse = new DefaultModalGraphMouse();
graphViewer.setGraphMouse(graphMouse);
graphMouse.setMode(ModalGraphMouse.Mode.PICKING);
graphViewer.setVertexToolTipTransformer(new Transformer<TSState, String>() {
public String transform(TSState st) {
return st.getTooltipText();
}
});
graphViewer.setBackground(Color.white);
graphViewer.setBorder(new EtchedBorder(EtchedBorder.LOWERED));
add(graphViewer, BorderLayout.CENTER);
}
public void updateTransitionSystemStructure(String tsString) {
// Clear graph
clear();
// Load graph structure
int nextId = 1;
HashMap<String, TSState> nodes = new HashMap<>();
TSState src = null;
for (String line : tsString.split("\\R")) {
char type = line.charAt(0);
line = line.substring(2).strip();
int end = line.lastIndexOf('}');
String automatonState = line.substring(end + 1, line.length() - 1).strip();
String stateStr = line.substring(line.indexOf('{'), end + 1);
TSState state = nodes.get( isCompressed ? stateStr : line);
if (state == null) {
state = new TSState(nextId++, stateStr, isCompressed ? null : automatonState);
nodes.put(isCompressed ? stateStr : line, state);
tsGraph.addVertex(state);
}
if (type == 'G') {
src = state;
}
else if (type == 's') {
tsGraph.addEdge(new TSTransition(), src, state);
}
}
// Find the initial state
for (TSState state : tsGraph.getVertices()) {
if (tsGraph.getInEdges(state).size() == 0) {
state.setAsInitial();
break;
}
}
relax();
graphViewer.repaint();
computed = true;
}
public void lockNodesPositions(boolean lock) {
if (lock)
graphMouse.setMode(ModalGraphMouse.Mode.TRANSFORMING);
else
graphMouse.setMode(ModalGraphMouse.Mode.PICKING);
}
public void clear() {
for (TSState st : new java.util.ArrayList<TSState>(tsGraph.getVertices())) {
tsGraph.removeVertex(st);
}
repaint();
}
/** For better vertex placement */
private void relax() {
graphLayout.initialize();
graphLayout.setSize(graphViewer.getSize());
Relaxer relaxer = graphViewer.getModel().getRelaxer();
if (relaxer != null) {
relaxer.stop();
relaxer.prerelax();
relaxer.relax();
}
}
//---------------------------------------------------------------------------------------------
// For drawing the initial state
//---------------------------------------------------------------------------------------------
static class SquareIcon implements Icon {
private final Color fillColor;
private final int width;
public SquareIcon(Color fColor, int width) {
fillColor = fColor;
this.width = width;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
g.setColor(fillColor);
g.fillRect(x, y, width, width);
g.setColor(Color.black);
g.drawRect(x, y, width, width);
}
public int getIconWidth() {
return width;
}
public int getIconHeight() {
return width;
}
}
//---------------------------------------------------------------------------------------------
// For drawing all non-initial states
//---------------------------------------------------------------------------------------------
static class CircleIcon implements Icon {
private final Color fillColor;
private final int radius;
public CircleIcon(Color fColor, int radius) {
fillColor = fColor;
this.radius = radius;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
g.setColor(fillColor);
g.fillOval(x, y, radius, radius);
g.setColor(Color.black);
g.drawOval(x, y, radius, radius);
}
public int getIconWidth() {
return radius;
}
public int getIconHeight() {
return radius;
}
}
}
//-------------------------------------------------------------------------------------------------
// Single node of the Transition system graph
//-------------------------------------------------------------------------------------------------
class TSState {
public final static Color fillColor = Color.lightGray;
public final static Color initialColor = new Color(102, 178, 255);
private static final int nodeWidth = 30;
private static final int nodeHeight = 30;
private final int id;
private final String rsDetails;
private final String aState;
private boolean initial = false;
public TSState(int id, String stateStr) {
this.id = id;
this.rsDetails = formatRSDetails(stateStr.substring(1, stateStr.length() - 1).strip());
this.aState = null;
}
public TSState(int id, String stateStr, String aState) {
this.id = id;
this.rsDetails = formatRSDetails(stateStr.substring(1, stateStr.length() - 1).strip());
this.aState = aState;
}
public boolean isInitial() {
return initial;
}
public void setAsInitial() {
initial = true;
}
public String toString() {
return String.valueOf(id);
}
public int getWidth() {
return nodeWidth;
}
public int getHeight() {
return nodeHeight;
}
Color getFillColor() {
return initial ? initialColor : fillColor;
}
public String getTooltipText() {
if (aState != null)
return "<html>"+
"<b>CA</b> " + aState+ "<br/><hr>" + rsDetails +
"</html>";
else
return "<html>"+
rsDetails +
"</html>";
}
private String formatRSDetails(String rsDetails) {
return rsDetails.replaceAll("\\} (proc\\d+=\\{)", "}<br/>$1");
}
}
//-------------------------------------------------------------------------------------------------
// Single edge of the Transition system graph
//-------------------------------------------------------------------------------------------------
class TSTransition { }