4746 lines
156 KiB
Java
4746 lines
156 KiB
Java
/*
|
|
* Copyright (c) 2006, Swedish Institute of Computer Science. All rights
|
|
* reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer. 2. Redistributions in
|
|
* binary form must reproduce the above copyright notice, this list of
|
|
* conditions and the following disclaimer in the documentation and/or other
|
|
* materials provided with the distribution. 3. Neither the name of the
|
|
* Institute nor the names of its contributors may be used to endorse or promote
|
|
* products derived from this software without specific prior written
|
|
* permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
package org.contikios.cooja;
|
|
|
|
import java.awt.BorderLayout;
|
|
import java.awt.Color;
|
|
import java.awt.Component;
|
|
import java.awt.Container;
|
|
import java.awt.Dialog;
|
|
import java.awt.Dialog.ModalityType;
|
|
import java.awt.Dimension;
|
|
import java.awt.Frame;
|
|
import java.awt.GraphicsDevice;
|
|
import java.awt.GraphicsEnvironment;
|
|
import java.awt.GridLayout;
|
|
import java.awt.Point;
|
|
import java.awt.Rectangle;
|
|
import java.awt.Window;
|
|
import java.awt.event.ActionEvent;
|
|
import java.awt.event.ActionListener;
|
|
import java.awt.event.ComponentAdapter;
|
|
import java.awt.event.ComponentEvent;
|
|
import java.awt.event.KeyEvent;
|
|
import java.awt.event.WindowAdapter;
|
|
import java.awt.event.WindowEvent;
|
|
import java.beans.PropertyVetoException;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.FileOutputStream;
|
|
import java.io.FileReader;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.io.PrintStream;
|
|
import java.io.StringReader;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.net.URL;
|
|
import java.net.URLClassLoader;
|
|
import java.security.AccessControlException;
|
|
import java.text.DecimalFormat;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Enumeration;
|
|
import java.util.List;
|
|
import java.util.Observable;
|
|
import java.util.Observer;
|
|
import java.util.Properties;
|
|
import java.util.Vector;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
import java.util.zip.GZIPInputStream;
|
|
import java.util.zip.GZIPOutputStream;
|
|
|
|
import javax.swing.AbstractAction;
|
|
import javax.swing.Action;
|
|
import javax.swing.BorderFactory;
|
|
import javax.swing.Box;
|
|
import javax.swing.DefaultDesktopManager;
|
|
import javax.swing.InputMap;
|
|
import javax.swing.JApplet;
|
|
import javax.swing.JButton;
|
|
import javax.swing.JCheckBox;
|
|
import javax.swing.JCheckBoxMenuItem;
|
|
import javax.swing.JComponent;
|
|
import javax.swing.JDesktopPane;
|
|
import javax.swing.JDialog;
|
|
import javax.swing.JFileChooser;
|
|
import javax.swing.JFrame;
|
|
import javax.swing.JInternalFrame;
|
|
import javax.swing.JLabel;
|
|
import javax.swing.JMenu;
|
|
import javax.swing.JMenuBar;
|
|
import javax.swing.JMenuItem;
|
|
import javax.swing.JOptionPane;
|
|
import javax.swing.JPanel;
|
|
import javax.swing.JProgressBar;
|
|
import javax.swing.JScrollPane;
|
|
import javax.swing.JSeparator;
|
|
import javax.swing.JTabbedPane;
|
|
import javax.swing.JTextPane;
|
|
import javax.swing.KeyStroke;
|
|
import javax.swing.SwingUtilities;
|
|
import javax.swing.ToolTipManager;
|
|
import javax.swing.UIManager;
|
|
import javax.swing.UIManager.LookAndFeelInfo;
|
|
import javax.swing.UnsupportedLookAndFeelException;
|
|
import javax.swing.event.MenuEvent;
|
|
import javax.swing.event.MenuListener;
|
|
import javax.swing.filechooser.FileFilter;
|
|
|
|
import org.apache.log4j.BasicConfigurator;
|
|
import org.apache.log4j.Logger;
|
|
import org.apache.log4j.xml.DOMConfigurator;
|
|
import org.jdom.Document;
|
|
import org.jdom.Element;
|
|
import org.jdom.JDOMException;
|
|
import org.jdom.input.SAXBuilder;
|
|
import org.jdom.output.Format;
|
|
import org.jdom.output.XMLOutputter;
|
|
import org.contikios.cooja.MoteType.MoteTypeCreationException;
|
|
import org.contikios.cooja.VisPlugin.PluginRequiresVisualizationException;
|
|
import org.contikios.cooja.contikimote.ContikiMoteType;
|
|
import org.contikios.cooja.dialogs.AddMoteDialog;
|
|
import org.contikios.cooja.dialogs.BufferSettings;
|
|
import org.contikios.cooja.dialogs.ConfigurationWizard;
|
|
import org.contikios.cooja.dialogs.CreateSimDialog;
|
|
import org.contikios.cooja.dialogs.ExternalToolsDialog;
|
|
import org.contikios.cooja.dialogs.MessageList;
|
|
import org.contikios.cooja.dialogs.ProjectDirectoriesDialog;
|
|
import org.contikios.cooja.plugins.MoteTypeInformation;
|
|
import org.contikios.cooja.plugins.ScriptRunner;
|
|
import org.contikios.cooja.plugins.SimControl;
|
|
import org.contikios.cooja.plugins.SimInformation;
|
|
import org.contikios.cooja.util.ExecuteJAR;
|
|
|
|
/**
|
|
* Main file of COOJA Simulator. Typically contains a visualizer for the
|
|
* simulator, but can also be started without visualizer.
|
|
*
|
|
* This class loads external Java classes (in extension directories), and handles the
|
|
* COOJA plugins as well as the configuration system. If provides a number of
|
|
* help methods for the rest of the COOJA system, and is the starting point for
|
|
* loading and saving simulation configs.
|
|
*
|
|
* @author Fredrik Osterlind
|
|
*/
|
|
public class Cooja extends Observable {
|
|
private static JFrame frame = null;
|
|
private static JApplet applet = null;
|
|
private static final long serialVersionUID = 1L;
|
|
private static Logger logger = Logger.getLogger(Cooja.class);
|
|
|
|
/**
|
|
* External tools configuration.
|
|
*/
|
|
public static final String EXTERNAL_TOOLS_SETTINGS_FILENAME = "/external_tools.config";
|
|
|
|
/**
|
|
* External tools default Win32 settings filename.
|
|
*/
|
|
public static final String EXTERNAL_TOOLS_WIN32_SETTINGS_FILENAME = "/external_tools_win32.config";
|
|
|
|
/**
|
|
* External tools default Mac OS X settings filename.
|
|
*/
|
|
public static final String EXTERNAL_TOOLS_MACOSX_SETTINGS_FILENAME = "/external_tools_macosx.config";
|
|
|
|
/**
|
|
* External tools default FreeBSD settings filename.
|
|
*/
|
|
public static final String EXTERNAL_TOOLS_FREEBSD_SETTINGS_FILENAME = "/external_tools_freebsd.config";
|
|
|
|
/**
|
|
* External tools default Linux/Unix settings filename.
|
|
*/
|
|
public static final String EXTERNAL_TOOLS_LINUX_SETTINGS_FILENAME = "/external_tools_linux.config";
|
|
|
|
/**
|
|
* External tools default Linux/Unix settings filename for 64 bit architectures.
|
|
* Tested on Intel 64-bit Gentoo Linux.
|
|
*/
|
|
public static final String EXTERNAL_TOOLS_LINUX_64_SETTINGS_FILENAME = "/external_tools_linux_64.config";
|
|
|
|
/**
|
|
* External tools user settings filename.
|
|
*/
|
|
public static final String EXTERNAL_TOOLS_USER_SETTINGS_FILENAME = ".cooja.user.properties";
|
|
public static File externalToolsUserSettingsFile;
|
|
private static boolean externalToolsUserSettingsFileReadOnly = false;
|
|
|
|
private static String specifiedContikiPath = null;
|
|
|
|
/**
|
|
* Logger settings filename.
|
|
*/
|
|
public static final String LOG_CONFIG_FILE = "log4j_config.xml";
|
|
|
|
/**
|
|
* Default extension configuration filename.
|
|
*/
|
|
public static String PROJECT_DEFAULT_CONFIG_FILENAME = null;
|
|
|
|
/**
|
|
* User extension configuration filename.
|
|
*/
|
|
public static final String PROJECT_CONFIG_FILENAME = "cooja.config";
|
|
|
|
/**
|
|
* File filter only showing saved simulations files (*.csc).
|
|
*/
|
|
public static final FileFilter SAVED_SIMULATIONS_FILES = new FileFilter() {
|
|
public boolean accept(File file) {
|
|
if (file.isDirectory()) {
|
|
return true;
|
|
}
|
|
|
|
if (file.getName().endsWith(".csc")) {
|
|
return true;
|
|
}
|
|
if (file.getName().endsWith(".csc.gz")) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
public String getDescription() {
|
|
return "Cooja simulation (.csc, .csc.gz)";
|
|
}
|
|
public String toString() {
|
|
return ".csc";
|
|
}
|
|
};
|
|
|
|
// External tools setting names
|
|
public static Properties defaultExternalToolsSettings;
|
|
public static Properties currentExternalToolsSettings;
|
|
|
|
private static final String externalToolsSettingNames[] = new String[] {
|
|
"PATH_CONTIKI", "PATH_COOJA_CORE_RELATIVE","PATH_COOJA","PATH_APPS",
|
|
"PATH_APPSEARCH",
|
|
|
|
"PATH_MAKE",
|
|
"PATH_SHELL",
|
|
"PATH_C_COMPILER", "COMPILER_ARGS",
|
|
"PATH_LINKER", "LINK_COMMAND_1", "LINK_COMMAND_2",
|
|
"PATH_AR", "AR_COMMAND_1", "AR_COMMAND_2",
|
|
"PATH_OBJDUMP", "OBJDUMP_ARGS",
|
|
"PATH_OBJCOPY",
|
|
"PATH_JAVAC",
|
|
|
|
"CONTIKI_STANDARD_PROCESSES",
|
|
|
|
"CMD_GREP_PROCESSES", "REGEXP_PARSE_PROCESSES",
|
|
"CMD_GREP_INTERFACES", "REGEXP_PARSE_INTERFACES",
|
|
"CMD_GREP_SENSORS", "REGEXP_PARSE_SENSORS",
|
|
|
|
"DEFAULT_PROJECTDIRS",
|
|
"CORECOMM_TEMPLATE_FILENAME",
|
|
|
|
"PARSE_WITH_COMMAND",
|
|
|
|
"MAPFILE_DATA_START", "MAPFILE_DATA_SIZE",
|
|
"MAPFILE_BSS_START", "MAPFILE_BSS_SIZE",
|
|
"MAPFILE_COMMON_START", "MAPFILE_COMMON_SIZE",
|
|
"MAPFILE_VAR_NAME",
|
|
"MAPFILE_VAR_ADDRESS_1", "MAPFILE_VAR_ADDRESS_2",
|
|
"MAPFILE_VAR_SIZE_1", "MAPFILE_VAR_SIZE_2",
|
|
|
|
"PARSE_COMMAND",
|
|
"COMMAND_VAR_NAME_ADDRESS_SIZE",
|
|
"COMMAND_DATA_START", "COMMAND_DATA_END",
|
|
"COMMAND_BSS_START", "COMMAND_BSS_END",
|
|
"COMMAND_COMMON_START", "COMMAND_COMMON_END",
|
|
|
|
"HIDE_WARNINGS"
|
|
};
|
|
|
|
private static final int FRAME_NEW_OFFSET = 30;
|
|
|
|
private static final int FRAME_STANDARD_WIDTH = 150;
|
|
|
|
private static final int FRAME_STANDARD_HEIGHT = 300;
|
|
|
|
private static final String WINDOW_TITLE = "Cooja: The Contiki Network Simulator";
|
|
|
|
private Cooja cooja;
|
|
|
|
private Simulation mySimulation;
|
|
|
|
protected GUIEventHandler guiEventHandler = new GUIEventHandler();
|
|
|
|
private JMenu menuMoteTypeClasses, menuMoteTypes;
|
|
|
|
private JMenu menuOpenSimulation;
|
|
private boolean hasFileHistoryChanged;
|
|
|
|
private Vector<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 class HighlightObservable extends Observable {
|
|
private void setChangedAndNotify(Mote mote) {
|
|
setChanged();
|
|
notifyObservers(mote);
|
|
}
|
|
}
|
|
private HighlightObservable moteHighlightObservable = new HighlightObservable();
|
|
|
|
private class MoteRelationsObservable extends Observable {
|
|
private void setChangedAndNotify() {
|
|
setChanged();
|
|
notifyObservers();
|
|
}
|
|
}
|
|
private MoteRelationsObservable moteRelationObservable = new MoteRelationsObservable();
|
|
|
|
private JTextPane quickHelpTextPane;
|
|
private JScrollPane quickHelpScroll;
|
|
private Properties quickHelpProperties = null; /* quickhelp.txt */
|
|
|
|
/**
|
|
* Mote relation (directed).
|
|
*/
|
|
public static class MoteRelation {
|
|
public Mote source;
|
|
public Mote dest;
|
|
public Color color;
|
|
public MoteRelation(Mote source, Mote dest, Color color) {
|
|
this.source = source;
|
|
this.dest = dest;
|
|
this.color = color;
|
|
}
|
|
}
|
|
private ArrayList<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();
|
|
outputter.setFormat(Format.getPrettyFormat());
|
|
outputter.output(doc, out);
|
|
out.close();
|
|
|
|
logger.info("Saved to file: " + file.getAbsolutePath());
|
|
} catch (Exception e) {
|
|
logger.warn("Exception while saving simulation config: " + e);
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
public Element extractSimulationConfig() {
|
|
// Create simulation config
|
|
Element root = new Element("simconf");
|
|
|
|
/* Store extension directories meta data */
|
|
for (COOJAProject project: currentProjects) {
|
|
Element projectElement = new Element("project");
|
|
projectElement.addContent(createPortablePath(project.dir).getPath().replaceAll("\\\\", "/"));
|
|
projectElement.setAttribute("EXPORT", "discard");
|
|
root.addContent(projectElement);
|
|
}
|
|
|
|
Element simulationElement = new Element("simulation");
|
|
simulationElement.addContent(mySimulation.getConfigXML());
|
|
root.addContent(simulationElement);
|
|
|
|
// Create started plugins config
|
|
Collection<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;
|
|
}
|
|
};
|
|
|
|
}
|
|
|