Index: /trunk/src/org/openstreetmap/josm/data/Bounds.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/Bounds.java	(revision 2685)
+++ /trunk/src/org/openstreetmap/josm/data/Bounds.java	(revision 2686)
@@ -69,4 +69,13 @@
             }
         }
+        if (!LatLon.isValidLat(values[0]))
+            throw new IllegalArgumentException(tr("Illegal latitude value ''{0}''", values[0]));
+        if (!LatLon.isValidLon(values[1]))
+            throw new IllegalArgumentException(tr("Illegal longitude value ''{0}''", values[1]));
+        if (!LatLon.isValidLat(values[2]))
+            throw new IllegalArgumentException(tr("Illegal latitude value ''{0}''", values[2]));
+        if (!LatLon.isValidLon(values[3]))
+            throw new IllegalArgumentException(tr("Illegal latitude value ''{0}''", values[3]));
+
         this.min = new LatLon(values[0], values[1]);
         this.max = new LatLon(values[2], values[3]);
Index: /trunk/src/org/openstreetmap/josm/data/osm/Changeset.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/Changeset.java	(revision 2685)
+++ /trunk/src/org/openstreetmap/josm/data/osm/Changeset.java	(revision 2686)
@@ -37,4 +37,6 @@
      */
     private boolean incomplete;
