View Javadoc

1   /*
2    * Copyright (c) 1998-2004 The Jgroup Team.
3    *
4    * This program is free software; you can redistribute it and/or modify
5    * it under the terms of the GNU Lesser General Public License version 2 as
6    * published by the Free Software Foundation.
7    *
8    * This program is distributed in the hope that it will be useful,
9    * but WITHOUT ANY WARRANTY; without even the implied warranty of
10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11   * GNU Lesser General Public License for more details.
12   *
13   * You should have received a copy of the GNU Lesser General Public License
14   * along with this program; if not, write to the Free Software
15   * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16   *
17   */
18  
19  package jgroup.experiment.gui.views.helpers;
20  
21  import java.awt.Color;
22  import java.awt.GridBagConstraints;
23  import java.awt.GridBagLayout;
24  import java.awt.GridLayout;
25  import java.awt.Insets;
26  import java.awt.event.ActionEvent;
27  import java.awt.event.ActionListener;
28  import java.io.File;
29  import java.util.Enumeration;
30  import java.util.HashMap;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.NoSuchElementException;
35  import java.util.StringTokenizer;
36  import java.util.TreeMap;
37  
38  import javax.swing.BorderFactory;
39  import javax.swing.JButton;
40  import javax.swing.JComboBox;
41  import javax.swing.JLabel;
42  import javax.swing.JList;
43  import javax.swing.JOptionPane;
44  import javax.swing.JPanel;
45  import javax.swing.JScrollPane;
46  import javax.swing.JSpinner;
47  import javax.swing.JTabbedPane;
48  import javax.swing.JTextField;
49  import javax.swing.JTree;
50  import javax.swing.ScrollPaneConstants;
51  import javax.swing.SpinnerNumberModel;
52  import javax.swing.border.EtchedBorder;
53  import javax.swing.event.ListSelectionEvent;
54  import javax.swing.event.ListSelectionListener;
55  import javax.swing.event.TreeSelectionEvent;
56  import javax.swing.event.TreeSelectionListener;
57  import javax.swing.tree.DefaultMutableTreeNode;
58  import javax.swing.tree.DefaultTreeModel;
59  import javax.swing.tree.TreePath;
60  import javax.swing.tree.TreeSelectionModel;
61  import javax.xml.parsers.DocumentBuilder;
62  import javax.xml.parsers.DocumentBuilderFactory;
63  
64  import jgroup.experiment.PropertyDefinition;
65  import jgroup.experiment.Runnable;
66  import jgroup.relacs.config.ExperimentConfig;
67  
68  import org.w3c.dom.Document;
69  import org.w3c.dom.Element;
70  import org.w3c.dom.Node;
71  import org.w3c.dom.NodeList;
72  
73  /**
74   * @author Bjarte Svaeren
75   */
76  public class RunListPanelManager
77    implements PanelManager
78  {
79    ////////////////////////////////////////////////////////////////////////////////////////////
80    // Constants
81    ////////////////////////////////////////////////////////////////////////////////////////////
82  
83    private static final String PREEXP_TYPE  = "PreExperiment";
84    private static final String EXP_TYPE     = "Experiment";
85    private static final String POSTEXP_TYPE = "PostExperiment";
86    
87  
88    ////////////////////////////////////////////////////////////////////////////////////////////
89    // Static fields
90    ////////////////////////////////////////////////////////////////////////////////////////////
91    
92  
93    // TreeMaps containing all known targets and runnables
94    // Key: target/runnable name  Value: HashMap with properties
95    // Static to prevent repeated parsing and loading
96    private static TreeMap runnablesMap = null;
97    private static TreeMap targetsMap = null;
98  
99  
100   ////////////////////////////////////////////////////////////////////////////////////////////
101   // GUI fields
102   ////////////////////////////////////////////////////////////////////////////////////////////
103 
104   // The panel to managed:
105   private JPanel                 runListPanel;
106     
107   // Components of runListPanel:
108   private JPanel                 bottomPanel;
109   
110   private JPanel                 choicePanel;
111   private JList                  runnablesList;
112   private JScrollPane            runnablesListPane;
113   private JList                  targetList;
114   private JScrollPane            targetListPane;
115   private JPanel                 choiceButtonPanel;
116   private JButton                changeButton;
117   private JButton                addButton;
118   private JButton                removeButton;
119   private JButton                sleepButton;
120   private JTree                  runListTree;
121   private DefaultMutableTreeNode rootNode;
122   private DefaultTreeModel       treeModel;
123   private JScrollPane            runListPane;
124     
125   // Components of bottomBox:
126   private JLabel                 nameLabel;
127   private JLabel                 valueLabel;
128   private JLabel                 referenceLabel;
129   private JTextField             nameField;
130   private JTextField             valueField;
131   private JComboBox              referenceCombo;
132   private JLabel                 sleepLabel;
133   private JLabel                 timeUnitLabel;
134   private JSpinner               sleepSpinner;
135   private SpinnerNumberModel     spinnerModel;
136   private JButton                applyButton;
137 
138   ////////////////////////////////////////////////////////////////////////////////////////////
139   // Data fields
140   ////////////////////////////////////////////////////////////////////////////////////////////
141   
142   // String describing the type of tab to be managed
143   private String runListType;  
144   // ExperimentConfig object containing information about the experiment
145   private ExperimentConfig experimentConfig;
146   // List containing the tabs runlist
147   private List rList;
148   // HashMap containing the global properties  
149   private HashMap properties;
150   
151   
152 
153   ////////////////////////////////////////////////////////////////////////////////////////////
154   // Constructor
155   ////////////////////////////////////////////////////////////////////////////////////////////
156   
157   public RunListPanelManager(JPanel parentPanel)
158   {
159     runListPanel = parentPanel;
160     properties = new HashMap();
161   }
162 
163 
164   ////////////////////////////////////////////////////////////////////////////////////////////
165   // Implemented methods from interface PanelManager
166   ////////////////////////////////////////////////////////////////////////////////////////////
167 
168   /* (non-Javadoc)
169    * @see jgroup.experiment.gui.views.helpers.PanelManager#makePanel()
170    */
171   public void makePanel()
172   {
173     // Set string runListType to the tab title
174     JTabbedPane tabs = (JTabbedPane) runListPanel.getParent();
175     int index = tabs.indexOfComponent(runListPanel);
176     runListType = tabs.getTitleAt(index);
177 
178     initComponents();
179     addListeners();
180     addComponents();
181   }
182 
183   /* (non-Javadoc)
184    * @see jgroup.experiment.gui.views.helpers.PanelManager#updatePanel(jgroup.relacs.config.ExperimentConfig)
185    */
186   public void updatePanel(ExperimentConfig expConfig)
187   {
188     experimentConfig = expConfig;
189     properties = (HashMap) experimentConfig.getProperties();
190         
191     for (Iterator iter = properties.entrySet().iterator(); iter.hasNext();) {
192       Map.Entry propertyEntry = (Map.Entry) iter.next();
193       // Add the property to the reference combo box        
194       referenceCombo.addItem("${" + propertyEntry.getKey() + "}");
195     } 
196 
197     // Check that the parent of runListPanel is JTabbedPane
198     if( (runListPanel.getParent() instanceof JTabbedPane) == false )
199       throw new IllegalArgumentException("The parent of runListPanel must be a JTabbedPane");
200     
201     // Determine the type of tab, and get the respective runlist
202     rList = null;
203     if(runListType.equals(PREEXP_TYPE))
204       rList = experimentConfig.getPreExperimentRunList();
205     else if(runListType.equals(EXP_TYPE))
206       rList = experimentConfig.getExperimentRunList();
207     else if(runListType.equals(POSTEXP_TYPE))
208       rList = experimentConfig.getPostExperimentRunList();
209       
210     // Iterate through the runlist, and display
211     String elementName = null;
212     
213     int runListIndex = 0;
214     for (Iterator iter = rList.iterator(); iter.hasNext();) {
215       Object element = iter.next();
216       DefaultMutableTreeNode[] propNodeArray = null;
217       
218       if(element instanceof String) {
219         String command = (String) element;
220         int index      = command.indexOf(' ');
221         elementName    = "Target " + command.substring(0, index);
222         String options = command.substring(index +1, command.length());
223         String[] props = parseOptions(options);
224         if(props != null && props.length > 0) {                
225           propNodeArray  = new DefaultMutableTreeNode[props.length];
226           for (int i = 0; i < props.length; i++) {
227             propNodeArray[i] = new DefaultMutableTreeNode(props[i], false);
228           }
229         }
230       } else if(element instanceof Runnable) {
231         String className  = element.getClass().getName();
232         elementName       = "Runnable " 
233                           + className.substring(className.lastIndexOf('.') + 1);
234 
235         Runnable runnable = (Runnable) element;
236         PropertyDefinition[] defs = runnable.getProperties();
237         if(defs != null && defs.length > 0) {
238           propNodeArray = new DefaultMutableTreeNode[defs.length];
239           for (int i = 0; i < defs.length; i++) {
240             String nodeName  = defs[i].getPropertyName();
241             String nodeValue;
242             try {
243               nodeValue = experimentConfig.getProperty(runnable, nodeName);              
244             } catch (NoSuchElementException e1) {
245               nodeValue = "?";
246             }
247             propNodeArray[i] = new DefaultMutableTreeNode(nodeName + " : " 
248                                                         + nodeValue, false);
249           }
250         }        
251       } else if(element instanceof Integer) {
252         elementName = "Sleep : " + element;
253       } else {
254         JOptionPane.showMessageDialog(runListPanel.getTopLevelAncestor(),
255                                       "Uknown type in run list.",
256                                       "Run list error",
257                                       JOptionPane.ERROR_MESSAGE);
258         return;
259       }
260         
261       DefaultMutableTreeNode runListNode 
262                            = new DefaultMutableTreeNode(elementName, true);
263 
264       if(propNodeArray != null && propNodeArray.length > 0) {
265         for (int i = 0; i < propNodeArray.length; i++) {
266           runListNode.insert(propNodeArray[i], i);
267         }
268       }
269       
270       treeModel.insertNodeInto(runListNode, rootNode, runListIndex++);
271     }
272   }
273 
274   /* (non-Javadoc)
275    * @see jgroup.experiment.gui.views.helpers.PanelManager#setParentPanel(javax.swing.JPanel)
276    */
277   public void setParentPanel(JPanel panel)
278   {
279     if(runListPanel == null)
280       throw new IllegalArgumentException("JPanel expPanel has already been set.");
281       
282     runListPanel = panel;
283   }
284 
285   /* (non-Javadoc)
286    * @see jgroup.experiment.gui.views.helpers.PanelManager#clear()
287    */
288   public void clear()
289   {
290     experimentConfig = null;
291     rootNode.removeAllChildren();
292     treeModel.reload();
293     properties.clear();
294     referenceCombo.removeAllItems();
295     nameField.setText("");
296     valueField.setText("");
297   }
298   
299   public void save(Element parentElement, Document document)
300   {
301     String typeName = "";
302 
303     if(runListType.equals(PREEXP_TYPE))
304       typeName = "PreExp";
305     else if(runListType.equals(POSTEXP_TYPE))
306       typeName = "PostExp";
307     else
308       typeName = "Exp";
309       
310     Element typeElement = document.createElement(typeName);
311     parentElement.appendChild(typeElement);
312     
313     DefaultMutableTreeNode rootNode 
314                          = (DefaultMutableTreeNode) treeModel.getRoot();
315     
316     // Iterate through the run elements, and add them to the DOM
317     for(Enumeration e = rootNode.children(); e.hasMoreElements();) {
318       DefaultMutableTreeNode currentNode
319                            = (DefaultMutableTreeNode) e.nextElement();
320       appendRunElement(currentNode, typeElement, document);
321     }
322   }
323 
324   ////////////////////////////////////////////////////////////////////////////////////////////
325   // Private methods
326   ////////////////////////////////////////////////////////////////////////////////////////////
327 
328   private void initComponents()
329   {
330     runListPanel.setBorder(BorderFactory.createCompoundBorder(
331                            BorderFactory.createEtchedBorder(),
332                            BorderFactory.createEmptyBorder(5,5,5,5)));                           
333     
334     bottomPanel       = new JPanel(new GridBagLayout());
335     bottomPanel.setBorder(BorderFactory.createCompoundBorder(
336                           BorderFactory.createTitledBorder(
337                           BorderFactory.createEtchedBorder(EtchedBorder.LOWERED),
338                           "Property:"),
339                           BorderFactory.createEmptyBorder(0,10,10,10)));
340 
341     choicePanel       = new JPanel(new GridLayout(2,1));
342     runnablesList     = new JList(loadRunnables());
343     runnablesListPane = new JScrollPane(runnablesList);
344     runnablesListPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);    
345     runnablesListPane.setBorder(BorderFactory.createCompoundBorder(
346                                 BorderFactory.createTitledBorder(
347                                 BorderFactory.createEmptyBorder(),
348                                 "Runnables:"),
349                                 BorderFactory.createCompoundBorder(
350                                 BorderFactory.createLineBorder(Color.BLACK,2),
351                                 BorderFactory.createEtchedBorder())));
352 
353     targetList        = new JList(loadTargets());
354     targetListPane    = new JScrollPane(targetList);
355     targetListPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
356     targetListPane.setBorder(BorderFactory.createCompoundBorder(
357                              BorderFactory.createTitledBorder(
358                              BorderFactory.createEmptyBorder(),
359                              "Targets:"),
360                              BorderFactory.createCompoundBorder(
361                              BorderFactory.createLineBorder(Color.BLACK,2),
362                              BorderFactory.createEtchedBorder())));
363     
364     choiceButtonPanel = new JPanel(new GridBagLayout());
365     changeButton      = new JButton("Change");
366     addButton         = new JButton("Add");
367     removeButton      = new JButton("Remove");
368     sleepButton       = new JButton("Sleep");
369 
370     // Create the run list panel and tree
371     String tagName = null;
372     if(runListType.equals(PREEXP_TYPE))
373       tagName = "PreExp"; 
374     else if(runListType.equals(EXP_TYPE))
375       tagName = "Exp"; 
376     else if(runListType.equals(POSTEXP_TYPE))
377       tagName = "PostExp";
378        
379     rootNode     = new DefaultMutableTreeNode(tagName);
380     treeModel    = new DefaultTreeModel(rootNode);
381     runListTree  = new JTree(treeModel);
382     runListPane  = new JScrollPane(runListTree);
383     runListPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
384     runListPane.setBorder(BorderFactory.createCompoundBorder(
385                           BorderFactory.createTitledBorder(
386                           BorderFactory.createEmptyBorder(),
387                           runListType + " run list:"),
388                           BorderFactory.createCompoundBorder(
389                           BorderFactory.createLineBorder(Color.BLACK,2),
390                           BorderFactory.createEtchedBorder())));
391                                    
392     treeModel.setAsksAllowsChildren(true);
393     runListTree.getSelectionModel().setSelectionMode
394                     (TreeSelectionModel.SINGLE_TREE_SELECTION);
395     
396     nameLabel      = new JLabel("Name:");
397     valueLabel     = new JLabel("Value:");
398     referenceLabel = new JLabel("References:");
399     nameField      = new JTextField(10);
400     valueField     = new JTextField(10);
401     referenceCombo = new ScrollableComboBox();
402     // Set the width of the combobox using example text
403     referenceCombo.setPrototypeDisplayValue("this.is.a.property");    
404     nameField.setEditable(false);
405     valueField.setEditable(false);
406     
407     sleepLabel     = new JLabel("Sleep for ");
408     timeUnitLabel  = new JLabel(" seconds");
409     spinnerModel   = new SpinnerNumberModel(10, 0, 9999, 10);
410     sleepSpinner   = new JSpinner(spinnerModel);
411     applyButton    = new JButton("Apply");
412     
413     // Set the size of the spinner
414     JSpinner.DefaultEditor editor = (JSpinner.DefaultEditor) sleepSpinner.getEditor();
415     editor.getTextField().setColumns(4);
416   }
417   
418   
419   private void addListeners()
420   {
421     addListListeners();
422     addButtonListeners();
423     addTreeListeners();
424     
425     runListTree.setSelectionPath(new TreePath(rootNode));    
426   }
427 
428 
429   private void addComponents()
430   {
431     choicePanel.add(runnablesListPane);
432     choicePanel.add(targetListPane);
433     
434     // Do the button panel layout     
435     GridBagConstraints gbc = new GridBagConstraints();
436     gbc.fill      = GridBagConstraints.BOTH;
437     gbc.gridwidth = GridBagConstraints.REMAINDER;
438     choiceButtonPanel.add(changeButton, gbc);
439     choiceButtonPanel.add(addButton, gbc);
440     choiceButtonPanel.add(removeButton, gbc);
441     choiceButtonPanel.add(sleepButton, gbc);
442     choiceButtonPanel.setBorder(BorderFactory.createCompoundBorder(
443                                 BorderFactory.createEmptyBorder(5,5,5,5),
444                                 BorderFactory.createEmptyBorder(5,5,5,5)));
445         
446 
447     gbc = new GridBagConstraints();
448 
449     gbc.fill      = GridBagConstraints.BOTH;
450     gbc.gridwidth = 1;
451     gbc.weightx = 0.3;
452     gbc.weighty = 0.1;
453     runListPanel.add(choicePanel, gbc);
454 
455     gbc.fill    = GridBagConstraints.NONE;
456     gbc.weightx = 0.1;
457     runListPanel.add(choiceButtonPanel, gbc);
458 
459     addPropertyComponents();
460 
461     gbc = new GridBagConstraints();
462 
463     gbc.fill      = GridBagConstraints.BOTH;
464     gbc.weightx   = 0.6;
465     gbc.gridwidth = GridBagConstraints.REMAINDER;
466     runListPanel.add(runListPane, gbc);
467     
468     gbc.insets    = new Insets(20,0,0,0);   
469     runListPanel.add(bottomPanel, gbc);    
470   }
471   
472   
473   private void addPropertyComponents()
474   {
475     bottomPanel.removeAll();    
476     GridBagConstraints gbc = new GridBagConstraints();
477 
478     gbc.anchor = GridBagConstraints.LINE_START;
479     gbc.fill   = GridBagConstraints.BOTH;
480     bottomPanel.add(nameLabel, gbc);
481     gbc.gridwidth = GridBagConstraints.RELATIVE;
482     bottomPanel.add(valueLabel, gbc);
483     gbc.gridwidth = GridBagConstraints.REMAINDER;
484     bottomPanel.add(referenceLabel, gbc);
485 
486     gbc.gridwidth = 1;
487     gbc.weightx   = 0.1;
488     gbc.insets    = new Insets(0,0,0,20);
489     bottomPanel.add(nameField, gbc);
490     gbc.gridwidth = GridBagConstraints.RELATIVE;
491     bottomPanel.add(valueField, gbc);
492     gbc.gridwidth = GridBagConstraints.REMAINDER;
493     gbc.insets    = new Insets(0,0,0,0);
494     gbc.weightx   = 0;
495     bottomPanel.add(referenceCombo, gbc);
496 
497     bottomPanel.revalidate();    
498   }
499 
500 
501   private void addSleepComponents(DefaultMutableTreeNode selectedNode)
502   {
503     bottomPanel.removeAll();
504     GridBagConstraints gbc = new GridBagConstraints();
505     
506     gbc.insets    = new Insets(17,0,0,5);
507     gbc.anchor    = GridBagConstraints.LINE_END;
508     gbc.fill      = GridBagConstraints.BOTH;
509     bottomPanel.add(sleepLabel, gbc);
510     
511     gbc.anchor    = GridBagConstraints.CENTER;
512     gbc.fill      = GridBagConstraints.NONE;
513     bottomPanel.add(sleepSpinner, gbc);
514 
515     gbc.anchor    = GridBagConstraints.LINE_START;
516     gbc.fill      = GridBagConstraints.BOTH;
517     bottomPanel.add(timeUnitLabel, gbc);
518 
519     gbc.insets    = new Insets(17,30,0,0);
520     gbc.anchor    = GridBagConstraints.LINE_END;
521     bottomPanel.add(applyButton, gbc);
522 
523     bottomPanel.revalidate();
524 
525     String nodeTitle     = (String) selectedNode.getUserObject();
526     int    index         = nodeTitle.indexOf(" : ");
527     String propertyValue = nodeTitle.substring(index + 3, nodeTitle.length());
528     sleepSpinner.setValue(Integer.valueOf(propertyValue));
529   }
530 
531 
532   private void addListListeners() 
533   {
534     runnablesList.addListSelectionListener(new ListSelectionListener() {
535       public void valueChanged(ListSelectionEvent e) {
536         if(e.getValueIsAdjusting() == false) {
537           JList list = (JList) e.getSource();
538           
539           // If no element is selected, there's no need to update the tabs. 
540           // Most likely the 'NEW' button was pressed.
541           if(list.isSelectionEmpty())
542             return;
543     
544           targetList.clearSelection();
545         }
546       }
547     });
548     
549     targetList.addListSelectionListener(new ListSelectionListener() {
550       public void valueChanged(ListSelectionEvent e) {
551         if(e.getValueIsAdjusting() == false) {
552           JList list = (JList) e.getSource();
553           
554           // If no element is selected, there's no need to update the tabs. 
555           // Most likely the 'NEW' button was pressed.
556           if(list.isSelectionEmpty())
557             return;
558     
559           runnablesList.clearSelection();
560         }
561       }
562     });    
563   }
564   
565   
566   private void addButtonListeners()
567   {
568     ///////////////////////////////////////////////////////////////////////////
569     // ADD BUTTON
570     ///////////////////////////////////////////////////////////////////////////
571 
572     addButton.addActionListener(new ActionListener() {
573       public void actionPerformed(ActionEvent e) {
574         String title  = null;
575         HashMap props = null;
576         
577         if(!runnablesList.isSelectionEmpty()) {
578           title = "Runnable " + runnablesList.getSelectedValue();
579           props = (HashMap) runnablesMap.get(runnablesList.getSelectedValue());
580         } else if (!targetList.isSelectionEmpty()) {
581           title = "Target " + targetList.getSelectedValue();
582           props = (HashMap) targetsMap.get(targetList.getSelectedValue());
583         } else {
584           return;
585         }
586         
587         DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(title);                               
588         DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode)
589                                runListTree.getLastSelectedPathComponent();
590 
591         // If the element has properties, add them to the tree
592         if(props != null)        
593           addProps(newNode, props);
594         
595         if(selectedNode == null) {
596           treeModel.insertNodeInto(newNode, rootNode, 
597                                    treeModel.getChildCount(rootNode));        
598         } else if(selectedNode.isRoot()) {
599           treeModel.insertNodeInto(newNode, rootNode, 0);                  
600         } else {
601           treeModel.insertNodeInto(newNode, rootNode,
602                                    treeModel.getIndexOfChild(rootNode, selectedNode) + 1);        
603         }
604         
605         treeModel.reload();
606       }
607     });
608 
609 
610     ///////////////////////////////////////////////////////////////////////////
611     // REMOVE BUTTON
612     ///////////////////////////////////////////////////////////////////////////
613 
614     removeButton.addActionListener(new ActionListener() {
615       public void actionPerformed(ActionEvent e) {
616         DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode)
617                                runListTree.getLastSelectedPathComponent();
618         
619         if(selectedNode != null && !selectedNode.isRoot()) {
620           treeModel.removeNodeFromParent(selectedNode);        
621         }
622       }
623     });
624     
625 
626     ///////////////////////////////////////////////////////////////////////////
627     // SLEEP BUTTON
628     ///////////////////////////////////////////////////////////////////////////
629 
630     sleepButton.addActionListener(new ActionListener() {
631       public void actionPerformed(ActionEvent e) {
632 
633         DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("Sleep : ?");
634         DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode)
635                                runListTree.getLastSelectedPathComponent();
636 
637         if(selectedNode == null) {
638           treeModel.insertNodeInto(newNode, rootNode, 
639                                    treeModel.getChildCount(rootNode));        
640         } else if(selectedNode.isRoot()) {
641           treeModel.insertNodeInto(newNode, rootNode, 0);                  
642         } else {
643           treeModel.insertNodeInto(newNode, rootNode,
644                                    treeModel.getIndexOfChild(rootNode, selectedNode) + 1);        
645         }
646         
647         treeModel.reload();
648       }
649     });
650     
651 
652     ///////////////////////////////////////////////////////////////////////////
653     // CHANGE BUTTON
654     ///////////////////////////////////////////////////////////////////////////
655 
656     changeButton.addActionListener(new ActionListener() {
657       public void actionPerformed(ActionEvent e) {
658         DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode)
659                              runListTree.getLastSelectedPathComponent();
660         String nodeTitle = (String) selectedNode.getUserObject();
661         String oldName   = nodeTitle.substring(0, nodeTitle.indexOf(" :"));
662         String name      = nameField.getText();
663         String value     = valueField.getText();
664           
665         if(name == null || name.length() == 0) {
666           JOptionPane.showMessageDialog(runListPanel.getTopLevelAncestor(),
667                                         "The property must\n"
668                                        +"have a name.",
669                                         "Missing parameter",
670                                         JOptionPane.INFORMATION_MESSAGE);
671           return;          
672         } else if(value == null || value.length() == 0) {
673           JOptionPane.showMessageDialog(runListPanel.getTopLevelAncestor(),
674                                         "The property must\n"
675                                        +"have a value.",
676                                         "Missing parameter",
677                                         JOptionPane.INFORMATION_MESSAGE);
678           return;
679         } else if(properties.containsKey(name)) {
680           if(!name.equals(oldName)) {
681             JOptionPane.showMessageDialog(runListPanel.getTopLevelAncestor(),
682                                           "A property with the name\n"
683                                         + "\"" + name + "\"\n"
684                                         + "already exists.\n"
685                                         + "Use the Change button to\n"
686                                         + "change the value of the property.",
687                                           "Missing parameter",
688                                           JOptionPane.INFORMATION_MESSAGE);
689             return;
690           }
691         }
692 
693         // Create a node with the new name and value
694         DefaultMutableTreeNode newNode
695                              = new DefaultMutableTreeNode(name + " : "
696                                                         + value, false);
697                                                         
698         // Get the parent node of the selected node
699         DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)
700                                selectedNode.getParent();
701         // Find the index of the node to be replaced
702         int index = parentNode.getIndex(selectedNode);
703         // Remove the old node and insert the new node
704         parentNode.remove(index);
705         parentNode.insert(newNode, index);
706         properties.remove(oldName);
707         properties.put(name, value);
708         treeModel.reload();
709         runListTree.setSelectionPath(new TreePath(newNode.getPath()));
710       }
711     });
712     
713 
714     ///////////////////////////////////////////////////////////////////////////
715     // APPLY BUTTON
716     ///////////////////////////////////////////////////////////////////////////
717 
718     applyButton.addActionListener(new ActionListener() {
719       public void actionPerformed(ActionEvent e) {
720         DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode)
721                                runListTree.getLastSelectedPathComponent();
722         Integer spinnerValue = (Integer) sleepSpinner.getValue();
723   
724         DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("Sleep : " 
725                                                                   + spinnerValue);
726         // Get the parent node of the selected node
727         DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)
728                                selectedNode.getParent();
729         // Find the index of the node to be replaced
730         int index = parentNode.getIndex(selectedNode);
731         // Remove the old node and insert the new node
732         parentNode.remove(index);
733         parentNode.insert(newNode, index);
734         treeModel.reload();
735         runListTree.setSelectionPath(new TreePath(newNode.getPath()));
736       }
737     });
738   }
739   
740   
741   private void addTreeListeners()
742   {
743     runListTree.addTreeSelectionListener(new TreeSelectionListener() {
744       public void valueChanged(TreeSelectionEvent e) {
745         DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode)
746                                runListTree.getLastSelectedPathComponent();
747         if(selectedNode == null) return;
748 
749         // If the node is not a property, enable/disable
750         // the appropriate buttons        
751         if(selectedNode.getLevel() < 2) {
752           changeButton.setEnabled(false);
753           addButton.setEnabled(true);
754           removeButton.setEnabled(true);
755           sleepButton.setEnabled(true);
756           nameField.setEditable(false);
757           valueField.setEditable(false);
758           runnablesList.setEnabled(true);
759           targetList.setEnabled(true);
760           
761           String nodeTitle = (String) selectedNode.getUserObject();
762           if(nodeTitle.startsWith("Sleep"))
763             addSleepComponents(selectedNode);            
764 
765         // If the node is a property, enable/disable the 
766         // appropriate buttons, and display property information.        
767         } else {
768           changeButton.setEnabled(true);
769           addButton.setEnabled(false);
770           sleepButton.setEnabled(false);
771           valueField.setEditable(true);
772           runnablesList.setEnabled(false);
773           targetList.setEnabled(false);
774 
775           DefaultMutableTreeNode n = (DefaultMutableTreeNode) selectedNode.getParent();
776           String parentTitle = (String) n.getUserObject();
777           
778           if(parentTitle.startsWith("Target")) {
779             removeButton.setEnabled(true);
780             nameField.setEditable(true);
781           } else {
782             removeButton.setEnabled(false);
783             nameField.setEditable(false);
784           }
785           
786           displayProperty(selectedNode);
787         }
788       }
789     });
790   }
791   
792   
793   private void displayProperty(DefaultMutableTreeNode node)
794   {
795     addPropertyComponents();
796     
797     String nodeTitle     = (String) node.getUserObject();
798     int index = nodeTitle.indexOf(" : ");
799     String propertyName  = nodeTitle.substring(0, index);
800     String propertyValue = nodeTitle.substring(index + 3, nodeTitle.length());
801     
802     nameField.setText(propertyName);
803     valueField.setText( propertyValue.equals("?") ? "" : propertyValue ); 
804   }  
805     
806   
807   private Object[] loadRunnables()
808   {
809     if(runnablesMap != null)
810       return runnablesMap.keySet().toArray();
811 
812     // If the map is null, initialize it
813     runnablesMap = new TreeMap();
814           
815     String ch = File.separator;
816     File runnablesDir = new File("jgroup-experiment" + ch + "src" + ch + "main" + ch + "java" + ch + "jgroup" + ch + 
817                                  "experiment" + ch + "runnables");
818     if (!runnablesDir.exists())
819       JOptionPane.showMessageDialog(runListPanel.getTopLevelAncestor(),
820                                     "The runnables directory\n"
821                                    +"could not be found.",
822                                     "File Error",
823                                     JOptionPane.ERROR_MESSAGE);
824 
825     String[] runnableFiles = new FileList(runnablesDir, ".java").getFileList();
826 
827     // Iterate through the array and strip all filenames of the '.java' ending
828     for (int i = 0; i < runnableFiles.length; i++) {
829       String runnableName = runnableFiles[i].substring(0, runnableFiles[i].length() - 5);
830       HashMap propertiesMap = new HashMap();
831       
832       // Create an instance of the runnable and get its properties.
833       try {
834         Class cl = Class.forName("jgroup.experiment.runnables." + runnableName);
835         Runnable runnable = (Runnable) cl.newInstance();
836         PropertyDefinition[] propDefs = runnable.getProperties();
837         
838         if(propDefs != null && propDefs.length > 0) {
839           // If there are properties, store their names in the map
840 
841           for (int j = 0; j < propDefs.length; j++) {
842             propertiesMap.put(propDefs[j].getPropertyName(), propDefs[j]);
843           }
844         } else {
845           // If there are no properties, set the map to null
846           propertiesMap = null;
847         }        
848       } catch (ClassNotFoundException e) {
849         e.printStackTrace();
850       } catch (InstantiationException e) {
851         e.printStackTrace();
852       } catch (IllegalAccessException e) {
853         e.printStackTrace();
854       }
855 
856       // Finally, store the name of the runnable and its properties in a map
857       runnablesMap.put(runnableName, propertiesMap);
858     }
859     
860     return runnablesMap.keySet().toArray();
861   }
862   
863   
864   private Object[] loadTargets()
865   {
866     if(targetsMap != null)
867       return targetsMap.keySet().toArray();
868       
869     // If the map is null, initialize it
870     targetsMap = new TreeMap();
871     
872     // Should the URL be dynamic?
873     String buildURL = "file:build.xml";
874 
875     Document doc = null;
876     try {
877       DocumentBuilderFactory docBF = DocumentBuilderFactory.newInstance();
878       boolean validate = false;
879       String validateStr = System.getProperty("jgroup.config.validate");
880       if (validateStr != null && validateStr.equals("true")) {
881         validate = true;
882       }
883       docBF.setValidating(validate);
884       DocumentBuilder db = docBF.newDocumentBuilder();
885 //      db.setErrorHandler(new ParsingErrorHandler());
886       doc = db.parse(buildURL);
887     } catch (Exception e) {
888       e.printStackTrace();
889     }
890     
891     Element elm = doc.getDocumentElement();
892     NodeList nl = elm.getChildNodes();
893     for (int i = 1; i <= nl.getLength(); i++) {
894       Node node = nl.item(i);
895       if (node != null && !node.getNodeName().startsWith("#")) {
896         elm = (Element) node;
897         parseTarget(elm);
898       }
899     }
900     
901     return targetsMap.keySet().toArray();
902   }
903   
904   
905   private void parseTarget(Element elm)
906   {
907     String targetName = null;
908     
909     // If the node is a target, parse it and its properties
910     if(elm.getNodeName().equalsIgnoreCase("target")) {
911       targetName = elm.getAttribute("name");
912       HashMap propertiesMap = new HashMap();
913 
914       /* List of elements within <target> tag */
915       NodeList elementList = elm.getChildNodes();
916       /* Parse the elements in the list */
917       for (int i=1; i <= elementList.getLength(); i++) {
918         Node node = elementList.item(i);
919         if (node != null && !node.getNodeName().startsWith("#")) {
920           elm = (Element) node;
921 
922           // If it's a <java> tag, parse the sysproperties
923           if(elm.getNodeName().equalsIgnoreCase("java")) {
924             parseProps(elm, propertiesMap);
925           }
926         }
927       }
928       
929       targetsMap.put(targetName, propertiesMap);       
930     }
931   }
932   
933   
934   private void parseProps(Element elm, HashMap propertiesMap)
935   {
936     /* List of elements within <java> tag */
937     NodeList elementList = elm.getChildNodes();
938     /* Parse the elements in the list */
939     for (int i=1; i <= elementList.getLength(); i++) {
940       Node node = elementList.item(i);
941       if (node != null && !node.getNodeName().startsWith("#")) {
942         elm = (Element) node;
943 
944         // Get the sysproperties, and store them in a map
945         if(elm.getNodeName().equalsIgnoreCase("sysproperty")) {
946           String key   = elm.getAttribute("key");
947           String value = elm.getAttribute("value");
948           propertiesMap.put(key, value);
949         }
950       }
951     }
952   }
953   
954   
955   private String[] parseOptions(String options)
956   {
957     StringTokenizer tokenizer = new StringTokenizer(options, "-D");
958     String[] props = new String[tokenizer.countTokens()];
959 
960     int index = 0;    
961     while(tokenizer.hasMoreTokens()) {
962       StringTokenizer t    = new StringTokenizer(tokenizer.nextToken(), "=");
963       String propertyName  = t.nextToken();
964       String propertyValue = t.nextToken();
965       props[index++] = propertyName + " : " + propertyValue;
966     }
967     
968     return props;
969   }
970   
971   
972   private void addProps(DefaultMutableTreeNode parentNode, HashMap properties)
973   {
974     int index = 0;
975     for (Iterator iter = properties.entrySet().iterator(); iter.hasNext();) {
976       Map.Entry property = (Map.Entry) iter.next();
977       String propName    = (String) property.getKey();
978       String propValue   = null;
979       Object value       = property.getValue();
980       
981       if(value instanceof String)
982         propValue = (String) value;
983       else if(value instanceof PropertyDefinition)
984         propValue = "?";
985       else
986         throw new IllegalArgumentException("Unknown property type");
987 
988       DefaultMutableTreeNode propNode = new DefaultMutableTreeNode(
989                                         propName + " : " + propValue, 
990                                         false);
991       parentNode.insert(propNode, index++);
992     }    
993   }
994   
995   
996   private void appendRunElement(DefaultMutableTreeNode runNode, Element parentElement, Document document)
997   {
998     Element runElement = null;
999     String nodeTitle   = (String) runNode.getUserObject();
1000     
1001     if(nodeTitle.startsWith("Sleep")) {
1002       int    index      = nodeTitle.indexOf(" : ");
1003       String sleepValue = nodeTitle.substring(index + 3, nodeTitle.length());
1004       runElement = document.createElement("Sleep");
1005       runElement.setAttribute("delay", sleepValue);
1006     } else {
1007       int index  = nodeTitle.indexOf(" ") + 1;  
1008       runElement = document.createElement("Run");
1009       String elementName = nodeTitle.substring(index, nodeTitle.length());
1010 
1011       if(nodeTitle.startsWith("Runnable"))
1012         runElement.setAttribute("name", elementName);
1013       else
1014         runElement.setAttribute("target", elementName);
1015         
1016       // Iterate through the sysproperties, and add them to the DOM tree
1017       for(Enumeration e = runNode.children(); e.hasMoreElements();) {
1018         DefaultMutableTreeNode currentNode
1019                                = (DefaultMutableTreeNode) e.nextElement();
1020         appendProperty(currentNode, runElement, document);
1021       }
1022     }
1023     
1024     parentElement.appendChild(runElement);
1025   }
1026   
1027   
1028   private void appendProperty(DefaultMutableTreeNode propNode, Element runElement, Document document)
1029   {
1030     String nodeTitle   = (String) propNode.getUserObject();
1031     int    index       = nodeTitle.indexOf(" : ");
1032     String propKey     = "";
1033     String propValue   = "";
1034     
1035     if(index != -1) {
1036       propKey   = nodeTitle.substring(0, index);
1037       propValue = nodeTitle.substring(index + 3, nodeTitle.length());
1038     } else {
1039       propKey   = nodeTitle;
1040     }
1041 
1042     // Append the domain element, and set name and port
1043     Element propElement = document.createElement("sysproperty");
1044     propElement.setAttribute("key", propKey);
1045     if(propValue != null && propValue.length() > 0)
1046       propElement.setAttribute("value", propValue);
1047     runElement.appendChild(propElement);
1048   }    
1049 }