Source code: com/ubermq/chord/ui/ChordDisplayPanel.java
1 package com.ubermq.chord.ui;
2
3 import com.ubermq.chord.*;
4 import com.ubermq.chord.jms.*;
5 import java.awt.*;
6 import java.awt.event.*;
7 import java.awt.geom.*;
8 import java.net.*;
9 import java.util.*;
10 import javax.swing.*;
11
12 /**
13 * A panel that draws a chord infrastructure in
14 * a circular fashion, and provides user
15 * feedback to mouse movements.
16 */
17 public class ChordDisplayPanel
18 extends JPanel
19 implements InfrastructureModel.EventListener
20 {
21 private final InfrastructureModel m;
22
23 private Map nodeComponents;
24
25 // when we paint, we draw fingers for this guy.
26 private ChordNode drawFingersFor;
27
28 // icons
29 static final ImageIcon networkIcon =
30 new ImageIcon(Toolkit.getDefaultToolkit().getImage(Class.class.getResource("/images/network.png")));
31
32 /**
33 * Creates a chord display panel that
34 * renders data from the specified infrastructure
35 * data model.
36 *
37 * @param m an infrastructure data model.
38 */
39 public ChordDisplayPanel(InfrastructureModel m)
40 {
41 super(new MyLayoutManager(), true);
42 this.m = m;
43 this.nodeComponents = new HashMap();
44
45 // when i initially come up, grok
46 Iterator iter = m.getNodes().iterator();
47 while (iter.hasNext())
48 {
49 ChordNode n = (ChordNode)iter.next();
50 nodeAdded(n);
51 }
52
53 // add listener for incremental changes
54 m.addListener(this);
55 }
56
57 public void nodeAdded(final ChordNode x)
58 {
59 assert nodeComponents.get(x) == null;
60
61 // create the component for this node.
62 final JComponent label = new JLabel(x.identifier().toString(),
63 networkIcon,
64 JLabel.CENTER);
65 label.addMouseListener(new MouseAdapter() {
66 public void mouseEntered(MouseEvent e) {
67 drawFingersFor = x;
68 label.setToolTipText(x.toHtml());
69 repaint();
70 }
71 public void mouseExited(MouseEvent e) {
72 drawFingersFor = null;
73 repaint();
74 }
75 });
76 label.setToolTipText(x.toHtml());
77
78 // associate it with the node
79 nodeComponents.put(x, label);
80
81 // add to GUI
82 add(label);
83 }
84
85 public void nodeRemoved(ChordNode x)
86 {
87 // remove from associative table and the GUI
88 JComponent c = (JComponent)nodeComponents.remove(x);
89 remove(c);
90 }
91
92 public void nodeChanged(ChordNode x)
93 {
94 // what? nodes dont' change.
95 }
96
97
98 /**
99 * The core of the display panel involves laying out
100 * node representation components in a circle.
101 */
102 private static class MyLayoutManager
103 implements LayoutManager
104 {
105 private static final Dimension PREFERRED_SIZE = new Dimension(500, 400);
106
107 MyLayoutManager()
108 {
109 }
110
111 public void removeLayoutComponent(Component comp)
112 {
113 }
114
115 public void addLayoutComponent(String name, Component comp)
116 {
117 }
118
119 /**
120 * Lays out the specified container.
121 * @param parent the container to be laid out
122 */
123 public void layoutContainer(Container parent)
124 {
125 ChordDisplayPanel p = ((ChordDisplayPanel)parent);
126
127 int r = p.getIdentifierRadius();
128 Point origin = p.getIdentifierOrigin();
129
130 // go through each node component and set its position on the
131 // circle
132 Iterator iter = p.nodeComponents.keySet().iterator();
133 while (iter.hasNext())
134 {
135 ChordNode x = (ChordNode)iter.next();
136 JComponent m = (JComponent)p.nodeComponents.get(x);
137
138 Point pt = p.calculateNodeCoordinates(x, r);
139 m.setLocation(pt.x + origin.x , pt.y + origin.y);
140
141 Dimension d = m.getPreferredSize();
142 m.setSize(d.width, d.height);
143 }
144 }
145
146 public Dimension preferredLayoutSize(Container parent)
147 {
148 return PREFERRED_SIZE;
149 }
150
151 public Dimension minimumLayoutSize(Container parent)
152 {
153 return preferredLayoutSize(parent);
154 }
155 }
156
157 private static final int INSET_X = 50, INSET_Y = 50;
158 private static final Stroke DASH_DASH = new BasicStroke(1.0f,
159 BasicStroke.CAP_ROUND,
160 BasicStroke.JOIN_MITER,
161 1.0f,
162 new float[] {3f, 6f},
163 0f);
164 private static final Stroke BIG_PEN = new BasicStroke(2.0f,
165 BasicStroke.CAP_ROUND,
166 BasicStroke.JOIN_ROUND);
167 private static final Stroke NORMAL_PEN = new BasicStroke(1.0f,
168 BasicStroke.CAP_ROUND,
169 BasicStroke.JOIN_ROUND);
170
171 public void paint(Graphics g)
172 {
173 super.paint(g);
174
175 Graphics2D g2d = (Graphics2D)g.create();
176 g2d.setStroke(DASH_DASH);
177 g2d.setColor(Color.BLUE);
178 g2d.drawOval(INSET_X, INSET_Y, getIdentifierRadius()*2, getIdentifierRadius()*2);
179
180 // set up the graphics for the finger tables,
181 // translated to the origin.
182 Graphics2D fingerG = (Graphics2D)g.create();
183 fingerG.translate(getIdentifierOrigin().x, getIdentifierOrigin().y);
184
185 // draw the finger table for the guy under the mouse
186 if (drawFingersFor != null)
187 drawFingerFor(fingerG, drawFingersFor);
188 }
189
190 private void drawFingerFor(Graphics2D g, ChordNode me)
191 {
192 int r = getIdentifierRadius();
193 ChordNode[] f = m.getFingerTable(me);
194
195 int i=0;
196 while(i < f.length)
197 {
198 // as long as its the same finger, continue
199 int j = i + 1;
200 while(j < f.length && f[i].equals(f[j]))
201 j++;
202
203 // draw the finger interval (i, j)
204 drawFinger(g, me, f[i], i, j-1, r);
205 i=j;
206 }
207 }
208
209 private void drawFinger(Graphics2D g,
210 ChordNode from,
211 ChordNode to,
212 int i,
213 int j,
214 int r)
215 {
216 if (!from.equals(to))
217 {
218 Point ptFrom = calculateNodeCoordinates(from, r);
219 Point ptTo = calculateNodeCoordinates(to, r);
220
221 g.setStroke(BIG_PEN);
222 g.setColor(Color.RED);
223 g.drawLine(ptFrom.x, ptFrom.y, ptTo.x, ptTo.y);
224
225 // at the midpoint of the line, draw the finger index
226 g.setStroke(NORMAL_PEN);
227 g.setColor(Color.BLACK);
228 g.drawString((i == j) ? String.valueOf(i) : String.valueOf("(" + i + ", " + j + ")"),
229 ptFrom.x + (ptTo.x - ptFrom.x) / 2,
230 ptFrom.y + (ptTo.y - ptFrom.y) / 2);
231 }
232 }
233
234 private Point getIdentifierOrigin()
235 {
236 int r = getIdentifierRadius();
237 return new Point(INSET_X + r, INSET_Y + r);
238 }
239
240 private int getIdentifierRadius()
241 {
242 return Math.min(getWidth() - 2 * INSET_X, getHeight() - 2 * INSET_Y) / 2;
243 }
244
245 /**
246 * Calculates the angle representing where the identifier lies on
247 * the identifier circle, in radians. This method relies on
248 * the <code>hashCode</code> method to provide a reasonable
249 * mapping of identifier space onto the 2^32 values provided by
250 * the primitive <code>int</code> Java type.<P>
251 *
252 * @return number of radians, (0, 2pi], representing the theta
253 * coordinate of the identifier's location on the identifier plane.
254 */
255 private double translateIdentifier(ChordIdentifier i)
256 {
257 return 2 * Math.PI * i.normal();
258 }
259
260 /**
261 * Gets the rectangular location for a node on a circle of radius r,
262 * centered at the origin.
263 */
264 private Point calculateNodeCoordinates(ChordNode n, int r)
265 {
266 // figure out theta
267 double theta =
268 translateIdentifier(n.identifier());
269
270 // we know (r, theta) for the node.
271 // convert to rectangular and plot
272 int x = (int)Math.round(r * Math.cos(theta)),
273 y = (int)Math.round(r * Math.sin(theta));
274
275 return new Point(x,y);
276 }
277
278 /**
279 * Constructs the message viewer application window.
280 */
281 public static void main(String s[])
282 {
283 // connect to the URL specified on the cmd line.
284 try
285 {
286 ChordInfrastructure i = JMSChordInfrastructure.getInfrastructure(LocalChordNode.DEFAULT_IDENTIFIER_FACTORY,
287 URI.create(s[0]));
288 InfrastructureModel m = new InfrastructureModel();
289 m.connect(i);
290
291 ChordDisplayPanel p = new ChordDisplayPanel(m);
292
293 // display
294 JFrame frame = new JFrame("chord");
295 frame.getContentPane().add(p);
296
297 frame.addWindowListener(new WindowAdapter() {
298 public void windowClosing(WindowEvent e) {System.exit(0);}
299 });
300
301 frame.pack();
302 frame.setVisible(true);
303 }
304 catch (Exception e) {
305 e.printStackTrace();
306 }
307 }
308
309 }
310
311