+    /** the changeset content */
+    private ChangesetDataSet content = null;
 
     /**
@@ -275,4 +277,19 @@
         this.tags = new HashMap<String, String>(other.tags);
         this.incomplete = other.incomplete;
+
+        // FIXME: merging of content required?
+        this.content = other.content;
+    }
+
+    public boolean hasContent() {
+        return content != null;
+    }
+
+    public ChangesetDataSet getContent() {
+        return content;
+    }
+
+    public void setContent(ChangesetDataSet content) {
+        this.content = content;
     }
 }
Index: /trunk/src/org/openstreetmap/josm/data/osm/ChangesetCache.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/ChangesetCache.java	(revision 2685)
+++ /trunk/src/org/openstreetmap/josm/data/osm/ChangesetCache.java	(revision 2686)
@@ -5,7 +5,12 @@
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.logging.Logger;
+
+import javax.swing.SwingUtilities;
 
 import org.openstreetmap.josm.Main;
@@ -13,12 +18,36 @@
 import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
 
+/**
+ * ChangesetCache is global in-memory cache for changesets downloaded from
+ * an OSM API server. The unique instance is available as singleton, see
+ * {@see #getInstance()}.
+ *
+ * Clients interested in cache updates can register for {@see ChangesetCacheEvent}s
+ * using {@see #addChangesetCacheListener(ChangesetCacheListener)}. They can use
+ * {@see #removeChangesetCacheListener(ChangesetCacheListener)} to unregister as
+ * cache event listener.
+ * 
+ * The cache itself listens to {@see java.util.prefs.PreferenceChangeEvent}s. It
+ * clears itself if the OSM API URL is changed in the preferences.
+ * 
+ * {@see ChangesetCacheEvent}s are delivered on the EDT.
+ * 
+ */
 public class ChangesetCache implements PreferenceChangedListener{
-    //static private final Logger logger = Logger.getLogger(ChangesetCache.class.getName());
+    static private final Logger logger = Logger.getLogger(ChangesetCache.class.getName());
+
+    /** the unique instance */
     static private final ChangesetCache instance = new ChangesetCache();
 
+    /**
+     * Replies the unique instance of the cache
+     * 
+     * @return the unique instance of the cache
+     */
     public static ChangesetCache getInstance() {
         return instance;
     }
 
+    /** the cached changesets */
     private final Map<Integer, Changeset> cache  = new HashMap<Integer, Changeset>();
 
@@ -31,16 +60,31 @@
 
     public void addChangesetCacheListener(ChangesetCacheListener listener) {
-        if (listener != null) {
-            listeners.addIfAbsent(listener);
+        synchronized(listeners) {
+            if (listener != null && ! listeners.contains(listener)) {
+                listeners.add(listener);
+            }
         }
     }
 
     public void removeChangesetCacheListener(ChangesetCacheListener listener) {
-        listeners.remove(listener);
-    }
-
-    protected void fireChangesetCacheEvent(ChangesetCacheEvent e) {
-        for(ChangesetCacheListener l: listeners) {
-            l.changesetCacheUpdated(e);
+        synchronized(listeners) {
+            if (listener != null && listeners.contains(listener)) {
+                listeners.remove(listener);
+            }
+        }
+    }
+
+    protected void fireChangesetCacheEvent(final ChangesetCacheEvent e) {
+        Runnable r = new Runnable() {
+            public void run() {
+                for(ChangesetCacheListener l: listeners) {
+                    l.changesetCacheUpdated(e);
+                }
+            }
+        };
+        if (SwingUtilities.isEventDispatchThread()) {
+            r.run();
+        } else {
+            SwingUtilities.invokeLater(r);
         }
     }
@@ -89,4 +133,8 @@
     }
 
+    public Set<Changeset> getChangesets() {
+        return new HashSet<Changeset>(cache.values());
+    }
+
     protected void remove(int id, DefaultChangesetCacheEvent e) {
         if (id <= 0) return;
@@ -111,4 +159,24 @@
     }
 
+    /**
+     * Removes the changesets in <code>changesets</code> from the cache. A
+     * {@see ChangesetCacheEvent} is fired.
+     * 
+     * @param changesets the changesets to remove. Ignored if null.
+     */
+    public void remove(Collection<Changeset> changesets) {
+        if (changesets == null) return;
+        DefaultChangesetCacheEvent evt = new DefaultChangesetCacheEvent(this);
+        for (Changeset cs : changesets) {
+            if (cs == null || cs.isNew()) {
+                continue;
+            }
+            remove(cs.getId(), evt);
+        }
+        if (! evt.isEmpty()) {
+            fireChangesetCacheEvent(evt);
+        }
+    }
+
     public int size() {
         return cache.size();
Index: /trunk/src/org/openstreetmap/josm/data/osm/ChangesetDataSet.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/ChangesetDataSet.java	(revision 2686)
+++ /trunk/src/org/openstreetmap/josm/data/osm/ChangesetDataSet.java	(revision 2686)
@@ -0,0 +1,194 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+
+/**
+ * A ChangesetDataSet holds the content of a changeset.
+ */
+public class ChangesetDataSet {
+
+    public static enum ChangesetModificationType {
+        CREATED,
+        UPDATED,
+        DELETED
+    }
+
+    public static interface ChangesetDataSetEntry {
+        public ChangesetModificationType getModificationType();
+        public HistoryOsmPrimitive getPrimitive();
+    }
+
+    final private Map<PrimitiveId, HistoryOsmPrimitive> primitives = new HashMap<PrimitiveId, HistoryOsmPrimitive>();
+    final private Map<PrimitiveId, ChangesetModificationType> modificationTypes = new HashMap<PrimitiveId, ChangesetModificationType>();
+
+    /**
+     * Remembers a history primitive with the given modification type
+     * 
+     * @param primitive the primitive. Must not be null.
+     * @param cmt the modification type. Must not be null.
+     * @throws IllegalArgumentException thrown if primitive is null
+     * @throws IllegalArgumentException thrown if cmt is null
+     */
+    public void put(HistoryOsmPrimitive primitive, ChangesetModificationType cmt) throws IllegalArgumentException{
+        CheckParameterUtil.ensureParameterNotNull(primitive,"primitive");
+        CheckParameterUtil.ensureParameterNotNull(cmt,"cmt");
+        primitives.put(primitive.getPrimitiveId(), primitive);
+        modificationTypes.put(primitive.getPrimitiveId(), cmt);
+    }
+
+    /**
+     * Replies true if the changeset content contains the object with primitive <code>id</code>.
+     * @param id the id.
+     * @return true if the changeset content contains the object with primitive <code>id</code>
+     */
+    public boolean contains(PrimitiveId id) {
+        if (id == null) return false;
+        return primitives.containsKey(id);
+    }
+
+    /**
+     * Replies the modification type for the object with id <code>id</code>. Replies null, if id is null or
+     * if the object with id <code>id</code> isn't in the changeset content.
+     *
+     * @param id the id
+     * @return the modification type
+     */
+    public ChangesetModificationType getModificationType(PrimitiveId id) {
+        if (!contains(id)) return null;
+        return modificationTypes.get(id);
+    }
+
+    /**
+     * Replies true if the primitive with id <code>id</code> was created in this
+     * changeset. Replies false, if id is null.
+     * 
+     * @param id the id
+     * @return true if the primitive with id <code>id</code> was created in this
+     * changeset.
+     */
+    public boolean isCreated(PrimitiveId id) {
+        if (!contains(id)) return false;
+        return ChangesetModificationType.CREATED.equals(getModificationType(id));
+    }
+
+    /**
+     * Replies true if the primitive with id <code>id</code> was updated in this
+     * changeset. Replies false, if id is null.
+     * 
+     * @param id the id
+     * @return true if the primitive with id <code>id</code> was updated in this
+     * changeset.
+     */
+    public boolean isUpdated(PrimitiveId id) {
+        if (!contains(id)) return false;
+        return ChangesetModificationType.UPDATED.equals(getModificationType(id));
+    }
+
+    /**
+     * Replies true if the primitive with id <code>id</code> was deleted in this
+     * changeset. Replies false, if id is null.
+     * 
+     * @param id the id
+     * @return true if the primitive with id <code>id</code> was deleted in this
+     * changeset.
+     */
+    public boolean isDeleted(PrimitiveId id) {
+        if (!contains(id)) return false;
+        return ChangesetModificationType.DELETED.equals(getModificationType(id));
+    }
+
+    /**
+     * Replies the set of primitives with a specific modification type
+     * 
+     * @param cmt the modification type. Must not be null.
+     * @return the set of primitives
+     * @throws IllegalArgumentException thrown if cmt is null
+     */
+    public Set<HistoryOsmPrimitive> getPrimitivesByModificationType(ChangesetModificationType cmt) throws IllegalArgumentException {
+        CheckParameterUtil.ensureParameterNotNull(cmt,"cmt");
+        HashSet<HistoryOsmPrimitive> ret = new HashSet<HistoryOsmPrimitive>();
+        for (Entry<PrimitiveId, ChangesetModificationType> entry: modificationTypes.entrySet()) {
+            if (entry.getValue().equals(cmt)) {
+                ret.add(primitives.get(entry.getKey()));
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Replies the number of objects in the dataset
+     * 
+     * @return the number of objects in the dataset
+     */
+    public int size() {
+        return primitives.size();
+    }
+
+    /**
+     * Replies the {@see HistoryOsmPrimitive} with id <code>id</code> from this
+     * dataset. null, if there is no such primitive in the data set.
+     * 
+     * @param id the id
+     * @return  the {@see HistoryOsmPrimitive} with id <code>id</code> from this
+     * dataset
+     */
+    public HistoryOsmPrimitive getPrimitive(PrimitiveId id) {
+        if (id == null)  return null;
+        return primitives.get(id);
+    }
+
+
+    public Iterator<ChangesetDataSetEntry> iterator() {
+        return new DefaultIterator();
+    }
+
+    private static class DefaultChangesetDataSetEntry implements ChangesetDataSetEntry {
+        private ChangesetModificationType modificationType;
+        private HistoryOsmPrimitive primitive;
+
+        public DefaultChangesetDataSetEntry(ChangesetModificationType modificationType, HistoryOsmPrimitive primitive) {
+            this.modificationType = modificationType;
+            this.primitive = primitive;
+        }
+
+        public ChangesetModificationType getModificationType() {
+            return modificationType;
+        }
+
+        public HistoryOsmPrimitive getPrimitive() {
+            return primitive;
+        }
+    }
+
+    private class DefaultIterator implements Iterator<ChangesetDataSetEntry> {
+        private Iterator<Entry<PrimitiveId, ChangesetModificationType>> typeIterator;
+
+        public DefaultIterator() {
+            typeIterator = modificationTypes.entrySet().iterator();
+        }
+
+        public boolean hasNext() {
+            return typeIterator.hasNext();
+        }
+
+        public ChangesetDataSetEntry next() {
+            Entry<PrimitiveId, ChangesetModificationType> next = typeIterator.next();
+            ChangesetModificationType type = next.getValue();
+            HistoryOsmPrimitive primitive = primitives.get(next.getKey());
+            return new DefaultChangesetDataSetEntry(type, primitive);
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/data/osm/NameFormatter.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/NameFormatter.java	(revision 2685)
+++ /trunk/src/org/openstreetmap/josm/data/osm/NameFormatter.java	(revision 2686)
@@ -1,4 +1,5 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.data.osm;
+
 
 public interface NameFormatter {
Index: /trunk/src/org/openstreetmap/josm/data/osm/UserInfo.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/UserInfo.java	(revision 2685)
+++ /trunk/src/org/openstreetmap/josm/data/osm/UserInfo.java	(revision 2686)
@@ -9,5 +9,5 @@
 public class UserInfo {
     /** the user id */
-    private long id;
+    private int id;
     /** the display name */
     private String displayName;
@@ -27,8 +27,8 @@
     }
 
-    public long getId() {
+    public int getId() {
         return id;
     }
-    public void setId(long id) {
+    public void setId(int id) {
         this.id = id;
     }
Index: /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryNameFormatter.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryNameFormatter.java	(revision 2686)
+++ /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryNameFormatter.java	(revision 2686)
@@ -0,0 +1,8 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm.history;
+
+public interface HistoryNameFormatter {
+    String format(HistoryNode node);
+    String format(HistoryWay node);
+    String format(HistoryRelation node);
+}
Index: /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryNode.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryNode.java	(revision 2685)
+++ /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryNode.java	(revision 2686)
@@ -34,3 +34,8 @@
         this.coords = coords;
     }
+
+    @Override
+    public String getDisplayName(HistoryNameFormatter formatter) {
+        return formatter.format(this);
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryOsmPrimitive.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryOsmPrimitive.java	(revision 2685)
+++ /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryOsmPrimitive.java	(revision 2686)
@@ -7,4 +7,5 @@
 import java.util.Date;
 import java.util.HashMap;
+import java.util.Locale;
 import java.util.Map;
 
@@ -146,4 +147,51 @@
     }
 
+    /**
+     * Replies the name of this primitive. The default implementation replies the value
+     * of the tag <tt>name</tt> or null, if this tag is not present.
+     *
+     * @return the name of this primitive
+     */
+    public String getName() {
+        if (get("name") != null)
+            return get("name");
+        return null;
+    }
+
+    /**
+     * Replies the display name of a primitive formatted by <code>formatter</code>
+     *
+     * @return the display name
+     */
+    public abstract String getDisplayName(HistoryNameFormatter formatter);
+
+    /**
+     * Replies the a localized name for this primitive given by the value of the tags (in this order)
+     * <ul>
+     *   <li>name:lang_COUNTRY_Variant  of the current locale</li>
+     *   <li>name:lang_COUNTRY of the current locale</li>
+     *   <li>name:lang of the current locale</li>
+     *   <li>name of the current locale</li>
+     * </ul>
+     *
+     * null, if no such tag exists
+     *
+     * @return the name of this primitive
+     */
+    public String getLocalName() {
+        String key = "name:" + Locale.getDefault().toString();
+        if (get(key) != null)
+            return get(key);
+        key = "name:" + Locale.getDefault().getLanguage() + "_" + Locale.getDefault().getCountry();
+        if (get(key) != null)
+            return get(key);
+        key = "name:" + Locale.getDefault().getLanguage();
+        if (get(key) != null)
+            return get(key);
+        return getName();
+    }
+
+
+
     @Override
     public int hashCode() {
Index: /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryRelation.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryRelation.java	(revision 2685)
+++ /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryRelation.java	(revision 2686)
@@ -111,3 +111,8 @@
         members.add(member);
     }
+
+    @Override
+    public String getDisplayName(HistoryNameFormatter formatter) {
+        return formatter.format(this);
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryWay.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryWay.java	(revision 2685)
+++ /trunk/src/org/openstreetmap/josm/data/osm/history/HistoryWay.java	(revision 2686)
@@ -77,3 +77,17 @@
         nodeIds.add(ref);
     }
+
+    /**
+     * Replies true if this way is closed.
+     * 
+     * @return true if this way is closed.
+     */
+    public boolean isClosed() {
+        return getNumNodes() >= 3 && nodeIds.get(0) == nodeIds.get(nodeIds.size()-1);
+    }
+
+    @Override
+    public String getDisplayName(HistoryNameFormatter formatter) {
+        return formatter.format(this);
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/data/osm/visitor/BoundingXYVisitor.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/visitor/BoundingXYVisitor.java	(revision 2685)
+++ /trunk/src/org/openstreetmap/josm/data/osm/visitor/BoundingXYVisitor.java	(revision 2686)
@@ -1,4 +1,6 @@
 // License: GPL. Copyright 2007 by Immanuel Scholz and others
 package org.openstreetmap.josm.data.osm.visitor;
+
+import java.util.Collection;
 
 import org.openstreetmap.josm.Main;
@@ -29,6 +31,7 @@
     public void visit(Way w) {
         if (w.isIncomplete()) return;
-        for (Node n : w.getNodes())
+        for (Node n : w.getNodes()) {
             visit(n);
+        }
     }
 
@@ -61,8 +64,9 @@
         if(latlon != null)
         {
-            if(latlon instanceof CachedLatLon)
+            if(latlon instanceof CachedLatLon) {
                 visit(((CachedLatLon)latlon).getEastNorth());
-            else
+            } else {
                 visit(Main.proj.latlon2eastNorth(latlon));
+            }
         }
     }
@@ -70,8 +74,9 @@
     public void visit(EastNorth eastNorth) {
         if (eastNorth != null) {
-            if (bounds == null)
+            if (bounds == null) {
                 bounds = new ProjectionBounds(eastNorth);
-            else
+            } else {
                 bounds.extend(eastNorth);
+            }
         }
     }
@@ -111,6 +116,6 @@
         LatLon maxLatlon = Main.proj.eastNorth2latlon(bounds.max);
         bounds = new ProjectionBounds(
-        Main.proj.latlon2eastNorth(new LatLon(minLatlon.lat() - enlargeDegree, minLatlon.lon() - enlargeDegree)),
-        Main.proj.latlon2eastNorth(new LatLon(maxLatlon.lat() + enlargeDegree, maxLatlon.lon() + enlargeDegree)));
+                Main.proj.latlon2eastNorth(new LatLon(minLatlon.lat() - enlargeDegree, minLatlon.lon() - enlargeDegree)),
+                Main.proj.latlon2eastNorth(new LatLon(maxLatlon.lat() + enlargeDegree, maxLatlon.lon() + enlargeDegree)));
     }
 
@@ -118,3 +123,13 @@
         return "BoundingXYVisitor["+bounds+"]";
     }
+
+    public void computeBoundingBox(Collection<? extends OsmPrimitive> primitives) {
+        if (primitives == null) return;
+        for (OsmPrimitive p: primitives) {
+            if (p == null) {
+                continue;
+            }
+            p.visit(this);
+        }
+    }
 }
