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     */