Index: trunk/src/org/openstreetmap/josm/data/osm/TagCollection.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/TagCollection.java	(revision 10735)
+++ trunk/src/org/openstreetmap/josm/data/osm/TagCollection.java	(revision 10736)
@@ -4,4 +4,5 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -15,6 +16,9 @@
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Objects;
 import java.util.Set;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.openstreetmap.josm.Main;
@@ -45,5 +49,7 @@
  * @since 2008
  */
-public class TagCollection implements Iterable<Tag> {
+public class TagCollection implements Iterable<Tag>, Serializable {
+
+    private static final long serialVersionUID = 1;
 
     /**
@@ -146,5 +152,5 @@
     }
 
-    private final Set<Tag> tags = new HashSet<>();
+    private final Map<Tag, Integer> tags = new HashMap<>();
 
     /**
@@ -163,5 +169,5 @@
     public TagCollection(TagCollection other) {
         if (other != null) {
-            tags.addAll(other.tags);
+            tags.putAll(other.tags);
         }
     }
@@ -200,7 +206,17 @@
      */
     public final void add(Tag tag) {
-        if (tag == null) return;
-        if (tags.contains(tag)) return;
-        tags.add(tag);
+        if (tag != null) {
+            tags.merge(tag, 1, (i, j) -> i + j);
+        }
+    }
+
+    /**
+     * Gets the number of this this tag was added to the collection.
+     * @param tag The tag
+     * @return The number of thimes this tag is used in this collection.
+     * @since 10736
+     */
+    public int getTagOccurence(Tag tag) {
+        return tags.getOrDefault(tag, 0);
     }
 
@@ -225,6 +241,9 @@
      */
     public final void add(TagCollection tags) {
-        if (tags == null) return;
-        this.tags.addAll(tags.tags);
+        if (tags != null) {
+            for (Entry<Tag, Integer> entry : tags.tags.entrySet()) {
+                this.tags.merge(entry.getKey(), entry.getValue(), (i, j) -> i + j);
+            }
+        }
     }
 
@@ -247,6 +266,7 @@
      */
     public void remove(Collection<Tag> tags) {
-        if (tags == null) return;
-        this.tags.removeAll(tags);
+        if (tags != null) {
+            tags.stream().forEach(this::remove);
+        }
     }
 
@@ -258,6 +278,7 @@
      */
     public void remove(TagCollection tags) {
-        if (tags == null) return;
-        this.tags.removeAll(tags.tags);
+        if (tags != null) {
+            tags.tags.keySet().stream().forEach(this::remove);
+        }
     }
 
@@ -269,9 +290,10 @@
      */
     public void removeByKey(String key) {
-        if (key == null) return;
-        Iterator<Tag> it = tags.iterator();
-        while (it.hasNext()) {
-            if (it.next().matchesKey(key)) {
-                it.remove();
+        if (key != null) {
+            Iterator<Tag> it = tags.keySet().iterator();
+            while (it.hasNext()) {
+                if (it.next().matchesKey(key)) {
+                    it.remove();
+                }
             }
         }
@@ -298,5 +320,5 @@
      */
     public boolean contains(Tag tag) {
-        return tags.contains(tag);
+        return tags.containsKey(tag);
     }
 
@@ -306,11 +328,9 @@
      * @param key the key to look up
      * @return true if this tag collection contains at least one tag with key <code>key</code>; false, otherwise
-     */
+     * @deprecated Use {@link #hasTagsFor(String)} instead.
+     */
+    @Deprecated
     public boolean containsKey(String key) {
-        if (key == null) return false;
-        for (Tag tag: tags) {
-            if (tag.matchesKey(key)) return true;
-        }
-        return false;
+        return generateStreamForKey(key).findAny().isPresent();
     }
 
@@ -324,6 +344,9 @@
      */
     public boolean containsAll(Collection<Tag> tags) {
-        if (tags == null) return false;
-        return this.tags.containsAll(tags);
+        if (tags == null) {
+            return false;
+        } else {
+            return this.tags.keySet().containsAll(tags);
+        }
     }
 
@@ -336,12 +359,9 @@
      */
     public boolean containsAllKeys(Collection<String> keys) {
-        if (keys == null) return false;
-        for (String key: keys) {
-            if (key == null) {
-                continue;
-            }
-            if (!containsKey(key)) return false;
-        }
-        return true;
+        if (keys == null) {
+            return false;
+        } else {
+            return keys.stream().filter(Objects::nonNull).allMatch(this::hasTagsFor);
+        }
     }
 
@@ -350,15 +370,8 @@
      *
      * @param key the key to look up
-     * @return the number of tags with key <code>key</code>. 0, if key is null.
+     * @return the number of tags with key <code>key</code>, including the empty "" value. 0, if key is null.
      */
     public int getNumTagsFor(String key) {
-        if (key == null) return 0;
-        int count = 0;
-        for (Tag tag: tags) {
-            if (tag.matchesKey(key)) {
-                count++;
-            }
-        }
-        return count;
+        return (int) generateStreamForKey(key).count();
     }
 
@@ -381,8 +394,5 @@
      */
     public boolean hasValuesFor(String key) {
-        if (key == null) return false;
-        Set<String> values = getTagsFor(key).getValues();
-        values.remove("");
-        return !values.isEmpty();
+        return generateStreamForKey(key).filter(t -> !t.getValue().isEmpty()).findAny().isPresent();
     }
 
@@ -397,7 +407,5 @@
      */
     public boolean hasUniqueNonEmptyValue(String key) {
-        if (key == null) return false;
-        Set<String> values = getTagsFor(key).getValues();
-        return values.size() == 1 && !values.contains("");
+        return generateStreamForKey(key).filter(t -> !t.getValue().isEmpty()).count() == 1;
     }
 
@@ -410,7 +418,5 @@
      */
     public boolean hasEmptyValue(String key) {
-        if (key == null) return false;
-        Set<String> values = getTagsFor(key).getValues();
-        return values.contains("");
+        return generateStreamForKey(key).anyMatch(t -> t.getValue().isEmpty());
     }
 
@@ -424,6 +430,5 @@
      */
     public boolean hasUniqueEmptyValue(String key) {
-        if (key == null) return false;
-        Set<String> values = getTagsFor(key).getValues();
+        Set<String> values = getValues(key);
         return values.size() == 1 && values.contains("");
     }
@@ -439,11 +444,5 @@
     public TagCollection getTagsFor(String key) {
         TagCollection ret = new TagCollection();
-        if (key == null)
-            return ret;
-        for (Tag tag: tags) {
-            if (tag.matchesKey(key)) {
-                ret.add(tag);
-            }
-        }
+        generateStreamForKey(key).forEach(ret::add);
         return ret;
     }
@@ -475,5 +474,5 @@
      */
     public Set<Tag> asSet() {
-        return new HashSet<>(tags);
+        return new HashSet<>(tags.keySet());
     }
 
@@ -482,8 +481,8 @@
      * Note that the order of the list is not preserved between method invocations.
      *
-     * @return the tags of this tag collection as list.
+     * @return the tags of this tag collection as list. There are no dupplicate values.
      */
     public List<Tag> asList() {
-        return new ArrayList<>(tags);
+        return new ArrayList<>(tags.keySet());
     }
 
@@ -495,5 +494,5 @@
     @Override
     public Iterator<Tag> iterator() {
-        return tags.iterator();
+        return tags.keySet().iterator();
     }
 
@@ -504,9 +503,5 @@
      */
     public Set<String> getKeys() {
-        Set<String> ret = new HashSet<>();
-        for (Tag tag: tags) {
-            ret.add(tag.getKey());
-        }
-        return ret;
+        return generateKeyStream().collect(Collectors.toCollection(HashSet::new));
     }
 
@@ -517,16 +512,6 @@
      */
     public Set<String> getKeysWithMultipleValues() {
-        Map<String, Integer> counters = new HashMap<>();
-        for (Tag tag: tags) {
-            Integer v = counters.get(tag.getKey());
-            counters.put(tag.getKey(), (v == null) ? 1 : v+1);
-        }
-        Set<String> ret = new HashSet<>();
-        for (Entry<String, Integer> e : counters.entrySet()) {
-            if (e.getValue() > 1) {
-                ret.add(e.getKey());
-            }
-        }
-        return ret;
+        HashSet<String> singleKeys = new HashSet<>();
+        return generateKeyStream().filter(key -> !singleKeys.add(key)).collect(Collectors.toSet());
     }
 
@@ -562,9 +547,5 @@
      */
     public Set<String> getValues() {
-        Set<String> ret = new HashSet<>();
-        for (Tag tag: tags) {
-            ret.add(tag.getValue());
-        }
-        return ret;
+        return tags.keySet().stream().map(e -> e.getValue()).collect(Collectors.toSet());
     }
 
@@ -578,12 +559,6 @@
      */
     public Set<String> getValues(String key) {
-        Set<String> ret = new HashSet<>();
-        if (key == null) return ret;
-        for (Tag tag: tags) {
-            if (tag.matchesKey(key)) {
-                ret.add(tag.getValue());
-            }
-        }
-        return ret;
+        // null-safe
+        return generateStreamForKey(key).map(e -> e.getValue()).collect(Collectors.toSet());
     }
 
@@ -594,5 +569,5 @@
      */
     public boolean isApplicableToPrimitive() {
-        return size() == getKeys().size();
+        return getKeysWithMultipleValues().isEmpty();
     }
 
@@ -607,7 +582,6 @@
     public void applyTo(Tagged primitive) {
         if (primitive == null) return;
-        if (!isApplicableToPrimitive())
-            throw new IllegalStateException(tr("Tag collection cannot be applied to a primitive because there are keys with multiple values."));
-        for (Tag tag: tags) {
+        ensureApplicableToPrimitive();
+        for (Tag tag: tags.keySet()) {
             if (tag.getValue() == null || tag.getValue().isEmpty()) {
                 primitive.remove(tag.getKey());
@@ -628,6 +602,5 @@
     public void applyTo(Collection<? extends Tagged> primitives) {
         if (primitives == null) return;
-        if (!isApplicableToPrimitive())
-            throw new IllegalStateException(tr("Tag collection cannot be applied to a primitive because there are keys with multiple values."));
+        ensureApplicableToPrimitive();
         for (Tagged primitive: primitives) {
             applyTo(primitive);
@@ -645,8 +618,7 @@
     public void replaceTagsOf(Tagged primitive) {
         if (primitive == null) return;
-        if (!isApplicableToPrimitive())
-            throw new IllegalStateException(tr("Tag collection cannot be applied to a primitive because there are keys with multiple values."));
+        ensureApplicableToPrimitive();
         primitive.removeAll();
-        for (Tag tag: tags) {
+        for (Tag tag: tags.keySet()) {
             primitive.put(tag.getKey(), tag.getValue());
         }
@@ -663,9 +635,13 @@
     public void replaceTagsOf(Collection<? extends Tagged> primitives) {
         if (primitives == null) return;
+        ensureApplicableToPrimitive();
+        for (Tagged primitive: primitives) {
+            replaceTagsOf(primitive);
+        }
+    }
+
+    private void ensureApplicableToPrimitive() {
         if (!isApplicableToPrimitive())
             throw new IllegalStateException(tr("Tag collection cannot be applied to a primitive because there are keys with multiple values."));
-        for (Tagged primitive: primitives) {
-            replaceTagsOf(primitive);
-        }
     }
 
@@ -674,14 +650,10 @@
      *
      * @param other the other tag collection. If null, replies an empty tag collection.
-     * @return the intersection of this tag collection and another tag collection
+     * @return the intersection of this tag collection and another tag collection. All counts are set to 1.
      */
     public TagCollection intersect(TagCollection other) {
         TagCollection ret = new TagCollection();
         if (other != null) {
-            for (Tag tag: tags) {
-                if (other.contains(tag)) {
-                    ret.add(tag);
-                }
-            }
+            tags.keySet().stream().filter(other::contains).forEach(ret::add);
         }
         return ret;
@@ -706,5 +678,5 @@
      *
      * @param other the other tag collection. May be null.
-     * @return the union of this tag collection and another tag collection
+     * @return the union of this tag collection and another tag collection. The tag count is summed.
      */
     public TagCollection union(TagCollection other) {
@@ -758,8 +730,8 @@
 
     /**
-     * Replies the sum of all numeric tag values.
+     * Replies the sum of all numeric tag values. Ignores dupplicates.
      * @param key the key to look up
      *
-     * @return the sum of all numeric tag values, as string
+     * @return the sum of all numeric tag values, as string.
      * @since 7743
      */
@@ -776,4 +748,17 @@
     }
 
+    private Stream<String> generateKeyStream() {
+        return tags.keySet().stream().map(tag -> tag.getKey());
+    }
+
+    /**
+     * Get a stram for the given key.
+     * @param key The key
+     * @return The stream. An empty stream if key is <code>null</code>
+     */
+    private Stream<Tag> generateStreamForKey(String key) {
+        return tags.keySet().stream().filter(e -> e.matchesKey(key));
+    }
+
     @Override
     public String toString() {
Index: trunk/src/org/openstreetmap/josm/data/osm/TagMap.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/TagMap.java	(revision 10735)
+++ trunk/src/org/openstreetmap/josm/data/osm/TagMap.java	(revision 10736)
@@ -7,4 +7,5 @@
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.ConcurrentModificationException;
 import java.util.Iterator;
@@ -151,4 +152,15 @@
     }
 
+    /**
+     * Creates a new map using the given list of tags. For dupplicate keys the last value found is used.
+     * @param tags The tags
+     * @since 10736
+     */
+    public TagMap(Collection<Tag> tags) {
+        for (Tag tag : tags) {
+            put(tag.getKey(), tag.getValue());
+        }
+    }
+
     @Override
     public Set<Entry<String, String>> entrySet() {
Index: trunk/src/org/openstreetmap/josm/data/osm/Tagged.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/Tagged.java	(revision 10735)
+++ trunk/src/org/openstreetmap/josm/data/osm/Tagged.java	(revision 10736)
@@ -35,4 +35,14 @@
 
     /**
+     * Sets a key/value pairs
+     *
+     * @param tag The tag to set.
+     * @since 10736
+     */
+    default void put(Tag tag) {
+        put(tag.getKey(), tag.getValue());
+    }
+
+    /**
      * Replies the value of the given key; null, if there is no value for this key
      *
