001 /* 002 * Channel.java 003 * 004 * Developed for the "Rethinking CS101" project. See http://www.cs101.org, the 005 * CS101 homepage or email las@olin.edu. 006 * 007 * Please do not redistribute without obtaining permission. 008 */ 009 010 package nodenet; 011 012 import java.io.IOException; 013 import java.io.Serializable; 014 import java.io.ObjectInputStream; 015 016 import java.awt.Color; 017 import java.awt.Point; 018 import java.awt.Polygon; 019 import java.awt.Graphics; 020 import java.awt.FlowLayout; 021 022 import java.awt.event.ActionEvent; 023 import java.awt.event.WindowEvent; 024 import java.awt.event.WindowAdapter; 025 import java.awt.event.ActionListener; 026 027 import javax.swing.JFrame; 028 import javax.swing.JPanel; 029 import javax.swing.JLabel; 030 import javax.swing.JButton; 031 import javax.swing.JTextField; 032 033 import cs101.util.queue.Queue; 034 import cs101.util.queue.DefaultQueue; 035 036 /** 037 * A Channel object is a NodeNetElement that connects two Nodes. A 038 * channel implements both the <code>InputChannel</code> and 039 * <code>OutputChannel</code> interfaces. Channels have an assocated 040 * capacity and latency. The capacity of a channel indicates how many 041 * packets may be present before a <code>ChannelFullException</code> 042 * is thrown. The latency of a channel is a measure of how long (in 043 * ms) a packet takes to travel from the input to the output.<p> 044 * 045 * 046 * @author Todd C. Parnell, tparnell@ai.mit.edu 047 * @author Patrick G. Heck, gus.heck@olin.edu 048 * @version $Id: Channel.java,v 1.9 2004/01/14 21:43:17 gus Exp $ 049 */ 050 public class Channel implements NodeNetElement, Resettable, 051 InputChannel, OutputChannel, Serializable { 052 053 /** the maximum allowable latency */ 054 public final static int MAX_LATENCY = 5000; 055 /** the maximum allowable capacity */ 056 public final static int MAX_CAPACITY = 50; 057 058 private static ChannelConfigFrame cf; 059 private static int ID; 060 061 // JDK 1.2.2 appears to have a bug, wherein the .jar contains a 062 // java.awt.Queue (although it doesn't exist in the documentation). 063 // Explicitly labelling it cs101.util.Queue will pacify for now. 064 transient private final int myID = Channel.ID++; 065 transient private cs101.util.queue.Queue channel_objects = new DefaultQueue(), 066 timeStamps = new DefaultQueue(); 067 transient private float mvt_constant = 0; 068 private int capacity = 10; 069 private int latency = 0; 070 private String name; 071 private Node startNode, stopNode; 072 transient boolean selected, running; 073 private boolean enabled = true, destroyed; 074 075 // lock toprevent concurrent reads and writes. 076 transient private Object lock = new Object(); 077 078 // for painting performance 079 private Point p1, p2; 080 private Polygon poly1, poly2; 081 private int x_diff, y_diff; 082 083 // 084 // Constructors 085 // 086 087 /** 088 * Creates a new Channel with the given start and end nodes. If 089 * start and end are the same (as determined by 090 * <code>equals</code>), throws SameNodeException. 091 */ 092 public Channel(Node start, Node end) throws SameNodeException { 093 if (start.equals(end)) throw(new SameNodeException()); 094 095 this.name = "**UNNAMED**"; 096 this.startNode = start; 097 this.stopNode = end; 098 this.channel_objects = new DefaultQueue(); 099 this.timeStamps = new DefaultQueue(); 100 this.startNode.addOutputChannel((OutputChannel)this); 101 this.stopNode.addInputChannel((InputChannel)this); 102 103 this.p1 = startNode.getPos(); 104 this.p2 = stopNode.getPos(); 105 if (this.p1 == null || this.p2 == null) return; 106 int mid_x = (this.p1.x + this.p2.x) / 2; 107 int mid_y = (this.p1.y + this.p2.y) / 2; 108 this.x_diff = this.p2.x - this.p1.x; 109 this.y_diff = this.p2.y - this.p1.y; 110 float dist = (int)Math.sqrt(this.x_diff * this.x_diff + 111 this.y_diff * this.y_diff); 112 int x_ratio = (int)Math.round((this.y_diff * 3) / dist); 113 int y_ratio = (int)-Math.round((this.x_diff * 3) / dist); 114 115 this.poly1 = new Polygon(); 116 this.poly1.addPoint(this.p1.x - x_ratio, this.p1.y - y_ratio); 117 this.poly1.addPoint(this.p1.x + x_ratio, this.p1.y + y_ratio); 118 this.poly1.addPoint(this.p2.x + x_ratio, this.p2.y + y_ratio); 119 this.poly1.addPoint(this.p2.x - x_ratio, this.p2.y - y_ratio); 120 121 this.poly2 = new Polygon(); 122 this.poly2.addPoint(mid_x - 3 * x_ratio, mid_y - 3 * y_ratio); 123 this.poly2.addPoint(mid_x + 3 * x_ratio, mid_y + 3 * y_ratio); 124 this.poly2.addPoint(p1.x + x_diff * 7 / 10, p1.y + y_diff * 7 / 10); 125 } 126 127 /** 128 * Build a channel that is fully configured. This is used for recreation 129 * of networks from a template file. 130 */ 131 public Channel(Node start, Node end, boolean enabled, 132 int latency, int capacity) throws SameNodeException { 133 this(start, end); 134 setEnabled(enabled); 135 setLatency(latency); 136 setCapacity(capacity); 137 } 138 139 // 140 // Public instance methods 141 // 142 143 /** 144 * Insert an object into the channel. 145 * 146 * @param o The object to be inserted. 147 * @throws ChannelFullException if the number of objects in the 148 * channel already equals or exceeds the capacity. 149 * @throws ChannelDisabledException if the channel is currently 150 * disabled 151 */ 152 public void writeObject(Object o) throws ChannelFullException, ChannelDisabledException { 153 if (o == null) { 154 System.err.println("Warning: You just wrote null to a channel!"); 155 return; 156 } 157 synchronized (this.lock) { 158 if (this.isFull()) { 159 throw(new ChannelFullException()); 160 } 161 if (!this.enabled) { 162 throw(new ChannelDisabledException()); 163 } 164 165 this.channel_objects.enqueue(o); 166 this.timeStamps.enqueue(new Long(System.currentTimeMillis())); 167 this.mvt_constant = 0; 168 169 } 170 } 171 172 /** 173 * Read an object from the channel. 174 * 175 * @return the oldest (in terms of insertion time) object in the channel 176 * 177 * @throws ChannelEmptyException if no objects are present in the 178 * channel or the oldest object has not propigated through the 179 * channel yet (due to a non-zero latency). 180 * 181 * @throws ChannelDisabledException if the channel is diabled 182 */ 183 public Object readObject() throws ChannelEmptyException, ChannelDisabledException { 184 synchronized (this.lock) { 185 if (!this.enabled) throw new ChannelDisabledException(); 186 if (this.channel_objects.size() == 0) throw new ChannelEmptyException(); 187 // temporarily get the timestamp for current packet 188 long time = ((Long)this.timeStamps.peek()).longValue(); 189 // check if packet's been around long enough 190 if (System.currentTimeMillis() - time <= this.latency) { 191 throw new ChannelEmptyException(); 192 } 193 this.mvt_constant = 0.8f; 194 // remove the timestamp 195 this.timeStamps.dequeue(); 196 return(this.channel_objects.dequeue()); 197 } 198 } 199 200 public int getCapacity() { 201 return this.capacity; 202 } 203 204 public void setCapacity(int cap) { 205 if (cap > MAX_CAPACITY) cap = 50; 206 if (cap < 0) cap = 0; 207 this.capacity = cap; 208 } 209 210 public int getLatency() { 211 return this.latency; 212 } 213 214 public void setLatency(int lat) { 215 if (lat > MAX_LATENCY) lat = 50; 216 if (lat < 0) lat = 0; 217 this.latency = lat; 218 } 219 220 public void setSelected(boolean b) { this.selected = b; } 221 public boolean isSelected() { return(this.selected); } 222 223 public void setEnabled(boolean b) { this.enabled = b; } 224 public boolean isEnabled() { return(this.enabled); } 225 226 public boolean contains(int x, int y) { return(this.poly1.contains(x, y)); } 227 public boolean contains(Point p) { return(this.contains(p.x, p.y)); } 228 229 public void setName(String n) { this.name = n; } 230 public String getName() { return this.name; } 231 232 /** 233 * Destroy the Channel. <code>destroy()</code> should be called 234 * whenever a Channel will no longer be used. When destroyed, 235 * Channels notify all connected Nodes of the impending destruction. 236 * <I>Note: <code>Channel.finalize()</code> automatically calls 237 * <code>destroy()</code></I>. 238 */ 239 public void destroy() { 240 this.stop(); 241 if (this.destroyed) return; 242 this.destroyed = true; 243 this.startNode.notifyOfDestruction(this); 244 this.stopNode.notifyOfDestruction(this); 245 } 246 247 public boolean isDestroyed() { return this.destroyed; } 248 249 /** 250 * Notify this that a NodeNetElement has been destroyed. Typically, 251 * this method will be called by one of the two connected Nodes. If 252 * that is the case, the channel destroys itself. 253 */ 254 public void notifyOfDestruction(NodeNetElement nne) { 255 if (nne.equals(this.startNode) || nne.equals(this.stopNode)) { 256 this.destroy(); 257 } 258 } 259 260 public boolean isEditable() { return true; } 261 public void configure() { 262 if (Channel.cf == null) Channel.cf = new ChannelConfigFrame(); 263 Channel.cf.setChannel(this); 264 Channel.cf.show(); 265 } 266 267 private boolean isFull() { 268 return this.channel_objects.size() >= this.capacity; 269 } 270 271 public void paint(Graphics g) { 272 if (this.destroyed) return; 273 if (this.enabled && this.running) this.mvt_constant += .1f; 274 if (this.mvt_constant >= 1) this.mvt_constant = 0; 275 276 if (!this.enabled) g.setColor(Color.gray); 277 else if (this.isFull()) g.setColor(Color.red); 278 else g.setColor(Color.black); 279 g.fillPolygon(this.poly1); 280 g.fillPolygon(this.poly2); 281 if (this.selected) { 282 g.setColor(Color.red); 283 g.drawPolygon(this.poly2); 284 } 285 286 if (!this.enabled) g.setColor(Color.black); 287 else if (this.isFull()) g.setColor(Color.pink); 288 else /* std case*/ g.setColor(Color.red); 289 int j = this.channel_objects.size(); 290 for (int i = 1; (i <= j) && ( i <= 6); i++) { 291 int start_x = this.p1.x + 292 (int)(this.x_diff * (i + 2 + this.mvt_constant) / 12); 293 int start_y = this.p1.y + 294 (int)(this.y_diff * (i + 2 + this.mvt_constant) / 12); 295 g.fillOval(start_x, start_y, 8, 8); 296 } 297 } 298 299 public void start() { 300 this.running = true; 301 } 302 public void stop() { this.running = false; } 303 public void run() {} 304 305 /** Determines if two Channels are equal. */ 306 public boolean equals(Object o) { 307 if (!(o instanceof Channel)) return false; 308 Channel c = (Channel)o; 309 return (c.myID == this.myID); 310 } 311 312 public String templateXML() { 313 return 314 "<channel start=\"node" + getStartNode().getMyID() + 315 "\" end=\"node" + getEndNode().getMyID() + 316 "\" enabled=\"" + isEnabled() + 317 "\" latency=\"" + getLatency() + 318 "\" capacity=\"" + getCapacity() + 319 "\"/>"; 320 } 321 322 // 323 // Package instance methods 324 // 325 326 Node getStartNode() { return this.startNode; } 327 Node getEndNode() { return this.stopNode; } 328 329 330 // 331 // Protected instance methods 332 // 333 334 protected void finalize() throws IOException { 335 this.destroy(); 336 } 337 338 // 339 // Private instance methods 340 // 341 342 /** 343 * Custom de-serialization. Get NodeBehavior & color, then start 344 * Thread. 345 * 346 * private void readObject(ObjectInputStream in) 347 * throws IOException, ClassNotFoundException { 348 * in.defaultReadObject(); 349 * this.channel_objects = new DefaultQueue(); 350 * this.timeStamps = new DefaultQueue(); 351 * for (int i=0; i<this.numberQueueObjects; i++) { 352 * Object o = new Object(); 353 * this.channel_objects.enqueue(o); 354 * this.timeStamps.enqueue(new Long(System.currentTimeMillis())); 355 * } 356 * this.lock = new Object(); 357 * } 358 */ 359 360 public void reset() { 361 while (!this.channel_objects.isEmpty()) { 362 this.channel_objects.dequeue(); 363 } 364 while (!this.timeStamps.isEmpty()) { 365 this.timeStamps.dequeue(); 366 } 367 368 } 369 370 /** 371 * A configuration frame for Channels. 372 */ 373 private class ChannelConfigFrame extends JFrame implements Serializable { 374 375 transient Channel myChannel; 376 JTextField bandwidth, latency; 377 JButton ok, cancel; 378 379 public ChannelConfigFrame() { 380 381 setTitle("Channel Configuration"); 382 JPanel p = new JPanel(); 383 p.setLayout(new FlowLayout()); 384 p.add(new JLabel("Capacity [0-" + Channel.MAX_CAPACITY + "]:")); 385 bandwidth = new JTextField(4); 386 p.add(bandwidth); 387 p.add(new JLabel("Latency [0-" + Channel.MAX_LATENCY + "]:")); 388 latency = new JTextField(4); 389 p.add(latency); 390 this.getContentPane().add("Center", p); 391 392 JPanel q = new JPanel(); 393 q.setLayout(new FlowLayout()); 394 this.ok = new JButton("OK"); 395 this.ok.addActionListener(new ActionListener() { 396 public void actionPerformed(ActionEvent e) { 397 configureChannel(); 398 ChannelConfigFrame.this.setVisible(false); 399 } 400 }); 401 this.cancel = new JButton("Cancel"); 402 this.cancel.addActionListener(new ActionListener() { 403 public void actionPerformed(ActionEvent e) { 404 ChannelConfigFrame.this.setVisible(false); 405 } 406 }); 407 q.add(this.ok); 408 q.add(this.cancel); 409 this.getContentPane().add("South", q); 410 411 this.pack(); 412 413 this.addWindowListener(new WindowAdapter() { 414 public void windowClosing(WindowEvent e) { 415 ChannelConfigFrame.this.setVisible(false); 416 } 417 }); 418 } 419 420 void setChannel(Channel newChannel) { 421 this.myChannel = newChannel; 422 setTextFields(); 423 } 424 425 void setTextFields() { 426 bandwidth.setText(Integer.toString(getBandwidth())); 427 latency.setText(Integer.toString(getLatency())); 428 } 429 430 void configureChannel() { 431 if (this.myChannel == null) return; 432 try { 433 this.myChannel.setCapacity(Integer.parseInt(this.bandwidth.getText().trim())); 434 this.myChannel.setLatency(Integer.parseInt(this.latency.getText().trim())); 435 } 436 catch(NumberFormatException e) {} 437 } 438 439 int getBandwidth() { 440 if (this.myChannel == null) return 0; 441 return this.myChannel.getCapacity(); 442 } 443 444 int getLatency() { 445 if (this.myChannel == null) return 0; 446 return this.myChannel.getLatency(); 447 } 448 } // class ChannelConfigFrame 449 } // class Channel 450 451 /* 452 * $Log: Channel.java,v $ 453 * Revision 1.9 2004/01/14 21:43:17 gus 454 * more javadoc, plus reformat 455 * 456 * Revision 1.8 2004/01/14 20:23:21 gus 457 * Javadoc and comment cleanup 458 * 459 * Revision 1.7 2004/01/13 19:35:27 gus 460 * Simulation Panel, channel and node are all Resettable now. 461 * CountingNodeBehavior refactored into Counter 462 * 463 * Revision 1.6 2004/01/09 20:12:54 gus 464 * Switch to JLabels for a more consistant look 465 * 466 * Revision 1.5 2004/01/09 19:58:19 gus 467 * Channels now do nothing and complain when you write null to them (bug 150) 468 * 469 * Revision 1.4 2004/01/08 15:44:01 gus 470 * Create xml template and a convenience constructor to match. 471 * 472 * Revision 1.3 2003/02/19 15:39:30 gus 473 * Reformated 474 * 475 * Revision 1.2 2002/06/13 19:32:43 gus 476 * Commented out com.sun.java.swing package references, and updated obsolete 477 * imorts and references into cs101 package. 478 * 479 * Revision 1.1 2002/06/13 17:33:21 gus 480 * Moved all java files into the nodenet directory (who let them out anyway?) 481 * 482 * Revision 1.1.1.1 2002/06/05 21:56:35 root 483 * CS101 comes to Olin finally. 484 * 485 * Revision 1.3 2000/05/09 06:03:54 mharder 486 * Changed packagename from nodeNet to nodenet. 487 * 488 * Revision 1.2 1999/07/30 21:36:39 jsmthng 489 * Updated names of files to reflect change from 'BinSort' to 'nodeNet'; 490 * added InputChannelVector and OutputChannelVector classes (moving from 491 * Vectors to ChannelVectors, unless we move back). 492 * 493 * Revision 1.1 1999/07/30 01:09:18 jsmthng 494 * Renaming BinSort package to nodeNet; moving directories and files as 495 * necessary. 496 * 497 * Revision 1.6 1999/07/27 16:57:44 las 498 * Updating BinSort.Channel.java to use the new spelling of 499 * cs101.util.Queue.peek() :o) 500 * 501 * Revision 1.5 1999/01/25 19:31:51 tparnell 502 * Fixed packages. cs101.util.Queue is now an interface, replaced with DefaultQueue 503 * 504 * Revision 1.4 1999/01/21 20:57:05 tparnell 505 * it worked 506 * 507 * Revision 1.3 1998/08/12 19:29:28 tparnell 508 * Another pass after comments from las & natashao. Added support to 509 * dynamically add NodeBehaviors. Add keyboard shortcuts to menus. Added 510 * workaround to jdk bug relating to lighweight components. Misc other 511 * bugfixes. 512 * 513 * Revision 1.3 1998/08/10 17:45:50 tparnell 514 * Revision to use JDK1.2 and Swing. Redesign of GUI. Removed old kludge 515 * for file I/O and replaced with object serialization. Channel no longer 516 * requires animacy. Removed unnessary dependencies between classes. 517 * Added ability to configure channel's latency and capacity. Added 518 * javadoc to all files. General code cleanup. 519 * 520 */