/* * Copyright (c) 2006, Swedish Institute of Computer Science. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. 2. Redistributions in * binary form must reproduce the above copyright notice, this list of * conditions and the following disclaimer in the documentation and/or other * materials provided with the distribution. 3. Neither the name of the * Institute nor the names of its contributors may be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ package org.contikios.cooja; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dialog; import java.awt.Dialog.ModalityType; import java.awt.Dimension; import java.awt.Frame; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.GridLayout; import java.awt.Point; import java.awt.Rectangle; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.beans.PropertyVetoException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.io.StringReader; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.net.URLClassLoader; import java.security.AccessControlException; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.List; import java.util.Observable; import java.util.Observer; import java.util.Properties; import java.util.Vector; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.DefaultDesktopManager; import javax.swing.InputMap; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JDesktopPane; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JInternalFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JTabbedPane; import javax.swing.JTextPane; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.ToolTipManager; import javax.swing.UIManager; import javax.swing.UIManager.LookAndFeelInfo; import javax.swing.UnsupportedLookAndFeelException; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; import javax.swing.filechooser.FileFilter; import org.apache.log4j.BasicConfigurator; import org.apache.log4j.Logger; import org.apache.log4j.xml.DOMConfigurator; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import org.jdom.output.Format; import org.jdom.output.XMLOutputter; import org.contikios.cooja.MoteType.MoteTypeCreationException; import org.contikios.cooja.VisPlugin.PluginRequiresVisualizationException; import org.contikios.cooja.contikimote.ContikiMoteType; import org.contikios.cooja.dialogs.AddMoteDialog; import org.contikios.cooja.dialogs.BufferSettings; import org.contikios.cooja.dialogs.ConfigurationWizard; import org.contikios.cooja.dialogs.CreateSimDialog; import org.contikios.cooja.dialogs.ExternalToolsDialog; import org.contikios.cooja.dialogs.MessageList; import org.contikios.cooja.dialogs.ProjectDirectoriesDialog; import org.contikios.cooja.plugins.MoteTypeInformation; import org.contikios.cooja.plugins.ScriptRunner; import org.contikios.cooja.plugins.SimControl; import org.contikios.cooja.plugins.SimInformation; import org.contikios.cooja.util.ExecuteJAR; /** * Main file of COOJA Simulator. Typically contains a visualizer for the * simulator, but can also be started without visualizer. * * This class loads external Java classes (in extension directories), and handles the * COOJA plugins as well as the configuration system. If provides a number of * help methods for the rest of the COOJA system, and is the starting point for * loading and saving simulation configs. * * @author Fredrik Osterlind */ public class Cooja extends Observable { private static JFrame frame = null; private static JApplet applet = null; private static final long serialVersionUID = 1L; private static Logger logger = Logger.getLogger(Cooja.class); /** * External tools configuration. */ public static final String EXTERNAL_TOOLS_SETTINGS_FILENAME = "/external_tools.config"; /** * External tools default Win32 settings filename. */ public static final String EXTERNAL_TOOLS_WIN32_SETTINGS_FILENAME = "/external_tools_win32.config"; /** * External tools default Mac OS X settings filename. */ public static final String EXTERNAL_TOOLS_MACOSX_SETTINGS_FILENAME = "/external_tools_macosx.config"; /** * External tools default FreeBSD settings filename. */ public static final String EXTERNAL_TOOLS_FREEBSD_SETTINGS_FILENAME = "/external_tools_freebsd.config"; /** * External tools default Linux/Unix settings filename. */ public static final String EXTERNAL_TOOLS_LINUX_SETTINGS_FILENAME = "/external_tools_linux.config"; /** * External tools default Linux/Unix settings filename for 64 bit architectures. * Tested on Intel 64-bit Gentoo Linux. */ public static final String EXTERNAL_TOOLS_LINUX_64_SETTINGS_FILENAME = "/external_tools_linux_64.config"; /** * External tools user settings filename. */ public static final String EXTERNAL_TOOLS_USER_SETTINGS_FILENAME = ".cooja.user.properties"; public static File externalToolsUserSettingsFile; private static boolean externalToolsUserSettingsFileReadOnly = false; private static String specifiedContikiPath = null; /** * Logger settings filename. */ public static final String LOG_CONFIG_FILE = "log4j_config.xml"; /** * Default extension configuration filename. */ public static String PROJECT_DEFAULT_CONFIG_FILENAME = null; /** * User extension configuration filename. */ public static final String PROJECT_CONFIG_FILENAME = "cooja.config"; /** * File filter only showing saved simulations files (*.csc). */ public static final FileFilter SAVED_SIMULATIONS_FILES = new FileFilter() { public boolean accept(File file) { if (file.isDirectory()) { return true; } if (file.getName().endsWith(".csc")) { return true; } if (file.getName().endsWith(".csc.gz")) { return true; } return false; } public String getDescription() { return "Cooja simulation (.csc, .csc.gz)"; } public String toString() { return ".csc"; } }; // External tools setting names public static Properties defaultExternalToolsSettings; public static Properties currentExternalToolsSettings; private static final String externalToolsSettingNames[] = new String[] { "PATH_CONTIKI", "PATH_COOJA_CORE_RELATIVE","PATH_COOJA","PATH_APPS", "PATH_APPSEARCH", "PATH_MAKE", "PATH_SHELL", "PATH_C_COMPILER", "COMPILER_ARGS", "PATH_LINKER", "LINK_COMMAND_1", "LINK_COMMAND_2", "PATH_AR", "AR_COMMAND_1", "AR_COMMAND_2", "PATH_OBJDUMP", "OBJDUMP_ARGS", "PATH_OBJCOPY", "PATH_JAVAC", "CONTIKI_STANDARD_PROCESSES", "CMD_GREP_PROCESSES", "REGEXP_PARSE_PROCESSES", "CMD_GREP_INTERFACES", "REGEXP_PARSE_INTERFACES", "CMD_GREP_SENSORS", "REGEXP_PARSE_SENSORS", "DEFAULT_PROJECTDIRS", "CORECOMM_TEMPLATE_FILENAME", "PARSE_WITH_COMMAND", "MAPFILE_DATA_START", "MAPFILE_DATA_SIZE", "MAPFILE_BSS_START", "MAPFILE_BSS_SIZE", "MAPFILE_COMMON_START", "MAPFILE_COMMON_SIZE", "MAPFILE_VAR_NAME", "MAPFILE_VAR_ADDRESS_1", "MAPFILE_VAR_ADDRESS_2", "MAPFILE_VAR_SIZE_1", "MAPFILE_VAR_SIZE_2", "PARSE_COMMAND", "COMMAND_VAR_NAME_ADDRESS_SIZE", "COMMAND_DATA_START", "COMMAND_DATA_END", "COMMAND_BSS_START", "COMMAND_BSS_END", "COMMAND_COMMON_START", "COMMAND_COMMON_END", "HIDE_WARNINGS" }; private static final int FRAME_NEW_OFFSET = 30; private static final int FRAME_STANDARD_WIDTH = 150; private static final int FRAME_STANDARD_HEIGHT = 300; private static final String WINDOW_TITLE = "Cooja: The Contiki Network Simulator"; private Cooja cooja; private Simulation mySimulation; protected GUIEventHandler guiEventHandler = new GUIEventHandler(); private JMenu menuMoteTypeClasses, menuMoteTypes; private JMenu menuOpenSimulation; private boolean hasFileHistoryChanged; private Vector> menuMotePluginClasses = new Vector>(); private JDesktopPane myDesktopPane; private Vector startedPlugins = new Vector(); private ArrayList guiActions = new ArrayList(); // Platform configuration variables // Maintained via method reparseProjectConfig() private ProjectConfig projectConfig; private ArrayList currentProjects = new ArrayList(); public ClassLoader projectDirClassLoader; private Vector> moteTypeClasses = new Vector>(); private Vector> pluginClasses = new Vector>(); private Vector> radioMediumClasses = new Vector>(); private Vector> positionerClasses = new Vector>(); private class HighlightObservable extends Observable { private void setChangedAndNotify(Mote mote) { setChanged(); notifyObservers(mote); } } private HighlightObservable moteHighlightObservable = new HighlightObservable(); private class MoteRelationsObservable extends Observable { private void setChangedAndNotify() { setChanged(); notifyObservers(); } } private MoteRelationsObservable moteRelationObservable = new MoteRelationsObservable(); private JTextPane quickHelpTextPane; private JScrollPane quickHelpScroll; private Properties quickHelpProperties = null; /* quickhelp.txt */ /** * Mote relation (directed). */ public static class MoteRelation { public Mote source; public Mote dest; public Color color; public MoteRelation(Mote source, Mote dest, Color color) { this.source = source; this.dest = dest; this.color = color; } } private ArrayList moteRelations = new ArrayList(); /** * Creates a new COOJA Simulator GUI. * * @param desktop Desktop pane */ public Cooja(JDesktopPane desktop) { cooja = this; mySimulation = null; myDesktopPane = desktop; /* Help panel */ quickHelpTextPane = new JTextPane(); quickHelpTextPane.setContentType("text/html"); quickHelpTextPane.setEditable(false); quickHelpTextPane.setVisible(false); quickHelpScroll = new JScrollPane(quickHelpTextPane, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); quickHelpScroll.setPreferredSize(new Dimension(200, 0)); quickHelpScroll.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createLineBorder(Color.GRAY), BorderFactory.createEmptyBorder(0, 3, 0, 0) )); quickHelpScroll.setVisible(false); loadQuickHelp("GETTING_STARTED"); // Load default and overwrite with user settings (if any) loadExternalToolsDefaultSettings(); loadExternalToolsUserSettings(); final boolean showQuickhelp = getExternalToolsSetting("SHOW_QUICKHELP", "true").equalsIgnoreCase("true"); if (showQuickhelp) { SwingUtilities.invokeLater(new Runnable() { public void run() { JCheckBoxMenuItem checkBox = ((JCheckBoxMenuItem)showQuickHelpAction.getValue("checkbox")); if (checkBox == null) { return; } if (checkBox.isSelected()) { return; } checkBox.doClick(); } }); } /* Debugging - Break on repaints outside EDT */ /*RepaintManager.setCurrentManager(new RepaintManager() { public void addDirtyRegion(JComponent comp, int a, int b, int c, int d) { if(!java.awt.EventQueue.isDispatchThread()) { throw new RuntimeException("Repainting outside EDT"); } super.addDirtyRegion(comp, a, b, c, d); } });*/ // Register default extension directories String defaultProjectDirs = getExternalToolsSetting("DEFAULT_PROJECTDIRS", null); if (defaultProjectDirs != null && defaultProjectDirs.length() > 0) { String[] arr = defaultProjectDirs.split(";"); for (String p : arr) { File projectDir = restorePortablePath(new File(p)); currentProjects.add(new COOJAProject(projectDir)); } } //Scan for projects String searchProjectDirs = getExternalToolsSetting("PATH_APPSEARCH", null); if (searchProjectDirs != null && searchProjectDirs.length() > 0) { String[] arr = searchProjectDirs.split(";"); for (String d : arr) { File searchDir = restorePortablePath(new File(d)); File[] projects = COOJAProject.sarchProjects(searchDir, 3); if(projects == null) continue; for(File p : projects){ currentProjects.add(new COOJAProject(p)); } } } /* Parse current extension configuration */ try { reparseProjectConfig(); } catch (ParseProjectsException e) { logger.fatal("Error when loading extensions: " + e.getMessage(), e); if (isVisualized()) { JOptionPane.showMessageDialog(Cooja.getTopParentContainer(), "All Cooja extensions could not load.\n\n" + "To manage Cooja extensions:\n" + "Menu->Settings->Cooja extensions", "Reconfigure Cooja extensions", JOptionPane.INFORMATION_MESSAGE); showErrorDialog(getTopParentContainer(), "Cooja extensions load error", e, false); } } // Start all standard GUI plugins for (Class pluginClass : pluginClasses) { int pluginType = pluginClass.getAnnotation(PluginType.class).value(); if (pluginType == PluginType.COOJA_STANDARD_PLUGIN) { tryStartPlugin(pluginClass, this, null, null); } } } /** * Add mote highlight observer. * * @see #deleteMoteHighlightObserver(Observer) * @param newObserver * New observer */ public void addMoteHighlightObserver(Observer newObserver) { moteHighlightObservable.addObserver(newObserver); } /** * Delete mote highlight observer. * * @see #addMoteHighlightObserver(Observer) * @param observer * Observer to delete */ public void deleteMoteHighlightObserver(Observer observer) { moteHighlightObservable.deleteObserver(observer); } /** * @return True if simulator is visualized */ public static boolean isVisualized() { return isVisualizedInFrame() || isVisualizedInApplet(); } public static Container getTopParentContainer() { if (isVisualizedInFrame()) { return frame; } if (isVisualizedInApplet()) { /* Find parent frame for applet */ Container container = applet; while((container = container.getParent()) != null){ if (container instanceof Frame) { return container; } if (container instanceof Dialog) { return container; } if (container instanceof Window) { return container; } } logger.fatal("Returning null top owner container"); } return null; } public static boolean isVisualizedInFrame() { return frame != null; } public static URL getAppletCodeBase() { return applet.getCodeBase(); } public static boolean isVisualizedInApplet() { return applet != null; } /** * Tries to create/remove simulator visualizer. * * @param visualized Visualized */ public void setVisualizedInFrame(boolean visualized) { if (visualized) { if (!isVisualizedInFrame()) { configureFrame(cooja, false); } } else { if (frame != null) { frame.setVisible(false); frame.dispose(); frame = null; } } } public File getLastOpenedFile() { // Fetch current history String[] historyArray = getExternalToolsSetting("SIMCFG_HISTORY", "").split(";"); return historyArray.length > 0 ? new File(historyArray[0]) : null; } public File[] getFileHistory() { // Fetch current history String[] historyArray = getExternalToolsSetting("SIMCFG_HISTORY", "").split(";"); File[] history = new File[historyArray.length]; for (int i = 0; i < historyArray.length; i++) { history[i] = new File(historyArray[i]); } return history; } public void addToFileHistory(File file) { // Fetch current history String[] history = getExternalToolsSetting("SIMCFG_HISTORY", "").split(";"); String newFile = file.getAbsolutePath(); if (history.length > 0 && history[0].equals(newFile)) { // File already added return; } // Create new history StringBuilder newHistory = new StringBuilder(); newHistory.append(newFile); for (int i = 0, count = 1; i < history.length && count < 10; i++) { String historyFile = history[i]; if (newFile.equals(historyFile) || historyFile.length() == 0) { // File already added or empty file name } else { newHistory.append(';').append(historyFile); count++; } } setExternalToolsSetting("SIMCFG_HISTORY", newHistory.toString()); saveExternalToolsUserSettings(); hasFileHistoryChanged = true; } private void updateOpenHistoryMenuItems() { if (isVisualizedInApplet()) { return; } if (!hasFileHistoryChanged) { return; } hasFileHistoryChanged = false; File[] openFilesHistory = getFileHistory(); updateOpenHistoryMenuItems(openFilesHistory); } private void populateMenuWithHistory(JMenu menu, final boolean quick, File[] openFilesHistory) { JMenuItem lastItem; int index = 0; for (File file: openFilesHistory) { if (index < 10) { char mnemonic = (char) ('0' + (++index % 10)); lastItem = new JMenuItem(mnemonic + " " + file.getName()); lastItem.setMnemonic(mnemonic); } else { lastItem = new JMenuItem(file.getName()); } final File f = file; lastItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { doLoadConfigAsync(true, quick, f); } }); lastItem.putClientProperty("file", file); lastItem.setToolTipText(file.getAbsolutePath()); menu.add(lastItem); } } private void doLoadConfigAsync(final boolean ask, final boolean quick, final File file) { new Thread(new Runnable() { public void run() { cooja.doLoadConfig(ask, quick, file, null); } }).start(); } private void updateOpenHistoryMenuItems(File[] openFilesHistory) { menuOpenSimulation.removeAll(); /* Reconfigure submenu */ JMenu reconfigureMenu = new JMenu("Open and Reconfigure"); JMenuItem browseItem2 = new JMenuItem("Browse..."); browseItem2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { doLoadConfigAsync(true, false, null); } }); reconfigureMenu.add(browseItem2); reconfigureMenu.add(new JSeparator()); populateMenuWithHistory(reconfigureMenu, false, openFilesHistory); /* Open menu */ JMenuItem browseItem = new JMenuItem("Browse..."); browseItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { doLoadConfigAsync(true, true, null); } }); menuOpenSimulation.add(browseItem); menuOpenSimulation.add(new JSeparator()); menuOpenSimulation.add(reconfigureMenu); menuOpenSimulation.add(new JSeparator()); populateMenuWithHistory(menuOpenSimulation, true, openFilesHistory); } /** * Enables/disables menues and menu items depending on whether a simulation is loaded etc. */ void updateGUIComponentState() { if (!isVisualized()) { return; } /* Update action state */ for (GUIAction a: guiActions) { a.setEnabled(a.shouldBeEnabled()); } /* Mote and mote type menues */ if (menuMoteTypeClasses != null) { menuMoteTypeClasses.setEnabled(getSimulation() != null); } if (menuMoteTypes != null) { menuMoteTypes.setEnabled(getSimulation() != null); } } private JMenuBar createMenuBar() { JMenuItem menuItem; /* Prepare GUI actions */ guiActions.add(newSimulationAction); guiActions.add(closeSimulationAction); guiActions.add(reloadSimulationAction); guiActions.add(reloadRandomSimulationAction); guiActions.add(saveSimulationAction); /* guiActions.add(closePluginsAction);*/ guiActions.add(exportExecutableJARAction); guiActions.add(exitCoojaAction); guiActions.add(startStopSimulationAction); guiActions.add(removeAllMotesAction); guiActions.add(showBufferSettingsAction); /* Menus */ JMenuBar menuBar = new JMenuBar(); JMenu fileMenu = new JMenu("File"); JMenu simulationMenu = new JMenu("Simulation"); JMenu motesMenu = new JMenu("Motes"); final JMenu toolsMenu = new JMenu("Tools"); JMenu settingsMenu = new JMenu("Settings"); JMenu helpMenu = new JMenu("Help"); menuBar.add(fileMenu); menuBar.add(simulationMenu); menuBar.add(motesMenu); menuBar.add(toolsMenu); menuBar.add(settingsMenu); menuBar.add(helpMenu); fileMenu.setMnemonic(KeyEvent.VK_F); simulationMenu.setMnemonic(KeyEvent.VK_S); motesMenu.setMnemonic(KeyEvent.VK_M); toolsMenu.setMnemonic(KeyEvent.VK_T); helpMenu.setMnemonic(KeyEvent.VK_H); /* File menu */ fileMenu.addMenuListener(new MenuListener() { public void menuSelected(MenuEvent e) { updateGUIComponentState(); updateOpenHistoryMenuItems(); } public void menuDeselected(MenuEvent e) { } public void menuCanceled(MenuEvent e) { } }); fileMenu.add(new JMenuItem(newSimulationAction)); menuOpenSimulation = new JMenu("Open simulation"); menuOpenSimulation.setMnemonic(KeyEvent.VK_O); fileMenu.add(menuOpenSimulation); if (isVisualizedInApplet()) { menuOpenSimulation.setEnabled(false); menuOpenSimulation.setToolTipText("Not available in applet version"); } fileMenu.add(new JMenuItem(closeSimulationAction)); hasFileHistoryChanged = true; fileMenu.add(new JMenuItem(saveSimulationAction)); fileMenu.add(new JMenuItem(exportExecutableJARAction)); /* menu.addSeparator();*/ /* menu.add(new JMenuItem(closePluginsAction));*/ fileMenu.addSeparator(); fileMenu.add(new JMenuItem(exitCoojaAction)); /* Simulation menu */ simulationMenu.addMenuListener(new MenuListener() { public void menuSelected(MenuEvent e) { updateGUIComponentState(); } public void menuDeselected(MenuEvent e) { } public void menuCanceled(MenuEvent e) { } }); simulationMenu.add(new JMenuItem(startStopSimulationAction)); JMenuItem reloadSimulationMenuItem = new JMenu("Reload simulation"); reloadSimulationMenuItem.add(new JMenuItem(reloadSimulationAction)); reloadSimulationMenuItem.add(new JMenuItem(reloadRandomSimulationAction)); simulationMenu.add(reloadSimulationMenuItem); GUIAction guiAction = new StartPluginGUIAction("Control panel..."); menuItem = new JMenuItem(guiAction); guiActions.add(guiAction); menuItem.setMnemonic(KeyEvent.VK_C); menuItem.putClientProperty("class", SimControl.class); simulationMenu.add(menuItem); guiAction = new StartPluginGUIAction("Simulation..."); menuItem = new JMenuItem(guiAction); guiActions.add(guiAction); menuItem.setMnemonic(KeyEvent.VK_I); menuItem.putClientProperty("class", SimInformation.class); simulationMenu.add(menuItem); // Mote type menu motesMenu.addMenuListener(new MenuListener() { public void menuSelected(MenuEvent e) { updateGUIComponentState(); } public void menuDeselected(MenuEvent e) { } public void menuCanceled(MenuEvent e) { } }); // Mote type classes sub menu menuMoteTypeClasses = new JMenu("Create new mote type"); menuMoteTypeClasses.setMnemonic(KeyEvent.VK_C); menuMoteTypeClasses.addMenuListener(new MenuListener() { public void menuSelected(MenuEvent e) { // Clear menu menuMoteTypeClasses.removeAll(); // Recreate menu items JMenuItem menuItem; for (Class moteTypeClass : moteTypeClasses) { /* Sort mote types according to abstraction level */ String abstractionLevelDescription = Cooja.getAbstractionLevelDescriptionOf(moteTypeClass); if(abstractionLevelDescription == null) { abstractionLevelDescription = "[unknown cross-level]"; } /* Check if abstraction description already exists */ JSeparator abstractionLevelSeparator = null; for (Component component: menuMoteTypeClasses.getMenuComponents()) { if (component == null || !(component instanceof JSeparator)) { continue; } JSeparator existing = (JSeparator) component; if (abstractionLevelDescription.equals(existing.getToolTipText())) { abstractionLevelSeparator = existing; break; } } if (abstractionLevelSeparator == null) { abstractionLevelSeparator = new JSeparator(); abstractionLevelSeparator.setToolTipText(abstractionLevelDescription); menuMoteTypeClasses.add(abstractionLevelSeparator); } String description = Cooja.getDescriptionOf(moteTypeClass); menuItem = new JMenuItem(description + "..."); menuItem.setActionCommand("create mote type"); menuItem.putClientProperty("class", moteTypeClass); /* menuItem.setToolTipText(abstractionLevelDescription);*/ menuItem.addActionListener(guiEventHandler); if (isVisualizedInApplet() && moteTypeClass.equals(ContikiMoteType.class)) { menuItem.setEnabled(false); menuItem.setToolTipText("Not available in applet version"); } /* Add new item directly after cross level separator */ for (int i=0; i < menuMoteTypeClasses.getMenuComponentCount(); i++) { if (menuMoteTypeClasses.getMenuComponent(i) == abstractionLevelSeparator) { menuMoteTypeClasses.add(menuItem, i+1); break; } } } } public void menuDeselected(MenuEvent e) { } public void menuCanceled(MenuEvent e) { } }); // Mote menu motesMenu.addMenuListener(new MenuListener() { public void menuSelected(MenuEvent e) { updateGUIComponentState(); } public void menuDeselected(MenuEvent e) { } public void menuCanceled(MenuEvent e) { } }); // Mote types sub menu menuMoteTypes = new JMenu("Add motes"); menuMoteTypes.setMnemonic(KeyEvent.VK_A); menuMoteTypes.addMenuListener(new MenuListener() { public void menuSelected(MenuEvent e) { // Clear menu menuMoteTypes.removeAll(); if (mySimulation != null) { // Recreate menu items JMenuItem menuItem; for (MoteType moteType : mySimulation.getMoteTypes()) { menuItem = new JMenuItem(moteType.getDescription()); menuItem.setActionCommand("add motes"); menuItem.setToolTipText(getDescriptionOf(moteType.getClass())); menuItem.putClientProperty("motetype", moteType); menuItem.addActionListener(guiEventHandler); menuMoteTypes.add(menuItem); } if(mySimulation.getMoteTypes().length > 0) { menuMoteTypes.add(new JSeparator()); } } menuMoteTypes.add(menuMoteTypeClasses); } public void menuDeselected(MenuEvent e) { } public void menuCanceled(MenuEvent e) { } }); motesMenu.add(menuMoteTypes); guiAction = new StartPluginGUIAction("Mote types..."); menuItem = new JMenuItem(guiAction); guiActions.add(guiAction); menuItem.putClientProperty("class", MoteTypeInformation.class); motesMenu.add(menuItem); motesMenu.add(new JMenuItem(removeAllMotesAction)); /* Tools menu */ toolsMenu.addMenuListener(new MenuListener() { private ActionListener menuItemListener = new ActionListener() { public void actionPerformed(ActionEvent e) { Object pluginClass = ((JMenuItem)e.getSource()).getClientProperty("class"); Object mote = ((JMenuItem)e.getSource()).getClientProperty("mote"); tryStartPlugin((Class) pluginClass, cooja, getSimulation(), (Mote)mote); } }; private JMenuItem createMenuItem(Class newPluginClass, int pluginType) { String description = getDescriptionOf(newPluginClass); JMenuItem menuItem = new JMenuItem(description + "..."); menuItem.putClientProperty("class", newPluginClass); menuItem.addActionListener(menuItemListener); String tooltip = "
";
        if (pluginType == PluginType.COOJA_PLUGIN || pluginType == PluginType.COOJA_STANDARD_PLUGIN) {
          tooltip += "Cooja plugin: ";
        } else if (pluginType == PluginType.SIM_PLUGIN || pluginType == PluginType.SIM_STANDARD_PLUGIN
        		|| pluginType == PluginType.SIM_CONTROL_PLUGIN) {
          tooltip += "Simulation plugin: ";
          if (getSimulation() == null) {
            menuItem.setEnabled(false);
          }
        } else if (pluginType == PluginType.MOTE_PLUGIN) {
          tooltip += "Mote plugin: ";
        }
        tooltip += description + " (" + newPluginClass.getName() + ")";

        /* Check if simulation plugin depends on any particular radio medium */
        if ((pluginType == PluginType.SIM_PLUGIN || pluginType == PluginType.SIM_STANDARD_PLUGIN
        		|| pluginType == PluginType.SIM_CONTROL_PLUGIN) && (getSimulation() != null)) {
          if (newPluginClass.getAnnotation(SupportedArguments.class) != null) {
            boolean active = false;
            Class[] radioMediums = newPluginClass.getAnnotation(SupportedArguments.class).radioMediums();
            for (Class o: radioMediums) {
              if (o.isAssignableFrom(getSimulation().getRadioMedium().getClass())) {
                active = true;
                break;
              }
            }
            if (!active) {
              menuItem.setVisible(false);
            }
          }
        }

        /* Check if plugin was imported by a extension directory */
        File project =
          getProjectConfig().getUserProjectDefining(Cooja.class, "PLUGINS", newPluginClass.getName());
        if (project != null) {
          tooltip += "\nLoaded by extension: " + project.getPath();
        }

        tooltip += "";
        /*menuItem.setToolTipText(tooltip);*/
        return menuItem;
      }

      public void menuSelected(MenuEvent e) {
        /* Populate tools menu */
        toolsMenu.removeAll();

        /* Cooja plugins */
        boolean hasCoojaPlugins = false;
        for (Class pluginClass: pluginClasses) {
          int pluginType = pluginClass.getAnnotation(PluginType.class).value();
          if (pluginType != PluginType.COOJA_PLUGIN && pluginType != PluginType.COOJA_STANDARD_PLUGIN) {
            continue;
          }
          toolsMenu.add(createMenuItem(pluginClass, pluginType));
          hasCoojaPlugins = true;
        }

        /* Simulation plugins */
        boolean hasSimPlugins = false;
        for (Class pluginClass: pluginClasses) {
          if (pluginClass.equals(SimControl.class)) {
            continue; /* ignore */
          }
          if (pluginClass.equals(SimInformation.class)) {
            continue; /* ignore */
          }
          if (pluginClass.equals(MoteTypeInformation.class)) {
            continue; /* ignore */
          }

          int pluginType = pluginClass.getAnnotation(PluginType.class).value();
          if (pluginType != PluginType.SIM_PLUGIN && pluginType != PluginType.SIM_STANDARD_PLUGIN
        		  && pluginType != PluginType.SIM_CONTROL_PLUGIN) {
            continue;
          }

          if (hasCoojaPlugins) {
            hasCoojaPlugins = false;
            toolsMenu.addSeparator();
          }

          toolsMenu.add(createMenuItem(pluginClass, pluginType));
          hasSimPlugins = true;
        }

        for (Class pluginClass: pluginClasses) {
          int pluginType = pluginClass.getAnnotation(PluginType.class).value();
          if (pluginType != PluginType.MOTE_PLUGIN) {
            continue;
          }

          if (hasSimPlugins) {
            hasSimPlugins = false;
            toolsMenu.addSeparator();
          }

          toolsMenu.add(createMotePluginsSubmenu(pluginClass));
        }
      }
      public void menuDeselected(MenuEvent e) {
      }
      public void menuCanceled(MenuEvent e) {
      }
    });

    // Settings menu
    settingsMenu.addMenuListener(new MenuListener() {
      public void menuSelected(MenuEvent e) {
        updateGUIComponentState();
      }
      public void menuDeselected(MenuEvent e) {
      }
      public void menuCanceled(MenuEvent e) {
      }
    });

    menuItem = new JMenuItem("External tools paths...");
    menuItem.setActionCommand("edit paths");
    menuItem.addActionListener(guiEventHandler);
    settingsMenu.add(menuItem);
    if (isVisualizedInApplet()) {
      menuItem.setEnabled(false);
      menuItem.setToolTipText("Not available in applet version");
    }

    menuItem = new JMenuItem("Cooja extensions...");
    menuItem.setActionCommand("manage extensions");
    menuItem.addActionListener(guiEventHandler);
    settingsMenu.add(menuItem);
    if (isVisualizedInApplet()) {
      menuItem.setEnabled(false);
      menuItem.setToolTipText("Not available in applet version");
    }

    menuItem = new JMenuItem("Cooja mote configuration wizard...");
    menuItem.setActionCommand("configuration wizard");
    menuItem.addActionListener(guiEventHandler);
    settingsMenu.add(menuItem);
    if (isVisualizedInApplet()) {
      menuItem.setEnabled(false);
      menuItem.setToolTipText("Not available in applet version");
    }

    settingsMenu.add(new JMenuItem(showBufferSettingsAction));

    /* Help */
    helpMenu.add(new JMenuItem(showGettingStartedAction));
    helpMenu.add(new JMenuItem(showKeyboardShortcutsAction));
    JCheckBoxMenuItem checkBox = new JCheckBoxMenuItem(showQuickHelpAction);
    showQuickHelpAction.putValue("checkbox", checkBox);
    helpMenu.add(checkBox);

    helpMenu.addSeparator();

    menuItem = new JMenuItem("Java version: "
        + System.getProperty("java.version") + " ("
        + System.getProperty("java.vendor") + ")");
    menuItem.setEnabled(false);
    helpMenu.add(menuItem);
    menuItem = new JMenuItem("System \"os.arch\": "
        + System.getProperty("os.arch"));
    menuItem.setEnabled(false);
    helpMenu.add(menuItem);
    menuItem = new JMenuItem("System \"sun.arch.data.model\": "
        + System.getProperty("sun.arch.data.model"));
    menuItem.setEnabled(false);
    helpMenu.add(menuItem);

    return menuBar;
  }

  private static void configureFrame(final Cooja gui, boolean createSimDialog) {

    if (frame == null) {
      frame = new JFrame(WINDOW_TITLE);
    }
    frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);

    /* Menu bar */
    frame.setJMenuBar(gui.createMenuBar());

    /* Scrollable desktop */
    JComponent desktop = gui.getDesktopPane();
    desktop.setOpaque(true);

    JScrollPane scroll = new JScrollPane(desktop);
    scroll.setBorder(null);

    JPanel container = new JPanel(new BorderLayout());
    container.add(BorderLayout.CENTER, scroll);
    container.add(BorderLayout.EAST, gui.quickHelpScroll);
    frame.setContentPane(container);

    frame.setSize(700, 700);
    frame.setLocationRelativeTo(null);
    frame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        gui.doQuit(true);
      }
    });
    frame.addComponentListener(new ComponentAdapter() {
      public void componentResized(ComponentEvent e) {
        updateDesktopSize(gui.getDesktopPane());
      }
    });

    /* Restore frame size and position */
    int framePosX = Integer.parseInt(getExternalToolsSetting("FRAME_POS_X", "0"));
    int framePosY = Integer.parseInt(getExternalToolsSetting("FRAME_POS_Y", "0"));
    int frameWidth = Integer.parseInt(getExternalToolsSetting("FRAME_WIDTH", "0"));
    int frameHeight = Integer.parseInt(getExternalToolsSetting("FRAME_HEIGHT", "0"));
    String frameScreen = getExternalToolsSetting("FRAME_SCREEN", "");

    /* Restore position to the same graphics device */
    GraphicsDevice device = null;
    GraphicsDevice all[] = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
    for (GraphicsDevice gd : all) {
      if (gd.getIDstring().equals(frameScreen)) {
        device = gd;
      }
    }

    /* Check if frame should be maximized */
    if (device != null) {
      if (frameWidth == Integer.MAX_VALUE && frameHeight == Integer.MAX_VALUE) {
        frame.setLocation(device.getDefaultConfiguration().getBounds().getLocation());
        frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
      } else if (frameWidth > 0 && frameHeight > 0) {

        /* Sanity-check: will Cooja be visible on screen? */
        boolean intersects =
          device.getDefaultConfiguration().getBounds().intersects(
              new Rectangle(framePosX, framePosY, frameWidth, frameHeight));

        if (intersects) {
          frame.setLocation(framePosX, framePosY);
          frame.setSize(frameWidth, frameHeight);
        }

      }
    }

    frame.setVisible(true);

    if (createSimDialog) {
      SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          gui.doCreateSimulation(true);
        }
      });
    }
  }

  private static void configureApplet(final Cooja gui, boolean createSimDialog) {
    applet = CoojaApplet.applet;

    // Add menu bar
    JMenuBar menuBar = gui.createMenuBar();
    applet.setJMenuBar(menuBar);

    JComponent newContentPane = gui.getDesktopPane();
    newContentPane.setOpaque(true);
    applet.setContentPane(newContentPane);
    applet.setSize(700, 700);

    if (createSimDialog) {
      SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          gui.doCreateSimulation(true);
        }
      });
    }
  }

  /**
   * @return Current desktop pane (simulator visualizer)
   */
  public JDesktopPane getDesktopPane() {
    return myDesktopPane;
  }

  public static void setLookAndFeel() {

    JFrame.setDefaultLookAndFeelDecorated(true);
    JDialog.setDefaultLookAndFeelDecorated(true);

    ToolTipManager.sharedInstance().setDismissDelay(60000);

    /* Nimbus */
    try {
      String osName = System.getProperty("os.name").toLowerCase();
      if (osName.startsWith("linux")) {
        try {
          for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
            if ("Nimbus".equals(info.getName())) {
                UIManager.setLookAndFeel(info.getClassName());
                break;
            }
          }

        } catch (UnsupportedLookAndFeelException e) {
          UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
        }
      } else {
        UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
      }
      return;
    } catch (Exception e) {
    }

    /* System */
    try {
      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
      return;
    } catch (Exception e) {
    }
  }

  private static void updateDesktopSize(final JDesktopPane desktop) {
    if (desktop == null || !desktop.isVisible() || desktop.getParent() == null) {
      return;
    }

    Rectangle rect = desktop.getVisibleRect();
    Dimension pref = new Dimension(rect.width - 1, rect.height - 1);
    for (JInternalFrame frame : desktop.getAllFrames()) {
      if (pref.width < frame.getX() + frame.getWidth() - 20) {
        pref.width = frame.getX() + frame.getWidth();
      }
      if (pref.height < frame.getY() + frame.getHeight() - 20) {
        pref.height = frame.getY() + frame.getHeight();
      }
    }
    desktop.setPreferredSize(pref);
    desktop.revalidate();
  }

  private static JDesktopPane createDesktopPane() {
    final JDesktopPane desktop = new JDesktopPane() {
			private static final long serialVersionUID = -8272040875621119329L;
			public void setBounds(int x, int y, int w, int h) {
        super.setBounds(x, y, w, h);
        updateDesktopSize(this);
      }
      public void remove(Component c) {
        super.remove(c);
        updateDesktopSize(this);
      }
      public Component add(Component comp) {
        Component c = super.add(comp);
        updateDesktopSize(this);
        return c;
      }
    };
    desktop.setDesktopManager(new DefaultDesktopManager() {
			private static final long serialVersionUID = -5987404936292377152L;
			public void endResizingFrame(JComponent f) {
        super.endResizingFrame(f);
        updateDesktopSize(desktop);
      }
      public void endDraggingFrame(JComponent f) {
        super.endDraggingFrame(f);
        updateDesktopSize(desktop);
      }
    });
    desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
    return desktop;
  }

  public static Simulation quickStartSimulationConfig(File config, boolean vis, Long manualRandomSeed) {
    logger.info("> Starting Cooja");
    JDesktopPane desktop = createDesktopPane();
    if (vis) {
      frame = new JFrame(WINDOW_TITLE);
    }
    Cooja gui = new Cooja(desktop);
    if (vis) {
      configureFrame(gui, false);
    }

    if (vis) {
      gui.doLoadConfig(false, true, config, manualRandomSeed);
      return gui.getSimulation();
    } else {
      try {
        Simulation newSim = gui.loadSimulationConfig(config, true, manualRandomSeed);
        if (newSim == null) {
          return null;
        }
        gui.setSimulation(newSim, false);
        return newSim;
      } catch (Exception e) {
        logger.fatal("Exception when loading simulation: ", e);
        return null;
      }
    }
  }

  /**
   * Allows user to create a simulation with a single mote type.
   *
   * @param source Contiki application file name
   * @return True if simulation was created
   */
  private static Simulation quickStartSimulation(String source) {
    logger.info("> Starting Cooja");
    JDesktopPane desktop = createDesktopPane();
    frame = new JFrame(WINDOW_TITLE);
    Cooja gui = new Cooja(desktop);
    configureFrame(gui, false);

    logger.info("> Creating simulation");
    Simulation sim = new Simulation(gui);
    sim.setTitle("Quickstarted simulation: " + source);
    boolean simOK = CreateSimDialog.showDialog(Cooja.getTopParentContainer(), sim);
    if (!simOK) {
      logger.fatal("No simulation, aborting quickstart");
      System.exit(1);
    }
    gui.setSimulation(sim, true);

    logger.info("> Creating mote type");
    ContikiMoteType moteType = new ContikiMoteType();
    moteType.setContikiSourceFile(new File(source));
    moteType.setDescription("Cooja mote type (" + source + ")");

    try {
      boolean compileOK = moteType.configureAndInit(Cooja.getTopParentContainer(), sim, true);
      if (!compileOK) {
        logger.fatal("Mote type initialization failed, aborting quickstart");
        return null;
      }
    } catch (MoteTypeCreationException e1) {
      logger.fatal("Mote type initialization failed, aborting quickstart");
      return null;
    }
    sim.addMoteType(moteType);

    logger.info("> Adding motes");
    gui.doAddMotes(moteType);
    return sim;
  }

  //// PROJECT CONFIG AND EXTENDABLE PARTS METHODS ////

  /**
   * Register new mote type class.
   *
   * @param moteTypeClass
   *          Class to register
   */
  public void registerMoteType(Class moteTypeClass) {
    moteTypeClasses.add(moteTypeClass);
  }

  /**
   * Unregister all mote type classes.
   */
  public void unregisterMoteTypes() {
    moteTypeClasses.clear();
  }

  /**
   * @return All registered mote type classes
   */
  public Vector> getRegisteredMoteTypes() {
    return moteTypeClasses;
  }

  /**
   * Register new positioner class.
   *
   * @param positionerClass
   *          Class to register
   * @return True if class was registered
   */
  public boolean registerPositioner(Class positionerClass) {
    // Check that interval constructor exists
    try {
      positionerClass
      .getConstructor(new Class[] { int.class, double.class, double.class,
          double.class, double.class, double.class, double.class });
    } catch (Exception e) {
      logger.fatal("No interval constructor found of positioner: "
          + positionerClass);
      return false;
    }

    positionerClasses.add(positionerClass);
    return true;
  }

  /**
   * Unregister all positioner classes.
   */
  public void unregisterPositioners() {
    positionerClasses.clear();
  }

  /**
   * @return All registered positioner classes
   */
  public Vector> getRegisteredPositioners() {
    return positionerClasses;
  }

  /**
   * Register new radio medium class.
   *
   * @param radioMediumClass
   *          Class to register
   * @return True if class was registered
   */
  public boolean registerRadioMedium(
      Class radioMediumClass) {
    // Check that simulation constructor exists
    try {
      radioMediumClass.getConstructor(new Class[] { Simulation.class });
    } catch (Exception e) {
      logger.fatal("No simulation constructor found of radio medium: "
          + radioMediumClass);
      return false;
    }

    radioMediumClasses.add(radioMediumClass);
    return true;
  }

  /**
   * Unregister all radio medium classes.
   */
  public void unregisterRadioMediums() {
    radioMediumClasses.clear();
  }

  /**
   * @return All registered radio medium classes
   */
  public Vector> getRegisteredRadioMediums() {
    return radioMediumClasses;
  }

  /**
   * Builds new extension configuration using current extension directories settings.
   * Reregisters mote types, plugins, positioners and radio
   * mediums. This method may still return true even if all classes could not be
   * registered, but always returns false if all extension directory configuration
   * files were not parsed correctly.
   */
  public void reparseProjectConfig() throws ParseProjectsException {
    if (PROJECT_DEFAULT_CONFIG_FILENAME == null) {
      if (isVisualizedInApplet()) {
        PROJECT_DEFAULT_CONFIG_FILENAME = "/cooja_applet.config";
      } else {
        PROJECT_DEFAULT_CONFIG_FILENAME = "/cooja_default.config";
      }
    }

    /* Remove current dependencies */
    unregisterMoteTypes();
    unregisterPlugins();
    unregisterPositioners();
    unregisterRadioMediums();
    projectDirClassLoader = null;

    /* Build cooja configuration */
    try {
      projectConfig = new ProjectConfig(true);
    } catch (FileNotFoundException e) {
      logger.fatal("Could not find default extension config file: " + PROJECT_DEFAULT_CONFIG_FILENAME);
      throw (ParseProjectsException) new ParseProjectsException(
          "Could not find default extension config file: " + PROJECT_DEFAULT_CONFIG_FILENAME).initCause(e);
    } catch (IOException e) {
      logger.fatal("Error when reading default extension config file: " + PROJECT_DEFAULT_CONFIG_FILENAME);
      throw (ParseProjectsException) new ParseProjectsException(
          "Error when reading default extension config file: " + PROJECT_DEFAULT_CONFIG_FILENAME).initCause(e);
    }
    if (!isVisualizedInApplet()) {
      for (COOJAProject project: currentProjects) {
        try {
          projectConfig.appendProjectDir(project.dir);
        } catch (FileNotFoundException e) {
          throw (ParseProjectsException) new ParseProjectsException(
              "Error when loading extension: " + e.getMessage()).initCause(e);
        } catch (IOException e) {
          throw (ParseProjectsException) new ParseProjectsException(
              "Error when reading extension config: " + e.getMessage()).initCause(e);
        }
      }

      /* Create extension class loader */
      try {
        projectDirClassLoader = createClassLoader(currentProjects);
      } catch (ClassLoaderCreationException e) {
        throw (ParseProjectsException) new ParseProjectsException(
        "Error when creating class loader").initCause(e);
      }
    }

    // Register mote types
    String[] moteTypeClassNames = projectConfig.getStringArrayValue(Cooja.class,
    "MOTETYPES");
    if (moteTypeClassNames != null) {
      for (String moteTypeClassName : moteTypeClassNames) {
        if (moteTypeClassName.trim().isEmpty()) {
          continue;
        }
        Class moteTypeClass = tryLoadClass(this,
            MoteType.class, moteTypeClassName);

        if (moteTypeClass != null) {
          registerMoteType(moteTypeClass);
          // logger.info("Loaded mote type class: " + moteTypeClassName);
        } else {
          logger.warn("Could not load mote type class: " + moteTypeClassName);
        }
      }
    }

    // Register plugins
    registerPlugin(SimControl.class);
    registerPlugin(SimInformation.class);
    registerPlugin(MoteTypeInformation.class);
    String[] pluginClassNames = projectConfig.getStringArrayValue(Cooja.class,
    "PLUGINS");
    if (pluginClassNames != null) {
      for (String pluginClassName : pluginClassNames) {
        Class pluginClass = tryLoadClass(this, Plugin.class,
            pluginClassName);

        if (pluginClass != null) {
          registerPlugin(pluginClass);
          // logger.info("Loaded plugin class: " + pluginClassName);
        } else {
          logger.warn("Could not load plugin class: " + pluginClassName);
        }
      }
    }

    // Register positioners
    String[] positionerClassNames = projectConfig.getStringArrayValue(
        Cooja.class, "POSITIONERS");
    if (positionerClassNames != null) {
      for (String positionerClassName : positionerClassNames) {
        Class positionerClass = tryLoadClass(this,
            Positioner.class, positionerClassName);

        if (positionerClass != null) {
          registerPositioner(positionerClass);
          // logger.info("Loaded positioner class: " + positionerClassName);
        } else {
          logger
          .warn("Could not load positioner class: " + positionerClassName);
        }
      }
    }

    // Register radio mediums
    String[] radioMediumsClassNames = projectConfig.getStringArrayValue(
        Cooja.class, "RADIOMEDIUMS");
    if (radioMediumsClassNames != null) {
      for (String radioMediumClassName : radioMediumsClassNames) {
        Class radioMediumClass = tryLoadClass(this,
            RadioMedium.class, radioMediumClassName);

        if (radioMediumClass != null) {
          registerRadioMedium(radioMediumClass);
          // logger.info("Loaded radio medium class: " + radioMediumClassName);
        } else {
          logger.warn("Could not load radio medium class: "
              + radioMediumClassName);
        }
      }
    }

  }

  /**
   * Returns the current extension configuration common to the entire simulator.
   *
   * @return Current extension configuration
   */
  public ProjectConfig getProjectConfig() {
    return projectConfig;
  }

  /**
   * Returns the current extension directories common to the entire simulator.
   *
   * @return Current extension directories.
   */
  public COOJAProject[] getProjects() {
    return currentProjects.toArray(new COOJAProject[0]);
  }

  // // PLUGIN METHODS ////

  /**
   * Show a started plugin in working area.
   *
   * @param plugin Plugin
   */
  public void showPlugin(final Plugin plugin) {
    new RunnableInEDT() {
      public Boolean work() {
        JInternalFrame pluginFrame = plugin.getCooja();
        if (pluginFrame == null) {
          logger.fatal("Failed trying to show plugin without visualizer.");
          return false;
        }

        myDesktopPane.add(pluginFrame);

        /* Set size if not already specified by plugin */
        if (pluginFrame.getWidth() <= 0 || pluginFrame.getHeight() <= 0) {
          pluginFrame.setSize(FRAME_STANDARD_WIDTH, FRAME_STANDARD_HEIGHT);
        }

        /* Set location if not already set */
        if (pluginFrame.getLocation().x <= 0 && pluginFrame.getLocation().y <= 0) {
          pluginFrame.setLocation(determineNewPluginLocation());
        }

        pluginFrame.setVisible(true);

        /* Select plugin */
        try {
          for (JInternalFrame existingPlugin : myDesktopPane.getAllFrames()) {
            existingPlugin.setSelected(false);
          }
          pluginFrame.setSelected(true);
        } catch (Exception e) { }
        myDesktopPane.moveToFront(pluginFrame);

        return true;
      }
    }.invokeAndWait();
  }

  /**
   * Determines suitable location for placing new plugin.
   * 

* If possible, this is below right of the second last activated * internfal frame (offset is determined by FRAME_NEW_OFFSET). * * @return Resulting placement position */ private Point determineNewPluginLocation() { Point topFrameLoc; JInternalFrame[] iframes = myDesktopPane.getAllFrames(); if (iframes.length > 1) { topFrameLoc = iframes[1].getLocation(); } else { topFrameLoc = new Point( myDesktopPane.getSize().width / 2, myDesktopPane.getSize().height / 2); } return new Point( topFrameLoc.x + FRAME_NEW_OFFSET, topFrameLoc.y + FRAME_NEW_OFFSET); } /** * Close all mote plugins for given mote. * * @param mote Mote */ public void closeMotePlugins(Mote mote) { for (Plugin p: startedPlugins.toArray(new Plugin[0])) { if (!(p instanceof MotePlugin)) { continue; } Mote pluginMote = ((MotePlugin)p).getMote(); if (pluginMote == mote) { removePlugin(p, false); } } } /** * Remove a plugin from working area. * * @param plugin * Plugin to remove * @param askUser * If plugin is the last one, ask user if we should remove current * simulation also? */ public void removePlugin(final Plugin plugin, final boolean askUser) { new RunnableInEDT() { public Boolean work() { /* Free resources */ plugin.closePlugin(); startedPlugins.remove(plugin); updateGUIComponentState(); /* Dispose visualized components */ if (plugin.getCooja() != null) { plugin.getCooja().dispose(); } /* (OPTIONAL) Remove simulation if all plugins are closed */ if (getSimulation() != null && askUser && startedPlugins.isEmpty()) { doRemoveSimulation(true); } return true; } }.invokeAndWait(); } /** * Same as the {@link #startPlugin(Class, Cooja, Simulation, Mote)} method, * but does not throw exceptions. If COOJA is visualised, an error dialog * is shown if plugin could not be started. * * @see #startPlugin(Class, Cooja, Simulation, Mote) * @param pluginClass Plugin class * @param argGUI Plugin GUI argument * @param argSimulation Plugin simulation argument * @param argMote Plugin mote argument * @return Started plugin */ private Plugin tryStartPlugin(final Class pluginClass, final Cooja argGUI, final Simulation argSimulation, final Mote argMote, boolean activate) { try { return startPlugin(pluginClass, argGUI, argSimulation, argMote, activate); } catch (PluginConstructionException ex) { if (Cooja.isVisualized()) { Cooja.showErrorDialog(Cooja.getTopParentContainer(), "Error when starting plugin", ex, false); } else { /* If the plugin requires visualization, inform user */ Throwable cause = ex; do { if (cause instanceof PluginRequiresVisualizationException) { logger.info("Visualized plugin was not started: " + pluginClass); return null; } } while (cause != null && (cause=cause.getCause()) != null); logger.fatal("Error when starting plugin", ex); } } return null; } public Plugin tryStartPlugin(final Class pluginClass, final Cooja argGUI, final Simulation argSimulation, final Mote argMote) { return tryStartPlugin(pluginClass, argGUI, argSimulation, argMote, true); } public Plugin startPlugin(final Class pluginClass, final Cooja argGUI, final Simulation argSimulation, final Mote argMote) throws PluginConstructionException { return startPlugin(pluginClass, argGUI, argSimulation, argMote, true); } /** * Starts given plugin. If visualized, the plugin is also shown. * * @see PluginType * @param pluginClass Plugin class * @param argGUI Plugin GUI argument * @param argSimulation Plugin simulation argument * @param argMote Plugin mote argument * @return Started plugin * @throws PluginConstructionException At errors */ private Plugin startPlugin(final Class pluginClass, final Cooja argGUI, final Simulation argSimulation, final Mote argMote, boolean activate) throws PluginConstructionException { // Check that plugin class is registered if (!pluginClasses.contains(pluginClass)) { throw new PluginConstructionException("Tool class not registered: " + pluginClass); } // Construct plugin depending on plugin type int pluginType = pluginClass.getAnnotation(PluginType.class).value(); Plugin plugin; try { if (pluginType == PluginType.MOTE_PLUGIN) { if (argGUI == null) { throw new PluginConstructionException("No GUI argument for mote plugin"); } if (argSimulation == null) { throw new PluginConstructionException("No simulation argument for mote plugin"); } if (argMote == null) { throw new PluginConstructionException("No mote argument for mote plugin"); } plugin = pluginClass.getConstructor(new Class[] { Mote.class, Simulation.class, Cooja.class }) .newInstance(argMote, argSimulation, argGUI); } else if (pluginType == PluginType.SIM_PLUGIN || pluginType == PluginType.SIM_STANDARD_PLUGIN || pluginType == PluginType.SIM_CONTROL_PLUGIN) { if (argGUI == null) { throw new PluginConstructionException("No GUI argument for simulation plugin"); } if (argSimulation == null) { throw new PluginConstructionException("No simulation argument for simulation plugin"); } plugin = pluginClass.getConstructor(new Class[] { Simulation.class, Cooja.class }) .newInstance(argSimulation, argGUI); } else if (pluginType == PluginType.COOJA_PLUGIN || pluginType == PluginType.COOJA_STANDARD_PLUGIN) { if (argGUI == null) { throw new PluginConstructionException("No GUI argument for GUI plugin"); } plugin = pluginClass.getConstructor(new Class[] { Cooja.class }) .newInstance(argGUI); } else { throw new PluginConstructionException("Bad plugin type: " + pluginType); } } catch (PluginRequiresVisualizationException e) { PluginConstructionException ex = new PluginConstructionException("Tool class requires visualization: " + pluginClass.getName()); ex.initCause(e); throw ex; } catch (Exception e) { PluginConstructionException ex = new PluginConstructionException("Construction error for tool of class: " + pluginClass.getName()); ex.initCause(e); throw ex; } if (activate) { plugin.startPlugin(); } // Add to active plugins list startedPlugins.add(plugin); updateGUIComponentState(); // Show plugin if visualizer type if (activate && plugin.getCooja() != null) { cooja.showPlugin(plugin); } return plugin; } /** * Unregister a plugin class. Removes any plugin menu items links as well. * * @param pluginClass Plugin class */ public void unregisterPlugin(Class pluginClass) { pluginClasses.remove(pluginClass); menuMotePluginClasses.remove(pluginClass); } /** * Register a plugin to be included in the GUI. * * @param pluginClass New plugin to register * @return True if this plugin was registered ok, false otherwise */ public boolean registerPlugin(final Class pluginClass) { /* Check plugin type */ final int pluginType; if (pluginClass.isAnnotationPresent(PluginType.class)) { pluginType = pluginClass.getAnnotation(PluginType.class).value(); } else { logger.fatal("Could not register plugin, no plugin type found: " + pluginClass); return false; } /* Check plugin constructor */ try { if (pluginType == PluginType.COOJA_PLUGIN || pluginType == PluginType.COOJA_STANDARD_PLUGIN) { pluginClass.getConstructor(new Class[] { Cooja.class }); } else if (pluginType == PluginType.SIM_PLUGIN || pluginType == PluginType.SIM_STANDARD_PLUGIN || pluginType == PluginType.SIM_CONTROL_PLUGIN) { pluginClass.getConstructor(new Class[] { Simulation.class, Cooja.class }); } else if (pluginType == PluginType.MOTE_PLUGIN) { pluginClass.getConstructor(new Class[] { Mote.class, Simulation.class, Cooja.class }); menuMotePluginClasses.add(pluginClass); } else { logger.fatal("Could not register plugin, bad plugin type: " + pluginType); return false; } pluginClasses.add(pluginClass); } catch (NoClassDefFoundError e) { logger.fatal("No plugin class: " + pluginClass + ": " + e.getMessage()); return false; } catch (NoSuchMethodException e) { logger.fatal("No plugin class constructor: " + pluginClass + ": " + e.getMessage()); return false; } return true; } /** * Unregister all plugin classes */ public void unregisterPlugins() { menuMotePluginClasses.clear(); pluginClasses.clear(); } /** * Returns started plugin that ends with given class name, if any. * * @param classname Class name * @return Plugin instance */ public Plugin getPlugin(String classname) { for (Plugin p: startedPlugins) { if (p.getClass().getName().endsWith(classname)) { return p; } } return null; } /** * Returns started plugin with given class name, if any. * * @param classname Class name * @return Plugin instance * @deprecated */ @Deprecated public Plugin getStartedPlugin(String classname) { return getPlugin(classname); } public Plugin[] getStartedPlugins() { return startedPlugins.toArray(new Plugin[0]); } private boolean isMotePluginCompatible(Class motePluginClass, Mote mote) { if (motePluginClass.getAnnotation(SupportedArguments.class) == null) { return true; } /* Check mote interfaces */ boolean moteInterfacesOK = true; Class[] moteInterfaces = motePluginClass.getAnnotation(SupportedArguments.class).moteInterfaces(); StringBuilder moteTypeInterfacesError = new StringBuilder(); moteTypeInterfacesError.append( "The plugin:\n" + getDescriptionOf(motePluginClass) + "\nrequires the following mote interfaces:\n" ); for (Class requiredMoteInterface: moteInterfaces) { moteTypeInterfacesError.append(getDescriptionOf(requiredMoteInterface) + "\n"); if (mote.getInterfaces().getInterfaceOfType(requiredMoteInterface) == null) { moteInterfacesOK = false; } } /* Check mote type */ boolean moteTypeOK = false; Class[] motes = motePluginClass.getAnnotation(SupportedArguments.class).motes(); StringBuilder moteTypeError = new StringBuilder(); moteTypeError.append( "The plugin:\n" + getDescriptionOf(motePluginClass) + "\ndoes not support motes of type:\n" + getDescriptionOf(mote) + "\n\nIt only supports motes of types:\n" ); for (Class supportedMote: motes) { moteTypeError.append(getDescriptionOf(supportedMote) + "\n"); if (supportedMote.isAssignableFrom(mote.getClass())) { moteTypeOK = true; } } /*if (!moteInterfacesOK) { menuItem.setToolTipText( "

" + moteTypeInterfacesError + ""
      );
    }
    if (!moteTypeOK) {
      menuItem.setToolTipText(
          "
" + moteTypeError + ""
      );
    }*/

    return moteInterfacesOK && moteTypeOK;
  }

  public JMenu createMotePluginsSubmenu(Class pluginClass) {
    JMenu menu = new JMenu(getDescriptionOf(pluginClass));
    if (getSimulation() == null || getSimulation().getMotesCount() == 0) {
      menu.setEnabled(false);
      return menu;
    }

    ActionListener menuItemListener = new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        Object pluginClass = ((JMenuItem)e.getSource()).getClientProperty("class");
        Object mote = ((JMenuItem)e.getSource()).getClientProperty("mote");
        tryStartPlugin((Class) pluginClass, cooja, getSimulation(), (Mote)mote);
      }
    };

    final int MAX_PER_ROW = 30;
    final int MAX_COLUMNS = 5;

    int added = 0;
    for (Mote mote: getSimulation().getMotes()) {
      if (!isMotePluginCompatible(pluginClass, mote)) {
        continue;
      }

      JMenuItem menuItem = new JMenuItem(mote.toString() + "...");
      menuItem.putClientProperty("class", pluginClass);
      menuItem.putClientProperty("mote", mote);
      menuItem.addActionListener(menuItemListener);

      menu.add(menuItem);
      added++;

      if (added == MAX_PER_ROW) {
        menu.getPopupMenu().setLayout(new GridLayout(MAX_PER_ROW, MAX_COLUMNS));
      }
      if (added >= MAX_PER_ROW*MAX_COLUMNS) {
        break;
      }
    }
    if (added == 0) {
      menu.setEnabled(false);
    }

    return menu;
  }

  /**
   * Return a mote plugins submenu for given mote.
   *
   * @param mote Mote
   * @return Mote plugins menu
   */
  public JMenu createMotePluginsSubmenu(Mote mote) {
    JMenu menuMotePlugins = new JMenu("Mote tools for " + mote);

    for (Class motePluginClass: menuMotePluginClasses) {
      if (!isMotePluginCompatible(motePluginClass, mote)) {
        continue;
      }

      GUIAction guiAction = new StartPluginGUIAction(getDescriptionOf(motePluginClass) + "...");
      JMenuItem menuItem = new JMenuItem(guiAction);
      menuItem.putClientProperty("class", motePluginClass);
      menuItem.putClientProperty("mote", mote);

      menuMotePlugins.add(menuItem);
    }
    return menuMotePlugins;
  }

  // // GUI CONTROL METHODS ////

  /**
   * @return Current simulation
   */
  public Simulation getSimulation() {
    return mySimulation;
  }

  public void setSimulation(Simulation sim, boolean startPlugins) {
    if (sim != null) {
      doRemoveSimulation(false);
    }
    mySimulation = sim;
    updateGUIComponentState();

    // Set frame title
    if (frame != null) {
      frame.setTitle(sim.getTitle() + " - " + WINDOW_TITLE);
    }

    // Open standard plugins (if none opened already)
    if (startPlugins) {
      for (Class pluginClass : pluginClasses) {
        int pluginType = pluginClass.getAnnotation(PluginType.class).value();
        if (pluginType == PluginType.SIM_STANDARD_PLUGIN) {
          tryStartPlugin(pluginClass, this, sim, null);
        }
      }
    }

    setChanged();
    notifyObservers();
  }

  /**
   * Creates a new mote type of the given mote type class.
   * This may include displaying a dialog for user configurations.
   *
   * If mote type is created successfully, the add motes dialog will appear.
   *
   * @param moteTypeClass Mote type class
   */
  public void doCreateMoteType(Class moteTypeClass) {
    doCreateMoteType(moteTypeClass, true);
  }

  /**
   * Creates a new mote type of the given mote type class.
   * This may include displaying a dialog for user configurations.
   *
   * @param moteTypeClass Mote type class
   * @param addMotes Show add motes dialog after successfully adding mote type
   */
  public void doCreateMoteType(Class moteTypeClass, boolean addMotes) {
    if (mySimulation == null) {
      logger.fatal("Can't create mote type (no simulation)");
      return;
    }
    mySimulation.stopSimulation();

    // Create mote type
    MoteType newMoteType = null;
    try {
      newMoteType = moteTypeClass.newInstance();
      if (!newMoteType.configureAndInit(Cooja.getTopParentContainer(), mySimulation, isVisualized())) {
        return;
      }
      mySimulation.addMoteType(newMoteType);
    } catch (Exception e) {
      logger.fatal("Exception when creating mote type", e);
      if (isVisualized()) {
        showErrorDialog(getTopParentContainer(), "Mote type creation error", e, false);
      }
      return;
    }

    /* Allow user to immediately add motes */
    if (addMotes) {
      doAddMotes(newMoteType);
    }
  }

  /**
   * Remove current simulation
   *
   * @param askForConfirmation
   *          Should we ask for confirmation if a simulation is already active?
   * @return True if no simulation exists when method returns
   */
  public boolean doRemoveSimulation(boolean askForConfirmation) {

    if (mySimulation == null) {
      return true;
    }

    if (askForConfirmation) {
      boolean ok = new RunnableInEDT() {
        public Boolean work() {
          String s1 = "Remove";
          String s2 = "Cancel";
          Object[] options = { s1, s2 };
          int n = JOptionPane.showOptionDialog(Cooja.getTopParentContainer(),
              "You have an active simulation.\nDo you want to remove it?",
              "Remove current simulation?", JOptionPane.YES_NO_OPTION,
              JOptionPane.QUESTION_MESSAGE, null, options, s2);
          if (n != JOptionPane.YES_OPTION) {
            return false;
          }
          return true;
        }
      }.invokeAndWait();

      if (!ok) {
        return false;
      }
    }

    // Close all started non-GUI plugins
    for (Object startedPlugin : startedPlugins.toArray()) {
      int pluginType = startedPlugin.getClass().getAnnotation(PluginType.class).value();
      if (pluginType != PluginType.COOJA_PLUGIN
          && pluginType != PluginType.COOJA_STANDARD_PLUGIN) {
        removePlugin((Plugin) startedPlugin, false);
      }
    }

    // Delete simulation
    mySimulation.deleteObservers();
    mySimulation.stopSimulation();
    mySimulation.removed();

    /* Clear current mote relations */
    MoteRelation relations[] = getMoteRelations();
    for (MoteRelation r: relations) {
      removeMoteRelation(r.source, r.dest);
    }

    mySimulation = null;
    updateGUIComponentState();

    // Reset frame title
    if (isVisualizedInFrame()) {
      frame.setTitle(WINDOW_TITLE);
    }

    setChanged();
    notifyObservers();

    return true;
  }

  /**
   * Load a simulation configuration file from disk
   *
   * @param askForConfirmation Ask for confirmation before removing any current simulation
   * @param quick Quick-load simulation
   * @param configFile Configuration file to load, if null a dialog will appear
   */
  public void doLoadConfig(boolean askForConfirmation, final boolean quick, File configFile, Long manualRandomSeed) {
    if (isVisualizedInApplet()) {
      return;
    }

    /* Warn about memory usage */
    if (warnMemory()) {
      return;
    }

    /* Remove current simulation */
    if (!doRemoveSimulation(true)) {
      return;
    }

    /* Use provided configuration, or open File Chooser */
    if (configFile != null && !configFile.isDirectory()) {
      if (!configFile.exists() || !configFile.canRead()) {
        logger.fatal("No read access to file: " + configFile.getAbsolutePath());
        /* File does not exist, open dialog */
        doLoadConfig(askForConfirmation, quick, null, manualRandomSeed);
        return;
      }
    } else {
      final File suggestedFile = configFile;
      configFile = new RunnableInEDT() {
        public File work() {
          JFileChooser fc = new JFileChooser();

          fc.setFileFilter(Cooja.SAVED_SIMULATIONS_FILES);

          if (suggestedFile != null && suggestedFile.isDirectory()) {
            fc.setCurrentDirectory(suggestedFile);
          } else {
            /* Suggest file using file history */
            File suggestedFile = getLastOpenedFile();
            if (suggestedFile != null) {
              fc.setSelectedFile(suggestedFile);
            }
          }

          int returnVal = fc.showOpenDialog(Cooja.getTopParentContainer());
          if (returnVal != JFileChooser.APPROVE_OPTION) {
            logger.info("Load command cancelled by user...");
            return null;
          }

          File file = fc.getSelectedFile();

          if (!file.exists()) {
            /* Try default file extension */
            file = new File(file.getParent(), file.getName() + SAVED_SIMULATIONS_FILES);
          }

          if (!file.exists() || !file.canRead()) {
            logger.fatal("No read access to file");
            return null;
          }

          return file;
        }
      }.invokeAndWait();

      if (configFile == null) {
        return;
      }
    }

    addToFileHistory(configFile);

    final JDialog progressDialog;
    final String progressTitle = configFile == null
    ? "Loading" : ("Loading " + configFile.getAbsolutePath());

    if (quick) {
      final Thread loadThread = Thread.currentThread();

      progressDialog = new RunnableInEDT() {
        public JDialog work() {
          final JDialog progressDialog = new JDialog((Window) Cooja.getTopParentContainer(), progressTitle, ModalityType.APPLICATION_MODAL);

          JPanel progressPanel = new JPanel(new BorderLayout());
          JProgressBar progressBar;
          JButton button;

          progressBar = new JProgressBar(0, 100);
          progressBar.setValue(0);
          progressBar.setIndeterminate(true);

          PROGRESS_BAR = progressBar; /* Allow various parts of COOJA to show messages */

          button = new JButton("Abort");
          button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
              if (loadThread.isAlive()) {
                loadThread.interrupt();
                doRemoveSimulation(false);
              }
            }
          });

          progressPanel.add(BorderLayout.CENTER, progressBar);
          progressPanel.add(BorderLayout.SOUTH, button);
          progressPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));

          progressPanel.setVisible(true);

          progressDialog.getContentPane().add(progressPanel);
          progressDialog.setSize(400, 200);

          progressDialog.getRootPane().setDefaultButton(button);
          progressDialog.setLocationRelativeTo(Cooja.getTopParentContainer());
          progressDialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);

          java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
              progressDialog.setVisible(true);
            }
          });

          return progressDialog;
        }
      }.invokeAndWait();
    } else {
      progressDialog = null;
    }

    // Load simulation in this thread, while showing progress monitor
    final File fileToLoad = configFile;
    Simulation newSim = null;
    boolean shouldRetry = false;
    do {
      try {
        shouldRetry = false;
        cooja.doRemoveSimulation(false);
        PROGRESS_WARNINGS.clear();
        newSim = loadSimulationConfig(fileToLoad, quick, manualRandomSeed);
        cooja.setSimulation(newSim, false);

        /* Optionally show compilation warnings */
        boolean hideWarn = Boolean.parseBoolean(
            Cooja.getExternalToolsSetting("HIDE_WARNINGS", "false")
        );
        if (quick && !hideWarn && !PROGRESS_WARNINGS.isEmpty()) {
          showWarningsDialog(frame, PROGRESS_WARNINGS.toArray(new String[0]));
        }
        PROGRESS_WARNINGS.clear();

      } catch (UnsatisfiedLinkError e) {
        shouldRetry = showErrorDialog(Cooja.getTopParentContainer(), "Simulation load error", e, true);
      } catch (SimulationCreationException e) {
        shouldRetry = showErrorDialog(Cooja.getTopParentContainer(), "Simulation load error", e, true);
      }
    } while (shouldRetry);

    if (progressDialog != null && progressDialog.isDisplayable()) {
      progressDialog.dispose();
    }
    return;
  }

  /**
   * Reload currently configured simulation.
   * Reloading a simulation may include recompiling Contiki.
   *
   * @param autoStart Start executing simulation when loaded
   * @param randomSeed Simulation's next random seed
   */
  public void reloadCurrentSimulation(final boolean autoStart, final long randomSeed) {
    if (getSimulation() == null) {
      logger.fatal("No simulation to reload");
      return;
    }

    /* Warn about memory usage */
    if (warnMemory()) {
      return;
    }

    final JDialog progressDialog = new JDialog(frame, "Reloading", true);
    final Thread loadThread = new Thread(new Runnable() {
      public void run() {

        /* Get current simulation configuration */
        Element root = new Element("simconf");
        Element simulationElement = new Element("simulation");

        simulationElement.addContent(getSimulation().getConfigXML());
        root.addContent(simulationElement);
        Collection pluginsConfig = getPluginsConfigXML();
        if (pluginsConfig != null) {
          root.addContent(pluginsConfig);
        }

        /* Remove current simulation, and load config */
        boolean shouldRetry = false;
        do {
          try {
            shouldRetry = false;
            cooja.doRemoveSimulation(false);
            PROGRESS_WARNINGS.clear();
            Simulation newSim = loadSimulationConfig(root, true, new Long(randomSeed));
            cooja.setSimulation(newSim, false);

            if (autoStart) {
              newSim.startSimulation();
            }

            /* Optionally show compilation warnings */
            boolean hideWarn = Boolean.parseBoolean(
                Cooja.getExternalToolsSetting("HIDE_WARNINGS", "false")
            );
            if (!hideWarn && !PROGRESS_WARNINGS.isEmpty()) {
              showWarningsDialog(frame, PROGRESS_WARNINGS.toArray(new String[0]));
            }
            PROGRESS_WARNINGS.clear();

          } catch (UnsatisfiedLinkError e) {
            shouldRetry = showErrorDialog(frame, "Simulation reload error", e, true);

            cooja.doRemoveSimulation(false);
          } catch (SimulationCreationException e) {
            shouldRetry = showErrorDialog(frame, "Simulation reload error", e, true);

            cooja.doRemoveSimulation(false);
          }
        } while (shouldRetry);

        if (progressDialog.isDisplayable()) {
          progressDialog.dispose();
        }
      }
    });

    // Display progress dialog while reloading
    JProgressBar progressBar = new JProgressBar(0, 100);
    progressBar.setValue(0);
    progressBar.setIndeterminate(true);

    PROGRESS_BAR = progressBar; /* Allow various parts of COOJA to show messages */

    JButton button = new JButton("Abort");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (loadThread.isAlive()) {
          loadThread.interrupt();
          doRemoveSimulation(false);
        }
      }
    });

    JPanel progressPanel = new JPanel(new BorderLayout());
    progressPanel.add(BorderLayout.CENTER, progressBar);
    progressPanel.add(BorderLayout.SOUTH, button);
    progressPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));

    progressPanel.setVisible(true);

    progressDialog.getContentPane().add(progressPanel);
    progressDialog.setSize(400, 200);

    progressDialog.getRootPane().setDefaultButton(button);
    progressDialog.setLocationRelativeTo(frame);
    progressDialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);

    loadThread.start();
    progressDialog.setVisible(true);
  }

  private boolean warnMemory() {
    long max = Runtime.getRuntime().maxMemory();
    long used  = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
    double memRatio = (double) used / (double) max;
    if (memRatio < 0.8) {
      return false;
    }

    DecimalFormat format = new DecimalFormat("0.000");
    logger.warn("Memory usage is getting critical. Reboot Cooja to avoid out of memory error. Current memory usage is " + format.format(100*memRatio) + "%.");
    if (isVisualized()) {
      int n = JOptionPane.showOptionDialog(
          Cooja.getTopParentContainer(),
          "Reboot Cooja to avoid out of memory error.\n" +
          "Current memory usage is " + format.format(100*memRatio) + "%.",
          "Out of memory warning",
          JOptionPane.YES_NO_OPTION,
          JOptionPane.WARNING_MESSAGE, null,
          new String[] { "Continue", "Abort"}, "Abort");
      if (n != JOptionPane.YES_OPTION) {
        return true;
      }
    }

    return false;
  }

  /**
   * Reload currently configured simulation.
   * Reloading a simulation may include recompiling Contiki.
   * The same random seed is used.
   *
   * @see #reloadCurrentSimulation(boolean, long)
   * @param autoStart Start executing simulation when loaded
   */
  public void reloadCurrentSimulation(boolean autoStart) {
    reloadCurrentSimulation(autoStart, getSimulation().getRandomSeed());
  }

  /**
   * Save current simulation configuration to disk
   *
   * @param askForConfirmation
   *          Ask for confirmation before overwriting file
   */
  public File doSaveConfig(boolean askForConfirmation) {
    if (isVisualizedInApplet()) {
      return null;
    }

    if (mySimulation == null) {
      return null;
    }

    mySimulation.stopSimulation();

    JFileChooser fc = new JFileChooser();
    fc.setFileFilter(Cooja.SAVED_SIMULATIONS_FILES);

    // Suggest file using history
    File suggestedFile = getLastOpenedFile();
    if (suggestedFile != null) {
      fc.setSelectedFile(suggestedFile);
    }

    int returnVal = fc.showSaveDialog(myDesktopPane);
    if (returnVal == JFileChooser.APPROVE_OPTION) {
      File saveFile = fc.getSelectedFile();
      if (!fc.accept(saveFile)) {
        saveFile = new File(saveFile.getParent(), saveFile.getName() + SAVED_SIMULATIONS_FILES);
      }
      if (saveFile.exists()) {
        if (askForConfirmation) {
          String s1 = "Overwrite";
          String s2 = "Cancel";
          Object[] options = { s1, s2 };
          int n = JOptionPane.showOptionDialog(
              Cooja.getTopParentContainer(),
              "A file with the same name already exists.\nDo you want to remove it?",
              "Overwrite existing file?", JOptionPane.YES_NO_OPTION,
              JOptionPane.QUESTION_MESSAGE, null, options, s1);
          if (n != JOptionPane.YES_OPTION) {
            return null;
          }
        }
      }
      if (!saveFile.exists() || saveFile.canWrite()) {
        saveSimulationConfig(saveFile);
        addToFileHistory(saveFile);
        return saveFile;
      } else {
      	JOptionPane.showMessageDialog(
      			getTopParentContainer(), "No write access to " + saveFile, "Save failed",
      			JOptionPane.ERROR_MESSAGE);
        logger.fatal("No write access to file: " + saveFile.getAbsolutePath());
      }
    } else {
      logger.info("Save command cancelled by user...");
    }
    return null;
  }

  /**
   * Add new mote to current simulation
   */
  public void doAddMotes(MoteType moteType) {
    if (mySimulation != null) {
      mySimulation.stopSimulation();

      Vector newMotes = AddMoteDialog.showDialog(getTopParentContainer(), mySimulation,
          moteType);
      if (newMotes != null) {
        for (Mote newMote : newMotes) {
          mySimulation.addMote(newMote);
        }
      }

    } else {
      logger.warn("No simulation active");
    }
  }

  /**
   * Create a new simulation
   *
   * @param askForConfirmation
   *          Should we ask for confirmation if a simulation is already active?
   */
  public void doCreateSimulation(boolean askForConfirmation) {
    /* Remove current simulation */
    if (!doRemoveSimulation(askForConfirmation)) {
      return;
    }

    // Create new simulation
    Simulation newSim = new Simulation(this);
    boolean createdOK = CreateSimDialog.showDialog(Cooja.getTopParentContainer(), newSim);
    if (createdOK) {
      cooja.setSimulation(newSim, true);
    }
  }

  /**
   * Quit program
   *
   * @param askForConfirmation Should we ask for confirmation before quitting?
   */
  public void doQuit(boolean askForConfirmation) {
    doQuit(askForConfirmation, 0);
  }
  
  public void doQuit(boolean askForConfirmation, int exitCode) {
    if (isVisualizedInApplet()) {
      return;
    }

    if (askForConfirmation) {
      if (getSimulation() != null) {
        /* Save? */
        String s1 = "Yes";
        String s2 = "No";
        String s3 = "Cancel";
        Object[] options = { s1, s2, s3 };
        int n = JOptionPane.showOptionDialog(Cooja.getTopParentContainer(),
            "Do you want to save the current simulation?",
            WINDOW_TITLE, JOptionPane.YES_NO_CANCEL_OPTION,
            JOptionPane.WARNING_MESSAGE, null, options, s1);
        if (n == JOptionPane.YES_OPTION) {
          if (cooja.doSaveConfig(true) == null) {
            return;
          }
        } else if (n == JOptionPane.CANCEL_OPTION) {
          return;
        } else if (n != JOptionPane.NO_OPTION) {
          return;
        }
      }
    }

    if (getSimulation() != null) {
      doRemoveSimulation(false);
    }

    // Clean up resources
    Object[] plugins = startedPlugins.toArray();
    for (Object plugin : plugins) {
      removePlugin((Plugin) plugin, false);
    }

    /* Store frame size and position */
    if (isVisualizedInFrame()) {
      setExternalToolsSetting("FRAME_SCREEN", frame.getGraphicsConfiguration().getDevice().getIDstring());
      setExternalToolsSetting("FRAME_POS_X", "" + frame.getLocationOnScreen().x);
      setExternalToolsSetting("FRAME_POS_Y", "" + frame.getLocationOnScreen().y);

      if (frame.getExtendedState() == JFrame.MAXIMIZED_BOTH) {
        setExternalToolsSetting("FRAME_WIDTH", "" + Integer.MAX_VALUE);
        setExternalToolsSetting("FRAME_HEIGHT", "" + Integer.MAX_VALUE);
      } else {
        setExternalToolsSetting("FRAME_WIDTH", "" + frame.getWidth());
        setExternalToolsSetting("FRAME_HEIGHT", "" + frame.getHeight());
      }
    }
    saveExternalToolsUserSettings();

    System.exit(exitCode);
  }

  // // EXTERNAL TOOLS SETTINGS METHODS ////

  /**
   * @return Number of external tools settings
   */
  public static int getExternalToolsSettingsCount() {
    return externalToolsSettingNames.length;
  }

  /**
   * Get name of external tools setting at given index.
   *
   * @param index
   *          Setting index
   * @return Name
   */
  public static String getExternalToolsSettingName(int index) {
    return externalToolsSettingNames[index];
  }

  /**
   * @param name
   *          Name of setting
   * @return Value
   */
  public static String getExternalToolsSetting(String name) {
    return getExternalToolsSetting(name, null);
  }

  /**
   * @param name
   *          Name of setting
   * @param defaultValue
   *          Default value
   * @return Value
   */
  public static String getExternalToolsSetting(String name, String defaultValue) {
    if (specifiedContikiPath != null && "PATH_CONTIKI".equals(name)) {
      return specifiedContikiPath;
    }
    return currentExternalToolsSettings.getProperty(name, defaultValue);
  }

  /**
   * @param name
   *          Name of setting
   * @param defaultValue
   *          Default value
   * @return Value
   */
  public static String getExternalToolsDefaultSetting(String name, String defaultValue) {
    return defaultExternalToolsSettings.getProperty(name, defaultValue);
  }

  /**
   * @param name
   *          Name of setting
   * @param newVal
   *          New value
   */
  public static void setExternalToolsSetting(String name, String newVal) {
    currentExternalToolsSettings.setProperty(name, newVal);
  }

  /**
   * Load external tools settings from default file.
   */
  public static void loadExternalToolsDefaultSettings() {
    String osName = System.getProperty("os.name").toLowerCase();
    String osArch = System.getProperty("os.arch").toLowerCase();

    String filename = null;
    if (osName.startsWith("win")) {
      filename = Cooja.EXTERNAL_TOOLS_WIN32_SETTINGS_FILENAME;
    } else if (osName.startsWith("mac os x")) {
      filename = Cooja.EXTERNAL_TOOLS_MACOSX_SETTINGS_FILENAME;
    } else if (osName.startsWith("freebsd")) {
      filename = Cooja.EXTERNAL_TOOLS_FREEBSD_SETTINGS_FILENAME;
    } else if (osName.startsWith("linux")) {
      filename = Cooja.EXTERNAL_TOOLS_LINUX_SETTINGS_FILENAME;
      if (osArch.startsWith("amd64")) {
        filename = Cooja.EXTERNAL_TOOLS_LINUX_64_SETTINGS_FILENAME;
      }
    } else {
      logger.warn("Unknown system: " + osName);
      logger.warn("Using default linux external tools configuration");
      filename = Cooja.EXTERNAL_TOOLS_LINUX_SETTINGS_FILENAME;
    }

    try {
      InputStream in = Cooja.class.getResourceAsStream(EXTERNAL_TOOLS_SETTINGS_FILENAME);
      if (in == null) {
        throw new FileNotFoundException(filename + " not found");
      }
      Properties settings = new Properties();
      settings.load(in);
      in.close();

      in = Cooja.class.getResourceAsStream(filename);
      if (in == null) {
        throw new FileNotFoundException(filename + " not found");
      }
      settings.load(in);
      in.close();

      currentExternalToolsSettings = settings;
      defaultExternalToolsSettings = (Properties) currentExternalToolsSettings.clone();
      logger.info("External tools default settings: " + filename);
    } catch (IOException e) {
      logger.warn("Error when reading external tools settings from " + filename, e);
    } finally {
      if (currentExternalToolsSettings == null) {
        defaultExternalToolsSettings = new Properties();
        currentExternalToolsSettings = new Properties();
      }
    }
  }

  /**
   * Load user values from external properties file
   */
  public static void loadExternalToolsUserSettings() {
    if (externalToolsUserSettingsFile == null) {
      return;
    }

    try {
      FileInputStream in = new FileInputStream(externalToolsUserSettingsFile);
      Properties settings = new Properties();
      settings.load(in);
      in.close();

      Enumeration en = settings.keys();
      while (en.hasMoreElements()) {
        String key = (String) en.nextElement();
        setExternalToolsSetting(key, settings.getProperty(key));
      }
      logger.info("External tools user settings: " + externalToolsUserSettingsFile);
    } catch (FileNotFoundException e) {
      logger.warn("Error when reading user settings from: " + externalToolsUserSettingsFile);
    } catch (IOException e) {
      logger.warn("Error when reading user settings from: " + externalToolsUserSettingsFile);
    }
  }

  /**
   * Save external tools user settings to file.
   */
  public static void saveExternalToolsUserSettings() {
    if (isVisualizedInApplet()) {
      return;
    }

    if (externalToolsUserSettingsFileReadOnly) {
      return;
    }

    try {
      FileOutputStream out = new FileOutputStream(externalToolsUserSettingsFile);

      Properties differingSettings = new Properties();
      Enumeration keyEnum = currentExternalToolsSettings.keys();
      while (keyEnum.hasMoreElements()) {
        String key = (String) keyEnum.nextElement();
        String defaultSetting = getExternalToolsDefaultSetting(key, "");
        String currentSetting = currentExternalToolsSettings.getProperty(key, "");
        if (!defaultSetting.equals(currentSetting)) {
          differingSettings.setProperty(key, currentSetting);
        }
      }

      differingSettings.store(out, "Cooja External Tools (User specific)");
      out.close();
    } catch (FileNotFoundException ex) {
      // Could not open settings file for writing, aborting
      logger.warn("Could not save external tools user settings to "
          + externalToolsUserSettingsFile + ", aborting");
    } catch (IOException ex) {
      // Could not open settings file for writing, aborting
      logger.warn("Error while saving external tools user settings to "
          + externalToolsUserSettingsFile + ", aborting");
    }
  }

  // // GUI EVENT HANDLER ////

  private class GUIEventHandler implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if (e.getActionCommand().equals("create mote type")) {
        cooja.doCreateMoteType((Class) ((JMenuItem) e
            .getSource()).getClientProperty("class"));
      } else if (e.getActionCommand().equals("add motes")) {
        cooja.doAddMotes((MoteType) ((JMenuItem) e.getSource())
            .getClientProperty("motetype"));
      } else if (e.getActionCommand().equals("edit paths")) {
        ExternalToolsDialog.showDialog(Cooja.getTopParentContainer());
      } else if (e.getActionCommand().equals("manage extensions")) {
        COOJAProject[] newProjects = ProjectDirectoriesDialog.showDialog(
            Cooja.getTopParentContainer(),
            Cooja.this,
            getProjects()
        );
        if (newProjects != null) {
        	currentProjects.clear();
        	for (COOJAProject p: newProjects) {
            currentProjects.add(p);
        	}
          try {
            reparseProjectConfig();
          } catch (ParseProjectsException ex) {
            logger.fatal("Error when loading extensions: " + ex.getMessage(), ex);
            if (isVisualized()) {
            	JOptionPane.showMessageDialog(Cooja.getTopParentContainer(),
            			"All Cooja extensions could not load.\n\n" +
            			"To manage Cooja extensions:\n" +
            			"Menu->Settings->Cooja extensions",
            			"Reconfigure Cooja extensions", JOptionPane.INFORMATION_MESSAGE);
            }
            showErrorDialog(getTopParentContainer(), "Cooja extensions load error", ex, false);
          }
        }
      } else if (e.getActionCommand().equals("configuration wizard")) {
        ConfigurationWizard.startWizard(Cooja.getTopParentContainer(), Cooja.this);
      } else {
        logger.warn("Unhandled action: " + e.getActionCommand());
      }
    }
  }

  // // VARIOUS HELP METHODS ////

  /**
   * Help method that tries to load and initialize a class with given name.
   *
   * @param  Class extending given class type
   * @param classType Class type
   * @param className Class name
   * @return Class extending given class type or null if not found
   */
  public  Class tryLoadClass(
      Object callingObject, Class classType, String className) {

    if (callingObject != null) {
      try {
        return callingObject.getClass().getClassLoader().loadClass(className).asSubclass(classType);
      } catch (ClassNotFoundException e) {
      } catch (UnsupportedClassVersionError e) {
      }
    }

    try {
      return Class.forName(className).asSubclass(classType);
    } catch (ClassNotFoundException e) {
    } catch (UnsupportedClassVersionError e) {
    }

    if (!isVisualizedInApplet()) {
      try {
        if (projectDirClassLoader != null) {
          return projectDirClassLoader.loadClass(className).asSubclass(
              classType);
        }
      } catch (NoClassDefFoundError e) {
      } catch (ClassNotFoundException e) {
      } catch (UnsupportedClassVersionError e) {
      }
    }

    return null;
  }

  private ClassLoader createClassLoader(Collection projects)
  throws ClassLoaderCreationException {
    return createClassLoader(ClassLoader.getSystemClassLoader(), projects);
  }

  public static File findJarFile(File projectDir, String jarfile) {
    File fp = new File(jarfile);
    if (!fp.exists()) {
      fp = new File(projectDir, jarfile);
    }
    if (!fp.exists()) {
      fp = new File(projectDir, "java/" + jarfile);
    }
    if (!fp.exists()) {
      fp = new File(projectDir, "java/lib/" + jarfile);
    }
    if (!fp.exists()) {
      fp = new File(projectDir, "lib/" + jarfile);
    }
    return fp.exists() ? fp : null;
  }

  private ClassLoader createClassLoader(ClassLoader parent, Collection projects)
  throws ClassLoaderCreationException {
    if (projects == null || projects.isEmpty()) {
      return parent;
    }

    /* Create class loader from JARs */
    ArrayList urls = new ArrayList();
    for (COOJAProject project: projects) {
    	File projectDir = project.dir;
      try {
        urls.add((new File(projectDir, "java")).toURI().toURL());

        // Read configuration to check if any JAR files should be loaded
        ProjectConfig projectConfig = new ProjectConfig(false);
        projectConfig.appendProjectDir(projectDir);
        String[] projectJarFiles = projectConfig.getStringArrayValue(
            Cooja.class, "JARFILES");
        if (projectJarFiles != null && projectJarFiles.length > 0) {
          for (String jarfile : projectJarFiles) {
            File jarpath = findJarFile(projectDir, jarfile);
            if (jarpath == null) {
              throw new FileNotFoundException(jarfile);
            }
            urls.add(jarpath.toURI().toURL());
          }
        }

      } catch (Exception e) {
        logger.fatal("Error when trying to read JAR-file in " + projectDir
            + ": " + e);
        throw (ClassLoaderCreationException) new ClassLoaderCreationException(
            "Error when trying to read JAR-file in " + projectDir).initCause(e);
      }
    }

    URL[] urlsArray = urls.toArray(new URL[urls.size()]);
    /* TODO Load from webserver if applet */
    return new URLClassLoader(urlsArray, parent);
  }

  /**
   * Help method that returns the description for given object. This method
   * reads from the object's class annotations if existing. Otherwise it returns
   * the simple class name of object's class.
   *
   * @param object
   *          Object
   * @return Description
   */
  public static String getDescriptionOf(Object object) {
    return getDescriptionOf(object.getClass());
  }

  /**
   * Help method that returns the description for given class. This method reads
   * from class annotations if existing. Otherwise it returns the simple class
   * name.
   *
   * @param clazz
   *          Class
   * @return Description
   */
  public static String getDescriptionOf(Class clazz) {
    if (clazz.isAnnotationPresent(ClassDescription.class)) {
      return clazz.getAnnotation(ClassDescription.class).value();
    }
    return clazz.getSimpleName();
  }

  /**
   * Help method that returns the abstraction level description for given mote type class.
   *
   * @param clazz
   *          Class
   * @return Description
   */
  public static String getAbstractionLevelDescriptionOf(Class clazz) {
    if (clazz.isAnnotationPresent(AbstractionLevelDescription.class)) {
      return clazz.getAnnotation(AbstractionLevelDescription.class).value();
    }
    return null;
  }

  /**
   * Load configurations and create a GUI.
   *
   * @param args
   *          null
   */
  public static void main(String[] args) {
    String logConfigFile = null;
    Long randomSeed = null;
    
    
    for (String element : args) {
      if (element.startsWith("-log4j=")) {
        String arg = element.substring("-log4j=".length());
        logConfigFile = arg;
      }
    }

    try {
      // Configure logger
      if (logConfigFile != null) {
        if (new File(logConfigFile).exists()) {
          DOMConfigurator.configure(logConfigFile);
        } else {
          logger.error("Failed to open " + logConfigFile);
          System.exit(1);
        }
      } else if (new File(LOG_CONFIG_FILE).exists()) {
        DOMConfigurator.configure(LOG_CONFIG_FILE);
      } else {
        // Used when starting from jar
        DOMConfigurator.configure(Cooja.class.getResource("/" + LOG_CONFIG_FILE));
      }

      externalToolsUserSettingsFile = new File(System.getProperty("user.home"), EXTERNAL_TOOLS_USER_SETTINGS_FILENAME);
    } catch (AccessControlException e) {
      BasicConfigurator.configure();
      externalToolsUserSettingsFile = null;
    }

    /* Look and Feel: Nimbus */
    setLookAndFeel();

    /* Warn at no JAVA_HOME */
    String javaHome = System.getenv().get("JAVA_HOME");
    if (javaHome == null || javaHome.equals("")) {
      logger.warn("JAVA_HOME environment variable not set, Cooja motes may not compile");
    }

    // Parse general command arguments
    for (String element : args) {
      if (element.startsWith("-contiki=")) {
        String arg = element.substring("-contiki=".length());
        Cooja.specifiedContikiPath = arg;
      }

      if (element.startsWith("-external_tools_config=")) {
        String arg = element.substring("-external_tools_config=".length());
        File specifiedExternalToolsConfigFile = new File(arg);
        if (!specifiedExternalToolsConfigFile.exists()) {
          logger.fatal("Specified external tools configuration not found: " + specifiedExternalToolsConfigFile);
          specifiedExternalToolsConfigFile = null;
          System.exit(1);
        } else {
          Cooja.externalToolsUserSettingsFile = specifiedExternalToolsConfigFile;
          Cooja.externalToolsUserSettingsFileReadOnly = true;
        }
      }
      
      if (element.startsWith("-random-seed=")) {
        String arg = element.substring("-random-seed=".length());
        try {          
          randomSeed =  Long.valueOf(arg);
        } catch (Exception e) {
          logger.error("Failed to convert \"" + arg +"\" to an integer.");
        }
      }
    }

    // Check if simulator should be quick-started
    if (args.length > 0 && args[0].startsWith("-quickstart=")) {
      String contikiApp = args[0].substring("-quickstart=".length());

      /* Cygwin fix */
      if (contikiApp.startsWith("/cygdrive/")) {
        char driveCharacter = contikiApp.charAt("/cygdrive/".length());
        contikiApp = contikiApp.replace("/cygdrive/" + driveCharacter + "/", driveCharacter + ":/");
      }

      Simulation sim = null;
      if (contikiApp.endsWith(".csc")) {
        sim = quickStartSimulationConfig(new File(contikiApp), true, randomSeed);
      } else {
        if (contikiApp.endsWith(".cooja")) {
          contikiApp = contikiApp.substring(0, contikiApp.length() - ".cooja".length());
        }
        if (!contikiApp.endsWith(".c")) {
          contikiApp += ".c";
        }

        sim = quickStartSimulation(contikiApp);
      }

      if (sim == null) {
        System.exit(1);
      }
      

    } else if (args.length > 0 && args[0].startsWith("-nogui=")) {

      /* Load simulation */
      String config = args[0].substring("-nogui=".length());
      File configFile = new File(config);
      Simulation sim = quickStartSimulationConfig(configFile, false, randomSeed);
      if (sim == null) {
        System.exit(1);
      }
      Cooja gui = sim.getCooja();

      /* Make sure at least one plugin controlling the simulation */
      boolean hasController = false;
      for (Plugin startedPlugin : gui.startedPlugins) {
    	int pluginType = startedPlugin.getClass().getAnnotation(PluginType.class).value();
    	if (pluginType == PluginType.SIM_CONTROL_PLUGIN) {
    	  hasController = true;
    	}
      }

      /* Backwards compatibility:
       * simulation has no control plugin, but has external (old style) test script.
       * We will manually start a test editor from here. */
      if (!hasController) {
        File scriptFile = new File(config.substring(0, config.length()-4) + ".js");
        if (scriptFile.exists()) {
          logger.info("Detected old simulation test, starting test editor manually from: " + scriptFile);
          ScriptRunner plugin = (ScriptRunner) gui.tryStartPlugin(ScriptRunner.class, gui, sim, null);
          if (plugin == null) {
            System.exit(1);
          }
          plugin.updateScript(scriptFile);
          try {
            plugin.setScriptActive(true);
          } catch (Exception e) {
            logger.fatal("Error: " + e.getMessage(), e);
            System.exit(1);
          }
        } else {
          logger.fatal("No plugin controlling simulation, aborting");
          System.exit(1);
        }
      }


      
    } else if (args.length > 0 && args[0].startsWith("-applet")) {

      String tmpWebPath=null, tmpBuildPath=null, tmpEsbFirmware=null, tmpSkyFirmware=null;
      for (int i = 1; i < args.length; i++) {
        if (args[i].startsWith("-web=")) {
          tmpWebPath = args[i].substring("-web=".length());
        } else if (args[i].startsWith("-sky_firmware=")) {
          tmpSkyFirmware = args[i].substring("-sky_firmware=".length());
        } else if (args[i].startsWith("-esb_firmware=")) {
          tmpEsbFirmware = args[i].substring("-esb_firmware=".length());
        } else if (args[i].startsWith("-build=")) {
          tmpBuildPath = args[i].substring("-build=".length());
        }
      }

      // Applet start-up
      final String webPath = tmpWebPath, buildPath = tmpBuildPath;
      final String skyFirmware = tmpSkyFirmware, esbFirmware = tmpEsbFirmware;
      javax.swing.SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          JDesktopPane desktop = createDesktopPane();

          applet = CoojaApplet.applet;
          Cooja gui = new Cooja(desktop);

          Cooja.setExternalToolsSetting("PATH_CONTIKI_BUILD", buildPath);
          Cooja.setExternalToolsSetting("PATH_CONTIKI_WEB", webPath);

          Cooja.setExternalToolsSetting("SKY_FIRMWARE", skyFirmware);
          Cooja.setExternalToolsSetting("ESB_FIRMWARE", esbFirmware);

          configureApplet(gui, false);
        }
      });

    } else {

      // Frame start-up
      javax.swing.SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          JDesktopPane desktop = createDesktopPane();
          frame = new JFrame(WINDOW_TITLE);
          Cooja gui = new Cooja(desktop);
          configureFrame(gui, false);
        }
      });

    }
  }

  /**
   * Loads a simulation configuration from given file.
   *
   * When loading Contiki mote types, the libraries must be recompiled. User may
   * change mote type settings at this point.
   *
   * @see #saveSimulationConfig(File)
   * @param file
   *          File to read
   * @return New simulation or null if recompiling failed or aborted
   * @throws UnsatisfiedLinkError
   *           If associated libraries could not be loaded
   */
  public Simulation loadSimulationConfig(File file, boolean quick, Long manualRandomSeed)
  throws UnsatisfiedLinkError, SimulationCreationException {
    this.currentConfigFile = file; /* Used to generate config relative paths */
    try {
      this.currentConfigFile = this.currentConfigFile.getCanonicalFile();
    } catch (IOException e) {
    }

    try {
      SAXBuilder builder = new SAXBuilder();
    	InputStream in = new FileInputStream(file);
      if (file.getName().endsWith(".gz")) {
      	in = new GZIPInputStream(in);
      }
      Document doc = builder.build(in);
      Element root = doc.getRootElement();
      in.close();

      return loadSimulationConfig(root, quick, manualRandomSeed);
    } catch (JDOMException e) {
      throw (SimulationCreationException) new SimulationCreationException("Config not wellformed").initCause(e);
    } catch (IOException e) {
      throw (SimulationCreationException) new SimulationCreationException("Load simulation error").initCause(e);
    }
  }

  public Simulation loadSimulationConfig(Element root, boolean quick, Long manualRandomSeed)
  throws SimulationCreationException {
    Simulation newSim = null;

    try {
      // Check that config file version is correct
      if (!root.getName().equals("simconf")) {
        logger.fatal("Not a valid Cooja simulation config.");
        return null;
      }

      /* Verify extension directories */
      boolean projectsOk = verifyProjects(root.getChildren(), !quick);

      /* GENERATE UNIQUE MOTE TYPE IDENTIFIERS */
      root.detach();
      String configString = new XMLOutputter().outputString(new Document(root));

      /* Locate Contiki mote types in config */
      Properties moteTypeIDMappings = new Properties();
      String identifierExtraction = ContikiMoteType.class.getName() + "[\\s\\n]*([^<]*)";
      Matcher matcher = Pattern.compile(identifierExtraction).matcher(configString);
      while (matcher.find()) {
        moteTypeIDMappings.setProperty(matcher.group(1), "");
      }

      /* Create old to new identifier mappings */
      Enumeration existingIdentifiers = moteTypeIDMappings.keys();
      while (existingIdentifiers.hasMoreElements()) {
        String existingIdentifier = (String) existingIdentifiers.nextElement();
        MoteType[] existingMoteTypes = null;
        if (mySimulation != null) {
          existingMoteTypes = mySimulation.getMoteTypes();
        }
        ArrayList reserved = new ArrayList();
        reserved.addAll(moteTypeIDMappings.keySet());
        reserved.addAll(moteTypeIDMappings.values());
        String newID = ContikiMoteType.generateUniqueMoteTypeID(existingMoteTypes, reserved);
        moteTypeIDMappings.setProperty(existingIdentifier, newID);
      }

      /* Create new config */
      existingIdentifiers = moteTypeIDMappings.keys();
      while (existingIdentifiers.hasMoreElements()) {
        String existingIdentifier = (String) existingIdentifiers.nextElement();
        configString = configString.replaceAll(
            "" + existingIdentifier + "",
            "" + moteTypeIDMappings.get(existingIdentifier) + "");
        configString = configString.replaceAll(
            "" + existingIdentifier + "",
            "" + moteTypeIDMappings.get(existingIdentifier) + "");
      }

      /* Replace existing config */
      root = new SAXBuilder().build(new StringReader(configString)).getRootElement();

      // Create new simulation from config
      for (Object element : root.getChildren()) {
        if (((Element) element).getName().equals("simulation")) {
          Collection config = ((Element) element).getChildren();
          newSim = new Simulation(this);
          System.gc();
          boolean createdOK = newSim.setConfigXML(config, !quick, manualRandomSeed);
          if (!createdOK) {
            logger.info("Simulation not loaded");
            return null;
          }
        }
      }

      // Restart plugins from config
      setPluginsConfigXML(root.getChildren(), newSim, !quick);

    } catch (JDOMException e) {
      throw (SimulationCreationException) new SimulationCreationException(
          "Configuration file not wellformed: " + e.getMessage()).initCause(e);
    } catch (IOException e) {
      throw (SimulationCreationException) new SimulationCreationException(
          "No access to configuration file: " + e.getMessage()).initCause(e);
    } catch (MoteTypeCreationException e) {
      throw (SimulationCreationException) new SimulationCreationException(
          "Mote type creation error: " + e.getMessage()).initCause(e);
    } catch (Exception e) {
      throw (SimulationCreationException) new SimulationCreationException(
          "Unknown error: " + e.getMessage()).initCause(e);
    }

    return newSim;
  }

  /**
   * Saves current simulation configuration to given file and notifies
   * observers.
   *
   * @see #loadSimulationConfig(File, boolean)
   * @param file
   *          File to write
   */
   public void saveSimulationConfig(File file) {
    this.currentConfigFile = file; /* Used to generate config relative paths */
    try {
      this.currentConfigFile = this.currentConfigFile.getCanonicalFile();
    } catch (IOException e) {
    }

    try {
      // Create and write to document
      Document doc = new Document(extractSimulationConfig());
      OutputStream out = new FileOutputStream(file);

      if (file.getName().endsWith(".gz")) {
      	out = new GZIPOutputStream(out);
      }

      XMLOutputter outputter = new XMLOutputter();
      outputter.setFormat(Format.getPrettyFormat());
      outputter.output(doc, out);
      out.close();

      logger.info("Saved to file: " + file.getAbsolutePath());
    } catch (Exception e) {
      logger.warn("Exception while saving simulation config: " + e);
      e.printStackTrace();
    }
  }

  public Element extractSimulationConfig() {
    // Create simulation config
    Element root = new Element("simconf");

    /* Store extension directories meta data */
    for (COOJAProject project: currentProjects) {
      Element projectElement = new Element("project");
      projectElement.addContent(createPortablePath(project.dir).getPath().replaceAll("\\\\", "/"));
      projectElement.setAttribute("EXPORT", "discard");
      root.addContent(projectElement);
    }

    Element simulationElement = new Element("simulation");
    simulationElement.addContent(mySimulation.getConfigXML());
    root.addContent(simulationElement);

    // Create started plugins config
    Collection pluginsConfig = getPluginsConfigXML();
    if (pluginsConfig != null) {
      root.addContent(pluginsConfig);
    }

    return root;
  }

  /**
   * Returns started plugins config.
   *
   * @return Config or null
   */
  public Collection getPluginsConfigXML() {
    ArrayList config = new ArrayList();
    Element pluginElement, pluginSubElement;

    /* Loop over all plugins */
    for (Plugin startedPlugin : startedPlugins) {
      int pluginType = startedPlugin.getClass().getAnnotation(PluginType.class).value();

      // Ignore GUI plugins
      if (pluginType == PluginType.COOJA_PLUGIN
          || pluginType == PluginType.COOJA_STANDARD_PLUGIN) {
        continue;
      }

      pluginElement = new Element("plugin");
      pluginElement.setText(startedPlugin.getClass().getName());

      // Create mote argument config (if mote plugin)
      if (pluginType == PluginType.MOTE_PLUGIN) {
        pluginSubElement = new Element("mote_arg");
        Mote taggedMote = ((MotePlugin) startedPlugin).getMote();
        for (int moteNr = 0; moteNr < mySimulation.getMotesCount(); moteNr++) {
          if (mySimulation.getMote(moteNr) == taggedMote) {
            pluginSubElement.setText(Integer.toString(moteNr));
            pluginElement.addContent(pluginSubElement);
            break;
          }
        }
      }

      // Create plugin specific configuration
      Collection pluginXML = startedPlugin.getConfigXML();
      if (pluginXML != null) {
        pluginSubElement = new Element("plugin_config");
        pluginSubElement.addContent(pluginXML);
        pluginElement.addContent(pluginSubElement);
      }

      // If plugin is visualizer plugin, create visualization arguments
      if (startedPlugin.getCooja() != null) {
        JInternalFrame pluginFrame = startedPlugin.getCooja();

        pluginSubElement = new Element("width");
        pluginSubElement.setText("" + pluginFrame.getSize().width);
        pluginElement.addContent(pluginSubElement);

        pluginSubElement = new Element("z");
        pluginSubElement.setText("" + getDesktopPane().getComponentZOrder(pluginFrame));
        pluginElement.addContent(pluginSubElement);

        pluginSubElement = new Element("height");
        pluginSubElement.setText("" + pluginFrame.getSize().height);
        pluginElement.addContent(pluginSubElement);

        pluginSubElement = new Element("location_x");
        pluginSubElement.setText("" + pluginFrame.getLocation().x);
        pluginElement.addContent(pluginSubElement);

        pluginSubElement = new Element("location_y");
        pluginSubElement.setText("" + pluginFrame.getLocation().y);
        pluginElement.addContent(pluginSubElement);

        if (pluginFrame.isIcon()) {
          pluginSubElement = new Element("minimized");
          pluginSubElement.setText("" + true);
          pluginElement.addContent(pluginSubElement);
        }
      }

      config.add(pluginElement);
    }

    return config;
  }

  public boolean verifyProjects(Collection configXML, boolean visAvailable) {
    boolean allOk = true;

    /* Match current extensions against extensions in simulation config */
    for (final Element pluginElement : configXML.toArray(new Element[0])) {
      if (pluginElement.getName().equals("project")) {
        File projectFile = restorePortablePath(new File(pluginElement.getText()));
        try {
          projectFile = projectFile.getCanonicalFile();
        } catch (IOException e) {
        }

        boolean found = false;
        for (COOJAProject currentProject: currentProjects) {
          if (projectFile.getPath().replaceAll("\\\\", "/").
              equals(currentProject.dir.getPath().replaceAll("\\\\", "/"))) {
            found = true;
            break;
          }
        }

        if (!found) {
          logger.warn("Loaded simulation may depend on not found  extension: '" + projectFile + "'");
          allOk = false;
        }
      }
    }

    return allOk;
  }


  /**
   * Starts plugins with arguments in given config.
   *
   * @param configXML
   *          Config XML elements
   * @param simulation
   *          Simulation on which to start plugins
   * @return True if all plugins started, false otherwise
   */
  public boolean setPluginsConfigXML(Collection configXML,
      Simulation simulation, boolean visAvailable) {

    for (final Element pluginElement : configXML.toArray(new Element[0])) {
      if (pluginElement.getName().equals("plugin")) {

        // Read plugin class
        String pluginClassName = pluginElement.getText().trim();

        /* Backwards compatibility: se.sics -> org.contikios */
        if (pluginClassName.startsWith("se.sics")) {
        	pluginClassName = pluginClassName.replaceFirst("se\\.sics", "org.contikios");
        }

        /* Backwards compatibility: old visualizers were replaced */
        if (pluginClassName.equals("org.contikios.cooja.plugins.VisUDGM") ||
        		pluginClassName.equals("org.contikios.cooja.plugins.VisBattery") ||
        		pluginClassName.equals("org.contikios.cooja.plugins.VisTraffic") ||
        		pluginClassName.equals("org.contikios.cooja.plugins.VisState") ||
        		pluginClassName.equals("org.contikios.cooja.plugins.VisUDGM")) {
        	logger.warn("Old simulation config detected: visualizers have been remade");
        	pluginClassName = "org.contikios.cooja.plugins.Visualizer";
        }

        Class pluginClass =
          tryLoadClass(this, Plugin.class, pluginClassName);
        if (pluginClass == null) {
          logger.fatal("Could not load plugin class: " + pluginClassName);
          return false;
        }

        // Parse plugin mote argument (if any)
        Mote mote = null;
        for (Element pluginSubElement : (List) pluginElement.getChildren()) {
          if (pluginSubElement.getName().equals("mote_arg")) {
            int moteNr = Integer.parseInt(pluginSubElement.getText());
            if (moteNr >= 0 && moteNr < simulation.getMotesCount()) {
              mote = simulation.getMote(moteNr);
            }
          }
        }

        /* Start plugin */
        final Plugin startedPlugin = tryStartPlugin(pluginClass, this, simulation, mote, false);
        if (startedPlugin == null) {
          continue;
        }

        /* Apply plugin specific configuration */
        for (Element pluginSubElement : (List) pluginElement.getChildren()) {
          if (pluginSubElement.getName().equals("plugin_config")) {
            startedPlugin.setConfigXML(pluginSubElement.getChildren(), visAvailable);
          }
        }

        /* Activate plugin */
        startedPlugin.startPlugin();

        /* If Cooja not visualized, ignore window configuration */
        if (startedPlugin.getCooja() == null) {
          continue;
        }

        // If plugin is visualizer plugin, parse visualization arguments
        new RunnableInEDT() {
          public Boolean work() {
            Dimension size = new Dimension(100, 100);
            Point location = new Point(100, 100);

            for (Element pluginSubElement : (List) pluginElement.getChildren()) {
              if (pluginSubElement.getName().equals("width")) {
                size.width = Integer.parseInt(pluginSubElement.getText());
                startedPlugin.getCooja().setSize(size);
              } else if (pluginSubElement.getName().equals("height")) {
                size.height = Integer.parseInt(pluginSubElement.getText());
                startedPlugin.getCooja().setSize(size);
              } else if (pluginSubElement.getName().equals("z")) {
                int zOrder = Integer.parseInt(pluginSubElement.getText());
                startedPlugin.getCooja().putClientProperty("zorder", zOrder);
              } else if (pluginSubElement.getName().equals("location_x")) {
                location.x = Integer.parseInt(pluginSubElement.getText());
                startedPlugin.getCooja().setLocation(location);
              } else if (pluginSubElement.getName().equals("location_y")) {
                location.y = Integer.parseInt(pluginSubElement.getText());
                startedPlugin.getCooja().setLocation(location);
              } else if (pluginSubElement.getName().equals("minimized")) {
                boolean minimized = Boolean.parseBoolean(pluginSubElement.getText());
                final JInternalFrame pluginGUI = startedPlugin.getCooja();
                if (minimized && pluginGUI != null) {
                  SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                      try {
                        pluginGUI.setIcon(true);
                      } catch (PropertyVetoException e) {
                      }
                    };
                  });
                }
              }
            }

            showPlugin(startedPlugin);
            return true;
          }
        }.invokeAndWait();

      }
    }

    /* Z order visualized plugins */
    try {
    	for (int z=0; z < getDesktopPane().getAllFrames().length; z++) {
        for (JInternalFrame plugin : getDesktopPane().getAllFrames()) {
          if (plugin.getClientProperty("zorder") == null) {
          	continue;
          }
          int zOrder = ((Integer) plugin.getClientProperty("zorder")).intValue();
          if (zOrder != z) {
          	continue;
          }
          getDesktopPane().setComponentZOrder(plugin, zOrder);
          if (z == 0) {
            plugin.setSelected(true);
          }
          plugin.putClientProperty("zorder", null);
          break;
        }
        getDesktopPane().repaint();
    	}
    } catch (Exception e) { }

    return true;
  }

  public class ParseProjectsException extends Exception {
		private static final long serialVersionUID = 1508168026300714850L;
		public ParseProjectsException(String message) {
      super(message);
    }
  }

  public class ClassLoaderCreationException extends Exception {
		private static final long serialVersionUID = 1578001681266277774L;
		public ClassLoaderCreationException(String message) {
      super(message);
    }
  }

  public class SimulationCreationException extends Exception {
		private static final long serialVersionUID = -2414899187405770448L;
		public SimulationCreationException(String message) {
      super(message);
    }
  }

  public class PluginConstructionException extends Exception {
		private static final long serialVersionUID = 8004171223353676751L;
		public PluginConstructionException(String message) {
      super(message);
    }
  }

  /**
   * A simple error dialog with compilation output and stack trace.
   *
   * @param parentComponent
   *          Parent component
   * @param title
   *          Title of error window
   * @param exception
   *          Exception causing window to be shown
   * @param retryAvailable
   *          If true, a retry option is presented
   * @return Retry failed operation
   */
  public static boolean showErrorDialog(final Component parentComponent,
      final String title, final Throwable exception, final boolean retryAvailable) {

    return new RunnableInEDT() {
      public Boolean work() {
        JTabbedPane tabbedPane = new JTabbedPane();
        final JDialog errorDialog;
        if (parentComponent instanceof Dialog) {
          errorDialog = new JDialog((Dialog) parentComponent, title, true);
        } else if (parentComponent instanceof Frame) {
          errorDialog = new JDialog((Frame) parentComponent, title, true);
        } else {
          errorDialog = new JDialog((Frame) null, title);
        }
        Box buttonBox = Box.createHorizontalBox();

        if (exception != null) {
          /* Contiki error */
          if (exception instanceof ContikiError) {
            String contikiError = ((ContikiError) exception).getContikiError();
            MessageList list = new MessageList();
            for (String l: contikiError.split("\n")) {
              list.addMessage(l);
            }
            list.addPopupMenuItem(null, true);
            tabbedPane.addTab("Contiki error", new JScrollPane(list));
          }

          /* Compilation output */
          MessageList compilationOutput = null;
          if (exception instanceof MoteTypeCreationException
              && ((MoteTypeCreationException) exception).hasCompilationOutput()) {
            compilationOutput = ((MoteTypeCreationException) exception).getCompilationOutput();
          } else if (exception.getCause() != null
              && exception.getCause() instanceof MoteTypeCreationException
              && ((MoteTypeCreationException) exception.getCause()).hasCompilationOutput()) {
            compilationOutput = ((MoteTypeCreationException) exception.getCause()).getCompilationOutput();
          }
          if (compilationOutput != null) {
            compilationOutput.addPopupMenuItem(null, true);
            tabbedPane.addTab("Compilation output", new JScrollPane(compilationOutput));
          }

          /* Stack trace */
          MessageList stackTrace = new MessageList();
          PrintStream printStream = stackTrace.getInputStream(MessageList.NORMAL);
          exception.printStackTrace(printStream);
          stackTrace.addPopupMenuItem(null, true);
          tabbedPane.addTab("Java stack trace", new JScrollPane(stackTrace));

          /* Exception message */
          buttonBox.add(Box.createHorizontalStrut(10));
          buttonBox.add(new JLabel(exception.getMessage()));
          buttonBox.add(Box.createHorizontalStrut(10));
        }

        buttonBox.add(Box.createHorizontalGlue());

        if (retryAvailable) {
          Action retryAction = new AbstractAction() {
						private static final long serialVersionUID = 2370456199250998435L;
						public void actionPerformed(ActionEvent e) {
              errorDialog.setTitle("-RETRY-");
              errorDialog.dispose();
            }
          };
          JButton retryButton = new JButton(retryAction);
          retryButton.setText("Retry Ctrl+R");
          buttonBox.add(retryButton);

          InputMap inputMap = errorDialog.getRootPane().getInputMap(
              JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
          inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_R, KeyEvent.CTRL_DOWN_MASK, false), "retry");
          errorDialog.getRootPane().getActionMap().put("retry", retryAction);
        }

        AbstractAction closeAction = new AbstractAction(){
					private static final long serialVersionUID = 6225539435993362733L;
					public void actionPerformed(ActionEvent e) {
            errorDialog.dispose();
          }
        };

        JButton closeButton = new JButton(closeAction);
        closeButton.setText("Close");
        buttonBox.add(closeButton);

        InputMap inputMap = errorDialog.getRootPane().getInputMap(
            JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false), "close");
        errorDialog.getRootPane().getActionMap().put("close", closeAction);


        errorDialog.getRootPane().setDefaultButton(closeButton);
        errorDialog.getContentPane().add(BorderLayout.CENTER, tabbedPane);
        errorDialog.getContentPane().add(BorderLayout.SOUTH, buttonBox);
        errorDialog.setSize(700, 500);
        errorDialog.setLocationRelativeTo(parentComponent);
        errorDialog.setVisible(true); /* BLOCKS */

        if (errorDialog.getTitle().equals("-RETRY-")) {
          return true;
        }
        return false;

      }
    }.invokeAndWait();

  }

  private static void showWarningsDialog(final Frame parent, final String[] warnings) {
    new RunnableInEDT() {
      public Boolean work() {
        final JDialog dialog = new JDialog(parent, "Compilation warnings", false);
        Box buttonBox = Box.createHorizontalBox();

        /* Warnings message list */
        MessageList compilationOutput = new MessageList();
        for (String w: warnings) {
          compilationOutput.addMessage(w, MessageList.ERROR);
        }
        compilationOutput.addPopupMenuItem(null, true);

        /* Checkbox */
        buttonBox.add(Box.createHorizontalGlue());
        JCheckBox hideButton = new JCheckBox("Hide compilation warnings", false);
        hideButton.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            Cooja.setExternalToolsSetting("HIDE_WARNINGS",
                "" + ((JCheckBox)e.getSource()).isSelected());
          };
        });
        buttonBox.add(Box.createHorizontalStrut(10));
        buttonBox.add(hideButton);

        /* Close on escape */
        AbstractAction closeAction = new AbstractAction(){
					private static final long serialVersionUID = 2646163984382201634L;
					public void actionPerformed(ActionEvent e) {
            dialog.dispose();
          }
        };
        InputMap inputMap = dialog.getRootPane().getInputMap(
            JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false), "close");
        dialog.getRootPane().getActionMap().put("close", closeAction);

        /* Layout */
        dialog.getContentPane().add(BorderLayout.CENTER, new JScrollPane(compilationOutput));
        dialog.getContentPane().add(BorderLayout.SOUTH, buttonBox);
        dialog.setSize(700, 500);
        dialog.setLocationRelativeTo(parent);
        dialog.setVisible(true);
        return true;
      }
    }.invokeAndWait();
  }

  /**
   * Runs work method in event dispatcher thread.
   * Worker method returns a value.
   *
   * @author Fredrik Osterlind
   */
  public static abstract class RunnableInEDT {
    private T val;

    /**
     * Work method to be implemented.
     *
     * @return Return value
     */
    public abstract T work();

    /**
     * Runs worker method in event dispatcher thread.
     *
     * @see #work()
     * @return Worker method return value
     */
    public T invokeAndWait() {
      if(java.awt.EventQueue.isDispatchThread()) {
        return RunnableInEDT.this.work();
      }

      try {
        java.awt.EventQueue.invokeAndWait(new Runnable() {
          public void run() {
            val = RunnableInEDT.this.work();
          }
        });
      } catch (InterruptedException e) {
        e.printStackTrace();
      } catch (InvocationTargetException e) {
        e.printStackTrace();
      }

      return val;
    }
  }

  /**
   * This method can be used by various different modules in the simulator to
   * indicate for example that a mote has been selected. All mote highlight
   * listeners will be notified. An example application of mote highlightinh is
   * a simulator visualizer that highlights the mote.
   *
   * @see #addMoteHighlightObserver(Observer)
   * @param m
   *          Mote to highlight
   */
  public void signalMoteHighlight(Mote m) {
    moteHighlightObservable.setChangedAndNotify(m);
  }

  /**
   * Adds directed relation between given motes.
   *
   * @param source Source mote
   * @param dest Destination mote
   */
  public void addMoteRelation(Mote source, Mote dest) {
    addMoteRelation(source, dest, null);
  }

  /**
   * Adds directed relation between given motes.
   *
   * @param source Source mote
   * @param dest Destination mote
   * @param color The color to use when visualizing the mote relation
   */
  public void addMoteRelation(Mote source, Mote dest, Color color) {
    if (source == null || dest == null) {
      return;
    }
    removeMoteRelation(source, dest); /* Unique relations */
    moteRelations.add(new MoteRelation(source, dest, color));
    moteRelationObservable.setChangedAndNotify();
  }

  /**
   * Removes the relations between given motes.
   *
   * @param source Source mote
   * @param dest Destination mote
   */
  public void removeMoteRelation(Mote source, Mote dest) {
    if (source == null || dest == null) {
      return;
    }
    MoteRelation[] arr = getMoteRelations();
    for (MoteRelation r: arr) {
      if (r.source == source && r.dest == dest) {
        moteRelations.remove(r);
        /* Relations are unique */
        moteRelationObservable.setChangedAndNotify();
        break;
      }
    }
  }

  /**
   * @return All current mote relations.
   *
   * @see #addMoteRelationsObserver(Observer)
   */
  public MoteRelation[] getMoteRelations() {
    return moteRelations.toArray(new MoteRelation[moteRelations.size()]);
  }

  /**
   * Adds mote relation observer.
   * Typically used by visualizer plugins.
   *
   * @param newObserver Observer
   */
  public void addMoteRelationsObserver(Observer newObserver) {
    moteRelationObservable.addObserver(newObserver);
  }

  /**
   * Removes mote relation observer.
   * Typically used by visualizer plugins.
   *
   * @param observer Observer
   */
  public void deleteMoteRelationsObserver(Observer observer) {
    moteRelationObservable.deleteObserver(observer);
  }

  /**
   * Tries to convert given file to be "portable".
   * The portable path is either relative to Contiki, or to the configuration (.csc) file.
   *
   * If this method fails, it returns the original file.
   *
   * @param file Original file
   * @return Portable file, or original file is conversion failed
   */
  public File createPortablePath(File file) {
    return createPortablePath(file, true);
  }

  public File createPortablePath(File file, boolean allowConfigRelativePaths) {
    File portable = null;

    portable = createContikiRelativePath(file);
    if (portable != null) {
      /*logger.info("Generated Contiki relative path '" + file.getPath() + "' to '" + portable.getPath() + "'");*/
      return portable;
    }

    if (allowConfigRelativePaths) {
      portable = createConfigRelativePath(file);
      if (portable != null) {
        /*logger.info("Generated config relative path '" + file.getPath() + "' to '" + portable.getPath() + "'");*/
        return portable;
      }
    }

    logger.warn("Path is not portable: '" + file.getPath());
    return file;
  }

  /**
   * Tries to restore a previously "portable" file to be "absolute".
   * If the given file already exists, no conversion is performed.
   *
   * @see #createPortablePath(File)
   * @param file Portable file
   * @return Absolute file
   */
  public File restorePortablePath(File file) {
    if (file == null || file.exists()) {
      /* No conversion possible/needed */
      return file;
    }

    File absolute = null;
    absolute = restoreContikiRelativePath(file);
    if (absolute != null) {
      /*logger.info("Restored Contiki relative path '" + file.getPath() + "' to '" + absolute.getPath() + "'");*/
      return absolute;
    }

    absolute = restoreConfigRelativePath(file);
    if (absolute != null) {
      /*logger.info("Restored config relative path '" + file.getPath() + "' to '" + absolute.getPath() + "'");*/
      return absolute;
    }

    /*logger.info("Portable path was not restored: '" + file.getPath());*/
    return file;
  }

  private final static String[][] PATH_IDENTIFIER = {
	  {"[CONTIKI_DIR]","PATH_CONTIKI",""},
	  {"[COOJA_DIR]","PATH_COOJA","/tools/cooja"},
	  {"[APPS_DIR]","PATH_APPS","/tools/cooja/apps"}
  };
  
  private File createContikiRelativePath(File file) {
    try {
    	int elem = PATH_IDENTIFIER.length;
    	File path[] = new File [elem];
    	String canonicals[] = new String[elem];
    	int match = -1;
    	int mlength = 0;
    	String fileCanonical = file.getCanonicalPath();
      
    	//No so nice, but goes along with GUI.getExternalToolsSetting
    	String defp = Cooja.getExternalToolsSetting("PATH_CONTIKI", null);
    	
    	
		for(int i = 0; i < elem; i++){
			path[i] = new File(Cooja.getExternalToolsSetting(PATH_IDENTIFIER[i][1], defp + PATH_IDENTIFIER[i][2]));			
			canonicals[i] = path[i].getCanonicalPath();
			if (fileCanonical.startsWith(canonicals[i])){
				if(mlength < canonicals[i].length()){
					mlength = canonicals[i].length();
					match = i;
				}
 
	    	}
		}
      
	    if(match == -1) return null;


	    /* Replace Contiki's canonical path with Contiki identifier */
        String portablePath = fileCanonical.replaceFirst(
          java.util.regex.Matcher.quoteReplacement(canonicals[match]), 
          java.util.regex.Matcher.quoteReplacement(PATH_IDENTIFIER[match][0]));
        File portable = new File(portablePath);
      
        /* Verify conversion */
        File verify = restoreContikiRelativePath(portable);
        if (verify == null || !verify.exists()) {
        	/* Error: did file even exist pre-conversion? */
        	return null;
        }

        return portable;
    } catch (IOException e1) {
      /*logger.warn("Error when converting to Contiki relative path: " + e1.getMessage());*/
      return null;
    }
  }
  
  
  private File restoreContikiRelativePath(File portable) {
  	int elem = PATH_IDENTIFIER.length;
  	File path = null;
	String canonical = null;
	
    try {
    	    	
    	String portablePath = portable.getPath();
    	
        int i = 0;
        //logger.info("PPATH: " + portablePath);
        
    	for(; i < elem; i++){
    		if (portablePath.startsWith(PATH_IDENTIFIER[i][0])) break;
    		
    	}
    	
    	
    	if(i == elem) return null;
    	//logger.info("Found: " + PATH_IDENTIFIER[i][0]);
    	
    	//No so nice, but goes along with GUI.getExternalToolsSetting
    	String defp = Cooja.getExternalToolsSetting("PATH_CONTIKI", null);
    	path = new File(Cooja.getExternalToolsSetting(PATH_IDENTIFIER[i][1], defp + PATH_IDENTIFIER[i][2]));
    	
    	//logger.info("Config: " + PATH_IDENTIFIER[i][1] + ", " + defp + PATH_IDENTIFIER[i][2] + " = " + path.toString());
		canonical = path.getCanonicalPath();
    	
		
    	File absolute = new File(portablePath.replace(PATH_IDENTIFIER[i][0], canonical));
		if(!absolute.exists()){
			logger.warn("Replaced " + portable  + " with " + absolute.toString() + " (default: "+ defp + PATH_IDENTIFIER[i][2] +"), but could not find it. This does not have to be an error, as the file might be created later.");
		}
    	     
      
    	return absolute;
    } catch (IOException e) {
    	return null;
    }
  }

  private final static String PATH_CONFIG_IDENTIFIER = "[CONFIG_DIR]";
  public File currentConfigFile = null; /* Used to generate config relative paths */
  private File createConfigRelativePath(File file) {
    String id = PATH_CONFIG_IDENTIFIER;
    if (currentConfigFile == null) {
      return null;
    }
    try {
      File configPath = currentConfigFile.getParentFile();
      if (configPath == null) {
        /* File is in current directory */
        configPath = new File("");
      }
      String configCanonical = configPath.getCanonicalPath();

      String fileCanonical = file.getCanonicalPath();
      if (!fileCanonical.startsWith(configCanonical)) {
        /* SPECIAL CASE: Allow one parent directory */
        File parent = new File(configCanonical).getParentFile();
        if (parent != null) {
          configCanonical = parent.getCanonicalPath();
          id += "/..";
        }
      }
      if (!fileCanonical.startsWith(configCanonical)) {
        /* SPECIAL CASE: Allow two parent directories */
        File parent = new File(configCanonical).getParentFile();
        if (parent != null) {
          configCanonical = parent.getCanonicalPath();
          id += "/..";
        }
      }
      if (!fileCanonical.startsWith(configCanonical)) {
        /* SPECIAL CASE: Allow three parent directories */
        File parent = new File(configCanonical).getParentFile();
        if (parent != null) {
          configCanonical = parent.getCanonicalPath();
          id += "/..";
        }
      }
      if (!fileCanonical.startsWith(configCanonical)) {
        /* File is not in a config subdirectory */
        /*logger.info("File is not in a config subdirectory: " + file.getAbsolutePath());*/
        return null;
      }

      /* Replace config's canonical path with config identifier */
      String portablePath = fileCanonical.replaceFirst(
          java.util.regex.Matcher.quoteReplacement(configCanonical),
          java.util.regex.Matcher.quoteReplacement(id));
      File portable = new File(portablePath);

      /* Verify conversion */
      File verify = restoreConfigRelativePath(portable);
      if (verify == null || !verify.exists()) {
        /* Error: did file even exist pre-conversion? */
        return null;
      }

      return portable;
    } catch (IOException e1) {
      /*logger.warn("Error when converting to config relative path: " + e1.getMessage());*/
      return null;
    }
  }
  private File restoreConfigRelativePath(File portable) {
    return restoreConfigRelativePath(currentConfigFile, portable);
  }
  public static File restoreConfigRelativePath(File configFile, File portable) {
    if (configFile == null) {
      return null;
    }
    File configPath = configFile.getParentFile();
    if (configPath == null) {
        /* File is in current directory */
        configPath = new File("");
    }
    String portablePath = portable.getPath();
    if (!portablePath.startsWith(PATH_CONFIG_IDENTIFIER)) {
      return null;
    }
    File absolute = new File(portablePath.replace(PATH_CONFIG_IDENTIFIER, configPath.getAbsolutePath()));
    return absolute;
  }

  private static JProgressBar PROGRESS_BAR = null;
  private static ArrayList PROGRESS_WARNINGS = new ArrayList();
  public static void setProgressMessage(String msg) {
    setProgressMessage(msg, MessageList.NORMAL);
  }
  public static void setProgressMessage(String msg, int type) {
    if (PROGRESS_BAR != null && PROGRESS_BAR.isShowing()) {
      PROGRESS_BAR.setString(msg);
      PROGRESS_BAR.setStringPainted(true);
    }
    if (type != MessageList.NORMAL) {
      PROGRESS_WARNINGS.add(msg);
    }
  }

  /**
   * Load quick help for given object or identifier. Note that this method does not
   * show the quick help pane.
   *
   * @param obj If string: help identifier. Else, the class name of the argument
   * is used as help identifier.
   */
  public void loadQuickHelp(final Object obj) {
    if (obj == null) {
      return;
    }

    String key;
    if (obj instanceof String) {
      key = (String) obj;
    } else {
      key = obj.getClass().getName();
    }

    String help = null;
    if (obj instanceof HasQuickHelp) {
      help = ((HasQuickHelp) obj).getQuickHelp();
    } else {
      if (quickHelpProperties == null) {
        /* Load quickhelp.txt */
        try {
          quickHelpProperties = new Properties();
          quickHelpProperties.load(new FileReader("quickhelp.txt"));
        } catch (Exception e) {
          quickHelpProperties = null;
          help = "Failed to read quickhelp.txt:

" + e.getMessage() + ""; } } if (quickHelpProperties != null) { help = quickHelpProperties.getProperty(key); } } if (help != null) { quickHelpTextPane.setText("" + help + ""); } else { quickHelpTextPane.setText( "" + getDescriptionOf(obj) +"" + "

No help available"); } quickHelpTextPane.setCaretPosition(0); } /* GUI actions */ abstract class GUIAction extends AbstractAction { private static final long serialVersionUID = 6946179457635198477L; public GUIAction(String name) { super(name); } public GUIAction(String name, int nmenomic) { this(name); putValue(Action.MNEMONIC_KEY, nmenomic); } public GUIAction(String name, KeyStroke accelerator) { this(name); putValue(Action.ACCELERATOR_KEY, accelerator); } public GUIAction(String name, int nmenomic, KeyStroke accelerator) { this(name, nmenomic); putValue(Action.ACCELERATOR_KEY, accelerator); } public abstract boolean shouldBeEnabled(); } GUIAction newSimulationAction = new GUIAction("New simulation...", KeyEvent.VK_N, KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK)) { private static final long serialVersionUID = 5053703908505299911L; public void actionPerformed(ActionEvent e) { cooja.doCreateSimulation(true); } public boolean shouldBeEnabled() { return true; } }; GUIAction closeSimulationAction = new GUIAction("Close simulation", KeyEvent.VK_C) { private static final long serialVersionUID = -4783032948880161189L; public void actionPerformed(ActionEvent e) { cooja.doRemoveSimulation(true); } public boolean shouldBeEnabled() { return getSimulation() != null; } }; GUIAction reloadSimulationAction = new GUIAction("Reload with same random seed", KeyEvent.VK_K, KeyStroke.getKeyStroke(KeyEvent.VK_R, ActionEvent.CTRL_MASK)) { private static final long serialVersionUID = 66579555555421977L; public void actionPerformed(ActionEvent e) { if (getSimulation() == null) { /* Reload last opened simulation */ final File file = getLastOpenedFile(); new Thread(new Runnable() { public void run() { cooja.doLoadConfig(true, true, file, null); } }).start(); return; } /* Reload current simulation */ long seed = getSimulation().getRandomSeed(); reloadCurrentSimulation(getSimulation().isRunning(), seed); } public boolean shouldBeEnabled() { return true; } }; GUIAction reloadRandomSimulationAction = new GUIAction("Reload with new random seed", KeyEvent.VK_N, KeyStroke.getKeyStroke(KeyEvent.VK_R, ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK)) { private static final long serialVersionUID = -4494402222740250203L; public void actionPerformed(ActionEvent e) { /* Replace seed before reloading */ if (getSimulation() != null) { getSimulation().setRandomSeed(getSimulation().getRandomSeed()+1); reloadSimulationAction.actionPerformed(null); } } public boolean shouldBeEnabled() { return getSimulation() != null; } }; GUIAction saveSimulationAction = new GUIAction("Save simulation as...", KeyEvent.VK_S) { private static final long serialVersionUID = 1132582220401954286L; public void actionPerformed(ActionEvent e) { cooja.doSaveConfig(true); } public boolean shouldBeEnabled() { if (isVisualizedInApplet()) { return false; } return getSimulation() != null; } }; /* GUIAction closePluginsAction = new GUIAction("Close all plugins") { private static final long serialVersionUID = -37575622808266989L; public void actionPerformed(ActionEvent e) { Object[] plugins = startedPlugins.toArray(); for (Object plugin : plugins) { removePlugin((Plugin) plugin, false); } } public boolean shouldBeEnabled() { return !startedPlugins.isEmpty(); } };*/ GUIAction exportExecutableJARAction = new GUIAction("Export simulation...") { private static final long serialVersionUID = -203601967460630049L; public void actionPerformed(ActionEvent e) { getSimulation().stopSimulation(); /* Info message */ String[] options = new String[] { "OK", "Cancel" }; int n = JOptionPane.showOptionDialog( Cooja.getTopParentContainer(), "This function attempts to build an executable Cooja JAR from the current simulation.\n" + "The JAR will contain all simulation dependencies, including extension JAR files and mote firmware files.\n" + "\nExecutable simulations can be used to run already prepared simulations on several computers.\n" + "\nThis is an experimental feature.", "Export simulation to executable JAR", JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE, null, options, options[0]); if (n != JOptionPane.OK_OPTION) { return; } /* Select output file */ JFileChooser fc = new JFileChooser(); FileFilter jarFilter = new FileFilter() { public boolean accept(File file) { if (file.isDirectory()) { return true; } if (file.getName().endsWith(".jar")) { return true; } return false; } public String getDescription() { return "Java archive"; } public String toString() { return ".jar"; } }; fc.setFileFilter(jarFilter); File suggest = new File(getExternalToolsSetting("EXECUTE_JAR_LAST", "cooja_simulation.jar")); fc.setSelectedFile(suggest); int returnVal = fc.showSaveDialog(Cooja.getTopParentContainer()); if (returnVal != JFileChooser.APPROVE_OPTION) { return; } File outputFile = fc.getSelectedFile(); if (outputFile.exists()) { options = new String[] { "Overwrite", "Cancel" }; n = JOptionPane.showOptionDialog( Cooja.getTopParentContainer(), "A file with the same name already exists.\nDo you want to remove it?", "Overwrite existing file?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); if (n != JOptionPane.YES_OPTION) { return; } outputFile.delete(); } final File finalOutputFile = outputFile; setExternalToolsSetting("EXECUTE_JAR_LAST", outputFile.getPath()); new Thread() { public void run() { try { ExecuteJAR.buildExecutableJAR(Cooja.this, finalOutputFile); } catch (RuntimeException ex) { JOptionPane.showMessageDialog(Cooja.getTopParentContainer(), ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); } } }.start(); } public boolean shouldBeEnabled() { return getSimulation() != null; } }; GUIAction exitCoojaAction = new GUIAction("Exit", 'x') { private static final long serialVersionUID = 7523822251658687665L; public void actionPerformed(ActionEvent e) { cooja.doQuit(true); } public boolean shouldBeEnabled() { if (isVisualizedInApplet()) { return false; } return true; } }; GUIAction startStopSimulationAction = new GUIAction("Start simulation", KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK)) { private static final long serialVersionUID = 6750107157493939710L; public void actionPerformed(ActionEvent e) { /* Start/Stop current simulation */ Simulation s = getSimulation(); if (s == null) { return; } if (s.isRunning()) { s.stopSimulation(); } else { s.startSimulation(); } } public void setEnabled(boolean newValue) { if (getSimulation() == null) { putValue(NAME, "Start simulation"); } else if (getSimulation().isRunning()) { putValue(NAME, "Pause simulation"); } else { putValue(NAME, "Start simulation"); } super.setEnabled(newValue); } public boolean shouldBeEnabled() { return getSimulation() != null && getSimulation().isRunnable(); } }; class StartPluginGUIAction extends GUIAction { private static final long serialVersionUID = 7368495576372376196L; public StartPluginGUIAction(String name) { super(name); } public void actionPerformed(final ActionEvent e) { new Thread(new Runnable() { public void run() { Class pluginClass = (Class) ((JMenuItem) e.getSource()).getClientProperty("class"); Mote mote = (Mote) ((JMenuItem) e.getSource()).getClientProperty("mote"); tryStartPlugin(pluginClass, cooja, mySimulation, mote); } }).start(); } public boolean shouldBeEnabled() { return getSimulation() != null; } } GUIAction removeAllMotesAction = new GUIAction("Remove all motes") { private static final long serialVersionUID = 4709776747913364419L; public void actionPerformed(ActionEvent e) { Simulation s = getSimulation(); if (s.isRunning()) { s.stopSimulation(); } while (s.getMotesCount() > 0) { s.removeMote(getSimulation().getMote(0)); } } public boolean shouldBeEnabled() { Simulation s = getSimulation(); return s != null && s.getMotesCount() > 0; } }; GUIAction showQuickHelpAction = new GUIAction("Quick help", KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0)) { private static final long serialVersionUID = 3151729036597971681L; public void actionPerformed(ActionEvent e) { if (!(e.getSource() instanceof JCheckBoxMenuItem)) { return; } boolean show = ((JCheckBoxMenuItem) e.getSource()).isSelected(); quickHelpTextPane.setVisible(show); quickHelpScroll.setVisible(show); setExternalToolsSetting("SHOW_QUICKHELP", new Boolean(show).toString()); ((JPanel)frame.getContentPane()).revalidate(); updateDesktopSize(getDesktopPane()); } public boolean shouldBeEnabled() { return true; } }; GUIAction showGettingStartedAction = new GUIAction("Getting started") { private static final long serialVersionUID = 2382848024856978524L; public void actionPerformed(ActionEvent e) { loadQuickHelp("GETTING_STARTED"); JCheckBoxMenuItem checkBox = ((JCheckBoxMenuItem)showQuickHelpAction.getValue("checkbox")); if (checkBox == null) { return; } if (checkBox.isSelected()) { return; } checkBox.doClick(); } public boolean shouldBeEnabled() { return true; } }; GUIAction showKeyboardShortcutsAction = new GUIAction("Keyboard shortcuts") { private static final long serialVersionUID = 2382848024856978524L; public void actionPerformed(ActionEvent e) { loadQuickHelp("KEYBOARD_SHORTCUTS"); JCheckBoxMenuItem checkBox = ((JCheckBoxMenuItem)showQuickHelpAction.getValue("checkbox")); if (checkBox == null) { return; } if (checkBox.isSelected()) { return; } checkBox.doClick(); } public boolean shouldBeEnabled() { return true; } }; GUIAction showBufferSettingsAction = new GUIAction("Buffer sizes...") { private static final long serialVersionUID = 7018661735211901837L; public void actionPerformed(ActionEvent e) { if (mySimulation == null) { return; } BufferSettings.showDialog(myDesktopPane, mySimulation); } public boolean shouldBeEnabled() { return mySimulation != null; } }; }