001 /* 002 * NodeNetFrame.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 org.xml.sax.SAXException; 013 014 import java.io.File; 015 import java.io.FileWriter; 016 import java.io.IOException; 017 import java.io.FileNotFoundException; 018 019 import java.awt.AWTEvent; 020 import java.awt.Dimension; 021 022 import java.awt.event.ActionEvent; 023 import java.awt.event.WindowEvent; 024 import java.awt.event.WindowAdapter; 025 import java.awt.event.ComponentEvent; 026 import java.awt.event.ActionListener; 027 import java.awt.event.ComponentAdapter; 028 029 import java.net.URL; 030 import java.net.URLClassLoader; 031 032 import java.beans.PropertyChangeEvent; 033 import java.beans.PropertyChangeListener; 034 035 import java.util.Iterator; 036 import java.util.HashSet; 037 038 import javax.xml.parsers.ParserConfigurationException; 039 040 import javax.swing.JMenu; 041 import javax.swing.JFrame; 042 import javax.swing.JMenuBar; 043 import javax.swing.JMenuItem; 044 import javax.swing.JSplitPane; 045 import javax.swing.JOptionPane; 046 import javax.swing.JFileChooser; 047 048 049 /** 050 * The top level frame for nodeNet. Creates a SimulationPanel and a 051 * NodeTypeSelector, sets up menus, and leaves the rest to the event 052 * handlers.<p> 053 * 054 * @see nodenet.SimulationPanel 055 * @see nodenet.NodeTypeSelector 056 * 057 * @author Todd C. Parnell, tparnell@ai.mit.edu 058 * @author Patrick G. Heck, gus.heck@olin.edu 059 * @version $Id: NodeNetFrame.java,v 1.23 2004/02/09 19:17:49 gus Exp $ 060 */ 061 public class NodeNetFrame extends JFrame { 062 063 private SimulationPanel sp; 064 private JSplitPane jsp; 065 private NodeTypeSelector nts; 066 067 // this class loader must be found by the Main thread so we have to 068 // cache it for later use. Otherwise it won't work in Java WebStart. 069 private ClassLoader cl = Thread.currentThread().getContextClassLoader(); 070 071 private URLClassLoader uLoad = URLClassLoader.newInstance(new URL[] {}, cl); 072 private static final int MIN_X = 250; 073 private static final int MIN_Y = 150; 074 075 /** 076 * Constructs the GUI. 077 * 078 * @param nodeBehaviors a (possibly empty) list of NodeBehaviors to 079 * add to the default list of NodeBehaviors. Typically a student's 080 * class. 081 */ 082 public NodeNetFrame(String[] nodeBehaviors) { 083 super("Network Simulation"); 084 085 this.nts = new NodeTypeSelector(); 086 this.sp = new SimulationPanel(); 087 this.nts.addPropertyChangeListener(this.sp); 088 this.jsp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, 089 this.nts, this.sp); 090 this.jsp.setResizeWeight(0); 091 this.getContentPane().add(this.jsp); 092 093 // try to keep the window from getting too small, and the 094 // JSeparator from getting stuck on the right side. 095 this.addComponentListener( new ComponentAdapter() { 096 public void componentResized(ComponentEvent ce) { 097 //System.out.println(ce.paramString()); 098 int width = NodeNetFrame.this.getWidth(); 099 int height = NodeNetFrame.this.getHeight(); 100 if (width < MIN_X || height < MIN_Y) { 101 // There are serious timing issues with resize events. 102 // This delay attempts to make us the last event sent. 103 // There is also some sort of timeout for event processing 104 // which is about 2-3 seconds long the first time, and then 105 // seems to drop to less than a second. This is probably 106 // awt trying to keep alive when it has too many events or 107 // an event is blocking. The result is that if you resize 108 // to a very small size, and do not release the mouse 109 // button for for 4-5 sec the size enforcment breaks 110 // because the event queue starts dropping resize events 111 // quickly without waiting for the user to release the 112 // mouse button and give control back to Java. This results 113 // in java thinking it has updated the window size when in 114 // fact it has not. increasing the delay below makes the 115 // resize more sluggish, but more reliable. For now we just 116 // have to live with this hack I think. 117 try {Thread.sleep(500);} catch (InterruptedException ie) {} 118 NodeNetFrame.this.setSize(1,1); 119 try {Thread.sleep(50);} catch (InterruptedException ie) {} 120 NodeNetFrame.this.setSize( 121 width < MIN_X ? MIN_X : width, 122 height < MIN_Y ? MIN_Y : height); 123 124 jsp.setDividerLocation(0.5); 125 //System.out.println("resized to " + NodeNetFrame.this.getSize()); 126 127 } 128 } 129 }); 130 131 JMenuBar menubar = new JMenuBar(); 132 this.setJMenuBar(menubar); 133 JMenu menu = new JMenu("File"); 134 menu.setMnemonic('f'); 135 136 menubar.add(menu); 137 138 JMenuItem menuitem = new JMenuItem("Load Behavior..."); 139 menuitem.setMnemonic('l'); 140 menuitem.addActionListener(new ActionListener() { 141 public void actionPerformed(ActionEvent e) { 142 String temp = ""; 143 try { 144 JOptionPane jop = new JOptionPane(); 145 146 temp = JOptionPane.showInputDialog("Enter a class that " + 147 "is on the classpath"); 148 if (temp == null || "".equals(temp)) { 149 return; 150 } 151 Class clazz; 152 synchronized (uLoad) { 153 clazz = NodeNetFrame.this.nts.loadBehavior(temp, 154 NodeNetFrame.this.uLoad); 155 } 156 if (clazz == null) { 157 throw new ClassNotFoundException(); 158 } 159 NodeNetFrame.this.nts.setNodeBehaviors( 160 NodeTypeSelector.NEXT, clazz); 161 } catch (java.security.AccessControlException ace) { 162 // we're running as an applet 163 NodeNetFrame.this.showAppletWarning(); 164 } catch (ClassNotFoundException cnfe) { 165 String temp2 = "nodenet." + temp; 166 try { 167 Class clazz; 168 synchronized (uLoad) { 169 clazz = NodeNetFrame.this.nts.loadBehavior(temp2, 170 NodeNetFrame.this.uLoad); 171 } 172 if (clazz == null) { 173 throw new ClassNotFoundException(); 174 } 175 NodeNetFrame.this.nts.setNodeBehaviors( 176 NodeTypeSelector.NEXT, clazz); 177 } catch (ClassNotFoundException cnfe2) { 178 StringBuffer err = new StringBuffer(); 179 err.append("Unable to find the class '"); 180 err.append(temp + "' or 'nodenet." + temp +"'." ); 181 err.append("\nExpanded classpath URLs:"); 182 URL[] foobar = NodeNetFrame.this.uLoad.getURLs(); 183 for (int i = 0; i < foobar.length; i++) { 184 err.append("\n" + foobar[i].toString()); 185 } 186 System.err.println(err); 187 JOptionPane.showMessageDialog(null, err); 188 } catch (IllegalArgumentException iae) { 189 190 System.err.println("Error instantiating class '" + 191 temp + "' or nodenet." + temp + "'."); 192 URL[] foobar = NodeNetFrame.this.uLoad.getURLs(); 193 for (int i = 0; i < foobar.length; i++) { 194 System.err.println(foobar[i]); 195 } 196 iae.printStackTrace(); 197 } 198 } catch (Exception ex) { 199 JOptionPane.showMessageDialog(null,ex); 200 } 201 } 202 }); 203 menu.add(menuitem); 204 menuitem = new JMenuItem("Expand Classpath..."); 205 menuitem.setMnemonic('x'); 206 menuitem.addActionListener(new ActionListener() { 207 public void actionPerformed(ActionEvent e) { 208 String temp = ""; 209 try { 210 JFileChooser chooser = new JFileChooser(); 211 chooser.setFileFilter( 212 new javax.swing.filechooser.FileFilter() { 213 public String getDescription() { 214 return "Java Archive (*.jar) or Directory"; 215 } 216 public boolean accept(File f) { 217 return 218 f.getName().matches(".*\\.jar") || f.isDirectory(); 219 } 220 }); 221 chooser.setFileSelectionMode( 222 JFileChooser.FILES_AND_DIRECTORIES); 223 chooser.setMultiSelectionEnabled(true); 224 int returnVal = chooser.showOpenDialog( NodeNetFrame.this ); 225 File[] files = chooser.getSelectedFiles(); 226 URL[] prev = uLoad.getURLs(); 227 URL[] newURLs = new URL[prev.length + files.length]; 228 for (int i=0; i < files.length; i++) { 229 //System.out.print("new:" + files[i]); 230 newURLs[i] = files[i].toURL(); 231 } 232 for (int i=files.length; i < newURLs.length; i++) { 233 //System.out.println("old:" + prev[i - files.length]); 234 newURLs[i] = prev[i - files.length]; 235 } 236 synchronized (uLoad) { 237 uLoad = new URLClassLoader(newURLs, cl); 238 } 239 240 } catch (java.security.AccessControlException ace) { 241 // we're running as an applet 242 NodeNetFrame.this.showAppletWarning(); 243 } catch (Exception e2) { 244 e2.printStackTrace(); 245 } 246 } 247 }); 248 menu.add(menuitem); 249 250 menu.addSeparator(); 251 252 menuitem = new JMenuItem("Open a Template..."); 253 menuitem.setMnemonic('o'); 254 menuitem.addActionListener(new ActionListener() { 255 public void actionPerformed(ActionEvent e) { 256 TemplateParser tparse = null; 257 try { 258 JFileChooser chooser = new JFileChooser(); 259 chooser.setFileFilter( 260 new javax.swing.filechooser.FileFilter() { 261 public String getDescription() { 262 return "Node Net Template File (*.nnt)"; 263 } 264 public boolean accept(File f) { 265 return 266 f.getName().matches(".*\\.nnt") || f.isDirectory(); 267 } 268 }); 269 int returnVal = chooser.showOpenDialog( NodeNetFrame.this ); 270 if (returnVal != JFileChooser.APPROVE_OPTION || 271 chooser.getSelectedFile() == null) return; 272 String file = chooser.getCurrentDirectory() + 273 System.getProperty("file.separator") + 274 chooser.getSelectedFile().getName(); 275 276 tparse = 277 new TemplateParser( 278 NodeNetFrame.this.sp.getBehaviorSource()); 279 280 NodeNetFrame.this.sp.clear(); 281 NodeNetFrame.this.sp.setBsElements(tparse.parse(file)); 282 283 } catch (java.security.AccessControlException ace) { 284 // we're running as an applet 285 NodeNetFrame.this.showAppletWarning(); 286 } catch (SAXException saxe) { 287 // print out the error 288 saxe.printStackTrace(); 289 JOptionPane.showMessageDialog(null, saxe); 290 } catch (ParserConfigurationException pce) { 291 // print out the error 292 pce.printStackTrace(); 293 JOptionPane.showMessageDialog(null, pce); 294 } catch (FileNotFoundException fnfe) { 295 // print out the error 296 JOptionPane.showMessageDialog(null,"File Not Found!"); 297 } catch (IOException ioe) { 298 // print out the error 299 ioe.printStackTrace(); 300 JOptionPane.showMessageDialog(null, ioe); 301 } catch (Throwable t) { 302 // display anything else that goes wrong. 303 JOptionPane.showMessageDialog(null, t); 304 } finally { 305 if (tparse != null) { 306 // Identical warnings should be coalesced into a single 307 // warning to avoid dialog spam. 308 HashSet s = new HashSet(tparse.getWarnings()); 309 for (Iterator iter = s.iterator(); iter.hasNext();) { 310 String message = (String) iter.next(); 311 JOptionPane.showMessageDialog(null, message); 312 } 313 } 314 } 315 } 316 }); 317 318 menu.add(menuitem); 319 320 menuitem = new JMenuItem("Save Template As..."); 321 menuitem.setMnemonic('s'); 322 menuitem.addActionListener(new ActionListener() { 323 public void actionPerformed(ActionEvent e) { 324 try { 325 JFileChooser chooser = new JFileChooser(); 326 327 chooser.setFileFilter( 328 new javax.swing.filechooser.FileFilter() { 329 public String getDescription() { 330 return "Node Net Template file (.nnt)"; 331 } 332 public boolean accept(File f) { 333 return 334 f.getName().matches(".*\\.nnt") || f.isDirectory(); 335 } 336 }); 337 int returnVal = chooser.showSaveDialog(NodeNetFrame.this); 338 if (returnVal != JFileChooser.APPROVE_OPTION || 339 chooser.getSelectedFile() == null) return; 340 String file = chooser.getCurrentDirectory() + 341 System.getProperty("file.separator") + 342 chooser.getSelectedFile().getName(); 343 if (file.indexOf(".") == -1) { 344 file = file + ".nnt"; 345 } 346 347 FileWriter fw = new FileWriter(file); 348 String xml = NodeNetFrame.this.sp.templateXML(); 349 fw.write(xml); 350 fw.flush(); 351 fw.close(); 352 } catch (java.security.AccessControlException ace) { 353 // we're running as an applet 354 NodeNetFrame.this.showAppletWarning(); 355 } catch (Exception e2) { 356 System.out.println(e2); 357 } 358 } 359 }); 360 menu.add(menuitem); 361 362 menu.addSeparator(); 363 364 menuitem = new JMenuItem("Quit"); 365 menuitem.setMnemonic('q'); 366 menuitem.addActionListener(new ActionListener() { 367 public void actionPerformed(ActionEvent e) { 368 try { 369 System.exit(0); 370 } catch (java.security.AccessControlException ace) { 371 NodeNetFrame.this.showAppletWarning(); 372 } 373 } 374 }); 375 menu.add(menuitem); 376 377 menu = new JMenu("Simulation"); 378 menu.setMnemonic('s'); 379 menubar.add(menu); 380 381 menuitem = new JMenuItem("Start"); 382 menuitem.setMnemonic('s'); 383 menuitem.addActionListener(new ActionListener() { 384 public void actionPerformed(ActionEvent e) { 385 NodeNetFrame.this.sp.startSimulation(); 386 } 387 }); 388 menu.add(menuitem); 389 390 menuitem = new JMenuItem("Pause"); 391 menuitem.setMnemonic('p'); 392 menuitem.addActionListener(new ActionListener() { 393 public void actionPerformed(ActionEvent e) { 394 NodeNetFrame.this.sp.stopSimulation(); 395 } 396 }); 397 menu.add(menuitem); 398 399 menuitem = new JMenuItem("Reset"); 400 menuitem.setMnemonic('r'); 401 menuitem.addActionListener(new ActionListener() { 402 public void actionPerformed(ActionEvent e) { 403 NodeNetFrame.this.sp.reset(); 404 } 405 }); 406 menu.add(menuitem); 407 408 menu.addSeparator(); 409 410 menuitem = new JMenuItem("Clear"); 411 menuitem.setMnemonic('c'); 412 menuitem.addActionListener(new ActionListener() { 413 public void actionPerformed(ActionEvent e) { 414 NodeNetFrame.this.sp.clear(); 415 } 416 }); 417 menu.add(menuitem); 418 419 420 menu = new JMenu("Selection"); 421 menu.setMnemonic('e'); 422 menubar.add(menu); 423 424 menuitem = new JMenuItem("Enable"); 425 menuitem.setMnemonic('e'); 426 menuitem.addActionListener(new ActionListener() { 427 public void actionPerformed(ActionEvent e) { 428 sp.setSelectionEnabled(true); 429 } 430 }); 431 menu.add(menuitem); 432 433 menuitem = new JMenuItem("Disable"); 434 menuitem.setMnemonic('d'); 435 menuitem.addActionListener(new ActionListener() { 436 public void actionPerformed(ActionEvent e) { 437 sp.setSelectionEnabled(false); 438 } 439 }); 440 menu.add(menuitem); 441 442 menuitem = new JMenuItem("Configure"); 443 menuitem.setMnemonic('c'); 444 menuitem.addActionListener(new ActionListener() { 445 public void actionPerformed(ActionEvent e) { 446 sp.configureSelection(); 447 } 448 }); 449 menu.add(menuitem); 450 451 menu.addSeparator(); 452 453 menuitem = new JMenuItem("Remove"); 454 menuitem.setMnemonic('r'); 455 menuitem.addActionListener(new ActionListener() { 456 public void actionPerformed(ActionEvent e) { 457 sp.deleteSelection(); 458 } 459 }); 460 menu.add(menuitem); 461 462 this.addWindowListener(new WindowAdapter() { 463 public void windowClosing(WindowEvent e) { 464 try { 465 System.exit(0); 466 } catch (java.security.AccessControlException ace) { 467 } 468 } 469 }); 470 471 this.pack(); 472 this.jsp.setDividerLocation(this.nts.getWidth()); 473 this.show(); 474 } 475 476 /** 477 * Pops up a warning dialog. 478 */ 479 private void showAppletWarning() { 480 Object[] options = { "Cancel" }; 481 JOptionPane.showOptionDialog( 482 null, 483 "This action not available when running as an applet.", 484 "Network Simulation", 485 JOptionPane.DEFAULT_OPTION, 486 JOptionPane.WARNING_MESSAGE, 487 null, options, options[0]); 488 } 489 } 490 491 /* 492 * $Log: NodeNetFrame.java,v $ 493 * Revision 1.23 2004/02/09 19:17:49 gus 494 * Remove debugging JOptionPane 495 * 496 * Revision 1.22 2004/02/09 17:22:25 gus 497 * more debugging stuff to track down web start problem. 498 * 499 * Revision 1.21 2004/02/05 00:00:55 gus 500 * more error info in graphical only environments 501 * 502 * Revision 1.20 2004/02/04 22:17:44 gus 503 * helpful comment (I hope) 504 * 505 * Revision 1.19 2004/02/04 21:57:30 gus 506 * Better error reporting 507 * 508 * Revision 1.18 2004/02/04 21:41:13 gus 509 * slightly more robust classloader... might work under webstart 510 * 511 * Revision 1.17 2004/01/26 18:19:38 gus 512 * remove prints and just live with the imperfect hack. 513 * 514 * Revision 1.16 2004/01/16 20:48:59 gus 515 * Allow user to add jars and dirs to the classpath by using a custom 516 * ClassLoader. 517 * Also (try to) prevent the window from getting too small since funny things 518 * happen with the JSeparator and very small window sizes. 519 * 520 * Revision 1.15 2004/01/14 23:42:35 gus 521 * specifying the encoding is clearly causing more problems that it is worth 522 * 523 * Revision 1.14 2004/01/14 23:28:11 gus 524 * another attempt to get the encoding right 525 * 526 * Revision 1.13 2004/01/14 23:19:17 gus 527 * Make sure the encoding matches the file, rather than assuming utf-8 528 * 529 * Revision 1.12 2004/01/14 21:43:17 gus 530 * more javadoc, plus reformat 531 * 532 * Revision 1.11 2004/01/14 21:04:16 gus 533 * More javadoc fixes 534 * 535 * Revision 1.10 2004/01/14 20:23:21 gus 536 * Javadoc and comment cleanup 537 * 538 * Revision 1.9 2004/01/13 19:35:27 gus 539 * Simulation Panel, channel and node are all Resettable now. 540 * CountingNodeBehavior refactored into Counter 541 * 542 * Revision 1.8 2004/01/09 20:02:33 gus 543 * Clean up the loading of classes from the classpath. We should not be using a file 544 * dialog. We are not selecting files. Replaced with JOptionPane. 545 * 546 * Revision 1.7 2004/01/09 18:15:01 gus 547 * Account for more types in template than loaded behaviors. 548 * Tell user when file not found. 549 * Filter template dialogs to *.nnt 550 * 551 * Revision 1.6 2004/01/08 22:48:08 gus 552 * Can now read xml template file (only tested breifly) 553 * 554 * Revision 1.5 2004/01/07 20:10:11 gus 555 * Update comments to refelect disappearance of ControlPanel Class 556 * 557 * Revision 1.4 2004/01/07 19:51:59 gus 558 * Put the buttons in a scroll pane so that the JSplitPane is useful again 559 * 560 * Revision 1.3 2004/01/07 18:32:47 gus 561 * Ripped out ControlPanel and NodeNetRadioButton. 562 * Replaced them with NodeTypeSelector, which provides 563 * The loaded User NodeBehavior types as an indexed 564 * property. Simulation panel now implements PropertyChangeListener 565 * to listen for changes in the selected node behavior. The 566 * buttons in the display are now JToggleButtons, and will 567 * use the public field BEHAVIOR_NAME to lable the buttons 568 * if it is declared for any NodeBehavior. 569 * 570 * Revision 1.2 2002/08/20 15:41:09 gus 571 * corrected package name 572 * 573 * Revision 1.1 2002/06/13 17:33:21 gus 574 * Moved all java files into the nodenet directory (who let them out anyway?) 575 * 576 * Revision 1.1.1.1 2002/06/05 21:56:35 root 577 * CS101 comes to Olin finally. 578 * 579 * Revision 1.4 2000/05/09 06:03:54 mharder 580 * Changed packagename from nodeNet to nodenet. 581 * 582 * Revision 1.3 1999/08/04 09:08:54 jsmthng 583 * Added javadoc comments to InputChannelVector and OutputChannelVector; 584 * finished updating the rest of the nodeNet package to reflect new 585 * changes in name and code. 586 * 587 * Modified index.html to reflect the new nodeNet code, as well as to 588 * clarify some parts of the problem set. 589 * 590 * Revision 1.1 1999/07/30 01:09:23 jsmthng 591 * Renaming BinSort package to nodeNet; moving directories and files as 592 * necessary. 593 * 594 * Revision 1.5 1999/01/25 19:31:49 tparnell 595 * Fixed packages. cs101.util.Queue is now an interface, replaced with DefaultQueue 596 * 597 * Revision 1.4 1999/01/21 20:57:03 tparnell 598 * it worked 599 * 600 * Revision 1.3 1998/08/14 16:17:43 tparnell 601 * now appearing in applet version 602 * 603 * Revision 1.2 1998/08/12 19:29:26 tparnell 604 * Another pass after comments from las & natashao. Added support to 605 * dynamically add NodeBehaviors. Add keyboard shortcuts to menus. Added 606 * workaround to jdk bug relating to lighweight components. Misc other 607 * bugfixes. 608 * 609 * Revision 1.3 1998/08/10 17:45:50 tparnell 610 * Revision to use JDK1.2 and Swing. Redesign of GUI. Removed old kludge 611 * for file I/O and replaced with object serialization. Channel no longer 612 * requires animacy. Removed unnessary dependencies between classes. 613 * Added ability to configure channel's latency and capacity. Added 614 * javadoc to all files. General code cleanup. 615 * 616 */