Index: src/org/openstreetmap/josm/Main.java
===================================================================
--- src/org/openstreetmap/josm/Main.java	(revision 140)
+++ src/org/openstreetmap/josm/Main.java	(revision 142)
@@ -32,4 +32,5 @@
 import org.openstreetmap.josm.actions.DownloadAction;
 import org.openstreetmap.josm.actions.ExitAction;
+import org.openstreetmap.josm.actions.ExternalToolsAction;
 import org.openstreetmap.josm.actions.GpxExportAction;
 import org.openstreetmap.josm.actions.OpenAction;
@@ -204,4 +205,8 @@
 		mainMenu.add(editMenu);
 
+		JMenu externalMenu = ExternalToolsAction.buildMenu();
+		if (externalMenu != null)
+			mainMenu.add(externalMenu);
+
 		mainMenu.add(new JSeparator());
 		final JMenu helpMenu = new JMenu(tr("Help"));
Index: src/org/openstreetmap/josm/actions/ExternalToolsAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/ExternalToolsAction.java	(revision 142)
+++ src/org/openstreetmap/josm/actions/ExternalToolsAction.java	(revision 142)
@@ -0,0 +1,277 @@
+package org.openstreetmap.josm.actions;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Stack;
+
+import javax.swing.AbstractAction;
+import javax.swing.JMenu;
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.command.ChangeCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.DeleteCommand;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Segment;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.visitor.AddVisitor;
+import org.openstreetmap.josm.data.osm.visitor.CollectBackReferencesVisitor;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.io.OsmReader;
+import org.openstreetmap.josm.io.OsmWriter;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+import uk.co.wilson.xml.MinML2;
+
+/**
+ * Launches external tools configured in the preferences.
+ *
+ * @author Imi
+ */
+public class ExternalToolsAction extends AbstractAction {
+
+	private final Collection<String> flags;
+	private final Collection<String> input;
+	private final String output;
+	private final String[] exec;
+
+	private final class ExecuteToolRunner extends PleaseWaitRunnable {
+    	private final Process p;
+    	private DataSet dataSet;
+    	private DataSet fromDataSet;
+    	private ExecuteToolRunner(String msg, Process p) {
+    		super(msg);
+    		this.p = p;
+    		currentAction.setText(tr("Executing {0}",getValue(NAME)));
+    	}
+    
+    	@Override protected void realRun() throws SAXException, IOException {
+    		if (!input.isEmpty()) {
+    			fromDataSet = new DataSet();
+    			AddVisitor adder = new AddVisitor(fromDataSet, flags.contains("include_references"));
+    			if (input.contains("selection")) {
+    				Collection<OsmPrimitive> sel = Main.ds.getSelected();
+    				for (OsmPrimitive osm : sel)
+    					osm.visit(adder);
+    				if (flags.contains("include_back_references")) {
+    					CollectBackReferencesVisitor v = new CollectBackReferencesVisitor(Main.ds);
+    					for (OsmPrimitive osm : sel)
+    						osm.visit(v);
+    					AddVisitor a = new AddVisitor(fromDataSet);
+    					for (OsmPrimitive osm : v.data)
+    						osm.visit(a);
+    				}
+    			}
+    			if (input.contains("all"))
+    				for (OsmPrimitive osm : Main.ds.allPrimitives())
+    					osm.visit(adder);
+    			if (input.contains("screen")) {
+    				if (Main.map == null) {
+    					errorMessage = tr("The Tool requires some data to be loaded.");
+    					cancel();
+    					return;
+    				}
+    				LatLon bottomLeft = Main.map.mapView.getLatLon(0,Main.map.mapView.getHeight());
+    				LatLon topRight = Main.map.mapView.getLatLon(Main.map.mapView.getWidth(), 0);
+    				Bounds b = new Bounds(bottomLeft, topRight);
+    				Collection<Node> nodes = new HashSet<Node>();
+    				for (Node n : Main.ds.nodes) {
+    					if (n.coor.isWithin(b)) {
+    						n.visit(adder);
+    						nodes.add(n);
+    					}
+    				}
+    				Collection<Segment> segments = new HashSet<Segment>();
+    				for (Segment s : Main.ds.segments) {
+    					if (nodes.contains(s.from) || nodes.contains(s.to)) {
+    						s.visit(adder);
+    						segments.add(s);
+    					}
+    				}
+    				for (Way w : Main.ds.ways) {
+    					for (Segment s : w.segments) {
+    						if (segments.contains(s)) {
+    							w.visit(adder);
+    							break;
+    						}
+    					}
+    				}
+    			}
+    			OutputStream out = p.getOutputStream();
+    			OsmWriter.output(out, fromDataSet, false);
+    			out.close();
+    		}
+    		if (output != null)
+    			dataSet = OsmReader.parseDataSet(p.getInputStream(), currentAction, progress);
+    	}
+    
+    	@Override protected void cancel() {
+    		p.destroy();
+    	}
+    
+    	@Override protected void finish() {
+    		if (dataSet == null || output == null || output.equals("discard"))
+    			return; // user cancelled or no stdout to process
+    		Collection<OsmPrimitive> allNew = dataSet.allPrimitives();
+    		Collection<OsmPrimitive> allOld = fromDataSet.allPrimitives();
+    		if (output.equals("replace")) {
+    			Command cmd = createCommand(allOld, allNew);
+    			if (cmd != null) {
+    				Main.main.editLayer().add(cmd);
+    				Main.ds.clearSelection();
+    			}
+    		} else if (output.equals("selection")) {
+    			Collection<OsmPrimitive> sel = new LinkedList<OsmPrimitive>();
+    			for (OsmPrimitive osm : Main.ds.allPrimitives())
+    				if (allNew.contains(osm))
+    					sel.add(osm);
+    			Main.ds.setSelected(sel);
+    		}
+    	}
+    
+    	/**
+    	 * Create a command that replaces all objects in from with those in to. The lists will be
+    	 * changed by createCommand.
+    	 */
+    	private Command createCommand(Collection<OsmPrimitive> from, Collection<OsmPrimitive> to) {
+    		// remove all objects in from/to, that are present in both lists.
+    		for (Iterator<OsmPrimitive> toIt = to.iterator(); toIt.hasNext();) {
+    			OsmPrimitive osm = toIt.next();
+    			for (Iterator<OsmPrimitive> fromIt = from.iterator(); fromIt.hasNext();) {
+    				if (fromIt.next().realEqual(osm)) {
+    					toIt.remove();
+    					fromIt.remove();
+    					break;
+    				}
+    			}
+    		}
+    
+    		Collection<Command> cmd = new LinkedList<Command>();
+    
+    		// extract all objects that have changed
+    		for (Iterator<OsmPrimitive> toIt = to.iterator(); toIt.hasNext();) {
+    			OsmPrimitive toOsm = toIt.next();
+    			for (Iterator<OsmPrimitive> fromIt = from.iterator(); fromIt.hasNext();) {
+    				OsmPrimitive fromOsm = fromIt.next();
+    				if (fromOsm.equals(toOsm)) {
+    					toIt.remove();
+    					fromIt.remove();
+    					cmd.add(new ChangeCommand(fromOsm, toOsm));
+    					break;
+    				}
+    			}
+    		}
+    		for (OsmPrimitive fromOsm : Main.ds.allPrimitives()) {
+    			for (Iterator<OsmPrimitive> it = to.iterator(); it.hasNext();) {
+    				OsmPrimitive toOsm = it.next();
+    				if (fromOsm.equals(toOsm)) {
+    					it.remove();
+    					cmd.add(new ChangeCommand(fromOsm, toOsm));
+    					break;
+    				}
+    			}
+    		}
+    
+    		// extract all added objects
+    		for (OsmPrimitive osm : to)
+    			cmd.add(new AddCommand(osm));
+    
+    		// extract all deleted objects. Delete references as well.
+    		CollectBackReferencesVisitor v = new CollectBackReferencesVisitor(Main.ds);
+    		for (OsmPrimitive osm : from)
+    			osm.visit(v);
+    		v.data.addAll(from);
+    		if (!v.data.isEmpty())
+    			cmd.add(new DeleteCommand(v.data));
+    
+    		if (cmd.isEmpty())
+    			return null;
+    		return new SequenceCommand(tr("Executing {0}",getValue(NAME)), cmd);
+    	}
+    }
+
+	public ExternalToolsAction(String name, String[] exec, String[] flags, String[] input, String output) {
+		super(name);
+		this.exec = exec;
+		this.flags = Arrays.asList(flags);
+		this.input = Arrays.asList(input);
+		this.output = output;
+	}
+
+	public void actionPerformed(ActionEvent e) {
+		try {
+			final Process p = new ProcessBuilder(exec).start();
+			PleaseWaitRunnable runner = new ExecuteToolRunner(tr("Executing {0}",getValue(NAME)), p);
+			Main.worker.execute(runner);
+			runner.pleaseWaitDlg.setVisible(true);
+		} catch (IOException e1) {
+			e1.printStackTrace();
+			JOptionPane.showMessageDialog(Main.parent, tr("Could not execute command: {0}", exec[0]));
+		}
+	}
+
+	/**
+	 * @return All external tools configured so far as array.
+	 */
+	public static JMenu buildMenu() {
+		if (!new File(Main.pref.getPreferencesDir()+"external_tools").exists())
+			return null;
+
+		final JMenu main = new JMenu(tr("Tools"));
+		main.setMnemonic('T');
+		MinML2 parser = new MinML2() {
+			Stack<JMenu> current = new Stack<JMenu>();
+			@Override public void startDocument() {
+				current.push(main);
+            }
+			@Override public void startElement(String ns, String lname, String qname, Attributes a) throws SAXException {
+				if (qname.equals("group")) {
+					JMenu m = current.peek();
+					current.push(new JMenu(a.getValue(("name"))));
+					m.add(current.peek());
+				}
+				if (!qname.equals("tool"))
+					return;
+				String flagValue = a.getValue("flags");
+				String[] flags = flagValue==null ? new String[]{} : flagValue.split(",");
+				String output = a.getValue("out");
+				String[] exec = a.getValue("exec").split(" ");
+				if (exec.length < 1)
+					throw new SAXException("Execute attribute must not be empty");
+				String inValue = a.getValue("in");
+				String[] input = inValue==null ? new String[]{} : inValue.split(",");
+
+				current.peek().add(new ExternalToolsAction(a.getValue("name"), exec, flags, input, output));
+			}
+			@Override public void endElement(String ns, String lname, String qname) {
+				if (qname.equals("group"))
+					current.pop();
+			}
+		};
+		try {
+			parser.parse(new FileReader(Main.pref.getPreferencesDir()+"external_tools"));
+		} catch (Exception e) {
+			e.printStackTrace();
+			JOptionPane.showMessageDialog(Main.parent, tr("Could not read external tool configuration."));
+		}
+		return main;
+	}
+}
Index: src/org/openstreetmap/josm/actions/SaveAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/SaveAction.java	(revision 140)
+++ src/org/openstreetmap/josm/actions/SaveAction.java	(revision 142)
@@ -43,4 +43,7 @@
 
 		save(file);
