Index: trunk/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java	(revision 4099)
+++ trunk/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java	(revision 4099)
@@ -0,0 +1,640 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.text.MessageFormat;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+
+public abstract class AbstractPrimitive implements IPrimitive {
+    
+    private static final AtomicLong idCounter = new AtomicLong(0);
+
+    static long generateUniqueId() {
+        return idCounter.decrementAndGet();
+    }
+
+    /**
+     * This flag shows, that the properties have been changed by the user
+     * and on upload the object will be send to the server
+     */
+    protected static final int FLAG_MODIFIED = 1 << 0;
+
+    /**
+     * The visible flag indicates, that an object is marked
+     * as deleted on the server.
+     */
+    protected static final int FLAG_VISIBLE  = 1 << 1;
+
+    /**
+     * An object that was deleted by the user.
+     * Deleted objects are usually hidden on the map and a request
+     * for deletion will be send to the server on upload.
+     * An object usually cannot be deleted if it has non-deleted
+     * objects still referring to it.
+     */
+    protected static final int FLAG_DELETED  = 1 << 2;
+
+    /**
+     * A primitive is incomplete if we know its id and type, but nothing more.
+     * Typically some members of a relation are incomplete until they are
+     * fetched from the server.
+     */
+    protected static final int FLAG_INCOMPLETE = 1 << 3;
+
+    /**
+     * Put several boolean flag to one short int field to save memory.
+     * Other bits of this field are used in subclasses.
+     */
+    protected volatile short flags = FLAG_VISIBLE;   // visible per default
+
+    /*-------------------
+     * OTHER PROPERTIES
+     *-------------------*/
+
+    /**
+     * Unique identifier in OSM. This is used to identify objects on the server.
+     * An id of 0 means an unknown id. The object has not been uploaded yet to
+     * know what id it will get.
+     *
+     */
+    protected long id = 0;
+
+    /**
+     * User that last modified this primitive, as specified by the server.
+     * Never changed by JOSM.
+     */
+    protected User user = null;
+
+    /**
+     * Contains the version number as returned by the API. Needed to
+     * ensure update consistency
+     */
+    protected int version = 0;
+
+    /**
+     * The id of the changeset this primitive was last uploaded to.
+     * 0 if it wasn't uploaded to a changeset yet of if the changeset
+     * id isn't known.
+     */
+    protected int changesetId;
+
+    protected int timestamp;
+
+    /**
+     * Get and write all attributes from the parameter. Does not fire any listener, so
+     * use this only in the data initializing phase
+     */
+    public void cloneFrom(AbstractPrimitive other) {
+        setKeys(other.getKeys());
+        id = other.id;
+        if (id <=0) {
+            // reset version and changeset id
+            version = 0;
+            changesetId = 0;
+        }
+        timestamp = other.timestamp;
+        if (id > 0) {
+            version = other.version;
+        }
+        flags = other.flags;
+        user= other.user;
+        if (id > 0 && other.changesetId > 0) {
+            // #4208: sometimes we cloned from other with id < 0 *and*
+            // an assigned changeset id. Don't know why yet. For primitives
+            // with id < 0 we don't propagate the changeset id any more.
+            //
+            setChangesetId(other.changesetId);
+        }
+    }
+
+    /**
+     * Replies the version number as returned by the API. The version is 0 if the id is 0 or
+     * if this primitive is incomplete.
+     *
+     * @see #setVersion(int)
+     */
+    @Override
+    public int getVersion() {
+        return version;
+    }
+
+    /**
+     * Replies the id of this primitive.
+     *
+     * @return the id of this primitive.
+     */
+    @Override
+    public long getId() {
+        long id = this.id;
+        return id >= 0?id:0;
+    }
+
+    /**
+     *
+     * @return Osm id if primitive already exists on the server. Unique negative value if primitive is new
+     */
+    @Override
+    public long getUniqueId() {
+        return id;
+    }
+
+    /**
+     *
+     * @return True if primitive is new (not yet uploaded the server, id <= 0)
+     */
+    @Override
+    public boolean isNew() {
+        return id <= 0;
+    }
+
+    /**
+     *
+     * @return True if primitive is new or undeleted
+     * @see #isNew()
+     * @see #isUndeleted()
+     */
+    public boolean isNewOrUndeleted() {
+        return (id <= 0) || ((flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0);
+    }
+
+    /**
+     * Sets the id and the version of this primitive if it is known to the OSM API.
+     *
+     * Since we know the id and its version it can't be incomplete anymore. incomplete
+     * is set to false.
+     *
+     * @param id the id. > 0 required
+     * @param version the version > 0 required
+     * @throws IllegalArgumentException thrown if id <= 0
+     * @throws IllegalArgumentException thrown if version <= 0
+     * @throws DataIntegrityProblemException If id is changed and primitive was already added to the dataset
+     */
+    @Override
+    public void setOsmId(long id, int version) {
+        if (id <= 0)
+            throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
+        if (version <= 0)
+            throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
+        this.id = id;
+        this.version = version;
+        this.setIncomplete(false);
+    }
+
+    /**
+     * Clears the id and version known to the OSM API. The id and the version is set to 0.
+     * incomplete is set to false. It's preferred to use copy constructor with clearId set to true instead
+     * of calling this method.
+     */
+    public void clearOsmId() {
+        // Not part of dataset - no lock necessary
+        this.id = generateUniqueId();
+        this.version = 0;
+        this.changesetId = 0; // reset changeset id on a new object
+        this.setIncomplete(false);
+    }
+
+    /**
+     * Replies the user who has last touched this object. May be null.
+     *
+     * @return the user who has last touched this object. May be null.
+     */
+    @Override
+    public User getUser() {
+        return user;
+    }
+
+    /**
+     * Sets the user who has last touched this object.
+     *
+     * @param user the user
+     */
+    @Override
+    public void setUser(User user) {
+        this.user = user;
+    }
+
+    /**
+     * Replies the id of the changeset this primitive was last uploaded to.
+     * 0 if this primitive wasn't uploaded to a changeset yet or if the
+     * changeset isn't known.
+     *
+     * @return the id of the changeset this primitive was last uploaded to.
+     */
+    @Override
+    public int getChangesetId() {
+        return changesetId;
+    }
+
+    /**
+     * Sets the changeset id of this primitive. Can't be set on a new
+     * primitive.
+     *
+     * @param changesetId the id. >= 0 required.
+     * @throws IllegalStateException thrown if this primitive is new.
+     * @throws IllegalArgumentException thrown if id < 0
+     */
+    @Override
+    public void setChangesetId(int changesetId) throws IllegalStateException, IllegalArgumentException {
+        if (this.changesetId == changesetId)
+            return;
+        if (changesetId < 0)
+            throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' >= 0 expected, got {1}", "changesetId", changesetId));
+        if (isNew() && changesetId > 0)
+            throw new IllegalStateException(tr("Cannot assign a changesetId > 0 to a new primitive. Value of changesetId is {0}", changesetId));
+
+        int old = this.changesetId;
+        this.changesetId = changesetId;
+    }
+
+    /**
+     * Replies the unique primitive id for this primitive
+     *
+     * @return the unique primitive id for this primitive
+     */
+    public PrimitiveId getPrimitiveId() {
+        return new SimplePrimitiveId(getUniqueId(), getType());
+    }
+
+    public OsmPrimitiveType getDisplayType() {
+        return getType();
+    }
+
+    @Override
+    public void setTimestamp(Date timestamp) {
+        this.timestamp = (int)(timestamp.getTime() / 1000);
+    }
+
+    /**
+     * Time of last modification to this object. This is not set by JOSM but
+     * read from the server and delivered back to the server unmodified. It is
+     * used to check against edit conflicts.
+     *
+     */
+    @Override
+    public Date getTimestamp() {
+        return new Date(timestamp * 1000l);
+    }
+
+    @Override
+    public boolean isTimestampEmpty() {
+        return timestamp == 0;
+    }
+
+    /* -------
+    /* FLAGS
+    /* ------*/
+
+    protected void updateFlags(int flag, boolean value) {
+        if (value) {
+            flags |= flag;
+        } else {
+            flags &= ~flag;
+        }
+    }
+    
+    /**
+     * Marks this primitive as being modified.
+     *
+     * @param modified true, if this primitive is to be modified
+     */
+    @Override
+    public void setModified(boolean modified) {
+        updateFlags(FLAG_MODIFIED, modified);
+    }
+
+    /**
+     * Replies <code>true</code> if the object has been modified since it was loaded from
+     * the server. In this case, on next upload, this object will be updated.
+     *
+     * Deleted objects are deleted from the server. If the objects are added (id=0),
+     * the modified is ignored and the object is added to the server.
+     *
+     * @return <code>true</code> if the object has been modified since it was loaded from
+     * the server
+     */
+    @Override
+    public boolean isModified() {
+        return (flags & FLAG_MODIFIED) != 0;
+    }
+
+    /**
+     * Replies <code>true</code>, if the object has been deleted.
+     *
+     * @return <code>true</code>, if the object has been deleted.
+     * @see #setDeleted(boolean)
+     */
+    @Override
+    public boolean isDeleted() {
+        return (flags & FLAG_DELETED) != 0;
+    }
+
+    /**
+     * Replies <code>true</code> if the object has been deleted on the server and was undeleted by the user.
+     * @return <code>true</code> if the object has been undeleted
+     */
+    public boolean isUndeleted() {
+        return (flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0;
+    }
+
+    /**
+     * Replies <code>true</code>, if the object is usable (i.e. complete
+     * and not deleted).
+     *
+     * @return <code>true</code>, if the object is usable.
+     * @see #delete(boolean)
+     */
+    public boolean isUsable() {
+        return (flags & (FLAG_DELETED + FLAG_INCOMPLETE)) == 0;
+    }
+
+    /**
+     * Replies true if this primitive is either unknown to the server (i.e. its id
+     * is 0) or it is known to the server and it hasn't be deleted on the server.
+     * Replies false, if this primitive is known on the server and has been deleted
+     * on the server.
+     *
+     * @see #setVisible(boolean)
+     */
+    @Override
+    public boolean isVisible() {
+        return (flags & FLAG_VISIBLE) != 0;
+    }
+    
+    /**
+     * Sets whether this primitive is visible, i.e. whether it is known on the server
+     * and not deleted on the server.
+     *
+     * @see #isVisible()
+     * @throws IllegalStateException thrown if visible is set to false on an primitive with
+     * id==0
+     */
+    @Override
+    public void setVisible(boolean visible) throws IllegalStateException{
+        if (isNew() && visible == false)
+            throw new IllegalStateException(tr("A primitive with ID = 0 cannot be invisible."));
+        updateFlags(FLAG_VISIBLE, visible);
+    }
+
+    /**
+     * Sets whether this primitive is deleted or not.
+     *
+     * Also marks this primitive as modified if deleted is true.
+     *
+     * @param deleted  true, if this primitive is deleted; false, otherwise
+     */
+    @Override
+    public void setDeleted(boolean deleted) {
+        updateFlags(FLAG_DELETED, deleted);
+        setModified(deleted ^ !isVisible());
+    }
+
+    /**
+     * If set to true, this object is incomplete, which means only the id
+     * and type is known (type is the objects instance class)
+     */
+    protected void setIncomplete(boolean incomplete) {
+        updateFlags(FLAG_INCOMPLETE, incomplete);
+    }
+
+    @Override
+    public boolean isIncomplete() {
+        return (flags & FLAG_INCOMPLETE) != 0;
+    }
+    
+    protected String getFlagsAsString() {
+        StringBuilder builder = new StringBuilder();
+
+        if (isIncomplete()) {
+            builder.append("I");
+        }
+        if (isModified()) {
+            builder.append("M");
+        }
+        if (isVisible()) {
+            builder.append("V");
+        }
+        if (isDeleted()) {
+            builder.append("D");
+        }
+        return builder.toString();
+    }
+
+    /*------------
+     * Keys handling
+     ------------*/
+
+    // Note that all methods that read keys first make local copy of keys array reference. This is to ensure thread safety - reading
+    // doesn't have to be locked so it's possible that keys array will be modified. But all write methods make copy of keys array so
+    // the array itself will be never modified - only reference will be changed
+
+    /**
+     * The key/value list for this primitive.
+     *
+     */
+    protected String[] keys;
+
+    /**
+     * Replies the map of key/value pairs. Never replies null. The map can be empty, though.
+     *
+     * @return tags of this primitive. Changes made in returned map are not mapped
+     * back to the primitive, use setKeys() to modify the keys
+     */
+    @Override
+    public Map<String, String> getKeys() {
+        Map<String, String> result = new HashMap<String, String>();
+        String[] keys = this.keys;
+        if (keys != null) {
+            for (int i=0; i<keys.length ; i+=2) {
+                result.put(keys[i], keys[i + 1]);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Sets the keys of this primitives to the key/value pairs in <code>keys</code>.
+     * If <code>keys</code> is null removes all existing key/value pairs.
+     *
+     * @param keys the key/value pairs to set. If null, removes all existing key/value pairs.
+     */
+    @Override
+    public void setKeys(Map<String, String> keys) {
+        Map<String, String> originalKeys = getKeys();
+        if (keys == null || keys.isEmpty()) {
+            this.keys = null;
+            keysChangedImpl(originalKeys);
+            return;
+        }
+        String[] newKeys = new String[keys.size() * 2];
+        int index = 0;
+        for (Entry<String, String> entry:keys.entrySet()) {
+            newKeys[index++] = entry.getKey();
+            newKeys[index++] = entry.getValue();
+        }
+        this.keys = newKeys;
+        keysChangedImpl(originalKeys);
+    }
+
+    /**
+     * Set the given value to the given key. If key is null, does nothing. If value is null,
+     * removes the key and behaves like {@see #remove(String)}.
+     *
+     * @param key  The key, for which the value is to be set. Can be null, does nothing in this case.
+     * @param value The value for the key. If null, removes the respective key/value pair.
+     *
+     * @see #remove(String)
+     */
+    @Override
+    public void put(String key, String value) {
+        Map<String, String> originalKeys = getKeys();
+        if (key == null)
+            return;
+        else if (value == null) {
+            remove(key);
+        } else if (keys == null){
+            keys = new String[] {key, value};
+            keysChangedImpl(originalKeys);
+        } else {
+            for (int i=0; i<keys.length;i+=2) {
+                if (keys[i].equals(key)) {
+                    keys[i+1] = value;  // This modifies the keys array but it doesn't make it invalidate for any time so its ok (see note no top)
+                    keysChangedImpl(originalKeys);
+                    return;
+                }
+            }
+            String[] newKeys = new String[keys.length + 2];
+            for (int i=0; i< keys.length;i+=2) {
+                newKeys[i] = keys[i];
+                newKeys[i+1] = keys[i+1];
+            }
+            newKeys[keys.length] = key;
+            newKeys[keys.length + 1] = value;
+            keys = newKeys;
+            keysChangedImpl(originalKeys);
+        }
+    }
+    
+    /**
+     * Remove the given key from the list
+     *
+     * @param key  the key to be removed. Ignored, if key is null.
+     */
+    @Override
+    public void remove(String key) {
+        if (key == null || keys == null) return;
+        if (!hasKey(key))
+            return;
+        Map<String, String> originalKeys = getKeys();
+        if (keys.length == 2) {
+            keys = null;
+            keysChangedImpl(originalKeys);
+            return;
+        }
+        String[] newKeys = new String[keys.length - 2];
+        int j=0;
+        for (int i=0; i < keys.length; i+=2) {
+            if (!keys[i].equals(key)) {
+                newKeys[j++] = keys[i];
+                newKeys[j++] = keys[i+1];
+            }
+        }
+        keys = newKeys;
+        keysChangedImpl(originalKeys);
+    }
+
+    /**
+     * Removes all keys from this primitive.
+     */
+    @Override
+    public void removeAll() {
+        if (keys != null) {
+            Map<String, String> originalKeys = getKeys();
+            keys = null;
+            keysChangedImpl(originalKeys);
+        }
+    }
+
+    /**
+     * Replies the value for key <code>key</code>. Replies null, if <code>key</code> is null.
+     * Replies null, if there is no value for the given key.
+     *
+     * @param key the key. Can be null, replies null in this case.
+     * @return the value for key <code>key</code>.
+     */
+    @Override
+    public final String get(String key) {
+        String[] keys = this.keys;
+        if (key == null)
+            return null;
+        if (keys == null)
+            return null;
+        for (int i=0; i<keys.length;i+=2) {
+            if (keys[i].equals(key)) return keys[i+1];
+        }
+        return null;
+    }
+
+    @Override
+    public final Collection<String> keySet() {
+        String[] keys = this.keys;
+        if (keys == null)
+            return Collections.emptySet();
+        Set<String> result = new HashSet<String>(keys.length / 2);
+        for (int i=0; i<keys.length; i+=2) {
+            result.add(keys[i]);
+        }
+        return result;
+    }
+
+    /**
+     * Replies true, if the map of key/value pairs of this primitive is not empty.
+     *
+     * @return true, if the map of key/value pairs of this primitive is not empty; false
+     *   otherwise
+     */
+    @Override
+    public final boolean hasKeys() {
+        return keys != null;
+    }
+
+    /**
+     * Replies true if this primitive has a tag with key <code>key</code>
+     *
+     * @param key the key
+     * @return true, if his primitive has a tag with key <code>key</code>
+     */
+    public boolean hasKey(String key) {
+        String[] keys = this.keys;
+        if (key == null) return false;
+        if (keys == null) return false;
+        for (int i=0; i< keys.length;i+=2) {
+            if (keys[i].equals(key)) return true;
+        }
+        return false;
+    }
+
+    /**
+     * Replies true if other isn't null and has the same tags (key/value-pairs) as this.
+     *
+     * @param other the other object primitive
+     * @return true if other isn't null and has the same tags (key/value-pairs) as this.
+     */
+    public boolean hasSameTags(OsmPrimitive other) {
+        return getKeys().equals(other.getKeys());
+    }
+
+    /**
+     * What to do, when the tags have changed by one of the tag-changing methods.
+     */
+    abstract protected void keysChangedImpl(Map<String, String> originalKeys);
+    
+}
Index: trunk/src/org/openstreetmap/josm/data/osm/NodeData.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/NodeData.java	(revision 4098)
+++ trunk/src/org/openstreetmap/josm/data/osm/NodeData.java	(revision 4099)
@@ -12,13 +12,4 @@
     public NodeData() {
 
-    }
-
-    public NodeData(double lat, double lon, String... keys) {
-        setCoor(new LatLon(lat, lon));
-        setKeysAsList(keys);
-    }
-
-    public NodeData(String... keys) {
-        setKeysAsList(keys);
     }
 
Index: trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java	(revision 4098)
+++ trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java	(revision 4099)
@@ -10,5 +10,4 @@
 import java.util.Collections;
 import java.util.Date;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
@@ -17,7 +16,5 @@
 import java.util.Locale;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicLong;
 
 import org.openstreetmap.josm.Main;
@@ -40,23 +37,5 @@
  * @author imi
  */
-abstract public class OsmPrimitive implements Comparable<OsmPrimitive>, IPrimitive {
-
-    private static final AtomicLong idCounter = new AtomicLong(0);
-
-    static long generateUniqueId() {
-        return idCounter.decrementAndGet();
-    }
-
-    /**
-     * This flag shows, that the properties have been changed by the user
-     * and on upload the object will be send to the server
-     */
-    private static final int FLAG_MODIFIED = 1 << 0;
-
-    /**
-     * The visible flag indicates, that an object is marked
-     * as deleted on the server.
-     */
-    private static final int FLAG_VISIBLE  = 1 << 1;
+abstract public class OsmPrimitive extends AbstractPrimitive implements Comparable<OsmPrimitive> {
 
     /**
@@ -67,14 +46,5 @@
      * while the filter is active.
      */
-    private static final int FLAG_DISABLED = 1 << 2;
-
-    /**
-     * An object that was deleted by the user.
-     * Deleted objects are usually hidden on the map and a request
-     * for deletion will be send to the server on upload.
-     * An object usually cannot be deleted if it has non-deleted
-     * objects still referring to it.
-     */
-    private static final int FLAG_DELETED  = 1 << 3;
+    protected static final int FLAG_DISABLED = 1 << 4;
 
     /**
@@ -87,5 +57,5 @@
      * unset as well (for efficient access).
      */
-    private static final int FLAG_HIDE_IF_DISABLED = 1 << 4;
+    protected static final int FLAG_HIDE_IF_DISABLED = 1 << 5;
 
     /**
@@ -94,5 +64,5 @@
      * (e.g. one way street.)
      */
-    private static final int FLAG_HAS_DIRECTIONS = 1 << 5;
+    protected static final int FLAG_HAS_DIRECTIONS = 1 << 6;
 
     /**
@@ -100,5 +70,5 @@
      * Some trivial tags like source=* are ignored here.
      */
-    private static final int FLAG_TAGGED = 1 << 6;
+    protected static final int FLAG_TAGGED = 1 << 7;
 
     /**
@@ -107,5 +77,5 @@
      * (E.g. oneway=-1.)
      */
-    private static final int FLAG_DIRECTION_REVERSED = 1 << 7;
+    protected static final int FLAG_DIRECTION_REVERSED = 1 << 8;
 
     /**
@@ -114,12 +84,5 @@
      * that the primitive is currently highlighted.
      */
-    private static final int FLAG_HIGHLIGHTED = 1 << 8;
-
-    /**
-     * A primitive is incomplete if we know its id and type, but nothing more.
-     * Typically some members of a relation are incomplete until they are
-     * fetched from the server.
-     */
-    private static final int FLAG_INCOMPLETE = 1 << 9;
+    protected static final int FLAG_HIGHLIGHTED = 1 << 9;
 
     /**
@@ -359,86 +322,4 @@
     }
 
-
-    /*-------------------
-     * OTHER PROPERTIES
-     *-------------------*/
-
-    /**
-     * Unique identifier in OSM. This is used to identify objects on the server.
-     * An id of 0 means an unknown id. The object has not been uploaded yet to
-     * know what id it will get.
-     *
-     */
-    private long id = 0;
-
-    /**
-     * User that last modified this primitive, as specified by the server.
-     * Never changed by JOSM.
-     */
-    private User user = null;
-
-    /**
-     * Contains the version number as returned by the API. Needed to
-     * ensure update consistency
-     */
-    private int version = 0;
-
-    /**
-     * The id of the changeset this primitive was last uploaded to.
-     * 0 if it wasn't uploaded to a changeset yet of if the changeset
-     * id isn't known.
-     */
-    private int changesetId;
-
-    /**
-     * Replies the version number as returned by the API. The version is 0 if the id is 0 or
-     * if this primitive is incomplete.
-     *
-     * @see #setVersion(int)
-     */
-    @Override
-    public int getVersion() {
-        return version;
-    }
-
-    /**
-     * Replies the id of this primitive.
-     *
-     * @return the id of this primitive.
-     */
-    @Override
-    public long getId() {
-        long id = this.id;
-        return id >= 0?id:0;
-    }
-
-    /**
-     *
-     * @return Osm id if primitive already exists on the server. Unique negative value if primitive is new
-     */
-    @Override
-    public long getUniqueId() {
-        return id;
-    }
-
-    /**
-     *
-     * @return True if primitive is new (not yet uploaded the server, id <= 0)
-     */
-    @Override
-    public boolean isNew() {
-        return id <= 0;
-    }
-
-    /**
-     *
-     * @return True if primitive is new or undeleted
-     * @see #isNew()
-     * @see #isUndeleted()
-     */
-    public boolean isNewOrUndeleted() {
-        return (id <= 0) || ((flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0);
-    }
-
     /**
      * Sets the id and the version of this primitive if it is known to the OSM API.
@@ -468,7 +349,5 @@
                 datasetCopy.addPrimitive(this);
             }
-            this.id = id;
-            this.version = version;
-            this.setIncomplete(false);
+            super.setOsmId(id, version);
         } finally {
             writeUnlock(locked);
@@ -485,73 +364,27 @@
      * @throws DataIntegrityProblemException If primitive was already added to the dataset
      */
+    @Override
     public void clearOsmId() {
         if (dataSet != null)
             throw new DataIntegrityProblemException("Method cannot be called after primitive was added to the dataset");
-
-        // Not part of dataset - no lock necessary
-        this.id = generateUniqueId();
-        this.version = 0;
-        this.changesetId = 0; // reset changeset id on a new object
-        this.setIncomplete(false);
-    }
-
-    /**
-     * Replies the user who has last touched this object. May be null.
-     *
-     * @return the user who has last touched this object. May be null.
-     */
-    @Override
-    public User getUser() {
-        return user;
-    }
-
-    /**
-     * Sets the user who has last touched this object.
-     *
-     * @param user the user
-     */
+        super.clearOsmId();
+    }
+
     @Override
     public void setUser(User user) {
         boolean locked = writeLock();
         try {
-            this.user = user;
-        } finally {
-            writeUnlock(locked);
-        }
-    }
-
-    /**
-     * Replies the id of the changeset this primitive was last uploaded to.
-     * 0 if this primitive wasn't uploaded to a changeset yet or if the
-     * changeset isn't known.
-     *
-     * @return the id of the changeset this primitive was last uploaded to.
-     */
-    @Override
-    public int getChangesetId() {
-        return changesetId;
-    }
-
-    /**
-     * Sets the changeset id of this primitive. Can't be set on a new
-     * primitive.
-     *
-     * @param changesetId the id. >= 0 required.
-     * @throws IllegalStateException thrown if this primitive is new.
-     * @throws IllegalArgumentException thrown if id < 0
-     */
+            super.setUser(user);
+        } finally {
+            writeUnlock(locked);
+        }
+    }
+
     @Override
     public void setChangesetId(int changesetId) throws IllegalStateException, IllegalArgumentException {
         boolean locked = writeLock();
         try {
-            if (this.changesetId == changesetId)
-                return;
-            if (changesetId < 0)
-                throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' >= 0 expected, got {1}", "changesetId", changesetId));
-            if (isNew() && changesetId > 0)
-                throw new IllegalStateException(tr("Cannot assign a changesetId > 0 to a new primitive. Value of changesetId is {0}", changesetId));
-
             int old = this.changesetId;
-            this.changesetId = changesetId;
+            super.setChangesetId(changesetId);
             if (dataSet != null) {
                 dataSet.fireChangesetIdChanged(this, old, changesetId);
@@ -562,44 +395,14 @@
     }
 
-    /**
-     * Replies the unique primitive id for this primitive
-     *
-     * @return the unique primitive id for this primitive
-     */
-    public PrimitiveId getPrimitiveId() {
-        return new SimplePrimitiveId(getUniqueId(), getType());
-    }
-
-    public OsmPrimitiveType getDisplayType() {
-        return getType();
-    }
-
     @Override
     public void setTimestamp(Date timestamp) {
         boolean locked = writeLock();
         try {
-            this.timestamp = (int)(timestamp.getTime() / 1000);
-        } finally {
-            writeUnlock(locked);
-        }
-    }
-
-    /**
-     * Time of last modification to this object. This is not set by JOSM but
-     * read from the server and delivered back to the server unmodified. It is
-     * used to check against edit conflicts.
-     *
-     */
-    @Override
-    public Date getTimestamp() {
-        return new Date(timestamp * 1000l);
-    }
-
-    @Override
-    public boolean isTimestampEmpty() {
-        return timestamp == 0;
-    }
-
-    private int timestamp;
+            super.setTimestamp(timestamp);
+        } finally {
+            writeUnlock(locked);
+        }
+    }
+
 
     /* -------
@@ -607,15 +410,10 @@
     /* ------*/
 
-    private volatile short flags = FLAG_VISIBLE;   // visible per default
-
-    private void updateFlagsNoLock(int flag, boolean value) {
-        if (value) {
-            flags |= flag;
-        } else {
-            flags &= ~flag;
-        }
-    }
-
-    private void updateFlags(int flag, boolean value) {
+    private void updateFlagsNoLock (int flag, boolean value) {
+        super.updateFlags(flag, value);
+    }
+    
+    @Override
+    protected final void updateFlags(int flag, boolean value) {
         boolean locked = writeLock();
         try {
@@ -681,58 +479,4 @@
     }
 
-    /**
-     * Marks this primitive as being modified.
-     *
-     * @param modified true, if this primitive is to be modified
-     */
-    @Override
-    public void setModified(boolean modified) {
-        updateFlags(FLAG_MODIFIED, modified);
-    }
-
-    /**
-     * Replies <code>true</code> if the object has been modified since it was loaded from
-     * the server. In this case, on next upload, this object will be updated.
-     *
-     * Deleted objects are deleted from the server. If the objects are added (id=0),
-     * the modified is ignored and the object is added to the server.
-     *
-     * @return <code>true</code> if the object has been modified since it was loaded from
-     * the server
-     */
-    @Override
-    public boolean isModified() {
-        return (flags & FLAG_MODIFIED) != 0;
-    }
-
-    /**
-     * Replies <code>true</code>, if the object has been deleted.
-     *
-     * @return <code>true</code>, if the object has been deleted.
-     * @see #setDeleted(boolean)
-     */
-    @Override
-    public boolean isDeleted() {
-        return (flags & FLAG_DELETED) != 0;
-    }
-
-    /**
-     * Replies <code>true</code> if the object has been deleted on the server and was undeleted by the user.
-     * @return <code>true</code> if the object has been undeleted
-     */
-    public boolean isUndeleted() {
-        return (flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0;
-    }
-
-    /**
-     * Replies <code>true</code>, if the object is usable (i.e. complete
-     * and not deleted).
-     *
-     * @return <code>true</code>, if the object is usable.
-     * @see #delete(boolean)
-     */
-    public boolean isUsable() {
-        return (flags & (FLAG_DELETED + FLAG_INCOMPLETE)) == 0;
-    }
 
     public boolean isSelectable() {
@@ -744,50 +488,19 @@
     }
 
-    /**
-     * Replies true if this primitive is either unknown to the server (i.e. its id
-     * is 0) or it is known to the server and it hasn't be deleted on the server.
-     * Replies false, if this primitive is known on the server and has been deleted
-     * on the server.
-     *
-     * @see #setVisible(boolean)
-     */
-    @Override
-    public boolean isVisible() {
-        return (flags & FLAG_VISIBLE) != 0;
-    }
-
-    /**
-     * Sets whether this primitive is visible, i.e. whether it is known on the server
-     * and not deleted on the server.
-     *
-     * @see #isVisible()
-     * @throws IllegalStateException thrown if visible is set to false on an primitive with
-     * id==0
-     */
-    @Override
-    public void setVisible(boolean visible) throws IllegalStateException{
-        boolean locked = writeLock();
-        try {
-            if (isNew() && visible == false)
-                throw new IllegalStateException(tr("A primitive with ID = 0 cannot be invisible."));
-            updateFlagsNoLock(FLAG_VISIBLE, visible);
-        } finally {
-            writeUnlock(locked);
-        }
-    }
-
-    /**
-     * Sets whether this primitive is deleted or not.
-     *
-     * Also marks this primitive as modified if deleted is true.
-     *
-     * @param deleted  true, if this primitive is deleted; false, otherwise
-     */
+    @Override
+    public void setVisible(boolean visible) throws IllegalStateException {
+        boolean locked = writeLock();
+        try {
+            super.setVisible(visible);
+        } finally {
+            writeUnlock(locked);
+        }
+    }
+
     @Override
     public void setDeleted(boolean deleted) {
         boolean locked = writeLock();
         try {
-            updateFlagsNoLock(FLAG_DELETED, deleted);
-            setModified(deleted ^ !isVisible());
+            super.setDeleted(deleted);
             if (dataSet != null) {
                 if (deleted) {
@@ -802,10 +515,6 @@
     }
 
-
-    /**
-     * If set to true, this object is incomplete, which means only the id
-     * and type is known (type is the objects instance class)
-     */
-    private void setIncomplete(boolean incomplete) {
+    @Override
+    protected void setIncomplete(boolean incomplete) {
         boolean locked = writeLock();
         try {
@@ -817,13 +526,8 @@
                 }
             }
-            updateFlagsNoLock(FLAG_INCOMPLETE, incomplete);
+            super.setIncomplete(incomplete);
         }  finally {
             writeUnlock(locked);
         }
-    }
-
-    @Override
-    public boolean isIncomplete() {
-        return (flags & FLAG_INCOMPLETE) != 0;
     }
 
@@ -997,201 +701,47 @@
      * Keys handling
      ------------*/
-
-    // Note that all methods that read keys first make local copy of keys array reference. This is to ensure thread safety - reading
-    // doesn't have to be locked so it's possible that keys array will be modified. But all write methods make copy of keys array so
-    // the array itself will be never modified - only reference will be changed
-
-    /**
-     * The key/value list for this primitive.
-     *
-     */
-    private String[] keys;
-
-    /**
-     * Replies the map of key/value pairs. Never replies null. The map can be empty, though.
-     *
-     * @return tags of this primitive. Changes made in returned map are not mapped
-     * back to the primitive, use setKeys() to modify the keys
-     */
-    @Override
-    public Map<String, String> getKeys() {
-        Map<String, String> result = new HashMap<String, String>();
-        String[] keys = this.keys;
-        if (keys != null) {
-            for (int i=0; i<keys.length ; i+=2) {
-                result.put(keys[i], keys[i + 1]);
-            }
-        }
-        return result;
-    }
-
-    /**
-     * Sets the keys of this primitives to the key/value pairs in <code>keys</code>.
-     * If <code>keys</code> is null removes all existing key/value pairs.
-     *
-     * @param keys the key/value pairs to set. If null, removes all existing key/value pairs.
-     */
-    @Override
-    public void setKeys(Map<String, String> keys) {
-        boolean locked = writeLock();
-        try {
-            Map<String, String> originalKeys = getKeys();
-            if (keys == null || keys.isEmpty()) {
-                this.keys = null;
-                keysChangedImpl(originalKeys);
-                return;
-            }
-            String[] newKeys = new String[keys.size() * 2];
-            int index = 0;
-            for (Entry<String, String> entry:keys.entrySet()) {
-                newKeys[index++] = entry.getKey();
-                newKeys[index++] = entry.getValue();
-            }
-            this.keys = newKeys;
-            keysChangedImpl(originalKeys);
-        } finally {
-            writeUnlock(locked);
-        }
-    }
-
-    /**
-     * Set the given value to the given key. If key is null, does nothing. If value is null,
-     * removes the key and behaves like {@see #remove(String)}.
-     *
-     * @param key  The key, for which the value is to be set. Can be null, does nothing in this case.
-     * @param value The value for the key. If null, removes the respective key/value pair.
-     *
-     * @see #remove(String)
-     */
+    
+    @Override
+    public final void setKeys(Map<String, String> keys) {
+        boolean locked = writeLock();
+        try {
+            super.setKeys(keys);
+        } finally {
+            writeUnlock(locked);
+        }
+    }
+    
     @Override
     public final void put(String key, String value) {
         boolean locked = writeLock();
         try {
-            Map<String, String> originalKeys = getKeys();
-            if (key == null)
-                return;
-            else if (value == null) {
-                remove(key);
-            } else if (keys == null){
-                keys = new String[] {key, value};
-                keysChangedImpl(originalKeys);
-            } else {
-                for (int i=0; i<keys.length;i+=2) {
-                    if (keys[i].equals(key)) {
-                        keys[i+1] = value;  // This modifies the keys array but it doesn't make it invalidate for any time so its ok (see note no top)
-                        keysChangedImpl(originalKeys);
-                        return;
-                    }
-                }
-                String[] newKeys = new String[keys.length + 2];
-                for (int i=0; i< keys.length;i+=2) {
-                    newKeys[i] = keys[i];
-                    newKeys[i+1] = keys[i+1];
-                }
-                newKeys[keys.length] = key;
-                newKeys[keys.length + 1] = value;
-                keys = newKeys;
-                keysChangedImpl(originalKeys);
-            }
-        } finally {
-            writeUnlock(locked);
-        }
-    }
-    /**
-     * Remove the given key from the list
-     *
-     * @param key  the key to be removed. Ignored, if key is null.
-     */
+            super.put(key, value);
+        } finally {
+            writeUnlock(locked);
+        }
+    }  
+    
     @Override
     public final void remove(String key) {
         boolean locked = writeLock();
         try {
-            if (key == null || keys == null) return;
-            if (!hasKey(key))
-                return;
-            Map<String, String> originalKeys = getKeys();
-            if (keys.length == 2) {
-                keys = null;
-                keysChangedImpl(originalKeys);
-                return;
-            }
-            String[] newKeys = new String[keys.length - 2];
-            int j=0;
-            for (int i=0; i < keys.length; i+=2) {
-                if (!keys[i].equals(key)) {
-                    newKeys[j++] = keys[i];
-                    newKeys[j++] = keys[i+1];
-                }
-            }
-            keys = newKeys;
-            keysChangedImpl(originalKeys);
-        } finally {
-            writeUnlock(locked);
-        }
-    }
-
-    /**
-     * Removes all keys from this primitive.
-     *
-     * @since 1843
-     */
+            super.remove(key);
+        } finally {
+            writeUnlock(locked);
+        }
+    }
+
     @Override
     public final void removeAll() {
         boolean locked = writeLock();
         try {
-            if (keys != null) {
-                Map<String, String> originalKeys = getKeys();
-                keys = null;
-                keysChangedImpl(originalKeys);
-            }
-        } finally {
-            writeUnlock(locked);
-        }
-    }
-
-    /**
-     * Replies the value for key <code>key</code>. Replies null, if <code>key</code> is null.
-     * Replies null, if there is no value for the given key.
-     *
-     * @param key the key. Can be null, replies null in this case.
-     * @return the value for key <code>key</code>.
-     */
-    @Override
-    public final String get(String key) {
-        String[] keys = this.keys;
-        if (key == null)
-            return null;
-        if (keys == null)
-            return null;
-        for (int i=0; i<keys.length;i+=2) {
-            if (keys[i].equals(key)) return keys[i+1];
-        }
-        return null;
-    }
-
-    @Override
-    public final Collection<String> keySet() {
-        String[] keys = this.keys;
-        if (keys == null)
-            return Collections.emptySet();
-        Set<String> result = new HashSet<String>(keys.length / 2);
-        for (int i=0; i<keys.length; i+=2) {
-            result.add(keys[i]);
-        }
-        return result;
-    }
-
-    /**
-     * Replies true, if the map of key/value pairs of this primitive is not empty.
-     *
-     * @return true, if the map of key/value pairs of this primitive is not empty; false
-     *   otherwise
-     */
-    @Override
-    public final boolean hasKeys() {
-        return keys != null;
-    }
-
-    private void keysChangedImpl(Map<String, String> originalKeys) {
+            super.removeAll();
+        } finally {
+            writeUnlock(locked);
+        }
+    }  
+    
+    @Override
+    protected final void keysChangedImpl(Map<String, String> originalKeys) {
         clearCachedStyle();
         if (dataSet != null) {
@@ -1205,30 +755,4 @@
             dataSet.fireTagsChanged(this, originalKeys);
         }
-    }
-
-    /**
-     * Replies true if this primitive has a tag with key <code>key</code>
-     *
-     * @param key the key
-     * @return true, if his primitive has a tag with key <code>key</code>
-     */
-    public boolean hasKey(String key) {
-        String[] keys = this.keys;
-        if (key == null) return false;
-        if (keys == null) return false;
-        for (int i=0; i< keys.length;i+=2) {
-            if (keys[i].equals(key)) return true;
-        }
-        return false;
-    }
-
-    /**
-     * Replies true if other isn't null and has the same tags (key/value-pairs) as this.
-     *
-     * @param other the other object primitive
-     * @return true if other isn't null and has the same tags (key/value-pairs) as this.
-     */
-    public boolean hasSameTags(OsmPrimitive other) {
-        return getKeys().equals(other.getKeys());
     }
 
@@ -1392,5 +916,4 @@
     abstract public void visit(Visitor visitor);
 
-
     /**
      * Get and write all attributes from the parameter. Does not fire any listener, so
@@ -1401,25 +924,6 @@
         if (id != other.id && dataSet != null)
             throw new DataIntegrityProblemException("Osm id cannot be changed after primitive was added to the dataset");
-        setKeys(other.getKeys());
-        id = other.id;
-        if (id <=0) {
-            // reset version and changeset id
-            version = 0;
-            changesetId = 0;
-        }
-        timestamp = other.timestamp;
-        if (id > 0) {
-            version = other.version;
-        }
-        setIncomplete(other.isIncomplete());
-        flags = other.flags;
-        user= other.user;
-        if (id > 0 && other.changesetId > 0) {
-            // #4208: sometimes we cloned from other with id < 0 *and*
-            // an assigned changeset id. Don't know why yet. For primitives
-            // with id < 0 we don't propagate the changeset id any more.
-            //
-            setChangesetId(other.changesetId);
-        }
+
+        super.cloneFrom(other);
         clearCachedStyle();
     }
@@ -1587,5 +1091,4 @@
     }
 
-
     public abstract BBox getBBox();
 
@@ -1599,19 +1102,8 @@
      *---------------*/
 
+    @Override
     protected String getFlagsAsString() {
-        StringBuilder builder = new StringBuilder();
-
-        if (isIncomplete()) {
-            builder.append("I");
-        }
-        if (isModified()) {
-            builder.append("M");
-        }
-        if (isVisible()) {
-            builder.append("V");
-        }
-        if (isDeleted()) {
-            builder.append("D");
-        }
+        StringBuilder builder = new StringBuilder(super.getFlagsAsString());
+
         if (isDisabled()) {
             if (isDisabledAndHidden()) {
Index: trunk/src/org/openstreetmap/josm/data/osm/PrimitiveData.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/PrimitiveData.java	(revision 4098)
+++ trunk/src/org/openstreetmap/josm/data/osm/PrimitiveData.java	(revision 4099)
@@ -2,10 +2,6 @@
 package org.openstreetmap.josm.data.osm;
 
-import static org.openstreetmap.josm.tools.I18n.tr;
-
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -18,11 +14,5 @@
  *
  */
-public abstract class PrimitiveData implements IPrimitive {
-
-    // Useful?
-    //private boolean disabled;
-    //private boolean filtered;
-    //private boolean selected;
-    //private boolean highlighted;
+public abstract class PrimitiveData extends AbstractPrimitive {
 
     public PrimitiveData() {
@@ -31,119 +21,21 @@
 
     public PrimitiveData(PrimitiveData data) {
-        this.keys.putAll(data.keys);
-        this.modified = data.modified;
-        this.visible = data.visible;
-        this.deleted = data.deleted;
-        this.id = data.id;
-        this.user = data.user;
-        this.version = data.version;
-        this.timestamp = data.timestamp;
-        this.incomplete = data.incomplete;
+        cloneFrom(data);
     }
 
-    private final Map<String, String> keys = new HashMap<String, String>();
-    private boolean modified;
-    private boolean visible = true;
-    private boolean deleted;
-    private boolean incomplete;
-    private long id;
-    private User user;
-    private int version;
-    private Date timestamp = new Date();
-    private int changesetId;
-
-    @Override
-    public boolean isModified() {
-        return modified;
-    }
-    @Override
-    public void setModified(boolean modified) {
-        this.modified = modified;
-    }
-    @Override
-    public boolean isVisible() {
-        return visible;
-    }
-    @Override
-    public void setVisible(boolean visible) {
-        this.visible = visible;
-    }
-    @Override
-    public boolean isDeleted() {
-        return deleted;
-    }
-    @Override
-    public void setDeleted(boolean deleted) {
-        this.deleted = deleted;
-    }
-    @Override
-    public long getId() {
-        return id > 0 ? id : 0;
-    }
     public void setId(long id) {
         this.id = id;
     }
-    @Override
-    public User getUser() {
-        return user;
-    }
-    @Override
-    public void setUser(User user) {
-        this.user = user;
-    }
-    @Override
-    public int getVersion() {
-        return version;
-    }
+    
     public void setVersion(int version) {
         this.version = version;
     }
+    
+    /**
+     * override to make it public
+     */
     @Override
-    public void setOsmId(long id, int version) {
-        if (id <= 0)
-            throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
-        if (version <= 0)
-            throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
-        this.id = id;
-        this.version = version;
-        this.setIncomplete(false);
-    }
-    @Override
-    public Date getTimestamp() {
-        return timestamp;
-    }
-    @Override
-    public void setTimestamp(Date timestamp) {
-        this.timestamp = timestamp;
-    }
-    @Override
-    public boolean isTimestampEmpty() {
-        return timestamp == null || timestamp.getTime() == 0;
-    }
-
-    @Override
-    public int getChangesetId() {
-        return changesetId;
-    }
-
-    @Override
-    public void setChangesetId(int changesetId) {
-        this.changesetId = changesetId;
-    }
-
-    @Override
-    public Map<String, String> getKeys() {
-        return keys;
-    }
-    @Override
-    public boolean isIncomplete() {
-        return incomplete;
-    }
     public void setIncomplete(boolean incomplete) {
-        this.incomplete = incomplete;
-    }
-
-    public void clearOsmId() {
-        id = OsmPrimitive.generateUniqueId();
+        super.setIncomplete(incomplete);
     }
 
@@ -153,62 +45,10 @@
     public String toString() {
         StringBuilder builder = new StringBuilder();
-
-        builder.append(id).append(keys);
-        if (modified) {
-            builder.append("M");
-        }
-        if (visible) {
-            builder.append("V");
-        }
-        if (deleted) {
-            builder.append("D");
-        }
-        if (incomplete) {
-            builder.append("I");
-        }
-
+        builder.append(id).append(keys).append(getFlagsAsString());
         return builder.toString();
     }
 
-    // Tagged implementation
-
-    @Override
-    public String get(String key) {
-        return keys.get(key);
-    }
-
-    @Override
-    public boolean hasKeys() {
-        return !keys.isEmpty();
-    }
-
-    @Override
-    public Collection<String> keySet() {
-        return keys.keySet();
-    }
-
-    @Override
-    public void put(String key, String value) {
-        keys.put(key, value);
-    }
-
-    @Override
-    public void remove(String key) {
-        keys.remove(key);
-    }
-
-    @Override
-    public void removeAll() {
-        keys.clear();
-    }
-
-    @Override
-    public void setKeys(Map<String, String> keys) {
-        this.keys.clear();
-        this.keys.putAll(keys);
-    }
-
     @SuppressWarnings("unchecked")
-    static public <T extends PrimitiveData>  List<T> getFilteredList(Collection<T> list, OsmPrimitiveType type) {
+    static public <T extends PrimitiveData> List<T> getFilteredList(Collection<T> list, OsmPrimitiveType type) {
         List<T> ret = new ArrayList<T>();
         for(PrimitiveData p: list) {
@@ -220,31 +60,6 @@
     }
 
-    protected void setKeysAsList(String... keys) {
-        assert keys.length % 2 == 0;
-        for (int i=0; i<keys.length/2; i++) {
-            this.keys.put(keys[i * 2], keys[i * 2 + 1]);
-        }
-    }
-
-    /**
-     * PrimitiveId implementation. Returns the same value as getId()
-     */
     @Override
-    public long getUniqueId() {
-        return id;
-    }
-
-    /**
-     * Returns a PrimitiveId object for this primitive
-     *
-     * @return the PrimitiveId for this primitive
-     */
-    public PrimitiveId getPrimitiveId() {
-        return new SimplePrimitiveId(getUniqueId(), getType());
-    }
-
-    @Override
-    public boolean isNew() {
-        return id <= 0;
+    protected final void keysChangedImpl(Map<String, String> originalKeys) {
     }
 
