From 7922108cbd466b84a91e4765b58fab1800a47bbd Mon Sep 17 00:00:00 2001 From: nifi Date: Sun, 24 Jan 2010 20:06:16 +0000 Subject: [PATCH] added support for multi-class application motes --- .../cooja/dialogs/ImportAppMoteDialog.java | 297 ++++++++++++++++++ .../sics/cooja/motes/ImportAppMoteType.java | 266 ++++++++++------ 2 files changed, 459 insertions(+), 104 deletions(-) create mode 100644 tools/cooja/java/se/sics/cooja/dialogs/ImportAppMoteDialog.java diff --git a/tools/cooja/java/se/sics/cooja/dialogs/ImportAppMoteDialog.java b/tools/cooja/java/se/sics/cooja/dialogs/ImportAppMoteDialog.java new file mode 100644 index 000000000..39404832a --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/dialogs/ImportAppMoteDialog.java @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2010, 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. + * + * $Id: ImportAppMoteDialog.java,v 1.1 2010/01/24 20:06:16 nifi Exp $ + */ + +package se.sics.cooja.dialogs; +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.io.FileNotFoundException; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.KeyStroke; +import javax.swing.filechooser.FileFilter; +import se.sics.cooja.GUI; +import se.sics.cooja.Mote; +import se.sics.cooja.Simulation; +import se.sics.cooja.motes.ImportAppMoteType; + +/** + * Java Application Mote Type compile dialog. + * + * @author Niclas Finne + */ +public class ImportAppMoteDialog extends JDialog { + + private static final long serialVersionUID = 1323772528826214008L; + protected final static Dimension LABEL_DIMENSION = new Dimension(170, 25); + + private static String lastPath; + private static String lastFile; + + private JTextField descriptionField; + private JTextField pathField; + private JTextField classField; + private JButton cancelButton; + private JButton createButton; + private boolean hasSelected = false; + + public ImportAppMoteDialog(Container parent, final Simulation simulation, final ImportAppMoteType moteType) { + super((Window)parent, "Create Mote Type: Application Mote", ModalityType.APPLICATION_MODAL); + + JPanel mainPanel = new JPanel(new BorderLayout()); + + JPanel topPanel = new JPanel(); + topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS)); + topPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + descriptionField = new JTextField(40); + if (moteType.getDescription() != null) { + descriptionField.setText(moteType.getDescription()); + } else { + descriptionField.setText("[enter mote type description]"); + } + topPanel.add(createPanel("Description", descriptionField)); + + pathField = new JTextField(40); + File moteClassPath = moteType.getMoteClassPath(); + if (moteClassPath != null) { + pathField.setText(moteClassPath.getPath()); + } else if (lastPath != null) { + pathField.setText(lastPath); + } + topPanel.add(createPanel("Java Class Path:", pathField)); + + classField = new JTextField(40); + String moteClass = moteType.getMoteClassName(); + if (moteClass != null && moteClass.length() > 0) { + classField.setText(moteClass); + } else if (lastFile != null) { + classField.setText(lastFile); + } + + JButton browseButton = new JButton("Browse"); + browseButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JFileChooser fc = new JFileChooser(); + + String path = pathField.getText(); + String name = classField.getText(); + if (name.indexOf('/') >= 0 || name.indexOf(File.separatorChar) >= 0) { + // Already full path + fc.setSelectedFile(new File(name)); + } else if (name.length() > 0) { + fc.setSelectedFile(new File(new File(path), name.replace(".", "/") + ".class")); + } else { + File fp = simulation.getGUI() + .restorePortablePath(new File(GUI.getExternalToolsSetting("IMPORT_APP_LAST", + "mymote.class"))); + if (path.length() > 0 && !fp.getAbsolutePath().startsWith(path)) { + fc.setCurrentDirectory(new File(path)); + } else { + fc.setSelectedFile(fp); + } + } + + fc.setFileSelectionMode(JFileChooser.FILES_ONLY); + fc.addChoosableFileFilter(new FileFilter() { + public boolean accept(File f) { + if (f.isDirectory()) { + return true; + } + String filename = f.getName(); + if (filename != null && filename.endsWith(".class")) { + return true; + } + return false; + } + + public String getDescription() { + return "Application Mote Java Class"; + } + }); + fc.setDialogTitle("Select Application Mote Java Class"); + + if (fc.showOpenDialog(ImportAppMoteDialog.this) == JFileChooser.APPROVE_OPTION) { + File fp = fc.getSelectedFile(); + if (fp != null) { + trySetClass(simulation, moteType, fp); + } + } + } + }); + topPanel.add(createPanel("Application Mote Java Class:", classField, browseButton)); + + ActionListener cancelAction = new ActionListener() { + + public void actionPerformed(ActionEvent e) { + setVisible(false); + dispose(); + } + }; + cancelButton = new JButton("Cancel"); + cancelButton.addActionListener(cancelAction); + + KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); + getRootPane().registerKeyboardAction(cancelAction, stroke, JComponent.WHEN_IN_FOCUSED_WINDOW); + + createButton = new JButton("Create"); + createButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + String className = classField.getText(); + if (className.length() == 0) { + JOptionPane.showMessageDialog(ImportAppMoteDialog.this, + "No class specified", "Failed to load class", JOptionPane.ERROR_MESSAGE); + return; + } + File classFile; + if (className.indexOf('/') >= 0 || className.indexOf(File.separatorChar) >= 0) { + classFile = new File(className); + } else { + classFile = new File(new File(pathField.getText()), + className.replace(".", "/") + ".class"); + } + if (trySetClass(simulation, moteType, classFile)) { + moteType.setDescription(descriptionField.getText()); + String path = pathField.getText(); + if (path.length() > 0) { + moteType.setMoteClassPath(new File(path)); + lastPath = path; + } else { + moteType.setMoteClassPath(null); + lastPath = null; + } + moteType.setMoteClassName(classField.getText()); + lastFile = classField.getText(); + + hasSelected = true; + + ImportAppMoteDialog.this.setVisible(false); + ImportAppMoteDialog.this.dispose(); + } + } + }); + getRootPane().setDefaultButton(createButton); + + JPanel buttonPanel = new JPanel(); + buttonPanel.add(createButton); + buttonPanel.add(cancelButton); + + /* Build panel */ + mainPanel.add(BorderLayout.NORTH, topPanel); + mainPanel.add(buttonPanel, BorderLayout.SOUTH); + setContentPane(mainPanel); + + setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + cancelButton.doClick(); + } + }); + + descriptionField.requestFocus(); + descriptionField.selectAll(); + + pack(); + setLocationRelativeTo(parent); + } + + private boolean trySetClass(Simulation simulation, ImportAppMoteType moteType, File classFile) { + try { + ImportAppMoteType.TestLoader loader = moteType.createTestLoader(classFile); + if (!loader.isTestSubclass(Mote.class)) { + JOptionPane.showMessageDialog(ImportAppMoteDialog.this, + "Class '" + classFile + "'\n is not of type Mote", "Failed to load class", + JOptionPane.ERROR_MESSAGE); + } else { + pathField.setText(loader.getTestClassPath().getPath()); + classField.setText(loader.getTestClassName()); + GUI.setExternalToolsSetting("IMPORT_APP_LAST", + simulation.getGUI().createPortablePath(classFile).getPath()); + return true; + } + } catch (FileNotFoundException e1) { + JOptionPane.showMessageDialog(ImportAppMoteDialog.this, + "Could not find class '" + classFile + "'", "Failed to load class", + JOptionPane.ERROR_MESSAGE); + } catch (Exception e1) { + e1.printStackTrace(); + JOptionPane.showMessageDialog(ImportAppMoteDialog.this, + "Could not load class '" + classFile + "':\n" + e1, "Failed to load class", + JOptionPane.ERROR_MESSAGE); + } catch (LinkageError e1) { + e1.printStackTrace(); + JOptionPane.showMessageDialog(ImportAppMoteDialog.this, + "Could not load class '" + classFile + "':\n" + e1, "Failed to load class", + JOptionPane.ERROR_MESSAGE); + } + return false; + } + + private Component createPanel(String title, JComponent field1) { + return createPanel(title, field1, null); + } + + private Component createPanel(String title, JComponent field1, JComponent field2) { + Box panel = Box.createHorizontalBox(); + JLabel label = new JLabel(title); + label.setPreferredSize(LABEL_DIMENSION); + panel.add(label); + + panel.add(field1); + if (field2 != null) { + panel.add(field2); + } + return panel; + } + + public boolean waitForUserResponse() { + setVisible(true); + return hasSelected; + } + +} diff --git a/tools/cooja/java/se/sics/cooja/motes/ImportAppMoteType.java b/tools/cooja/java/se/sics/cooja/motes/ImportAppMoteType.java index f08dd9d3b..f88bc6507 100644 --- a/tools/cooja/java/se/sics/cooja/motes/ImportAppMoteType.java +++ b/tools/cooja/java/se/sics/cooja/motes/ImportAppMoteType.java @@ -33,23 +33,20 @@ package se.sics.cooja.motes; import java.awt.Container; import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; import java.lang.reflect.Constructor; +import java.net.URL; +import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Collection; -import java.util.Random; - -import javax.swing.JFileChooser; -import javax.swing.filechooser.FileFilter; - -import org.apache.log4j.Logger; import org.jdom.Element; - import se.sics.cooja.AbstractionLevelDescription; import se.sics.cooja.ClassDescription; -import se.sics.cooja.GUI; import se.sics.cooja.Mote; import se.sics.cooja.MoteType; import se.sics.cooja.Simulation; +import se.sics.cooja.dialogs.ImportAppMoteDialog; import se.sics.cooja.util.ArrayUtils; /** @@ -58,11 +55,11 @@ import se.sics.cooja.util.ArrayUtils; @ClassDescription("Imported App Mote Type") @AbstractionLevelDescription("Application level") public class ImportAppMoteType extends AbstractApplicationMoteType { - private static Logger logger = Logger.getLogger(ImportAppMoteType.class); private Simulation simulation; - private File moteClassFile = null; + private File moteClassPath = null; + private String moteClassName = null; private Class moteClass = null; private Constructor moteConstructor = null; @@ -75,36 +72,20 @@ public class ImportAppMoteType extends AbstractApplicationMoteType { setDescription("Imported App Mote Type #" + identifier); } - private class TestClassLoader extends ClassLoader { - private int id; /* DEBUG */ - private File file; - public TestClassLoader(File f) { - id = new Random().nextInt(); - file = f; - } - public TestClassLoader(ClassLoader parent, File f) { - super(parent); - id = new Random().nextInt(); - file = f; - } - public Class findClass(String name) { - byte[] data = loadClassData(name); - return defineClass(data, 0, data.length); - } - private byte[] loadClassData(String name) { - return ArrayUtils.readFromFile(file); - } - public String toString() { - return "ImportAppMoteType classloader #" + id; - } - } - public Collection getConfigXML() { - ArrayList config = new ArrayList(); + Collection config = super.getConfigXML(); - Element element = new Element("moteclass"); - element.setText(simulation.getGUI().createPortablePath(moteClassFile).getPath()); - config.add(element); + if (moteClassPath != null) { + Element element = new Element("motepath"); + element.setText(simulation.getGUI().createPortablePath(moteClassPath).getPath()); + config.add(element); + } + + if (moteClassName != null) { + Element element = new Element("moteclass"); + element.setText(moteClassName); + config.add(element); + } return config; } @@ -117,11 +98,13 @@ public class ImportAppMoteType extends AbstractApplicationMoteType { for (Element element : configXML) { String name = element.getName(); if (name.equals("moteclass")) { - moteClassFile = simulation.getGUI().restorePortablePath(new File(element.getText())); + moteClassName = element.getText(); + } else if (name.equals("motepath")) { + moteClassPath = simulation.getGUI().restorePortablePath(new File(element.getText())); } } - return configureAndInit(GUI.getTopParentContainer(), simulation, visAvailable); + return super.setConfigXML(simulation, configXML, visAvailable); } public boolean configureAndInit(Container parentContainer, @@ -133,86 +116,56 @@ public class ImportAppMoteType extends AbstractApplicationMoteType { return false; } - boolean updateSuggestion = false; if (visAvailable) { /* Select mote class file */ - JFileChooser fileChooser = new JFileChooser(); - fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); - fileChooser.setDialogTitle("Select application class file"); - if (moteClassFile != null) { - fileChooser.setSelectedFile(moteClassFile); - } else { - updateSuggestion = true; - fileChooser.setSelectedFile(new File(GUI.getExternalToolsSetting("IMPORT_APP_LAST", "mymote.class"))); - } - fileChooser.setFileFilter(new FileFilter() { - public boolean accept(File file) { - if (file.isDirectory()) { - return true; - } - if (file.getName().endsWith(".class")) { - return true; - } - return false; - } - public String getDescription() { - return "Java class extending " + AbstractApplicationMoteType.class.getName(); - } - public String toString() { - return ".class"; - } - }); - - int reply = fileChooser.showOpenDialog(GUI.getTopParentContainer()); - if (reply != JFileChooser.APPROVE_OPTION) { + ImportAppMoteDialog dialog = new ImportAppMoteDialog(parentContainer, simulation, this); + if (!dialog.waitForUserResponse()) { return false; } - - moteClassFile = fileChooser.getSelectedFile(); } - - if (moteClassFile == null) { + if (moteClassName == null) { throw new MoteTypeCreationException("Unknown mote class file"); } - if (!moteClassFile.exists()) { - throw new MoteTypeCreationException("Mote class file does not exist"); - } - if (updateSuggestion) { - GUI.setExternalToolsSetting("IMPORT_APP_LAST", moteClassFile.getPath()); - } - /* Load class */ - TestClassLoader loader = new TestClassLoader(moteClassFile); try { - moteClass = - (Class) - loader.loadClass("ignored class name"); - moteConstructor = - (Constructor) - moteClass.getConstructor(new Class[] { MoteType.class, Simulation.class }); + if (moteClassPath == null) { + // No class path. Check if path is available in the class name. + convertPathToClass(); + } + + ClassLoader parentLoader = getParentClassLoader(); + ClassLoader loader; + if (moteClassPath != null) { + /* Load class */ + loader = new URLClassLoader(new java.net.URL[] { moteClassPath.toURI().toURL() }, + parentLoader); + } else { + loader = parentLoader; + } + + moteClass = loader.loadClass(moteClassName).asSubclass(AbstractApplicationMote.class); + moteConstructor = moteClass.getConstructor(new Class[] { MoteType.class, Simulation.class }); } catch (Exception e) { - throw (MoteTypeCreationException) - new MoteTypeCreationException("Error when loading class from: " + moteClassFile.getPath()).initCause(e); - } catch (Throwable t) { - throw (MoteTypeCreationException) - new MoteTypeCreationException("Error when loading class from: " + moteClassFile.getPath()).initCause(t); + throw createError(e); + } catch(LinkageError e) { + throw createError(e); } - /* XXX Extending GUI's project class loader. Nasty. */ - if (simulation.getGUI().projectDirClassLoader == null) { - simulation.getGUI().projectDirClassLoader = - new TestClassLoader(ClassLoader.getSystemClassLoader(), moteClassFile); - } else { - simulation.getGUI().projectDirClassLoader = - new TestClassLoader(simulation.getGUI().projectDirClassLoader, moteClassFile); + if (getDescription() == null || getDescription().length() == 0) { + setDescription("Imported Mote Type #" + moteClassName); } - - logger.info(moteClass.getClassLoader()); - setDescription("Imported Mote Type #" + moteClassFile.getName()); - return true; } + private MoteTypeCreationException createError(Throwable e) { + MoteTypeCreationException mte = + new MoteTypeCreationException("Error when loading class from: " + + (moteClassPath != null ? moteClassPath.getAbsolutePath() : "") + " " + + moteClassName); + mte.initCause(e); + return mte; + } + public Mote generateMote(Simulation simulation) { try { return moteConstructor.newInstance(ImportAppMoteType.this, simulation); @@ -220,4 +173,109 @@ public class ImportAppMoteType extends AbstractApplicationMoteType { throw (RuntimeException) new RuntimeException("Error when generating mote").initCause(e); } } + + public void setMoteClassPath(File moteClassPath) { + this.moteClassPath = moteClassPath; + } + + public File getMoteClassPath() { + return moteClassPath; + } + + public void setMoteClassName(String moteClassName) { + this.moteClassName = moteClassName; + } + + public String getMoteClassName() { + return moteClassName; + } + + private void convertPathToClass() { + if (moteClassName.indexOf('/') < 0 && moteClassName.indexOf(File.separatorChar) < 0) { + // No conversion possible + return; + } + File moteClassFile = new File(moteClassName); + if (moteClassFile.canRead()) { + try { + TestLoader test = createTestLoader(moteClassFile); + // Successfully found the class + moteClassPath = test.getTestClassPath(); + moteClassName = test.getTestClassName(); + } catch (Exception e) { + // Ignore + } catch (LinkageError e) { + // Ignore + } + } + } + + private ClassLoader getParentClassLoader() { + if (simulation.getGUI().projectDirClassLoader == null) { + return ClassLoader.getSystemClassLoader(); + } else { + return simulation.getGUI().projectDirClassLoader; + } + } + + public TestLoader createTestLoader(File classFile) throws IOException { + classFile = classFile.getCanonicalFile(); + ArrayList list = new ArrayList(); + for(File parent = classFile.getParentFile(); + parent != null; + parent = parent.getParentFile()) { + list.add(parent.toURI().toURL()); + } + return new TestLoader(list.toArray(new URL[list.size()]), + getParentClassLoader(), classFile); + } + + public static class TestLoader extends URLClassLoader { + private final File classFile; + private File classPath; + private Class testClass; + + private TestLoader(URL[] classpath, ClassLoader parentClassLoader, File classFile) + throws IOException + { + super(classpath, parentClassLoader); + this.classFile = classFile.getCanonicalFile(); + + byte[] data = ArrayUtils.readFromFile(classFile); + if (data == null) { + throw new FileNotFoundException(classFile.getAbsolutePath()); + } + this.testClass = defineClass(null, data, 0, data.length); + } + + public File getTestClassFile() { + return classFile; + } + + public boolean isTestSubclass(Class type) { + return type.isAssignableFrom(testClass); + } + + public Class getTestClass() { + return testClass; + } + + public String getTestClassName() { + return testClass.getCanonicalName(); + } + + public File getTestClassPath() { + if (classPath == null) { + String name = testClass.getCanonicalName(); + int index = name.indexOf('.'); + classPath = classFile.getParentFile(); + while(index >= 0) { + classPath = classPath.getParentFile(); + index = name.indexOf('.', index + 1); + } + } + return classPath; + } + } + }