+		Main.main.editLayer().name = file.getName();
+		Main.main.editLayer().associatedFile = file;
+		Main.parent.repaint();
 	}
 
Index: src/org/openstreetmap/josm/data/coor/LatLon.java
===================================================================
--- src/org/openstreetmap/josm/data/coor/LatLon.java	(revision 140)
+++ src/org/openstreetmap/josm/data/coor/LatLon.java	(revision 142)
@@ -1,4 +1,5 @@
 package org.openstreetmap.josm.data.coor;
 
+import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.data.projection.Projection;
 
@@ -42,4 +43,11 @@
 	}
 
+	/**
+	 * @return <code>true</code> if this is within the given bounding box.
+	 */
+	public boolean isWithin(Bounds b) {
+		return lat() >= b.min.lat() && lat() <= b.max.lat() && lon() > b.min.lon() && lon() < b.max.lon();
+	}
+	
     @Override public String toString() {
         return "LatLon[lat="+lat()+",lon="+lon()+"]";
Index: src/org/openstreetmap/josm/data/osm/DataSet.java
===================================================================
--- src/org/openstreetmap/josm/data/osm/DataSet.java	(revision 140)
+++ src/org/openstreetmap/josm/data/osm/DataSet.java	(revision 142)
@@ -5,4 +5,5 @@
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.LinkedList;
 
@@ -43,7 +44,7 @@
 
 	/**
-     * A list of listeners to selection changed events.
-     */
-    transient Collection<SelectionChangedListener> listeners = new LinkedList<SelectionChangedListener>();
+	 * A list of listeners to selection changed events.
+	 */
+	transient Collection<SelectionChangedListener> listeners = new LinkedList<SelectionChangedListener>();
 
 	/**
@@ -106,5 +107,5 @@
 		fireSelectionChanged(Arrays.asList(new OsmPrimitive[]{osm}));
 	}
-	
+
 	/**
 	 * Remove the selection from every value in the collection.
@@ -133,34 +134,52 @@
 
 	/**
-     * Remember to fire an selection changed event. A call to this will not fire
-     * the event immediately. For more, @see SelectionChangedListener
-     */
-    public void fireSelectionChanged(Collection<? extends OsmPrimitive> sel) {
+	 * Remember to fire an selection changed event. A call to this will not fire
+	 * the event immediately. For more, @see SelectionChangedListener
+	 */
+	public void fireSelectionChanged(Collection<? extends OsmPrimitive> sel) {
 		for (SelectionChangedListener l : listeners)
 			l.selectionChanged(sel);
-    }
+	}
 
 	/**
-     * Add a listener to the selection changed listener list. If <code>null</code>
-     * is passed, nothing happens.
-     * @param listener The listener to add to the list.
-     */
-    public void addSelectionChangedListener(SelectionChangedListener listener) {
-    	if (listener != null)
-    		listeners.add(listener);
-    }
+	 * Add a listener to the selection changed listener list. If <code>null</code>
+	 * is passed, nothing happens.
+	 * @param listener The listener to add to the list.
+	 */
+	public void addSelectionChangedListener(SelectionChangedListener listener) {
+		if (listener != null)
+			listeners.add(listener);
+	}
 
 	/**
-     * Remove a listener from the selection changed listener list. 
-     * If <code>null</code> is passed, nothing happens.
-     * @param listener The listener to remove from the list.
-     */
-    public void removeSelectionChangedListener(SelectionChangedListener listener) {
-    	if (listener != null)
-    		listeners.remove(listener);
-    }
+	 * Remove a listener from the selection changed listener list. 
+	 * If <code>null</code> is passed, nothing happens.
+	 * @param listener The listener to remove from the list.
+	 */
+	public void removeSelectionChangedListener(SelectionChangedListener listener) {
+		if (listener != null)
+			listeners.remove(listener);
+	}
 
 	public void addAllSelectionListener(DataSet ds) {
 		listeners.addAll(ds.listeners);
-    }
+	}
+
+	/**
+	 * Compares this and the parameter dataset and return <code>true</code> if both
+	 * contain the same data primitives (ignoring the selection)
+	 */
+	public boolean realEqual(Collection<OsmPrimitive> other) {
+		Collection<OsmPrimitive> my = allPrimitives();
+
+		if (my.size() != other.size())
+			return false;
+
+		Iterator<OsmPrimitive> it = other.iterator();
+		for (OsmPrimitive osm : my)
+			if (!osm.realEqual(it.next()))
+				return false;
+
+		return true;
+	}
 }
