Index: branch/0.5/src/org/openstreetmap/josm/actions/ReorderAction.java
===================================================================
--- branch/0.5/src/org/openstreetmap/josm/actions/ReorderAction.java	(revision 329)
+++ 	(revision )
@@ -1,252 +1,0 @@
-//License: GPL. Copyright 2007 by Immanuel Scholz and others
-package org.openstreetmap.josm.actions;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-import static org.openstreetmap.josm.tools.I18n.trn;
-
-import java.awt.event.ActionEvent;
-import java.awt.event.KeyEvent;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.HashMap;
-
-import javax.swing.JOptionPane;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.command.ChangeCommand;
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.command.SequenceCommand;
-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.NameVisitor;
-
-public class ReorderAction extends JosmAction {
-
-	public ReorderAction() {
-		super(tr("Reorder Segments"), "reorder", tr("Try to reorder segments of a way so that they are in a line. May try to flip segments around to match a line."), KeyEvent.VK_R, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK, true);
-	}
-
-	/**
-	 * This method first sorts all the segments in a way, then makes sure that all 
-	 * the segments are facing the same direction as the first one.
-	 */
-	public void actionPerformed(ActionEvent e) {
-		Collection<Way> ways = new LinkedList<Way>();
-		for (OsmPrimitive osm : Main.ds.getSelected())
-			if (osm instanceof Way)
-				ways.add((Way)osm);
-
-		if (ways.size() < 1) {
-			JOptionPane.showMessageDialog(Main.parent, tr("Please select at least one way."));
-			return;
-		}
-
-		if (ways.size() > 1) {
-			int answer = JOptionPane.showConfirmDialog(Main.parent, 
-					trn(null, "You selected more than one way. Reorder the segments of {0} ways?", ways.size(), ways.size()), 
-					tr("Reorder segments"), JOptionPane.OK_CANCEL_OPTION);
-			if (answer != JOptionPane.OK_OPTION)
-				return;
-		}
-		boolean doneSomething = false;
-		for (Way way : ways) {
-			if (!way.isIncomplete() && way.segments.size() > 1)
-			{			
-				doneSomething = true;
-				Command c = reorderWay(way);
-
-				if( c != null )
-					Main.main.undoRedo.add(c);
-			}
-		}
-		if (!doneSomething) {
-			JOptionPane.showMessageDialog(Main.parent, 
-					trn("The selected way is incomplete or has only one segment.",
-							"None of the selected ways are complete and have more than one segment.",
-							ways.size()));
-		}
-		Main.map.repaint();
-	}
-
-	/**
-	 * This method first sorts all the segments in a way, then makes sure that all 
-	 * the segments are facing the same direction as the first one.
-	 * @param way The way to reorder
-	 * @return The command needed to reorder the way
-	 */
-	public static Command reorderWay(Way way) {
-		final LinkedList<Segment> sel = new LinkedList<Segment>(sortSegments(new LinkedList<Segment>(way.segments), false));   	
-
-		Collection<Command> c = new LinkedList<Command>();
-
-		boolean direction = false;
-		// work out the "average" direction of the way, we use this to direct the rest of the segments
-		int dirCounter = 0;
-		for(int i = 0; i < sel.size() - 1; i++)
-		{
-			Segment firstSegment = sel.get(i);
-			Segment secondSegment = sel.get(i+1);
-			if ( firstSegment.to == secondSegment.from || firstSegment.to == secondSegment.to ) // direction = true when 'from' is the first node in the Way
-				dirCounter++;
-			else
-				dirCounter--;
-		}
-		if ( dirCounter <= 0 )
-			direction = false;
-		else
-			direction = true;
-
-		Node lastNode = null;
-
-		// we need to calculate what the first node in the way is, we work from there
-		Segment firstSegment = sel.getFirst();
-		Segment secondSegment = sel.get(1);
-		if (firstSegment.to == secondSegment.from || firstSegment.to == secondSegment.to)
-			lastNode = firstSegment.from;
-		else
-			lastNode = firstSegment.to;
-
-		// go through each segment and flip them if required
-		for (Segment s : sel) {
-			Segment snew = new Segment(s);
-			boolean segDirection = s.from == lastNode;
-			// segDirection = true when the 'from' node occurs before the 'to' node in the Way 
-			if (direction != segDirection)
-			{    			
-				// reverse the segment's direction
-				Node n = snew.from;
-				snew.from = snew.to;
-				snew.to = n;
-				c.add(new ChangeCommand(s, snew));
-			}	
-
-			if (direction) // if its facing forwards,
-				lastNode = snew.to; // our next node is the 'to' one
-			else
-				lastNode = snew.from; // otherwise its the 'from' one
-		}
-
-		LinkedList<Segment> segments = new LinkedList<Segment>();
-
-		// Now we recreate the segment list, in the correct order of the direction
-		for (Segment s : sel) 
-			if (!direction) 
-				segments.addFirst(s);
-			else
-				segments.addLast(s);
-
-		// Check if the new segment list is actually different from the old one
-		// before we go and add a change command for it
-		for(int i = 0; i < segments.size(); i++)
-			if (way.segments.get(i) != segments.get(i))
-			{
-				Way newWay = new Way(way);
-				newWay.segments.clear();
-				newWay.segments.addAll(segments);
-				c.add(new ChangeCommand(way, newWay));
-				break;
-			}
-
-		// Check we've got some change commands before we add a sequence command
-		if (c.size() != 0) {
-			NameVisitor v = new NameVisitor();
-			way.visit(v);
-			return new SequenceCommand(tr("Reorder segments for way {0}",v.name), c);
-		}
-		return null;
-	}
-
-	/**
-	 * This sort is based on the sort in the old ReorderAction, but it can work 
-	 * irresepective of the direction of the segments. This produces a sort 
-	 * that can be useful even if the segments are facing the wrong direction.
-	 * 
-	 * @param segments list of segments to be sorted
-	 * @param strict true if segment direction should be observed, false if not
-	 */
-	public static LinkedList<Segment> sortSegments(LinkedList<Segment> segments, boolean strict) {
-
-		LinkedList<Segment> sortedSegments = new LinkedList<Segment>();
-
-		while (!segments.isEmpty()) {
-			LinkedList<Segment> pivotList = new LinkedList<Segment>();
-			pivotList.add(firstSegment(segments));
-			segments.remove(pivotList.getLast());
-			boolean found;
-			do {
-				found = false;
-				//try working forwards first
-				for (Iterator<Segment> it = segments.iterator(); it.hasNext();) {
-					Segment ls = it.next();
-					if (ls.incomplete)
-						continue; // incomplete segments are never added to a new way
-					if (ls.from == pivotList.getLast().to) {
-						pivotList.addLast(ls);
-						it.remove();
-						found = true;
-					}
-				}
-				if(!found){
-					for (Iterator<Segment> it = segments.iterator(); it.hasNext();) {
-						Segment ls = it.next();
-						if (ls.incomplete)
-							continue; // incomplete segments are never added to a new way
-						if (ls.from == pivotList.getLast().to || (!strict && (ls.to == pivotList.getLast().to || ls.from == pivotList.getLast().from || ls.to == pivotList.getLast().from))) {
-							pivotList.addLast(ls);
-							it.remove();
-							found = true;
-						} else if (ls.to == pivotList.getFirst().from || (!strict && (ls.from == pivotList.getFirst().from || ls.to == pivotList.getFirst().to || ls.from == pivotList.getFirst().to))) {
-							pivotList.addFirst(ls);
-							it.remove(); 
-							found = true;
-						}
-					}
-				}
-			} while (found);
-			sortedSegments.addAll(pivotList);
-		}
-		return sortedSegments;
-	}
-
-	/**
-	 * This method searches for a good segment to start a reorder from.
-	 * In most cases this will be a segment with a start node that occurs only
-	 * once in the way. In cases with loops, this could be any odd number. If no nodes
-	 * are referenced an odd number of times, then any segment is a good start point.
-	 */
-	public static Segment firstSegment(Collection<Segment> segments) {
-		HashMap<Node, Integer> refCount = new HashMap<Node, Integer>(segments.size()*2);
-		//loop through all segments and count up how many times each node is referenced
-		for (Segment seg : segments) {
-			if (!refCount.containsKey(seg.from))
-				refCount.put(seg.from, 0);
-			refCount.put(seg.from,refCount.get(seg.from)+1);
-
-			if (!refCount.containsKey(seg.to))
-				refCount.put(seg.to, 0);
-			refCount.put(seg.to,refCount.get(seg.to)+1);
-		}
-
-		//now look for start nodes that are referenced only once
-		for (Segment seg : segments)
-			if (refCount.get(seg.from) == 1)
-				return seg;
-		//now look for start nodes that are referenced only (2n+1)
-		for (Segment seg : segments)
-			if (refCount.get(seg.from) % 2 == 1)
-				return seg;
-		//now look for end nodes that are referenced only once
-		for (Segment seg : segments)
-			if (refCount.get(seg.to) == 1)
-				return seg;
-		//now look for end nodes that are referenced only (2n+1)
-		for (Segment seg : segments)
-			if (refCount.get(seg.to) % 2 == 1)
-				return seg;
-
-		return segments.iterator().next();
-	}    
-}
Index: branch/0.5/src/org/openstreetmap/josm/actions/ReverseWayAction.java
===================================================================
--- branch/0.5/src/org/openstreetmap/josm/actions/ReverseWayAction.java	(revision 330)
+++ branch/0.5/src/org/openstreetmap/josm/actions/ReverseWayAction.java	(revision 330)
@@ -0,0 +1,58 @@
+// License: GPL. Copyright 2007 by Immanuel Scholz and others
+package org.openstreetmap.josm.actions;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.ChangeCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.visitor.Visitor;
+
+public final class ReverseWayAction extends JosmAction {
+
+    public ReverseWayAction() {
+    	super(tr("Reverse ways"), "wayflip",
+			tr("Reverse the direction of all selected ways."),
+			KeyEvent.VK_R, KeyEvent.CTRL_MASK | KeyEvent.SHIFT_MASK, true);
+    }
+
+	public void actionPerformed(ActionEvent e) {
+    	final Collection<Way> sel = new LinkedList<Way>();
+    	new Visitor(){
+			public void visit(Node n)    {}
+			public void visit(Way w)     {sel.add(w);}
+			public void visit(Relation e){}
+			public void visitAll() {
+				for (OsmPrimitive osm : Main.ds.getSelected())
+					osm.visit(this);
+			}
+    	}.visitAll();
+
+    	if (sel.isEmpty()) {
+    		JOptionPane.showMessageDialog(Main.parent,
+				tr("Please select at least one way."));
+    		return;
+    	}
+    	Collection<Command> c = new LinkedList<Command>();
+    	for (Way w : sel) {
+    		Way wnew = new Way(w);
+			Collections.reverse(wnew.nodes);
+    		c.add(new ChangeCommand(w, wnew));
+    	}
+    	Main.main.undoRedo.add(new SequenceCommand(tr("Reverse ways"), c));
+    	Main.map.repaint();
+    }
+}
Index: branch/0.5/src/org/openstreetmap/josm/data/osm/NodePair.java
===================================================================
--- branch/0.5/src/org/openstreetmap/josm/data/osm/NodePair.java	(revision 330)
+++ branch/0.5/src/org/openstreetmap/josm/data/osm/NodePair.java	(revision 330)
@@ -0,0 +1,32 @@
+package org.openstreetmap.josm.data.osm;
+import java.util.ArrayList;
+
+/**
+ * A pair of twe nodes.
+ */
+public final class NodePair {
+	public Node a, b;
+
+	public NodePair(Node a, Node b) {
+		this.a = a;
+		this.b = b;
+	}
+
+	@Override public int hashCode() {
+		return a.hashCode() ^ b.hashCode();
+	}
+
+	@Override public boolean equals(Object o) {
+		if (o == null || !(o instanceof NodePair)) {
+			return false;
+		}
+		return a == ((NodePair) o).a && b == ((NodePair) o).b;
+	}
+
+	public ArrayList<Node> toArrayList() {
+		ArrayList<Node> l = new ArrayList<Node>(2);
+		l.add(a);
+		l.add(b);
+		return l;
+	}
+}
Index: branch/0.5/src/org/openstreetmap/josm/data/osm/Relation.java
===================================================================
--- branch/0.5/src/org/openstreetmap/josm/data/osm/Relation.java	(revision 330)
+++ branch/0.5/src/org/openstreetmap/josm/data/osm/Relation.java	(revision 330)
@@ -0,0 +1,78 @@
+package org.openstreetmap.josm.data.osm;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map.Entry;
+
+import org.openstreetmap.josm.data.osm.visitor.Visitor;
+
+/**
+ * An relation, having a set of tags and any number (0...n) of members.
+ * 
+ * @author Frederik Ramm <frederik@remote.org>
+ */
+public final class Relation extends OsmPrimitive {
+	
+	/**
+	 * All members of this relation. Note that after changing this,
+	 * makeBackReferences and/or removeBackReferences should be called.
+	 */
+	public final List<RelationMember> members = new ArrayList<RelationMember>();
+
+	@Override public void visit(Visitor visitor) {
+		visitor.visit(this);
+	}
+
+	/**
+	 * Create an identical clone of the argument (including the id)
+	 */
+	public Relation(Relation clone) {
+		cloneFrom(clone);
+	}
+	
+	/**
+	 * Create an incomplete Relation.
+	 */
+	public Relation(long id) {
+		this.id = id;
+		incomplete = true;
+	}
+	
+	/** 
+	 * Create an empty Relation. Use this only if you set meaningful values
+	 * afterwards.
+	 */
+	public Relation() {	
+	}
+	
+	@Override public void cloneFrom(OsmPrimitive osm) {
+		super.cloneFrom(osm);
+		members.clear();
+		// we must not add the members themselves, but instead
+		// add clones of the members
+		for (RelationMember em : ((Relation)osm).members) {
+			members.add(new RelationMember(em));
+		}
+	}
+
+    @Override public String toString() {
+        return "{Relation id="+id+" members="+Arrays.toString(members.toArray())+"}";
+    }
+
+	@Override public boolean realEqual(OsmPrimitive osm, boolean semanticOnly) {
+		return osm instanceof Relation ? super.realEqual(osm, semanticOnly) && members.equals(((Relation)osm).members) : false;
+    }
+
+	public int compareTo(OsmPrimitive o) {
+	    return o instanceof Relation ? Long.valueOf(id).compareTo(o.id) : -1;
+    }
+	
+	public boolean isIncomplete() {
+		for (RelationMember m : members)
+			if (m.member == null)
+				return true;
+		return false;
+	}
+}
Index: branch/0.5/src/org/openstreetmap/josm/data/osm/RelationMember.java
===================================================================
--- branch/0.5/src/org/openstreetmap/josm/data/osm/RelationMember.java	(revision 330)
+++ branch/0.5/src/org/openstreetmap/josm/data/osm/RelationMember.java	(revision 330)
@@ -0,0 +1,35 @@
+package org.openstreetmap.josm.data.osm;
+
+/**
+ * A linkage class that can be used by an relation to keep a list of 
+ * members. Since membership may be qualified by a "role", a simple
+ * list is not sufficient.
+ * 
+ * @author Frederik Ramm <frederik@remote.org>
+ */
+public class RelationMember {
+
+	public String role;
+	public OsmPrimitive member;
+	
+	/**
+	 * Default constructor. Does nothing.
+	 */
+	public RelationMember() { };
+	
+	/**
+	 * Copy constructor.
+	 * @param other relation member to be copied.
+	 */
+	public RelationMember(RelationMember other) {
+		role = other.role;
+		member = other.member;
+	}
+	
+	@Override public boolean equals(Object other) {
+		if (!(other instanceof RelationMember)) return false;
+		RelationMember otherMember = (RelationMember) other;
+		return otherMember.role.equals(role) && otherMember.member.equals(member);
+	}
+
+}
Index: branch/0.5/src/org/openstreetmap/josm/data/osm/WaySegment.java
===================================================================
--- branch/0.5/src/org/openstreetmap/josm/data/osm/WaySegment.java	(revision 330)
+++ branch/0.5/src/org/openstreetmap/josm/data/osm/WaySegment.java	(revision 330)
@@ -0,0 +1,33 @@
+// License: GPL. Copyright 2007 by Gabriel Ebner
+package org.openstreetmap.josm.data.osm;
+
+/**
+ * A segment consisting of 2 consecutive nodes out of a way.
+ */
+public final class WaySegment {
+	/**
+	 * The way.
+	 */
+	public Way way;
+
+	/**
+	 * The index of one of the 2 nodes in the way.  The other node has the
+	 * index <code>lowerIndex + 1</code>.
+	 */
+	public int lowerIndex;
+
+	public WaySegment(Way w, int i) {
+		way = w;
+		lowerIndex = i;
+	}
+
+	@Override public boolean equals(Object o) {
+		return o != null && o instanceof WaySegment
+			&& ((WaySegment) o).way == way
+			&& ((WaySegment) o).lowerIndex == lowerIndex;
+	}
+
+	@Override public int hashCode() {
+		return way.hashCode() ^ lowerIndex;
+	}
+}
Index: branch/0.5/src/org/openstreetmap/josm/gui/dialogs/RelationEditor.java
===================================================================
--- branch/0.5/src/org/openstreetmap/josm/gui/dialogs/RelationEditor.java	(revision 330)
+++ branch/0.5/src/org/openstreetmap/josm/gui/dialogs/RelationEditor.java	(revision 330)
@@ -0,0 +1,282 @@
+package org.openstreetmap.josm.gui.dialogs;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.xnap.commons.i18n.I18n.marktr;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Collections;
+import java.util.Map.Entry;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.DefaultTableModel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.command.ChangeCommand;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * This dialog is for editing relations.
+ * 
+ * In the basic form, it provides two tables, one with the relation tags
+ * and one with the relation members. (Relation tags can be edited through 
+ * the normal properties dialog as well, if you manage to get a relation 
+ * selected!)
+ * 
+ * @author Frederik Ramm <frederik@remote.org>
+ *
+ */
+public class RelationEditor extends JFrame {
+
+	/**
+	 * The relation that this editor is working on, and the clone made for
+	 * editing.
+	 */
+	private final Relation relation;
+	private final Relation clone;
+	
+	/**
+	 * The property data.
+	 */
+	private final DefaultTableModel propertyData = new DefaultTableModel() {
+		@Override public boolean isCellEditable(int row, int column) {
+			return true;
+		}
+		@Override public Class<?> getColumnClass(int columnIndex) {
+			return String.class;
+		}
+	};
+
+	/**
+	 * The membership data.
+	 */
+	private final DefaultTableModel memberData = new DefaultTableModel() {
+		@Override public boolean isCellEditable(int row, int column) {
+			return column == 0;
+		}
+		@Override public Class<?> getColumnClass(int columnIndex) {
+			return columnIndex == 1 ? OsmPrimitive.class : String.class;
+		}
+	};
+	
+	/**
+	 * The properties and membership lists.
+	 */
+	private final JTable propertyTable = new JTable(propertyData);
+	private final JTable memberTable = new JTable(memberData);
+	
+	/**
+	 * Creates a new relation editor for the given relation. The relation
+	 * will be saved if the user selects "ok" in the editor.
+	 * 
+	 * If no relation is given, will create an editor for a new relation.
+	 * 
+	 * @param relation relation to edit, or null to create a new one.
+	 */
+	public RelationEditor(Relation relation)
+	{
+		super(tr("Edit Relation"));
+		this.relation = relation;
+		
+		if (relation == null) {
+			// create a new relation
+			this.clone = new Relation();
+		} else {
+			// edit an existing relation
+			this.clone = new Relation(relation);	
+		}
+		
+		getContentPane().setLayout(new BorderLayout());
+		JTabbedPane tabPane = new JTabbedPane();
+		getContentPane().add(tabPane, BorderLayout.CENTER);
+		
+		// (ab)use JOptionPane to make this look familiar;
+		// hook up with JOptionPane's property change event
+		// to detect button click
+		final JOptionPane okcancel = new JOptionPane("", 
+			JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null);
+		getContentPane().add(okcancel, BorderLayout.SOUTH);
+		
+		okcancel.addPropertyChangeListener(new PropertyChangeListener() {
+			public void propertyChange(PropertyChangeEvent event) {
+				if (event.getPropertyName().equals(JOptionPane.VALUE_PROPERTY) && event.getNewValue() != null) {
+					if ((Integer)event.getNewValue() == JOptionPane.OK_OPTION) {
+						// clicked ok!
+						if (RelationEditor.this.relation == null) {
+							Main.main.undoRedo.add(new AddCommand(clone));
+						} else if (!RelationEditor.this.relation.realEqual(clone, true)) {
+							Main.main.undoRedo.add(new ChangeCommand(RelationEditor.this.relation, clone));
+						}
+					}
+					setVisible(false);
+				}
+			}
+		});
+
+		JLabel help = new JLabel("<html><em>"+
+			"This is the basic relation editor which allows you to change the relation's tags " +
+			"as well as the members. In addition to this we should have a smart editor that " +
+			"detects the type of relationship and limits your choices in a sensible way.</em></html>");
+		
+		getContentPane().add(help, BorderLayout.NORTH);		
+		try { setAlwaysOnTop(true); } catch (SecurityException sx) {}
+		
+		// Basic Editor panel has two blocks; 
+		// a tag table at the top and a membership list below.
+
+		// setting up the properties table
+		
+		propertyData.setColumnIdentifiers(new String[]{tr("Key"),tr("Value")});
+		propertyTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+		propertyData.addTableModelListener(new TableModelListener() {
+			public void tableChanged(TableModelEvent tme) {
+				if (tme.getType() == TableModelEvent.UPDATE) {
+					int row = tme.getFirstRow();
+			
+					if (!(tme.getColumn() == 0 && row == propertyData.getRowCount() -1)) {
+						clone.entrySet().clear();
+						for (int i = 0; i < propertyData.getRowCount(); i++) {
+							String key = propertyData.getValueAt(i, 0).toString();
+							String value = propertyData.getValueAt(i, 1).toString();
+							if (key.length() > 0 && value.length() > 0) clone.put(key, value);
+						}
+						refreshTables();
+					}
+				}
+			}
+		});
+		propertyTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
+		
+		// setting up the member table
+		
+	    memberData.setColumnIdentifiers(new String[]{tr("Role"),tr("Occupied By")});
+		memberTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+		memberTable.getColumnModel().getColumn(1).setCellRenderer(new OsmPrimitivRenderer());
+		/*
+		memberTable.getColumnModel().getColumn(1).setCellRenderer(new DefaultTableCellRenderer() {
+			public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
+				Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
+				if (c instanceof JLabel) {
+					((OsmPrimitive)value).visit(nameVisitor);
+					((JLabel)c).setText(nameVisitor.name);
+				}
+				return c;
+			}
+		});	
+		*/
+		memberData.addTableModelListener(new TableModelListener() {
+			public void tableChanged(TableModelEvent tme) {
+				if (tme.getType() == TableModelEvent.UPDATE && tme.getColumn() == 0) {
+					int row = tme.getFirstRow();
+					clone.members.get(row).role = memberData.getValueAt(row, 0).toString();
+				}
+			}
+		});
+		memberTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
+
+		
+		// combine both tables and wrap them in a scrollPane
+		JPanel bothTables = new JPanel();
+		bothTables.setLayout(new GridBagLayout());
+		bothTables.add(new JLabel(tr("Tags (empty value deletes tag)")), GBC.eol().fill(GBC.HORIZONTAL));
+		bothTables.add(new JScrollPane(propertyTable), GBC.eop().fill(GBC.BOTH));
+		bothTables.add(new JLabel(tr("Members")), GBC.eol().fill(GBC.HORIZONTAL));
+		bothTables.add(new JScrollPane(memberTable), GBC.eol().fill(GBC.BOTH));
+		
+		JPanel buttonPanel = new JPanel(new GridLayout(1,3));
+		
+		buttonPanel.add(createButton(marktr("Add Selected"),tr("Add all currently selected objects as members"), KeyEvent.VK_A, new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				addSelected();
+			}
+		}));
+
+		buttonPanel.add(createButton(marktr("Delete"),tr("Remove the member in the current table row from this relation"), KeyEvent.VK_D, new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				int row = memberTable.getSelectedRow();
+				RelationMember mem = new RelationMember();
+				mem.role = memberTable.getValueAt(row, 0).toString();
+				mem.member = (OsmPrimitive) memberTable.getValueAt(row, 1);
+				clone.members.remove(mem);
+				refreshTables();
+			}
+		}));
+
+		buttonPanel.add(createButton(marktr("Select"),tr("Highlight the member from the current table row as JOSM's selection"), KeyEvent.VK_S, new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				int row = memberTable.getSelectedRow();
+				OsmPrimitive p = (OsmPrimitive) memberTable.getValueAt(row, 1);
+				Main.ds.setSelected(Collections.singleton(p));
+			}
+		}));
+		bothTables.add(buttonPanel, GBC.eop().fill(GBC.HORIZONTAL));
+
+		tabPane.add(bothTables, "Basic");
+		
+		refreshTables();
+		
+		setSize(new Dimension(400, 500));
+		setLocationRelativeTo(Main.parent);
+	}
+	
+	private void refreshTables() {
+		
+		// re-load property data
+		
+		propertyData.setRowCount(0);
+		for (Entry<String, String> e : clone.entrySet()) {
+			propertyData.addRow(new Object[]{e.getKey(), e.getValue()});
+		}
+		propertyData.addRow(new Object[]{"", ""});
+		
+		// re-load membership data
+		
+		memberData.setRowCount(0);
+		for (RelationMember em : clone.members) {
+			memberData.addRow(new Object[]{em.role, em.member});
+		}
+	}
+	
+	private JButton createButton(String name, String tooltip, int mnemonic, ActionListener actionListener) {
+		JButton b = new JButton(tr(name), ImageProvider.get("dialogs", name.toLowerCase()));
+		b.setActionCommand(name);
+		b.addActionListener(actionListener);
+		b.setToolTipText(tooltip);
+		b.setMnemonic(mnemonic);
+		b.putClientProperty("help", "Dialog/Properties/"+name);
+		return b;
+	}
+	
+	private void addSelected() {
+		for (OsmPrimitive p : Main.ds.getSelected()) {
+			RelationMember em = new RelationMember();
+			em.member = p;
+			em.role = "";
+			clone.members.add(em);
+		}
+		refreshTables();
+	}
+}
Index: branch/0.5/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java
===================================================================
--- branch/0.5/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java	(revision 330)
+++ branch/0.5/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java	(revision 330)
@@ -0,0 +1,133 @@
+package org.openstreetmap.josm.gui.dialogs;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.xnap.commons.i18n.I18n.marktr;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ListSelectionModel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.DeleteCommand;
+import org.openstreetmap.josm.data.SelectionChangedListener;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * A dialog showing all known relations, with buttons to add, edit, and
+ * delete them. 
+ * 
+ * We don't have such dialogs for nodes, segments, and ways, becaus those
+ * objects are visible on the map and can be selected there. Relations are not.
+ *
+ * @author Frederik Ramm <frederik@remote.org>
+ */
+public class RelationListDialog extends ToggleDialog {
+
+	/**
+	 * The selection's list data.
+	 */
+	private final DefaultListModel list = new DefaultListModel();
+
+	/**
+	 * The display list.
+	 */
+	private JList displaylist = new JList(list);
+
+	public RelationListDialog() {
+		super(tr("Relations"), "relationlist", tr("Open a list of all relations."), KeyEvent.VK_N, 150);
+		displaylist.setCellRenderer(new OsmPrimitivRenderer());
+		displaylist.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+		displaylist.addMouseListener(new MouseAdapter(){
+			@Override public void mouseClicked(MouseEvent e) {
+				if (e.getClickCount() < 2)
+					return;
+				Relation toEdit = (Relation) displaylist.getSelectedValue();
+				if (toEdit != null)
+					new RelationEditor(toEdit).setVisible(true);
+			}
+		});
+
+		add(new JScrollPane(displaylist), BorderLayout.CENTER);
+
+		JPanel buttonPanel = new JPanel(new GridLayout(1,3));
+
+		buttonPanel.add(createButton(marktr("Add Relation"), "addrelation", tr("Create a new relation"), KeyEvent.VK_A, new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				// call relation editor with null argument to create new relation
+				new RelationEditor(null).setVisible(true);
+			}
+		}));
+		
+		buttonPanel.add(createButton(marktr("Edit"), "edit", tr( "Open an editor for the selected relation"), KeyEvent.VK_E, new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				Relation toEdit = (Relation) displaylist.getSelectedValue();
+				if (toEdit != null)
+					new RelationEditor(toEdit).setVisible(true);				
+			}
+		}));
+		
+		buttonPanel.add(createButton(marktr("Delete"), "delete", tr("Delete the selected relation"), KeyEvent.VK_D, new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				Relation toDelete = (Relation) displaylist.getSelectedValue();
+				if (toDelete != null) {
+					Main.main.editLayer().add(new DeleteCommand(Collections.singleton(toDelete)));
+				}
+			}
+		}));
+		
+		add(buttonPanel, BorderLayout.SOUTH);
+		
+		/*
+		DataSet.dataListeners.add(new DataChangedListener() {
+			public void dataChanged() {
+				updateList();
+				repaint();
+			}
+		});
+		*/
+	}
+
+	private JButton createButton(String name, String imagename, String tooltip, int mnemonic, ActionListener actionListener) {
+		JButton b = new JButton(tr(name), ImageProvider.get("dialogs", imagename));
+		b.setActionCommand(name);
+		b.addActionListener(actionListener);
+		b.setToolTipText(tooltip);
+		b.setMnemonic(mnemonic);
+		b.putClientProperty("help", "Dialog/Properties/"+name);
+		return b;
+	}
+
+	@Override public void setVisible(boolean b) {
+		super.setVisible(b);
+		if (b) updateList();
+	}
+	
+	public void updateList() {
+		list.setSize(Main.ds.relations.size());
+		int i = 0;
+		for (Relation e : Main.ds.relations) {
+			list.setElementAt(e, i++);
+		}
+	}
+	
+}
Index: branch/0.5/src/org/openstreetmap/josm/io/IncompleteDownloader.java
===================================================================
--- branch/0.5/src/org/openstreetmap/josm/io/IncompleteDownloader.java	(revision 329)
+++ 	(revision )
@@ -1,175 +1,0 @@
-// License: GPL. Copyright 2007 by Immanuel Scholz and others
-package org.openstreetmap.josm.io;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.StringReader;
-import java.util.ArrayList;
-import java.util.Collection;
-
-import javax.swing.JOptionPane;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParserFactory;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.command.ChangeCommand;
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.command.SequenceCommand;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.Segment;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.data.osm.visitor.MergeVisitor;
-import org.xml.sax.Attributes;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-
-/**
- * Capable of downloading ways without having to fully parse their segments.
- *
- * @author Imi
- */
-public class IncompleteDownloader extends OsmServerReader {
-
-	/**
-	 * The new downloaded data will be inserted here.
-	 */
-	public final DataSet data = new DataSet();
-
-	/**
-	 * The list of incomplete Ways to download. The ways will be filled and are complete after download.
-	 */
-	private final Collection<Way> toDownload;
-	private MergeVisitor merger = new MergeVisitor(data, null);
-
-	public IncompleteDownloader(Collection<Way> toDownload) {
-		this.toDownload = toDownload;
-	}
-
-	public void parse() throws SAXException, IOException {
-		Main.pleaseWaitDlg.currentAction.setText(tr("Downloading incomplete ways..."));
-		Main.pleaseWaitDlg.progress.setMaximum(toDownload.size());
-		Main.pleaseWaitDlg.progress.setValue(0);
-		ArrayList<Command> cmds = new ArrayList<Command>();
-		int i = 0;
-		try {
-			for (Way w : toDownload) {
-				// if some of the way's segments fail to download and the user
-				// decides to delete them, the download method will return an
-				// "edit way" command.
-				Command cmd = download(w); 
-				if (cmd != null)
-					cmds.add(cmd);
-				Main.pleaseWaitDlg.progress.setValue(++i);
-			}
-		} catch (IOException e) {
-			if (!cancel)
-				throw e;
-		} catch (SAXException e) {
-			throw e;
-		} catch (Exception e) {
-			if (!cancel)
-				throw (e instanceof RuntimeException) ? (RuntimeException)e : new RuntimeException(e);
-		}
-		if (cmds.size() > 0)
-			Main.main.undoRedo.add(new SequenceCommand(tr("Fix data errors"), cmds));
-	}
-
-	private static class SegmentParser extends DefaultHandler {
-		public long from, to;
-		@Override public void startElement(String ns, String lname, String qname, Attributes a) {
-			if (qname.equals("segment")) {
-				from = Long.parseLong(a.getValue("from"));
-				to = Long.parseLong(a.getValue("to"));
-			}
-		}
-	}
-
-	/**
-	 * Downloads all missing segments from the given way. If segments fail do download, 
-	 * offers the user a chance to delete those segments from the way.
-	 * 
-	 * @param w way to complete
-	 * @return an "edit way" command if the user decided to delete segments
-	 * @throws IOException
-	 * @throws SAXException
-	 */
-	private Command download(Way w) throws IOException, SAXException {
-		// get all the segments
-		Way newway = null;
-		for (Segment s : w.segments) {
-			if (!s.incomplete)
-				continue;
-			BufferedReader segReader;
-			try {
-				segReader = new BufferedReader(new InputStreamReader(getInputStream("segment/"+s.id, null), "UTF-8"));
-			} catch (FileNotFoundException e) {
-				Object[] options = {"Delete", "Ignore", "Abort"};
-				int n = JOptionPane.showOptionDialog(Main.parent,
-						tr("Segment {0} is deleted but part of Way {1}",s.id, w.id),
-						tr("Data error"),
-						JOptionPane.YES_NO_CANCEL_OPTION,
-						JOptionPane.ERROR_MESSAGE,
-						null, options, options[2]);
-				if (n == 0)
-				{
-					if( newway == null )
-						newway = new Way(w);
-					newway.segments.remove(s);
-				}
-				else if (n == 2)
-				{
-					e.printStackTrace();
-					throw new IOException(tr("Data error: Segment {0} is deleted but part of Way {1}", s.id, w.id));
-				}
-				continue;
-			}
-			StringBuilder segBuilder = new StringBuilder();
-			for (String line = segReader.readLine(); line != null; line = segReader.readLine())
-				segBuilder.append(line+"\n");
-			SegmentParser segmentParser = new SegmentParser();
-			try {
-		        SAXParserFactory.newInstance().newSAXParser().parse(new InputSource(new StringReader(segBuilder.toString())), segmentParser);
-	        } catch (ParserConfigurationException e1) {
-	        	e1.printStackTrace(); // broken SAXException chaining
-	        	throw new SAXException(e1);
-	        }
-			if (segmentParser.from == 0 || segmentParser.to == 0)
-				throw new SAXException("Invalid segment response.");
-			if (!hasNode(segmentParser.from))
-				readNode(segmentParser.from, s.id).visit(merger);
-			if (!hasNode(segmentParser.to))
-				readNode(segmentParser.to, s.id).visit(merger);
-			readSegment(segBuilder.toString()).visit(merger);
-		}
-		if( newway != null )
-			return new ChangeCommand(w, newway);
-		return null;
-	}
-
-	private boolean hasNode(long id) {
-	    for (Node n : Main.ds.nodes)
-	    	if (n.id == id)
-	    		return true;
-	    return false;
-    }
-
-	private Segment readSegment(String seg) throws SAXException, IOException {
-        return OsmReader.parseDataSet(new ByteArrayInputStream(seg.getBytes("UTF-8")), data, null).segments.iterator().next();
-    }
-
-	private Node readNode(long id, long segId) throws SAXException, IOException {
-		try {
-	        return OsmReader.parseDataSet(getInputStream("node/"+id, null), data, null).nodes.iterator().next();
-        } catch (FileNotFoundException e) {
-	        e.printStackTrace();
-	        throw new IOException(tr("Data error: Node {0} is deleted but part of Segment {1}", id, segId));
-        }
-    }
-}
Index: branch/0.5/src/org/openstreetmap/josm/io/OsmReader.java.orig
===================================================================
--- branch/0.5/src/org/openstreetmap/josm/io/OsmReader.java.orig	(revision 330)
+++ branch/0.5/src/org/openstreetmap/josm/io/OsmReader.java.orig	(revision 330)
@@ -0,0 +1,417 @@
+// License: GPL. Copyright 2007 by Immanuel Scholz and others
+package org.openstreetmap.josm.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.openstreetmap.josm.Main;
+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.DataSource;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.User;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.visitor.AddVisitor;
+import org.openstreetmap.josm.data.osm.visitor.Visitor;
+import org.openstreetmap.josm.gui.PleaseWaitDialog;
+import org.openstreetmap.josm.tools.DateParser;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Parser for the Osm Api. Read from an input stream and construct a dataset out of it.
+ *
+ * Reading process takes place in three phases. During the first phase (including xml parse),
+ * all nodes are read and stored. Other information than nodes are stored in a raw list
+ *
+ * The second phase read all ways out of the remaining objects in the raw list.
+ *
+ * @author Imi
+ */
+public class OsmReader {
+
+	/**
+	 * This is used as (readonly) source for finding missing references when not transferred in the
+	 * file.
+	 */
+	private DataSet references;
+
+	/**
+	 * The dataset to add parsed objects to.
+	 */
+	private DataSet ds = new DataSet();
+
+	/**
+	 * The visitor to use to add the data to the set.
+	 */
+	private AddVisitor adder = new AddVisitor(ds);
+
+	/**
+	 * All read nodes after phase 1.
+	 */
+	private Map<Long, Node> nodes = new HashMap<Long, Node>();
+
+	// TODO: What the hack? Is this really from me? Please, clean this up!
+	private static class OsmPrimitiveData extends OsmPrimitive {
+		@Override public void visit(Visitor visitor) {}
+		public int compareTo(OsmPrimitive o) {return 0;}
+
+		public void copyTo(OsmPrimitive osm) {
+			osm.id = id;
+			osm.keys = keys;
+			osm.modified = modified;
+			osm.selected = selected;
+			osm.deleted = deleted;
+			osm.timestamp = timestamp;
+			osm.user = user;
+			osm.visible = visible;
+		}
+	}
+
+	/**
+	 * Used as a temporary storage for relation members, before they
+	 * are resolved into pointers to real objects.
+	 */
+	private static class RelationMemberData {
+		public String type;
+		public long id;
+		public RelationMember relationMember;
+	}
+
+	/**
+	 * Data structure for the remaining way objects
+	 */
+	private Map<OsmPrimitiveData, Collection<Long>> ways = new HashMap<OsmPrimitiveData, Collection<Long>>();
+
+	/** 
+	 * Data structure for relation objects
+	 */
+	private Map<OsmPrimitiveData, Collection<RelationMemberData>> relations = new HashMap<OsmPrimitiveData, Collection<RelationMemberData>>();
+
+	/** 
+	 * List of protocol versions that will be accepted on reading
+	 */
+	private HashSet<String> allowedVersions = new HashSet<String>();
+
+	private class Parser extends DefaultHandler {
+		/**
+		 * The current osm primitive to be read.
+		 */
+		private OsmPrimitive current;
+
+		@Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
+			try {
+				if (qName.equals("osm")) {
+					if (atts == null)
+						throw new SAXException(tr("Unknown version"));
+					if (!allowedVersions.contains(atts.getValue("version")))
+						throw new SAXException(tr("Unknown version")+": "+atts.getValue("version"));
+				} else if (qName.equals("bound")) {
+					String bbox = atts.getValue("box");
+					String origin = atts.getValue("origin");
+					if (bbox != null) {
+						String[] b = bbox.split(",");
+						Bounds bounds = null;
+						if (b.length == 4)
+							bounds = new Bounds(
+									new LatLon(Double.parseDouble(b[0]),Double.parseDouble(b[1])),
+									new LatLon(Double.parseDouble(b[2]),Double.parseDouble(b[3])));
+						DataSource src = new DataSource(bounds, origin);
+						ds.dataSources.add(src);
+					}
+					
+				// ---- PARSING NODES AND WAYS ----
+					
+				} else if (qName.equals("node")) {
+					current = new Node(new LatLon(getDouble(atts, "lat"), getDouble(atts, "lon")));
+					readCommon(atts, current);
+					nodes.put(current.id, (Node)current);
+				} else if (qName.equals("way")) {
+					current = new OsmPrimitiveData();
+					readCommon(atts, current);
+					ways.put((OsmPrimitiveData)current, new LinkedList<Long>());
+				} else if (qName.equals("nd")) {
+					Collection<Long> list = ways.get(current);
+					if (list == null)
+						throw new SAXException(tr("Found <nd> element in non-way."));
+					long id = getLong(atts, "ref");
+					if (id == 0)
+						throw new SAXException(tr("<nd> has zero ref"));
+					list.add(id);
+
+				// ---- PARSING ENTITIES ----			
+
+				} else if (qName.equals("relation")) {
+					current = new OsmPrimitiveData();
+					readCommon(atts, current);
+					relations.put((OsmPrimitiveData)current, new LinkedList<RelationMemberData>());
+				} else if (qName.equals("member")) {
+					Collection<RelationMemberData> list = relations.get(current);
+					if (list == null)
+						throw new SAXException(tr("Found <member> tag on non-relation."));
+					RelationMemberData emd = new RelationMemberData();
+					emd.relationMember = new RelationMember();
+					emd.id = getLong(atts, "ref");
+					emd.type=atts.getValue("type");
+					emd.relationMember.role = atts.getValue("role");
+					
+					if (emd.id == 0)
+						throw new SAXException(tr("Incomplete <member> specification with ref=0"));
+					
+					list.add(emd);
+					
+				// ---- PARSING TAGS (applicable to all objects) ----
+					
+				} else if (qName.equals("tag")) {
+					current.put(atts.getValue("k"), atts.getValue("v"));
+				}
+			} catch (NumberFormatException x) {
+				x.printStackTrace(); // SAXException does not chain correctly
+				throw new SAXException(x.getMessage(), x);
+			} catch (NullPointerException x) {
+				x.printStackTrace(); // SAXException does not chain correctly
+				throw new SAXException(tr("NullPointerException, Possibly some missing tags."), x);
+			}
+		}
+
+		private double getDouble(Attributes atts, String value) {
+			return Double.parseDouble(atts.getValue(value));
+		}
+	}
+	
+	/** 
+	 * Constructor initializes list of allowed protocol versions.
+	 */
+	public OsmReader() {
+		// first add the main server version
+		allowedVersions.add(Main.pref.get("osm-server.version", "0.5"));
+		// now also add all compatible versions
+		String[] additionalVersions = 
+			Main.pref.get("osm-server.additional-versions", "").split("/,/");
+		if (additionalVersions.length == 1 && additionalVersions[0].length() == 0)
+			additionalVersions = new String[] {};
+		allowedVersions.addAll(Arrays.asList(additionalVersions));	
+	}
+
+	/**
+	 * Read out the common attributes from atts and put them into this.current.
+	 */
+	void readCommon(Attributes atts, OsmPrimitive current) throws SAXException {
+		current.id = getLong(atts, "id");
+		if (current.id == 0)
+			throw new SAXException(tr("Illegal object with id=0"));
+
+		String time = atts.getValue("timestamp");
+		if (time != null && time.length() != 0) {
+			try {
+				current.timestamp = DateParser.parse(time);
+			} catch (ParseException e) {
+				e.printStackTrace();
+				throw new SAXException(tr("Couldn't read time format \"{0}\".",time));
+			}
+		}
+		
+		// user attribute added in 0.4 API
+		String user = atts.getValue("user");
+		if (user != null) {
+			// do not store literally; get object reference for string
+			current.user = User.get(user);
+		}
+		
+		// visible attribute added in 0.4 API
+		String visible = atts.getValue("visible");
+		if (visible != null) {
+			current.visible = Boolean.parseBoolean(visible);
+		}
+
+		String action = atts.getValue("action");
+		if (action == null)
+			return;
+		if (action.equals("delete"))
+			current.delete(true);
+		else if (action.startsWith("modify"))
+			current.modified = true;
+	}
+	private long getLong(Attributes atts, String value) throws SAXException {
+		String s = atts.getValue(value);
+		if (s == null)
+			throw new SAXException(tr("Missing required attribute \"{0}\".",value));
+		return Long.parseLong(s);
+	}
+
+	private Node findNode(long id) {
+	    Node n = nodes.get(id);
+	    if (n != null)
+	    	return n;
+	    for (Node node : references.nodes)
+	    	if (node.id == id)
+	    		return node;
+	    // TODO: This has to be changed to support multiple layers.
+	    for (Node node : Main.ds.nodes)
+	    	if (node.id == id)
+	    		return new Node(node);
+	    return null;
+    }
+
+	private void createWays() {
+		for (Entry<OsmPrimitiveData, Collection<Long>> e : ways.entrySet()) {
+			Way w = new Way();
+			boolean failed = false;
+			for (long id : e.getValue()) {
+				Node n = findNode(id);
+				if (n == null) {
+					failed = true;
+					break;
+				}
+				w.nodes.add(n);
+			}
+			if (failed) continue;
+			e.getKey().copyTo(w);
+			adder.visit(w);
+		}
+	}
+
+	/**
+	 * Return the Way object with the given id, or null if it doesn't
+	 * exist yet. This method only looks at ways stored in the data set.
+	 * 
+	 * @param id
+	 * @return way object or null
+	 */
+	private Way findWay(long id) {
+		for (Way wy : ds.ways)
+			if (wy.id == id)
+				return wy;
+		for (Way wy : Main.ds.ways)
+			if (wy.id == id)
+				return wy;
+		return null;
+	}
+
+	/**
+	 * Return the Relation object with the given id, or null if it doesn't
+	 * exist yet. This method only looks at relations stored in the data set.
+	 * 
+	 * @param id
+	 * @return relation object or null
+	 */
+	private Relation findRelation(long id) {
+		for (Relation e : ds.relations)
+			if (e.id == id)
+				return e;
+		for (Relation e : Main.ds.relations)
+			if (e.id == id)
+				return e;
+		return null;
+	}
+
+	/**
+	 * Create relations. This is slightly different than n/s/w because 
+	 * unlike other objects, relations may reference other relations; it
+	 * is not guaranteed that a referenced relation will have been created
+	 * before it is referenced. So we have to create all relations first,
+	 * and populate them later.
+	 */
+	private void createRelations() {
+		
+		// pass 1 - create all relations
+		for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) {
+			Relation en = new Relation();
+			e.getKey().copyTo(en);
+			adder.visit(en);
+		}
+
+		// pass 2 - sort out members
+		for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) {
+			Relation en = findRelation(e.getKey().id);
+			if (en == null) throw new Error("Failed to create relation " + e.getKey().id);
+			
+			for (RelationMemberData emd : e.getValue()) {
+				RelationMember em = emd.relationMember;
+				if (emd.type.equals("node")) {
+					em.member = findNode(emd.id);
+					if (em.member == null) {
+						em.member = new Node(emd.id);
+						adder.visit((Node)em.member);
+					}
+				} else if (emd.type.equals("way")) {
+					em.member = findWay(emd.id);
+					if (em.member == null) {
+						em.member = new Way(emd.id);
+						adder.visit((Way)em.member);
+					}
+				} else if (emd.type.equals("relation")) {
+					em.member = findRelation(emd.id);
+					if (em.member == null) {
+						em.member = new Relation(emd.id);
+						adder.visit((Relation)em.member);
+					}
+				} else {
+					// this is an error.
+				}
+				en.members.add(em);
+			}
+		}
+	}
+
+	/**
+	 * Parse the given input source and return the dataset.
+	 * @param ref The dataset that is search in for references first. If
+	 * 	the Reference is not found here, Main.ds is searched and a copy of the
+	 *  elemet found there is returned.
+	 */
+	public static DataSet parseDataSet(InputStream source, DataSet ref, PleaseWaitDialog pleaseWaitDlg) throws SAXException, IOException {
+		OsmReader osm = new OsmReader();
+		osm.references = ref == null ? new DataSet() : ref;
+
+		// phase 1: Parse nodes and read in raw ways
+		InputSource inputSource = new InputSource(new InputStreamReader(source, "UTF-8"));
+		try {
+	        SAXParserFactory.newInstance().newSAXParser().parse(inputSource, osm.new Parser());
+        } catch (ParserConfigurationException e1) {
+        	e1.printStackTrace(); // broken SAXException chaining
+        	throw new SAXException(e1);
+        }
+		if (pleaseWaitDlg != null) {
+			pleaseWaitDlg.progress.setValue(0);
+			pleaseWaitDlg.currentAction.setText(tr("Preparing data..."));
+		}
+		for (Node n : osm.nodes.values())
+			osm.adder.visit(n);
+
+		try {
+			osm.createWays();
+			osm.createRelations();
+		} catch (NumberFormatException e) {
+			e.printStackTrace();
+			throw new SAXException(tr("Illformed Node id"));
+		}
+
+		// clear all negative ids (new to this file)
+		for (OsmPrimitive o : osm.ds.allPrimitives())
+			if (o.id < 0)
+				o.id = 0;
+
+		return osm.ds;
+	}
+}
