/*
 * 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;
import org.contikios.cooja.util.ScnObservable;

/**
 * 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<Class<? extends Plugin>> menuMotePluginClasses = new Vector<Class<? extends Plugin>>();

  private JDesktopPane myDesktopPane;

  private Vector<Plugin> startedPlugins = new Vector<Plugin>();

  private ArrayList<GUIAction> guiActions = new ArrayList<GUIAction>();

  // Platform configuration variables
  // Maintained via method reparseProjectConfig()
  private ProjectConfig projectConfig;

  private ArrayList<COOJAProject> currentProjects = new ArrayList<COOJAProject>();

  public ClassLoader projectDirClassLoader;

  private Vector<Class<? extends MoteType>> moteTypeClasses = new Vector<Class<? extends MoteType>>();

  private Vector<Class<? extends Plugin>> pluginClasses = new Vector<Class<? extends Plugin>>();

  private Vector<Class<? extends RadioMedium>> radioMediumClasses = new Vector<Class<? extends RadioMedium>>();

  private Vector<Class<? extends Positioner>> positionerClasses = new Vector<Class<? extends Positioner>>();


  private ScnObservable moteHighlightObservable = new ScnObservable();

  private ScnObservable moteRelationObservable = new ScnObservable();

  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<MoteRelation> moteRelations = new ArrayList<MoteRelation>();

  /**
   * 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<? extends Plugin> 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<? extends MoteType> 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<? extends Plugin>) pluginClass, cooja, getSimulation(), (Mote)mote);
        }
      };
      private JMenuItem createMenuItem(Class<? extends Plugin> newPluginClass, int pluginType) {
        String description = getDescriptionOf(newPluginClass);
        JMenuItem menuItem = new JMenuItem(description + "...");
        menuItem.putClientProperty("class", newPluginClass);
        menuItem.addActionListener(menuItemListener);

        String tooltip = "<html><pre>";
        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<? extends RadioMedium>[] radioMediums = newPluginClass.getAnnotation(SupportedArguments.class).radioMediums();
            for (Class<? extends Object> 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 += "</html>";
        /*menuItem.setToolTipText(tooltip);*/
        return menuItem;
      }

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

        /* Cooja plugins */
        boolean hasCoojaPlugins = false;
        for (Class<? extends Plugin> 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<? extends Plugin> 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<? extends Plugin> 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<? extends MoteType> moteTypeClass) {
    moteTypeClasses.add(moteTypeClass);
  }

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

  /**
   * @return All registered mote type classes
   */
  public Vector<Class<? extends MoteType>> getRegisteredMoteTypes() {
    return moteTypeClasses;
  }

  /**
   * Register new positioner class.
   *
   * @param positionerClass
   *          Class to register
   * @return True if class was registered
   */
  public boolean registerPositioner(Class<? extends Positioner> 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<Class<? extends Positioner>> getRegisteredPositioners() {
    return positionerClasses;
  }

  /**
   * Register new radio medium class.
   *
   * @param radioMediumClass
   *          Class to register
   * @return True if class was registered
   */
  public boolean registerRadioMedium(
      Class<? extends RadioMedium> 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<Class<? extends RadioMedium>> 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<? extends MoteType> 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<? extends Plugin> 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<? extends Positioner> 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<? extends RadioMedium> 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<Boolean>() {
      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.
   * <p>
   * 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<Boolean>() {
      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<? extends Plugin> 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<? extends Plugin> pluginClass,
      final Cooja argGUI, final Simulation argSimulation, final Mote argMote) {
    return tryStartPlugin(pluginClass, argGUI, argSimulation, argMote, true);
  }

  public Plugin startPlugin(final Class<? extends Plugin> 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<? extends Plugin> 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<? extends Plugin> 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<? extends Plugin> 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<? extends Plugin> motePluginClass, Mote mote) {
    if (motePluginClass.getAnnotation(SupportedArguments.class) == null) {
      return true;
    }

    /* Check mote interfaces */
    boolean moteInterfacesOK = true;
    Class<? extends MoteInterface>[] 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<? extends MoteInterface> requiredMoteInterface: moteInterfaces) {
      moteTypeInterfacesError.append(getDescriptionOf(requiredMoteInterface) + "\n");
      if (mote.getInterfaces().getInterfaceOfType(requiredMoteInterface) == null) {
        moteInterfacesOK = false;
      }
    }

    /* Check mote type */
    boolean moteTypeOK = false;
    Class<? extends Mote>[] 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<? extends Mote> supportedMote: motes) {
      moteTypeError.append(getDescriptionOf(supportedMote) + "\n");
      if (supportedMote.isAssignableFrom(mote.getClass())) {
        moteTypeOK = true;
      }
    }

    /*if (!moteInterfacesOK) {
      menuItem.setToolTipText(
          "<html><pre>" + moteTypeInterfacesError + "</html>"
      );
    }
    if (!moteTypeOK) {
      menuItem.setToolTipText(
          "<html><pre>" + moteTypeError + "</html>"
      );
    }*/

    return moteInterfacesOK && moteTypeOK;
  }

  public JMenu createMotePluginsSubmenu(Class<? extends Plugin> 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<? extends Plugin>) 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<? extends Plugin> 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<? extends Plugin> 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<? extends MoteType> 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<? extends MoteType> 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<Boolean>() {
        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<File>() {
        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<JDialog>() {
        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<Element> 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<Mote> 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<Object> 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<? extends MoteType>) ((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 <N> 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 <N extends Object> Class<? extends N> tryLoadClass(
      Object callingObject, Class<N> 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<COOJAProject> 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<COOJAProject> projects)
  throws ClassLoaderCreationException {
    if (projects == null || projects.isEmpty()) {
      return parent;
    }

    /* Create class loader from JARs */
    ArrayList<URL> urls = new ArrayList<URL>();
    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<? extends Object> 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<? extends MoteType> 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]*<identifier>([^<]*)</identifier>";
      Matcher matcher = Pattern.compile(identifierExtraction).matcher(configString);
      while (matcher.find()) {
        moteTypeIDMappings.setProperty(matcher.group(1), "");
      }

      /* Create old to new identifier mappings */
      Enumeration<Object> existingIdentifiers = moteTypeIDMappings.keys();
      while (existingIdentifiers.hasMoreElements()) {
        String existingIdentifier = (String) existingIdentifiers.nextElement();
        MoteType[] existingMoteTypes = null;
        if (mySimulation != null) {
          existingMoteTypes = mySimulation.getMoteTypes();
        }
        ArrayList<Object> reserved = new ArrayList<Object>();
        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(
            "<identifier>" + existingIdentifier + "</identifier>",
            "<identifier>" + moteTypeIDMappings.get(existingIdentifier) + "</identifier>");
        configString = configString.replaceAll(
            "<motetype_identifier>" + existingIdentifier + "</motetype_identifier>",
            "<motetype_identifier>" + moteTypeIDMappings.get(existingIdentifier) + "</motetype_identifier>");
      }

      /* 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<Element> 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();
      Format fmt = Format.getPrettyFormat();
      fmt.setLineSeparator("\n");
      outputter.setFormat(fmt);
      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<Element> pluginsConfig = getPluginsConfigXML();
    if (pluginsConfig != null) {
      root.addContent(pluginsConfig);
    }

    return root;
  }

  /**
   * Returns started plugins config.
   *
   * @return Config or null
   */
  public Collection<Element> getPluginsConfigXML() {
    ArrayList<Element> config = new ArrayList<Element>();
    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<Element> 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<Element> 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<Element> 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<? extends Plugin> 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<Element>) 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<Element>) 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<Boolean>() {
          public Boolean work() {
            Dimension size = new Dimension(100, 100);
            Point location = new Point(100, 100);

            for (Element pluginSubElement : (List<Element>) 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<Boolean>() {
      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<Boolean>() {
      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<T> {
    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<String> PROGRESS_WARNINGS = new ArrayList<String>();
  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 = "<html><b>Failed to read quickhelp.txt:</b><p>" + e.getMessage() + "</html>";
        }
      }

      if (quickHelpProperties != null) {
        help = quickHelpProperties.getProperty(key);
      }
    }

    if (help != null) {
      quickHelpTextPane.setText("<html>" + help + "</html>");
    } else {
      quickHelpTextPane.setText(
          "<html><b>" + getDescriptionOf(obj) +"</b>" +
          "<p>No help available</html>");
    }
    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<Plugin> pluginClass =
            (Class<Plugin>) ((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;
    }
  };

}