Index: src/org/openstreetmap/josm/data/osm/visitor/AddVisitor.java
===================================================================
--- src/org/openstreetmap/josm/data/osm/visitor/AddVisitor.java	(revision 140)
+++ src/org/openstreetmap/josm/data/osm/visitor/AddVisitor.java	(revision 142)
@@ -18,17 +18,34 @@
 	
 	private final DataSet ds;
+	private final boolean includeReferences;
+	
+	public AddVisitor(DataSet ds, boolean includeReferences) {
+		this.ds = ds;
+		this.includeReferences = includeReferences;
+	}
 	
 	public AddVisitor(DataSet ds) {
-		this.ds = ds;
+		this(ds, false);
 	}
 	
 	public void visit(Node n) {
-		ds.nodes.add(n);
+		if (!includeReferences || !ds.nodes.contains(n))
+			ds.nodes.add(n);
 	}
-	public void visit(Segment ls) {
-		ds.segments.add(ls);
+	public void visit(Segment s) {
+		ds.segments.add(s);
+		if (includeReferences && !s.incomplete) {
+			if (!ds.nodes.contains(s.from))
+				s.from.visit(this);
+			if (!ds.nodes.contains(s.to))
+				s.to.visit(this);
+		}
 	}
-	public void visit(Way t) {
-		ds.ways.add(t);
+	public void visit(Way w) {
+		ds.ways.add(w);
+		if (includeReferences)
+			for (Segment s : w.segments)
+				if (!ds.segments.contains(s))
+					s.visit(this);
 	}
 }
Index: src/org/openstreetmap/josm/gui/MapStatus.java
===================================================================
--- src/org/openstreetmap/josm/gui/MapStatus.java	(revision 140)
+++ src/org/openstreetmap/josm/gui/MapStatus.java	(revision 142)
@@ -174,4 +174,5 @@
 					}
 				} catch (ConcurrentModificationException x) {
+				} catch (NullPointerException x) {
 				}
 			}
Index: src/org/openstreetmap/josm/tools/SearchCompiler.java
===================================================================
--- src/org/openstreetmap/josm/tools/SearchCompiler.java	(revision 140)
+++ src/org/openstreetmap/josm/tools/SearchCompiler.java	(revision 142)
@@ -78,5 +78,5 @@
 			if (value == null)
 				return notValue;
-			return (value.indexOf(this.value) != -1) != notValue;
+			return (value.toLowerCase().indexOf(this.value.toLowerCase()) != -1) != notValue;
 		}
 		@Override public String toString() {return key+"="+(notValue?"!":"")+value;}
@@ -90,5 +90,6 @@
 				return s.equals("");
 			for (Entry<String, String> e : osm.keys.entrySet())
-				if (e.getKey().indexOf(s) != -1 || e.getValue().indexOf(s) != -1)
+				if (e.getKey().toLowerCase().indexOf(s.toLowerCase()) != -1 
+						|| e.getValue().toLowerCase().indexOf(s.toLowerCase()) != -1)
 					return true;
 			return false;
@@ -111,5 +112,5 @@
 		@Override public String toString() {return "type="+type;}
 	}
-	
+
 	private static class Modified extends Match {
 		@Override public boolean match(OsmPrimitive osm) {
