supporting multiple path substituion rules in mspcodewatcher

This commit is contained in:
Fredrik Osterlind 2012-03-28 12:29:54 +02:00
parent 65b5fd0dde
commit 0c94b567b9

View File

@ -32,12 +32,15 @@ package se.sics.cooja.mspmote.plugins;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Color; import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.Window;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator;
import java.util.Observable; import java.util.Observable;
import java.util.Observer; import java.util.Observer;
@ -47,16 +50,17 @@ import javax.swing.Box;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JComboBox; import javax.swing.JComboBox;
import javax.swing.JDialog; import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JList; import javax.swing.JList;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane; import javax.swing.JTabbedPane;
import javax.swing.JTextField; import javax.swing.JTable;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileFilter;
import javax.swing.plaf.basic.BasicComboBoxRenderer; import javax.swing.plaf.basic.BasicComboBoxRenderer;
import javax.swing.table.AbstractTableModel;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.jdom.Element; import org.jdom.Element;
@ -72,6 +76,7 @@ import se.sics.cooja.VisPlugin;
import se.sics.cooja.Watchpoint; import se.sics.cooja.Watchpoint;
import se.sics.cooja.WatchpointMote; import se.sics.cooja.WatchpointMote;
import se.sics.cooja.WatchpointMote.WatchpointListener; import se.sics.cooja.WatchpointMote.WatchpointListener;
import se.sics.cooja.dialogs.MessageList;
import se.sics.cooja.mspmote.MspMote; import se.sics.cooja.mspmote.MspMote;
import se.sics.cooja.mspmote.MspMoteType; import se.sics.cooja.mspmote.MspMoteType;
import se.sics.mspsim.core.EmulationException; import se.sics.mspsim.core.EmulationException;
@ -103,11 +108,14 @@ public class MspCodeWatcher extends VisPlugin implements MotePlugin {
private WatchpointListener watchpointListener; private WatchpointListener watchpointListener;
private JComboBox fileComboBox; private JComboBox fileComboBox;
private String[] debugInfoMap = null;
private File[] sourceFiles; private File[] sourceFiles;
private JTabbedPane mainPane; private JTabbedPane mainPane;
private ArrayList<Rule> rules;
private ELFDebug debug;
private String[] debugSourceFiles;
/** /**
* Mini-debugger for MSP Motes. * Mini-debugger for MSP Motes.
* Visualizes instructions, source code and allows a user to manipulate breakpoints. * Visualizes instructions, source code and allows a user to manipulate breakpoints.
@ -121,6 +129,21 @@ public class MspCodeWatcher extends VisPlugin implements MotePlugin {
simulation = simulationToVisualize; simulation = simulationToVisualize;
this.mspMote = (MspMote) mote; this.mspMote = (MspMote) mote;
this.watchpointMote = (WatchpointMote) mote; this.watchpointMote = (WatchpointMote) mote;
try {
debug = ((MspMoteType)mspMote.getType()).getELF().getDebug();
if (debug == null) {
throw new RuntimeException("No debugging info found in firmware, aborting");
}
debugSourceFiles = debug.getSourceFiles();
if (debugSourceFiles == null) {
throw new RuntimeException("No debugging info found in firmware, aborting");
}
} catch (IOException e1) {
throw new RuntimeException("No debugging info found in firmware, aborting");
}
rules = new ArrayList<Rule>();
loadDefaultRules();
getContentPane().setLayout(new BorderLayout()); getContentPane().setLayout(new BorderLayout());
@ -150,7 +173,6 @@ public class MspCodeWatcher extends VisPlugin implements MotePlugin {
return this; return this;
} }
}); });
updateFileComboBox();
/* Browse code control (north) */ /* Browse code control (north) */
Box sourceCodeControl = Box.createHorizontalBox(); Box sourceCodeControl = Box.createHorizontalBox();
@ -223,8 +245,13 @@ public class MspCodeWatcher extends VisPlugin implements MotePlugin {
showCurrentPC(); showCurrentPC();
} }
public void startPlugin() {
super.startPlugin();
updateFileComboBox();
}
private void updateFileComboBox() { private void updateFileComboBox() {
sourceFiles = getSourceFiles(mspMote, debugInfoMap); sourceFiles = getSourceFiles(mspMote, rules);
fileComboBox.removeAllItems(); fileComboBox.removeAllItems();
fileComboBox.addItem("[view sourcefile]"); fileComboBox.addItem("[view sourcefile]");
for (File f: sourceFiles) { for (File f: sourceFiles) {
@ -288,39 +315,22 @@ public class MspCodeWatcher extends VisPlugin implements MotePlugin {
currentCodeFile = null; currentCodeFile = null;
try { try {
ELFDebug debug = ((MspMoteType)mspMote.getType()).getELF().getDebug();
if (debug == null) {
return;
}
int pc = mspMote.getCPU().getPC(); int pc = mspMote.getCPU().getPC();
DebugInfo debugInfo = debug.getDebugInfo(pc); DebugInfo debugInfo = debug.getDebugInfo(pc);
if (pc <= 0) {
return;
}
if (debugInfo == null) { if (debugInfo == null) {
if (pc != 0) { logger.warn("No source info at " + String.format("0x%04x", pc));
logger.warn("No sourcecode info at " + String.format("0x%04x", mspMote.getCPU().getPC())); return;
} }
File f = applySubstitutionRules(new File(debugInfo.getPath(), debugInfo.getFile()), rules);
if (f == null || !f.exists()) {
logger.warn("Unknown source at " + String.format("0x%04x", pc) + ": " + debugInfo.getPath() + " " + debugInfo.getFile());
return; return;
} }
String path = new File(debugInfo.getPath(), debugInfo.getFile()).getPath(); currentCodeFile = f;
if (path == null) {
return;
}
path = path.replace('\\', '/');
/* Debug info to source file map */
if (debugInfoMap != null && debugInfoMap.length == 2) {
if (path.startsWith(debugInfoMap[0])) {
path = debugInfoMap[1] + path.substring(debugInfoMap[0].length());
}
}
/* Nasty Cygwin-Windows fix */
if (path.length() > 10 && path.startsWith("/cygdrive/")) {
char driveCharacter = path.charAt(10);
path = driveCharacter + ":/" + path.substring(11);
}
currentCodeFile = new File(path).getCanonicalFile();
currentLineNumber = debugInfo.getLine(); currentLineNumber = debugInfo.getLine();
} catch (Exception e) { } catch (Exception e) {
logger.fatal("Exception: " + e.getMessage(), e); logger.fatal("Exception: " + e.getMessage(), e);
@ -329,147 +339,332 @@ public class MspCodeWatcher extends VisPlugin implements MotePlugin {
} }
} }
private void tryMapDebugInfo() { private int getLocatedSourcesCount() {
final String[] debugFiles; File files[] = getSourceFiles(mspMote, rules);
try { if (files == null) {
return 0;
ELFDebug debug = ((MspMoteType)mspMote.getType()).getELF().getDebug();
debugFiles = debug != null ? debug.getSourceFiles() : null;
if (debugFiles == null) {
logger.fatal("Error: No debug information is available");
return;
}
} catch (IOException e1) {
logger.fatal("Error: " + e1.getMessage(), e1);
return;
} }
String[] map = new RunnableInEDT<String[]>() { return files.length;
public String[] work() { }
/* Select which source file to use */
int counter = 0, n;
File correspondingFile = null;
while (true) {
n = JOptionPane.showOptionDialog(GUI.getTopParentContainer(),
"Choose which source file to manually locate.\n\n" +
"Some source files may not exist, as debug info is also inherited from the toolchain.\n" +
"\"Next File\" proceeds to the next source file in the debug info.\n\n" +
debugFiles[counter] + " (" + (counter+1) + '/' + debugFiles.length + ')',
"Select source file to locate", JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE, null,
new String[] { "Next File", "Locate File", "Cancel"}, "Next File");
if (n == JOptionPane.CANCEL_OPTION || n == JOptionPane.CLOSED_OPTION) {
return null;
}
if (n == JOptionPane.NO_OPTION) {
/* Locate file */
final String filename = new File(debugFiles[counter]).getName();
JFileChooser fc = new JFileChooser();
fc.setFileFilter(new FileFilter() {
public boolean accept(File file) {
if (file.isDirectory()) { return true; }
if (file.getName().equals(filename)) {
return true;
}
return false;
}
public String getDescription() {
return "Source file " + filename;
}
});
fc.setCurrentDirectory(new File(GUI.getExternalToolsSetting("PATH_CONTIKI", ".")));
int returnVal = fc.showOpenDialog(GUI.getTopParentContainer());
if (returnVal == JFileChooser.APPROVE_OPTION) {
correspondingFile = fc.getSelectedFile();
break;
}
}
if (n == JOptionPane.YES_OPTION) { private void updateRulesUsage() {
/* Next file */ for (Rule rule: rules) {
counter = (counter+1) % debugFiles.length; rule.prefixMatches = 0;
} rule.locatesFile = 0;
} }
getSourceFiles(mspMote, rules);
/* Match files */ rulesMatched = new int[rules.size()];
try { rulesOK = new int[rules.size()];
String canonDebug = debugFiles[counter]; for (int i=0; i < rules.size(); i++) {
String canonSelected = correspondingFile.getCanonicalFile().getPath().replace('\\', '/'); rulesMatched[i] = rules.get(i).prefixMatches;
rulesOK[i] = rules.get(i).locatesFile;
int offset = 0;
while (canonDebug.regionMatches(
true,
canonDebug.length()-offset,
canonSelected, canonSelected.length()-offset,
offset)) {
offset++;
if (offset >= canonDebug.length() ||
offset >= canonSelected.length())
break;
}
offset--;
String replace = canonDebug.substring(0, canonDebug.length() - offset);
String replacement = canonSelected.substring(0, canonSelected.length() - offset);
{
JTextField replaceInput = new JTextField(replace);
replaceInput.setEditable(true);
JTextField replacementInput = new JTextField(replacement);
replacementInput.setEditable(true);
Box box = Box.createVerticalBox();
box.add(new JLabel("Debug info file:"));
box.add(new JLabel(canonDebug));
box.add(new JLabel("Selected file:"));
box.add(new JLabel(canonSelected));
box.add(Box.createVerticalStrut(20));
box.add(new JLabel("Replacing:"));
box.add(replaceInput);
box.add(new JLabel("with:"));
box.add(replacementInput);
JOptionPane optionPane = new JOptionPane();
optionPane.setMessage(box);
optionPane.setMessageType(JOptionPane.INFORMATION_MESSAGE);
optionPane.setOptions(new String[] { "OK" });
optionPane.setInitialValue("OK");
JDialog dialog = optionPane.createDialog(
GUI.getTopParentContainer(),
"Mapping debug info to real sources");
dialog.setVisible(true);
replace = replaceInput.getText();
replacement = replacementInput.getText();
}
replace = replace.replace('\\', '/');
replacement = replacement.replace('\\', '/');
return new String[] { replace, replacement };
} catch (IOException e) {
logger.fatal("Error: " + e.getMessage(), e);
return null;
}
}
}.invokeAndWait();
if (map != null) {
debugInfoMap = map;
updateFileComboBox();
} }
} }
private static File[] getSourceFiles(MspMote mote, String[] map) { private class Rule {
final String[] sourceFiles; String from = "";
try { String to = "";
ELFDebug debug = ((MspMoteType)mote.getType()).getELF().getDebug(); int prefixMatches = 0;
sourceFiles = debug != null ? debug.getSourceFiles() : null; int locatesFile = 0;
if (sourceFiles == null) { public Rule(String from, String to) {
logger.fatal("Error: No debug information is available"); this.from = from;
return new File[0]; this.to = to;
}
File apply(File f) {
if (f == null) {
return null;
} }
} catch (IOException e1) { if (from == null) {
logger.fatal("Error: " + e1.getMessage(), e1); return null;
}
if (!f.getPath().startsWith(from)) {
if (rulesWithDebuggingOutput) {
rulesDebuggingOutput.addMessage(this + " does not match: " + f.getPath(), MessageList.ERROR);
}
return null;
}
prefixMatches++;
if (rulesWithDebuggingOutput) {
rulesDebuggingOutput.addMessage(this + " testing on: " + f.getPath());
}
if (to == null) {
if (rulesWithDebuggingOutput) {
rulesDebuggingOutput.addMessage(this + " enter substition: " + f.getPath(), MessageList.ERROR);
}
return null;
}
File file = new File(to + f.getPath().substring(from.length()));
if (!file.exists()) {
if (rulesWithDebuggingOutput) {
rulesDebuggingOutput.addMessage(this + " not found: " + file.getPath(), MessageList.ERROR);
}
return null;
}
if (rulesWithDebuggingOutput) {
rulesDebuggingOutput.addMessage(this + " OK: " + f.getPath());
}
locatesFile++;
return file;
}
public String toString() {
return "[" + from + "|" + to + "]";
}
}
private static File applySubstitutionRules(String file, Collection<Rule> rules) {
return applySubstitutionRules(new File(file), rules);
}
private static File applySubstitutionRules(File file, Collection<Rule> rules) {
if (file == null) {
return null; return null;
} }
if (file.exists()) {
return file;
}
for (Rule rule: rules) {
File f = rule.apply(file);
if (f != null && f.exists()) {
return f;
}
}
return null;
}
private void loadDefaultRules() {
String rulesString = GUI.getExternalToolsSetting("MSPCODEWATCHER_RULES", "/cygdrive/c/*c:/");
String[] rulesArr = rulesString.split("\\*");
rules.clear();
for (int i=0; i < rulesArr.length/2; i++) {
Rule r = new Rule(rulesArr[i*2], rulesArr[i*2+1]);
if (r.from.equals("[empty]")) {
r.from = "";
}
if (r.to.equals("[empty]")) {
r.to = "";
}
rules.add(r);
}
}
private MessageList rulesDebuggingOutput = new MessageList();
private boolean rulesWithDebuggingOutput = false;
private int[] rulesMatched = null;
private int[] rulesOK = null;
private JTable table = null;
private void tryMapDebugInfo() {
/* called from AWT */
int r = JOptionPane.showConfirmDialog(GUI.getTopParentContainer(),
"The firmware file " + mspMote.getType().getContikiFirmwareFile().getName() + " references " + debugSourceFiles.length + " source files.\n" +
"This function tries to locate these files on disk with a set of simple substitution rules.\n" +
"\n" +
"Right now " + getLocatedSourcesCount() + "/" + debugSourceFiles.length + " source files can be found.",
"Locate source files", JOptionPane.OK_CANCEL_OPTION);
if (r != JOptionPane.OK_OPTION) {
return;
}
/* table with rules */
rulesDebuggingOutput.clearMessages();
final JDialog dialog = new JDialog((Window)GUI.getTopParentContainer(), "Locate source files");
dialog.setModal(true);
updateRulesUsage();
AbstractTableModel model = new AbstractTableModel() {
public int getRowCount() {
return 10;
}
public int getColumnCount() {
return 4;
}
public String getColumnName(int col) {
if (col == 0) {
return "Prefix";
}
if (col == 1) {
return "Substitution";
}
if (col == 2) {
return "Matched";
}
if (col == 3) {
return "OK";
}
return null;
}
public Object getValueAt(int rowIndex, int columnIndex) {
if (rowIndex < 0 || rowIndex >= rules.size()) {
return null;
}
Rule rule = rules.get(rowIndex);
if (columnIndex == 0) {
return rule.from;
}
if (columnIndex == 1) {
return rule.to;
}
if (columnIndex == 2) {
if (rulesMatched == null || rowIndex >= rulesMatched.length) {
return "[click Apply]";
}
return rulesMatched[rowIndex];
}
if (columnIndex == 3) {
if (rulesOK == null || rowIndex >= rulesOK.length) {
return "[click Apply]";
}
return rulesOK[rowIndex];
}
return null;
}
public boolean isCellEditable(int row, int column) {
if (column == 0) {
return true;
}
if (column == 1) {
return true;
}
return false;
}
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
Rule rule;
if (rowIndex < 0) {
return;
}
if (rowIndex < rules.size()) {
rule = rules.get(rowIndex);
} else {
do {
rule = new Rule("", "");
rules.add(rule);
} while (rowIndex >= rules.size());
}
if (columnIndex == 0) {
rule.from = (String) aValue;
}
if (columnIndex == 1) {
rule.to = (String) aValue;
}
rulesMatched = null;
rulesOK = null;
table.invalidate();
table.repaint();
}
};
table = new JTable(model);
/* control panel: save/load, clear/apply/close */
final JButton applyButton = new JButton("Apply");
applyButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
/* Remove trailing empty rules */
ArrayList<Rule> trimmedRules = new ArrayList<Rule>();
for (Rule rule: rules) {
if (rule.from == null || rule.from.trim().isEmpty()) {
rule.from = "";
}
if (rule.to == null || rule.to.trim().isEmpty()) {
rule.to = "";
}
if (rule.from.isEmpty() && rule.to.isEmpty()) {
/* ignore */
continue;
}
trimmedRules.add(rule);
}
rules = trimmedRules;
rulesDebuggingOutput.clearMessages();
rulesDebuggingOutput.addMessage("Applying " + rules.size() + " substitution rules");
rulesWithDebuggingOutput = true;
updateRulesUsage();
rulesWithDebuggingOutput = false;
rulesDebuggingOutput.addMessage("Done! " + "Located sources: " + getLocatedSourcesCount() + "/" + debugSourceFiles.length);
rulesDebuggingOutput.addMessage(" ");
for (String s: debugSourceFiles) {
File f = applySubstitutionRules(s, rules);
if (f == null || !f.exists()) {
rulesDebuggingOutput.addMessage("Not yet located: " + s, MessageList.ERROR);
}
}
table.invalidate();
table.repaint();
}
});
JButton clearButton = new JButton("Clear");
clearButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
rules.clear();
applyButton.doClick();
}
});
JButton loadButton = new JButton("Load default");
loadButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
loadDefaultRules();
applyButton.doClick();
}
});
JButton saveButton = new JButton("Save as default");
saveButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
StringBuilder sb = new StringBuilder();
for (Rule r: rules) {
sb.append("*");
if (r.from.isEmpty()) {
sb.append("[empty]");
} else {
sb.append(r.from);
}
sb.append("*");
if (r.to.isEmpty()) {
sb.append("[empty]");
} else {
sb.append(r.to);
}
}
if (sb.length() >= 1) {
GUI.setExternalToolsSetting("MSPCODEWATCHER_RULES", sb.substring(1));
} else {
GUI.setExternalToolsSetting("MSPCODEWATCHER_RULES", "");
}
}
});
JButton closeButton = new JButton("Close");
closeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
updateFileComboBox();
dialog.dispose();
}
});
Box control = Box.createHorizontalBox();
control.add(loadButton);
control.add(saveButton);
control.add(Box.createHorizontalGlue());
control.add(clearButton);
control.add(applyButton);
control.add(closeButton);
final JSplitPane split = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
new JScrollPane(table),
new JScrollPane(rulesDebuggingOutput));
SwingUtilities.invokeLater(new Runnable() {
public void run() {
split.setDividerLocation(0.5);
applyButton.doClick();
}
});
dialog.getContentPane().add(BorderLayout.CENTER, split);
dialog.getContentPane().add(BorderLayout.SOUTH, control);
dialog.getRootPane().setDefaultButton(closeButton);
dialog.setSize(550, 500);
dialog.setLocationRelativeTo(GUI.getTopParentContainer());
dialog.setVisible(true);
}
private File[] getSourceFiles(MspMote mote, ArrayList<Rule> rules) {
File contikiSource = mote.getType().getContikiSourceFile(); File contikiSource = mote.getType().getContikiSourceFile();
if (contikiSource != null) { if (contikiSource != null) {
try { try {
@ -480,50 +675,25 @@ public class MspCodeWatcher extends VisPlugin implements MotePlugin {
/* Verify that files exist */ /* Verify that files exist */
ArrayList<File> existing = new ArrayList<File>(); ArrayList<File> existing = new ArrayList<File>();
for (String sourceFile: sourceFiles) { for (String sourceFile: debugSourceFiles) {
/* Debug info to source file map */ /* Debug info to source file map */
sourceFile = sourceFile.replace('\\', '/'); File f = applySubstitutionRules(sourceFile, rules);
if (map != null && map.length == 2) { if (f != null && f.exists()) {
if (sourceFile.startsWith(map[0])) { existing.add(f);
sourceFile = map[1] + sourceFile.substring(map[0].length());
}
}
/* Nasty Cygwin-Windows fix */
if (sourceFile.length() > 10 && sourceFile.contains("/cygdrive/")) {
char driveCharacter = sourceFile.charAt(10);
sourceFile = driveCharacter + ":/" + sourceFile.substring(11);
}
File file = new File(sourceFile);
try {
file = file.getCanonicalFile();
} catch (IOException e1) {
}
if (!GUI.isVisualizedInApplet()) {
if (file.exists() && file.isFile()) {
existing.add(file);
} else {
/*logger.warn("Can't locate source file, skipping: " + file.getPath());*/
}
} else {
/* Accept all files without existence check */
existing.add(file);
} }
} }
/* If no files were found, suggest map function */ /* If no files were found, suggest map function */
if (sourceFiles.length > 0 && existing.isEmpty() && GUI.isVisualized()) { if (debugSourceFiles.length > 0 && existing.isEmpty() && GUI.isVisualized()) {
new RunnableInEDT<Boolean>() { new RunnableInEDT<Boolean>() {
public Boolean work() { public Boolean work() {
JOptionPane.showMessageDialog( JOptionPane.showMessageDialog(
GUI.getTopParentContainer(), GUI.getTopParentContainer(),
"The firmware debug info specifies " + sourceFiles.length + " source files.\n" + "The firmware debug info specifies " + debugSourceFiles.length + " source files.\n" +
"However, Msp Code Watcher could not find any of these files.\n" + "However, MspCodeWatcher could not find any of these files.\n" +
"Make sure the source files were not moved after the firmware compilation.\n" + "Make sure the source files were not moved after the firmware compilation.\n" +
"\n" + "\n" +
"If you want to manually locate the sources, click \"Map\" button.", "If you want to manually locate the sources, click the \"Locate sources\" button.",
"No source files found", "No source files found",
JOptionPane.WARNING_MESSAGE); JOptionPane.WARNING_MESSAGE);
return true; return true;
@ -532,23 +702,13 @@ public class MspCodeWatcher extends VisPlugin implements MotePlugin {
} }
/* Sort alphabetically */ /* Sort alphabetically */
ArrayList<File> sorted = new ArrayList<File>(); File[] sorted = existing.toArray(new File[0]);
for (File file: existing) { Arrays.sort(sorted, new Comparator<File>() {
int index = 0; public int compare(File o1, File o2) {
for (index=0; index < sorted.size(); index++) { return o1.getName().compareToIgnoreCase(o2.getName());
if (file.getName().compareToIgnoreCase(sorted.get(index).getName()) < 0) {
break;
}
} }
sorted.add(index, file); });
} return sorted;
/* Add Contiki source first */
if (contikiSource != null && contikiSource.exists()) {
sorted.add(0, contikiSource);
}
return sorted.toArray(new File[0]);
} }
public Collection<Element> getConfigXML() { public Collection<Element> getConfigXML() {
@ -559,13 +719,27 @@ public class MspCodeWatcher extends VisPlugin implements MotePlugin {
element.addContent("" + mainPane.getSelectedIndex()); element.addContent("" + mainPane.getSelectedIndex());
config.add(element); config.add(element);
for (Rule rule: rules) {
element = new Element("rule");
element.setAttribute("from", rule.from==null?"":rule.from);
element.setAttribute("to", rule.to==null?"":rule.to);
config.add(element);
}
return config; return config;
} }
public boolean setConfigXML(Collection<Element> configXML, boolean visAvailable) { public boolean setConfigXML(Collection<Element> configXML, boolean visAvailable) {
boolean clearedRules = false;
for (Element element : configXML) { for (Element element : configXML) {
if (element.getName().equals("tab")) { if (element.getName().equals("tab")) {
mainPane.setSelectedIndex(Integer.parseInt(element.getText())); mainPane.setSelectedIndex(Integer.parseInt(element.getText()));
} else if (element.getName().equals("rule")) {
if (!clearedRules) {
rules.clear();
clearedRules = true;
}
rules.add(new Rule(element.getAttributeValue("from"), element.getAttributeValue("to")));
} }
} }
return true; return true;
@ -582,7 +756,7 @@ public class MspCodeWatcher extends VisPlugin implements MotePlugin {
} }
}; };
private AbstractAction mapAction = new AbstractAction("Locate sources") { private AbstractAction mapAction = new AbstractAction("Locate sources...") {
private static final long serialVersionUID = -3929432830596292495L; private static final long serialVersionUID = -3929432830596292495L;
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {