import java.net.URL; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.awt.geom.*; import javax.swing.*; import javax.swing.event.*; import java.lang.Integer; import java.util.*; public class TreeMutexToy extends JPanel implements ActionListener { static final String[] nodeNames = { "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", "mu", "xi", "omikron", "pi", "rho", "sigma", "tau", "upsilon", "phi", "chi", "psi", "omega" }; static final int MAX_NUM_NODES = 23; JLayeredPane layered_pane; static final String MOVE = "Move a Node"; static final String ADD = "Add a Child Node"; static final String DELETE = "Delete a Node"; static final String START = "Start"; static final String STOP = "Stop"; static final String RESET = "Reset"; static final String PREVIOUS = "Previous"; static final String NEXT = "Next"; static final String FOO = "Foo"; static final String BAR = "Bar"; static final String BAZ = "Baz"; JToolBar toolBar; JButton move_button; JButton add_button; JButton delete_button; JButton start_button; JButton stop_button; JButton reset_button; JButton previous_button; JButton next_button; JButton foo_button; JButton bar_button; JButton baz_button; JSpinner spinner; SpinnerNumberModel spinner_model; ArrayList nodeList = new ArrayList(); ArrayList edgeList = new ArrayList(); final static float dash1[] = {5.0f}; final static BasicStroke dashed = new BasicStroke (1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash1, 0.0f); final static float dash2[] = {2.5f}; final static BasicStroke dotted = new BasicStroke (1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash2, 0.0f); final static BasicStroke stroke = new BasicStroke(1.0f); final static BasicStroke boldStroke = new BasicStroke(3.0f); public class NodeLabel extends JLabel implements MouseInputListener { public String name; Point center = new Point(); // Storage members NodeLabel container; public ArrayList contents = new ArrayList(); int request_time = 0; boolean satisfied = false; boolean send = false; boolean waiting = false; NodeLabel parent = this; public ArrayList queue = new ArrayList(); public ArrayList messages = new ArrayList(); public void dump () { System.err.print (name + ": p(" + parent.name + ") q["); Iterator it = queue.iterator(); while (it.hasNext()) { System.err.print(((NodeLabel) it.next()).name + ","); } System.err.print("] - m["); it = messages.iterator(); while (it.hasNext()) { System.err.print(((NodeLabel) it.next()).name + ","); } System.err.println("]; " + satisfied); } public NodeLabel (String name) { this.name = name; final ImageIcon icon = new ImageIcon (NodeLabel.class.getResource("images/Node.gif")); request_time = spinner_model.getNumber().intValue(); setIcon(icon); rebuildLabelString(); setHorizontalTextPosition(JLabel.CENTER); setVerticalTextPosition(JLabel.BOTTOM); setSize(getPreferredSize()); addMouseListener(this); addMouseMotionListener(this); } private void rebuildLabelString () { String str = name + ":{"; Iterator it = queue.iterator(); boolean first = true; while (it.hasNext()) { if (!first) { str = str + ","; } str = str + ((NodeLabel) it.next()).name; first = false; } str = str + "}"; setText(str); setSize(getPreferredSize()); repairCenter(); } private void repairCenter () { Dimension d = getSize(); Point p = getLocation(); center.setLocation (p.x + d.width / 2, p.y + 10); } public void setLocation (Point p) { setLocation(p.x, p.y); } public void setLocation (int x, int y) { super.setLocation(x,y); repairCenter(); edge_layer.repaint(); } /* ****************************************************************** */ public void reset () { satisfied = false; waiting = false; send = false; messages.clear(); queue.clear(); rebuildLabelString(); if (container != null) { parent = container; } else { parent = this; } Iterator it = contents.iterator(); while (it.hasNext()) { ((NodeLabel) it.next()).reset(); } } public void step () { NodeLabel node; send = false; if (request_time == clock.time) { queue.add(this); } if (queue.isEmpty()) { return; } if (parent == this) { waiting = false; node = ((NodeLabel) queue.get(0)); queue.remove(node); if (node == this) { satisfied = true; return; } else { parent = node; send = true; } } else if (!waiting) { send = true; waiting = true; } } /* ****************************************************************** */ public void decorateNode (Graphics2D g2) { // Draw the request time, and it's state. if (clock.time < request_time) { g2.setPaint(Color.gray); } else if (satisfied) { g2.setPaint(Color.green); } else { g2.setPaint(Color.red); } g2.drawString (Integer.toString(request_time), center.x + 15, center.y - 15); if (parent == this) { g2.setPaint(Color.green); RoundRectangle2D token_rect = new RoundRectangle2D.Double (center.x - 15, center.y - 15, 30, 30, 15, 15); g2.fill(token_rect); } else { Point parent_marker = new Point(parent.center); parent_marker.translate(-center.x, -center.y); int mag = (int) parent_marker.distance(0, 0); if (mag < 0) { mag = - mag; } if (mag > 10) { parent_marker.x = 20 * parent_marker.x / mag; parent_marker.y = 20 * parent_marker.y / mag; parent_marker.translate(center.x, center.y); g2.setPaint(Color.blue); g2.setStroke(dotted); g2.fill(new Ellipse2D.Double (parent_marker.x - 3, parent_marker.y - 3, 8, 8)); } } } public void decorateEdge (Graphics2D g2) { if (parent == this) { return; } // The edge is always drawn by the child. if (parent.parent == this) { // The token is in transit. if (send) { // we are the old parent, do nothing. return; } else { drawReceivingEdge(g2, parent); return; } } else if (send) { // a request is in transit drawRequestingEdge(g2, parent); return; } else if (queue.isEmpty()) { drawPassiveEdge(g2, parent); return; } else { // I'm not sending, I don't have the token, // and I have elements in my queue; I must be // waiting. drawWaitingEdge(g2, parent); return; } } void drawPassiveEdge (Graphics2D g2, NodeLabel that) { Line2D line = new Line2D.Double(this.center, that.center); g2.setPaint(Color.gray); g2.setStroke(dashed); g2.draw(line); } void drawWaitingEdge (Graphics2D g2, NodeLabel that) { Line2D line = new Line2D.Double(this.center, that.center); g2.setPaint(Color.red); g2.setStroke(dashed); g2.draw(line); } void drawRequestingEdge (Graphics2D g2, NodeLabel that) { Point mid = new Point ((this.center.x + that.center.x) / 2, (this.center.y + that.center.y) / 2); Line2D a2m = new Line2D.Double(this.center, mid); Line2D m2b = new Line2D.Double(mid, that.center); g2.setPaint(Color.gray); g2.setStroke(dashed); g2.draw(m2b); g2.setPaint(Color.red); g2.setStroke(boldStroke); g2.draw(a2m); g2.fill(new Ellipse2D.Double(mid.x - 5, mid.y - 5, 10, 10)); } void drawReceivingEdge (Graphics2D g2, NodeLabel that) { Point mid = new Point ((this.center.x + that.center.x) / 2, (this.center.y + that.center.y) / 2); Line2D a2m = new Line2D.Double(this.center, mid); Line2D m2b = new Line2D.Double(mid, that.center); g2.setPaint(Color.red); g2.setStroke(dashed); g2.draw(a2m); g2.setPaint(Color.green); g2.setStroke(boldStroke); g2.draw(m2b); g2.fill(new Ellipse2D.Double(mid.x - 5, mid.y - 5, 10, 10)); } /* ****************************************************************** */ public void mouseClicked (MouseEvent e) { squeek("mouseClicked", e); // Never delete alpha. if ((mode == DELETE_MODE) && (this != alpha)) { removeNode(this); layered_pane.repaint(); move_button.doClick(); move_button.grabFocus(); add_button.setEnabled(true); // Never add more than 16. } else if ((mode == ADD_MODE) && (nodeList.size() < MAX_NUM_NODES)) { NodeLabel new_node = new NodeLabel(nodeNames[nodeList.size()]); nodeList.add(new_node); new_node.parent = this; new_node.container = this; contents.add(new_node); new_node.setLocation(center.x + 50, center.y + 50); layered_pane.add(new_node, new Integer(2), 0); move_button.doClick(); move_button.grabFocus(); if (nodeList.size() == MAX_NUM_NODES) { add_button.setEnabled(false); } } } public void mouseEntered (MouseEvent e) { squeek("mouseEntered", e); } public void mouseExited (MouseEvent e) { squeek("mouseExited", e); } Point offset; public void mousePressed (MouseEvent e) { squeek("mousePressed", e); if (mode == MOVE_MODE) { layered_pane.moveToFront(this); offset = e.getPoint(); } } public void mouseReleased (MouseEvent e) { squeek("mouseReleased", e); } public void mouseDragged (MouseEvent e) { squeek("mouseDragged", e); if (mode == MOVE_MODE) { Point p = getLocation(); Dimension d = getSize(); Point new_p = new Point (p.x - offset.x + e.getX(), p.y - offset.y + e.getY()); // Do not permit the center of a node to be // dragged off screen. if (layered_pane.contains (new_p.x + d.width / 2, new_p.y + d.height / 2)) { setLocation(new_p); } } } public void mouseMoved (MouseEvent e) { squeek("mouseMoved", e); } } class EdgeLayer extends JComponent { public EdgeLayer () { super(); } public void paint(Graphics g) { Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint (RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Iterator nodes = nodeList.iterator(); while (nodes.hasNext()) { ((NodeLabel) nodes.next()).decorateNode(g2); } // This ensures that the edges are layed on top of // the decoration for the nodes. nodes = nodeList.iterator(); while (nodes.hasNext()) { ((NodeLabel) nodes.next()).decorateEdge(g2); } } } void squeek (String eventDescription, MouseEvent e) { if (false) { System.err.println (eventDescription + " (" + e.getX() + "," + e.getY() + ")" + " detected on " + e.getComponent().getClass().getName()); } } void removeNode (NodeLabel node) { ArrayList del_list = new ArrayList(); del_list.add(node); while (del_list.size() != 0) { node = ((NodeLabel) del_list.get(0)); del_list.remove(0); nodeList.remove(node); layered_pane.remove(node); Iterator content = node.contents.iterator(); while (content.hasNext()) { del_list.add(content.next()); } node.parent = null; node.contents.clear(); node.queue.clear(); node.messages.clear(); } } TreeMutexToy toypanel; EdgeLayer edge_layer; class ClockLabel extends JLabel { public int time = 0; public ClockLabel (int time, int horizontalAlignment) { super("", horizontalAlignment); setTime(time); } public void setTime (int time) { this.time = time; setText("Clock: " + Integer.toString(time)); } public void step() { setTime(time + 1); } } ClockLabel clock; NodeLabel alpha; public TreeMutexToy () { super(new BorderLayout()); toypanel = this; setPreferredSize(new Dimension(600, 400)); //Create the toolbar. toolBar = new JToolBar("TreeMutexToy Toolbar"); toolBar.setFloatable(false); addButtons(); add(toolBar, BorderLayout.PAGE_START); layered_pane = new JLayeredPane(); layered_pane.setOpaque(true); add(layered_pane, BorderLayout.CENTER); clock = new ClockLabel(-1, JLabel.RIGHT); clock.setBackground(Color.white); add(clock, BorderLayout.PAGE_END); clock.setEnabled(false); edge_layer = new EdgeLayer(); edge_layer.setBounds(0, 0, 1000, 1000); layered_pane.add(edge_layer, new Integer(1), 0); layered_pane.addComponentListener(new ComponentAdapter() { public void componentResized (ComponentEvent e) { edge_layer.setSize(layered_pane.getSize()); } }); // We initialy have only a single node. alpha = new NodeLabel(nodeNames[nodeList.size()]); nodeList.add(alpha); alpha.setLocation(200, 100); layered_pane.add(alpha, new Integer(2), 0); } final static int MOVE_MODE = 1; final static int ADD_MODE = 2; final static int DELETE_MODE = 3; int mode = 1; public void tick () { NodeLabel node; NodeLabel sender; clock.step(); Iterator it = nodeList.iterator(); while (it.hasNext()) { node = ((NodeLabel) it.next()); if (node.send) { node.parent.messages.add(node); } } it = nodeList.iterator(); while (it.hasNext()) { node = ((NodeLabel) it.next()); Iterator mess = node.messages.iterator(); while (mess.hasNext()) { sender = ((NodeLabel) mess.next()); if (sender == node.parent) { node.parent = node; } else { node.queue.add(sender); } } node.messages.clear(); } it = nodeList.iterator(); while (it.hasNext()) { ((NodeLabel) it.next()).step(); } it = nodeList.iterator(); while (it.hasNext()) { ((NodeLabel) it.next()).rebuildLabelString(); } } public void resetState () { clock.setTime(-1); alpha.reset(); } public void editButtonsOn (boolean yes) { move_button.setEnabled(yes); add_button.setEnabled(yes); spinner.setEnabled(yes); delete_button.setEnabled(yes); start_button.setEnabled(yes); stop_button.setEnabled(!yes); reset_button.setEnabled(!yes); next_button.setEnabled(!yes); previous_button.setEnabled(!yes); clock.setEnabled(!yes); } public void actionPerformed (ActionEvent e) { String cmd = e.getActionCommand(); if (MOVE.equals(cmd)) { mode = MOVE_MODE; } else if (ADD.equals(cmd)) { mode = ADD_MODE; } else if (DELETE.equals(cmd)) { mode = DELETE_MODE; } else if (START.equals(cmd)) { mode = MOVE_MODE; resetState(); editButtonsOn(false); tick(); layered_pane.repaint(); edge_layer.repaint(); } else if (STOP.equals(cmd)) { resetState(); editButtonsOn(true); layered_pane.repaint(); edge_layer.repaint(); } else if (RESET.equals(cmd)) { resetState(); layered_pane.repaint(); edge_layer.repaint(); } else if (PREVIOUS.equals(cmd)) { int then = clock.time - 1; if (then < -1) { return; } resetState(); while (clock.time < then) { tick(); } layered_pane.repaint(); edge_layer.repaint(); } else if (NEXT.equals(cmd)) { tick(); layered_pane.repaint(); edge_layer.repaint(); } else if (FOO.equals(cmd)) { System.err.println ("lp bounds: " + layered_pane.getBounds().toString() ); System.err.println ("el bounds: " + edge_layer.getBounds().toString() ); } else if (BAR.equals(cmd)) { System.err.println ("alpha center: " + ((NodeLabel) nodeList.get(0)).center.toString()); } else if (BAZ.equals(cmd)) { System.err.println("--dump--"); Iterator it = nodeList.iterator(); while (it.hasNext()) { ((NodeLabel) it.next()).dump(); } } else { } } protected void addButtons () { move_button = makeButton ("images/Select24.gif", MOVE, "move a node"); toolBar.addSeparator(); add_button = makeButton ("images/Node24.gif", ADD, "add a node"); spinner_model = new SpinnerNumberModel(0, 0, 10, 1); spinner = new JSpinner(spinner_model); toolBar.add(spinner); toolBar.addSeparator(); delete_button = makeButton ("images/Cut24.gif", DELETE, "delete a node"); toolBar.addSeparator(); start_button = makeButton ("images/Play24.gif", START, "start simulation"); stop_button = makeButton ("images/Stop24.gif", STOP, "stop simulation"); stop_button.setEnabled(false); toolBar.addSeparator(); reset_button = makeButton ("images/Rewind24.gif", RESET, "reset simulation"); reset_button.setEnabled(false); previous_button = makeButton ("images/StepBack24.gif", PREVIOUS, "step simulation backwards"); previous_button.setEnabled(false); next_button = makeButton ("images/StepForward24.gif", NEXT, "step simulation forward"); next_button.setEnabled(false); if (false) { // Debuging buttons toolBar.addSeparator(); toolBar.addSeparator(); foo_button = makeButton("images/foo.gif", FOO, "debug: foo"); bar_button = makeButton("images/bar.gif", BAR, "debug: bar"); baz_button = makeButton("images/baz.gif", BAZ, "debug: baz"); } } protected JButton makeButton (String imageLocation, String actionCommand, String toolTipText) { //Look for the image. URL imageURL = TreeMutexToy.class.getResource(imageLocation); //Create and initialize the button. JButton button = new JButton(); button.setActionCommand(actionCommand); button.setToolTipText(toolTipText); button.addActionListener(this); if (imageURL != null) { //image found button.setIcon(new ImageIcon(imageURL, actionCommand)); } else { //no image found button.setText(actionCommand); System.err.println ("Resource not found: " + imageLocation); } toolBar.add(button); return button; } /* ****************************************************************** */ static void createAndShowGUI () { //Create and set up the window. JFrame frame = new JFrame("TreeMutexToy"); frame.setDefaultLookAndFeelDecorated(true); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //Create and set up the content pane. TreeMutexToy newContentPane = new TreeMutexToy(); newContentPane.setOpaque(true); //content panes must be opaque frame.setContentPane(newContentPane); //Display the window. frame.pack(); frame.setVisible(true); } /* ****************************************************************** */ public static void main (String[] args) { javax.swing.SwingUtilities.invokeLater (new Runnable() { public void run() { createAndShowGUI(); } }); } }