Index: /trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java	(revision 3341)
+++ /trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java	(revision 3342)
@@ -184,75 +184,66 @@
     }
 
-    /* mappaint data */
-    public ElemStyle mappaintStyle = null;
-    public int mappaintDrawnCode = 0;
-
-    /* This should not be called from outside. Fixing the UI to add relevant
-       get/set functions calling this implicitely is preferred, so we can have
-       transparent cache handling in the future. */
-    protected void clearCached()
-    {
-        mappaintDrawnCode = 0;
-        mappaintStyle = null;
-    }
-    /* end of mappaint data */
-
-    /**
-     * 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;
-
-    /** the parent dataset */
-    private DataSet dataSet;
-
-    /**
-     * This method should never ever by called from somewhere else than Dataset.addPrimitive or removePrimitive methods
-     * @param dataSet
-     */
-    void setDataset(DataSet dataSet) {
-        if (this.dataSet != null && dataSet != null && this.dataSet != dataSet)
-            throw new DataIntegrityProblemException("Primitive cannot be included in more than one Dataset");
-        this.dataSet = dataSet;
-    }
-
-    /**
-     *
-     * @return DataSet this primitive is part of.
-     */
-    public DataSet getDataSet() {
-        return dataSet;
-    }
-
-    /**
-     * Throws exception if primitive is not part of the dataset
-     */
-    public void checkDataset() {
-        if (dataSet == null)
-            throw new DataIntegrityProblemException("Primitive  must be part of the dataset: " + toString());
-    }
-
-    private volatile short flags = FLAG_VISIBLE;   // visible per default
-
-    /**
-     * 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;
+    /**
+     * Some predicates, that describe conditions on primitives.
+     */
+    public static final Predicate<OsmPrimitive> isUsablePredicate = new Predicate<OsmPrimitive>() {
+        public boolean evaluate(OsmPrimitive primitive) {
+            return primitive.isUsable();
+        }
+    };
+
+    public static final Predicate<OsmPrimitive> isSelectablePredicate = new Predicate<OsmPrimitive>() {
+        public boolean evaluate(OsmPrimitive primitive) {
+            return primitive.isSelectable();
+        }
+    };
+
+    public static final Predicate<OsmPrimitive> nonDeletedPredicate = new Predicate<OsmPrimitive>() {
+        public boolean evaluate(OsmPrimitive primitive) {
+            return !primitive.isDeleted();
+        }
+    };
+
+    public static final Predicate<OsmPrimitive> nonDeletedCompletePredicate = new Predicate<OsmPrimitive>() {
+        public boolean evaluate(OsmPrimitive primitive) {
+            return !primitive.isDeleted() && !primitive.isIncomplete();
+        }
+    };
+
+    public static final Predicate<OsmPrimitive> nonDeletedPhysicalPredicate = new Predicate<OsmPrimitive>() {
+        public boolean evaluate(OsmPrimitive primitive) {
+            return !primitive.isDeleted() && !primitive.isIncomplete() && !(primitive instanceof Relation);
+        }
+    };
+
+    public static final Predicate<OsmPrimitive> modifiedPredicate = new Predicate<OsmPrimitive>() {
+        public boolean evaluate(OsmPrimitive primitive) {
+            return primitive.isModified();
+        }
+    };
+
+    public static final Predicate<OsmPrimitive> nodePredicate = new Predicate<OsmPrimitive>() {
+        public boolean evaluate(OsmPrimitive primitive) {
+            return primitive.getClass() == Node.class;
+        }
+    };
+
+    public static final Predicate<OsmPrimitive> wayPredicate = new Predicate<OsmPrimitive>() {
+        public boolean evaluate(OsmPrimitive primitive) {
+            return primitive.getClass() == Way.class;
+        }
+    };
+
+    public static final Predicate<OsmPrimitive> relationPredicate = new Predicate<OsmPrimitive>() {
+        public boolean evaluate(OsmPrimitive primitive) {
+            return primitive.getClass() == Relation.class;
+        }
+    };
+
+    public static final Predicate<OsmPrimitive> allPredicate = new Predicate<OsmPrimitive>() {
+        public boolean evaluate(OsmPrimitive primitive) {
+            return true;
+        }
+    };
 
     /**
@@ -304,211 +295,84 @@
     }
 
-    /* ------------------------------------------------------------------------------------ */
-    /* accessors                                                                            */
-    /* ------------------------------------------------------------------------------------ */
-
-    /**
-     * Make the primitive disabled (e.g. if a filter applies).
-     * To enable the primitive again, use unsetDisabledState.
-     * @param hide if the primitive should be completely hidden from view or
-     *             just shown in gray color.
-     */
-    public void setDisabledState(boolean hide) {
-        flags |= FLAG_DISABLED;
-        if (hide) {
-            flags |= FLAG_HIDE_IF_DISABLED;
-        } else {
-            flags &= ~FLAG_HIDE_IF_DISABLED;
-        }
-    }
-
-    /**
-     * Remove the disabled flag from the primitive.
-     * Afterwards, the primitive is displayed normally and can be selected
-     * again.
-     */
-    public void unsetDisabledState() {
-        flags &= ~FLAG_DISABLED;
-        flags &= ~FLAG_HIDE_IF_DISABLED;
-    }
-
-    /**
-     * Replies true, if this primitive is disabled. (E.g. a filter
-     * applies)
-     */
-    public boolean isDisabled() {
-        return (flags & FLAG_DISABLED) != 0;
-    }
-
-    /**
-     * Replies true, if this primitive is disabled and marked as
-     * completely hidden on the map.
-     */
-    public boolean isDisabledAndHidden() {
-        return (((flags & FLAG_DISABLED) != 0) && ((flags & FLAG_HIDE_IF_DISABLED) != 0));
-    }
-
-    @Deprecated
-    public boolean isFiltered() {
-        return isDisabledAndHidden();
-    }
-
-    /**
-     * Marks this primitive as being modified.
-     *
-     * @param modified true, if this primitive is to be modified
-     */
-    public void setModified(boolean modified) {
-        if (modified) {
-            flags |= FLAG_MODIFIED;
-        } else {
-            flags &= ~FLAG_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
-     */
-    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)
-     */
-    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() {
-        return (flags & (FLAG_DELETED + FLAG_INCOMPLETE + FLAG_DISABLED + FLAG_HIDE_IF_DISABLED)) == 0;
-    }
-
-    public boolean isDrawable() {
-        return (flags & (FLAG_DELETED + FLAG_INCOMPLETE + FLAG_HIDE_IF_DISABLED)) == 0;
-    }
-
-    /**
-     * Some predicates, that describe conditions on primitives.
-     */
-    public static Predicate<OsmPrimitive> isUsablePredicate = new Predicate<OsmPrimitive>() {
-        public boolean evaluate(OsmPrimitive primitive) {
-            return primitive.isUsable();
-        }
-    };
-
-    public static Predicate<OsmPrimitive> isSelectablePredicate = new Predicate<OsmPrimitive>() {
-        public boolean evaluate(OsmPrimitive primitive) {
-            return primitive.isSelectable();
-        }
-    };
-
-    public static Predicate<OsmPrimitive> nonDeletedPredicate = new Predicate<OsmPrimitive>() {
-        public boolean evaluate(OsmPrimitive primitive) {
-            return !primitive.isDeleted();
-        }
-    };
-
-    public static Predicate<OsmPrimitive> nonDeletedCompletePredicate = new Predicate<OsmPrimitive>() {
-        public boolean evaluate(OsmPrimitive primitive) {
-            return !primitive.isDeleted() && !primitive.isIncomplete();
-        }
-    };
-
-    public static Predicate<OsmPrimitive> nonDeletedPhysicalPredicate = new Predicate<OsmPrimitive>() {
-        public boolean evaluate(OsmPrimitive primitive) {
-            return !primitive.isDeleted() && !primitive.isIncomplete() && !(primitive instanceof Relation);
-        }
-    };
-
-    public static Predicate<OsmPrimitive> modifiedPredicate = new Predicate<OsmPrimitive>() {
-        public boolean evaluate(OsmPrimitive primitive) {
-            return primitive.isModified();
-        }
-    };
-
-    public static Predicate<OsmPrimitive> nodePredicate = new Predicate<OsmPrimitive>() {
-        public boolean evaluate(OsmPrimitive primitive) {
-            return primitive.getClass() == Node.class;
-        }
-    };
-
-    public static Predicate<OsmPrimitive> wayPredicate = new Predicate<OsmPrimitive>() {
-        public boolean evaluate(OsmPrimitive primitive) {
-            return primitive.getClass() == Way.class;
-        }
-    };
-
-    public static Predicate<OsmPrimitive> relationPredicate = new Predicate<OsmPrimitive>() {
-        public boolean evaluate(OsmPrimitive primitive) {
-            return primitive.getClass() == Relation.class;
-        }
-    };
-
-    public static Predicate<OsmPrimitive> allPredicate = new Predicate<OsmPrimitive>() {
-        public boolean evaluate(OsmPrimitive primitive) {
-            return true;
-        }
-    };
-
-
-    /**
-     * 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)
-     */
-    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
-     */
-    public void setVisible(boolean visible) throws IllegalStateException{
-        if (isNew() && visible == false)
-            throw new IllegalStateException(tr("A primitive with ID = 0 cannot be invisible."));
-        if (visible) {
-            flags |= FLAG_VISIBLE;
-        } else {
-            flags &= ~FLAG_VISIBLE;
-        }
-    }
+
+    /*----------
+     * MAPPAINT
+     *--------*/
+    public ElemStyle mappaintStyle = null;
+    public int mappaintDrawnCode = 0;
+
+    /* This should not be called from outside. Fixing the UI to add relevant
+       get/set functions calling this implicitely is preferred, so we can have
+       transparent cache handling in the future. */
+    protected void clearCached()
+    {
+        mappaintDrawnCode = 0;
+        mappaintStyle = null;
+    }
+    /* end of mappaint data */
+
+    /*---------
+     * DATASET
+     *---------*/
+
+    /** the parent dataset */
+    private DataSet dataSet;
+
+    /**
+     * This method should never ever by called from somewhere else than Dataset.addPrimitive or removePrimitive methods
+     * @param dataSet
+     */
+    void setDataset(DataSet dataSet) {
+        if (this.dataSet != null && dataSet != null && this.dataSet != dataSet)
+            throw new DataIntegrityProblemException("Primitive cannot be included in more than one Dataset");
+        this.dataSet = dataSet;
+    }
+
+    /**
+     *
+     * @return DataSet this primitive is part of.
+     */
+    public DataSet getDataSet() {
+        return dataSet;
+    }
+
+    /**
+     * Throws exception if primitive is not part of the dataset
+     */
+    public void checkDataset() {
+        if (dataSet == null)
+            throw new DataIntegrityProblemException("Primitive  must be part of the dataset: " + toString());
+    }
+
+    /*-------------------
+     * 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;
 
     /**
@@ -604,151 +468,4 @@
     }
 
-    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.
-     *
-     */
-    public Date getTimestamp() {
-        return new Date(timestamp * 1000l);
-    }
-
-    public boolean isTimestampEmpty() {
-        return timestamp == 0;
-    }
-
-    private int timestamp;
-
-    private static volatile Collection<String> uninteresting = null;
-    /**
-     * Contains a list of "uninteresting" keys that do not make an object
-     * "tagged".  Entries that end with ':' are causing a whole namespace to be considered
-     * "uninteresting".  Only the first level namespace is considered.
-     * Initialized by isUninterestingKey()
-     */
-    public static Collection<String> getUninterestingKeys() {
-        if (uninteresting == null) {
-            uninteresting = Main.pref.getCollection("tags.uninteresting",
-                    Arrays.asList(new String[]{"source", "source_ref", "source:", "note", "comment",
-                            "converted_by", "created_by", "watch", "watch:", "fixme", "FIXME",
-                            "description"}));
-        }
-        return uninteresting;
-    }
-
-    /**
-     * Returns true if key is considered "uninteresting".
-     */
-    public static boolean isUninterestingKey(String key) {
-        getUninterestingKeys();
-        if (uninteresting.contains(key))
-            return true;
-        int pos = key.indexOf(':');
-        if (pos > 0)
-            return uninteresting.contains(key.substring(0, pos + 1));
-        return false;
-    }
-
-    private static volatile Match directionKeys = null;
-    private static volatile Match reversedDirectionKeys = null;
-
-    /**
-     * Contains a list of direction-dependent keys that make an object
-     * direction dependent.
-     * Initialized by checkDirectionTagged()
-     */
-    static {
-        // Legacy support - convert list of keys to search pattern
-        if (Main.pref.isCollection("tags.direction", false)) {
-            System.out.println("Collection of keys in tags.direction is no longer supported, value will converted to search pattern");
-            Collection<String> keys = Main.pref.getCollection("tags.direction", null);
-            StringBuilder builder = new StringBuilder();
-            for (String key:keys) {
-                builder.append(key);
-                builder.append("=* | ");
-            }
-            builder.delete(builder.length() - 3, builder.length());
-            Main.pref.put("tags.direction", builder.toString());
-        }
-
-        // FIXME: incline=\"-*\" search pattern does not work.
-        String reversedDirectionDefault = "oneway=\"-1\" | incline=down | incline=\"-*\"";
-
-        String directionDefault = "oneway? | incline=* | aerialway=* | "+
-        "waterway=stream | waterway=river | waterway=canal | waterway=drain | waterway=rapids | "+
-        "\"piste:type\"=downhill | \"piste:type\"=sled | man_made=\"piste:halfpipe\" | "+
-        "junction=roundabout";
-
-        try {
-            reversedDirectionKeys = SearchCompiler.compile(Main.pref.get("tags.reversed_direction", reversedDirectionDefault), false, false);
-        } catch (ParseError e) {
-            System.err.println("Unable to compile pattern for tags.reversed_direction, trying default pattern: " + e.getMessage());
-
-            try {
-                reversedDirectionKeys = SearchCompiler.compile(reversedDirectionDefault, false, false);
-            } catch (ParseError e2) {
-                throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage());
-            }
-        }
-        try {
-            directionKeys = SearchCompiler.compile(Main.pref.get("tags.direction", directionDefault), false, false);
-        } catch (ParseError e) {
-            System.err.println("Unable to compile pattern for tags.direction, trying default pattern: " + e.getMessage());
-
-            try {
-                directionKeys = SearchCompiler.compile(directionDefault, false, false);
-            } catch (ParseError e2) {
-                throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage());
-            }
-        }
-    }
-
-    /**
-     * Replies a list of direction-dependent keys that make an object
-     * direction dependent.
-     *
-     * @return  a list of direction-dependent keys that make an object
-     * direction dependent.
-     */
-    @Deprecated
-    public static Collection<String> getDirectionKeys() {
-        return Main.pref.getCollection("tags.direction",
-                Arrays.asList("oneway","incline","incline_steep","aerialway"));
-    }
-
-    /**
-     * Implementation of the visitor scheme. Subclasses have to call the correct
-     * visitor function.
-     * @param visitor The visitor from which the visit() function must be called.
-     */
-    abstract public void visit(Visitor visitor);
-
-    /**
-     * 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
-     */
-    public void setDeleted(boolean deleted) {
-        if (deleted) {
-            flags |= FLAG_DELETED;
-        } else {
-            flags &= ~FLAG_DELETED;
-        }
-        setModified(deleted ^ !isVisible());
-        if (dataSet != null) {
-            if (deleted) {
-                dataSet.firePrimitivesRemoved(Collections.singleton(this), false);
-            } else {
-                dataSet.firePrimitivesAdded(Collections.singleton(this), false);
-            }
-        }
-    }
-
     /**
      * Replies the user who has last touched this object. May be null.
@@ -803,21 +520,368 @@
 
     /**
-     * Equal, if the id (and class) is equal.
-     *
-     * An primitive is equal to its incomplete counter part.
-     */
-    @Override public boolean equals(Object obj) {
-        if (obj instanceof OsmPrimitive)
-            return ((OsmPrimitive)obj).id == id && obj.getClass() == getClass();
+     * 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 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.
+     *
+     */
+    public Date getTimestamp() {
+        return new Date(timestamp * 1000l);
+    }
+
+    public boolean isTimestampEmpty() {
+        return timestamp == 0;
+    }
+
+    private int timestamp;
+
+    /* -------
+    /* FLAGS
+    /* ------*/
+
+    private volatile short flags = FLAG_VISIBLE;   // visible per default
+
+    private void updateFlags(int flag, boolean value) {
+        if (value) {
+            flags |= flag;
+        } else {
+            flags &= ~flag;
+        }
+    }
+
+    /**
+     * Make the primitive disabled (e.g. if a filter applies).
+     * To enable the primitive again, use unsetDisabledState.
+     * @param hide if the primitive should be completely hidden from view or
+     *             just shown in gray color.
+     */
+    public void setDisabledState(boolean hide) {
+        flags |= FLAG_DISABLED;
+        if (hide) {
+            flags |= FLAG_HIDE_IF_DISABLED;
+        } else {
+            flags &= ~FLAG_HIDE_IF_DISABLED;
+        }
+    }
+
+    /**
+     * Remove the disabled flag from the primitive.
+     * Afterwards, the primitive is displayed normally and can be selected
+     * again.
+     */
+    public void unsetDisabledState() {
+        updateFlags(FLAG_DISABLED + FLAG_HIDE_IF_DISABLED, false);
+    }
+
+    /**
+     * Replies true, if this primitive is disabled. (E.g. a filter
+     * applies)
+     */
+    public boolean isDisabled() {
+        return (flags & FLAG_DISABLED) != 0;
+    }
+
+    /**
+     * Replies true, if this primitive is disabled and marked as
+     * completely hidden on the map.
+     */
+    public boolean isDisabledAndHidden() {
+        return (((flags & FLAG_DISABLED) != 0) && ((flags & FLAG_HIDE_IF_DISABLED) != 0));
+    }
+
+    @Deprecated
+    public boolean isFiltered() {
+        return isDisabledAndHidden();
+    }
+
+    /**
+     * Marks this primitive as being modified.
+     *
+     * @param modified true, if this primitive is to be modified
+     */
+    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
+     */
+    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)
+     */
+    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() {
+        return (flags & (FLAG_DELETED + FLAG_INCOMPLETE + FLAG_DISABLED + FLAG_HIDE_IF_DISABLED)) == 0;
+    }
+
+    public boolean isDrawable() {
+        return (flags & (FLAG_DELETED + FLAG_INCOMPLETE + FLAG_HIDE_IF_DISABLED)) == 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)
+     */
+    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
+     */
+    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
+     */
+    public void setDeleted(boolean deleted) {
+        updateFlags(FLAG_DELETED, deleted);
+        setModified(deleted ^ !isVisible());
+        if (dataSet != null) {
+            if (deleted) {
+                dataSet.firePrimitivesRemoved(Collections.singleton(this), false);
+            } else {
+                dataSet.firePrimitivesAdded(Collections.singleton(this), false);
+            }
+        }
+    }
+
+
+    /**
+     * 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) {
+        if (dataSet != null && incomplete != this.isIncomplete()) {
+            if (incomplete) {
+                dataSet.firePrimitivesRemoved(Collections.singletonList(this), true);
+            } else {
+                dataSet.firePrimitivesAdded(Collections.singletonList(this), true);
+            }
+        }
+        updateFlags(FLAG_INCOMPLETE, incomplete);
+    }
+
+    public boolean isIncomplete() {
+        return (flags & FLAG_INCOMPLETE) != 0;
+    }
+
+    public boolean isSelected() {
+        return dataSet != null && dataSet.isSelected(this);
+    }
+
+    public void setHighlighted(boolean highlighted) {
+        if (isHighlighted() != highlighted) {
+            updateFlags(FLAG_HIGHLIGHTED, highlighted);
+            if (dataSet != null) {
+                dataSet.fireHighlightingChanged(this);
+            }
+        }
+    }
+
+    public boolean isHighlighted() {
+        return (flags & FLAG_HIGHLIGHTED) != 0;
+    }
+
+    /*----------------------------------
+     * UNINTERESTING AND DIRECTION KEYS
+     *----------------------------------*/
+
+
+    private static volatile Collection<String> uninteresting = null;
+    /**
+     * Contains a list of "uninteresting" keys that do not make an object
+     * "tagged".  Entries that end with ':' are causing a whole namespace to be considered
+     * "uninteresting".  Only the first level namespace is considered.
+     * Initialized by isUninterestingKey()
+     */
+    public static Collection<String> getUninterestingKeys() {
+        if (uninteresting == null) {
+            uninteresting = Main.pref.getCollection("tags.uninteresting",
+                    Arrays.asList(new String[]{"source", "source_ref", "source:", "note", "comment",
+                            "converted_by", "created_by", "watch", "watch:", "fixme", "FIXME",
+                    "description"}));
+        }
+        return uninteresting;
+    }
+
+    /**
+     * Returns true if key is considered "uninteresting".
+     */
+    public static boolean isUninterestingKey(String key) {
+        getUninterestingKeys();
+        if (uninteresting.contains(key))
+            return true;
+        int pos = key.indexOf(':');
+        if (pos > 0)
+            return uninteresting.contains(key.substring(0, pos + 1));
         return false;
     }
 
-    /**
-     * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0.
-     *
-     * An primitive has the same hashcode as its incomplete counterpart.
-     */
-    @Override public final int hashCode() {
-        return (int)id;
+    private static volatile Match directionKeys = null;
+    private static volatile Match reversedDirectionKeys = null;
+
+    /**
+     * Contains a list of direction-dependent keys that make an object
+     * direction dependent.
+     * Initialized by checkDirectionTagged()
+     */
+    static {
+        // Legacy support - convert list of keys to search pattern
+        if (Main.pref.isCollection("tags.direction", false)) {
+            System.out.println("Collection of keys in tags.direction is no longer supported, value will converted to search pattern");
+            Collection<String> keys = Main.pref.getCollection("tags.direction", null);
+            StringBuilder builder = new StringBuilder();
+            for (String key:keys) {
+                builder.append(key);
+                builder.append("=* | ");
+            }
+            builder.delete(builder.length() - 3, builder.length());
+            Main.pref.put("tags.direction", builder.toString());
+        }
+
+        // FIXME: incline=\"-*\" search pattern does not work.
+        String reversedDirectionDefault = "oneway=\"-1\" | incline=down | incline=\"-*\"";
+
+        String directionDefault = "oneway? | incline=* | aerialway=* | "+
+        "waterway=stream | waterway=river | waterway=canal | waterway=drain | waterway=rapids | "+
+        "\"piste:type\"=downhill | \"piste:type\"=sled | man_made=\"piste:halfpipe\" | "+
+        "junction=roundabout";
+
+        try {
+            reversedDirectionKeys = SearchCompiler.compile(Main.pref.get("tags.reversed_direction", reversedDirectionDefault), false, false);
+        } catch (ParseError e) {
+            System.err.println("Unable to compile pattern for tags.reversed_direction, trying default pattern: " + e.getMessage());
+
+            try {
+                reversedDirectionKeys = SearchCompiler.compile(reversedDirectionDefault, false, false);
+            } catch (ParseError e2) {
+                throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage());
+            }
+        }
+        try {
+            directionKeys = SearchCompiler.compile(Main.pref.get("tags.direction", directionDefault), false, false);
+        } catch (ParseError e) {
+            System.err.println("Unable to compile pattern for tags.direction, trying default pattern: " + e.getMessage());
+
+            try {
+                directionKeys = SearchCompiler.compile(directionDefault, false, false);
+            } catch (ParseError e2) {
+                throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage());
+            }
+        }
+    }
+
+    private void updateTagged() {
+        if (keys != null) {
+            for (String key: keySet()) {
+                if (!isUninterestingKey(key)) {
+                    updateFlags(FLAG_TAGGED, true);
+                    return;
+                }
+            }
+        }
+        updateFlags(FLAG_TAGGED, false);
+    }
+
+    /**
+     * true if this object is considered "tagged". To be "tagged", an object
+     * must have one or more "interesting" tags. "created_by" and "source"
+     * are typically considered "uninteresting" and do not make an object
+     * "tagged".
+     */
+    public boolean isTagged() {
+        return (flags & FLAG_TAGGED) != 0;
+    }
+
+    private void updateDirectionFlags() {
+        boolean hasDirections = false;
+        boolean directionReversed = false;
+        if (reversedDirectionKeys.match(this)) {
+            hasDirections = true;
+            directionReversed = true;
+        }
+        if (directionKeys.match(this)) {
+            hasDirections = true;
+        }
+
+        updateFlags(FLAG_DIRECTION_REVERSED, directionReversed);
+        updateFlags(FLAG_HAS_DIRECTIONS, hasDirections);
+    }
+
+    /**
+     * true if this object has direction dependent tags (e.g. oneway)
+     */
+    public boolean hasDirectionKeys() {
+        return (flags & FLAG_HAS_DIRECTIONS) != 0;
+    }
+
+    public boolean reversedDirection() {
+        return (flags & FLAG_DIRECTION_REVERSED) != 0;
     }
 
@@ -1121,4 +1185,16 @@
     }
 
+    /*-----------------
+     * OTHER METHODS
+     *----------------/
+
+    /**
+     * Implementation of the visitor scheme. Subclasses have to call the correct
+     * visitor function.
+     * @param visitor The visitor from which the visit() function must be called.
+     */
+    abstract public void visit(Visitor visitor);
+
+
     /**
      * Get and write all attributes from the parameter. Does not fire any listener, so
@@ -1230,59 +1306,4 @@
     }
 
-    private void updateTagged() {
-        if (keys != null) {
-            for (String key: keySet()) {
-                if (!isUninterestingKey(key)) {
-                    flags |= FLAG_TAGGED;
-                    return;
-                }
-            }
-        }
-        flags &= ~FLAG_TAGGED;
-    }
-
-    /**
-     * true if this object is considered "tagged". To be "tagged", an object
-     * must have one or more "interesting" tags. "created_by" and "source"
-     * are typically considered "uninteresting" and do not make an object
-     * "tagged".
-     */
-    public boolean isTagged() {
-        return (flags & FLAG_TAGGED) != 0;
-    }
-
-    private void updateDirectionFlags() {
-        boolean hasDirections = false;
-        boolean directionReversed = false;
-        if (reversedDirectionKeys.match(this)) {
-            hasDirections = true;
-            directionReversed = true;
-        }
-        if (directionKeys.match(this)) {
-            hasDirections = true;
-        }
-
-        if (directionReversed) {
-            flags |= FLAG_DIRECTION_REVERSED;
-        } else {
-            flags &= ~FLAG_DIRECTION_REVERSED;
-        }
-        if (hasDirections) {
-            flags |= FLAG_HAS_DIRECTIONS;
-        } else {
-            flags &= ~FLAG_HAS_DIRECTIONS;
-        }
-    }
-
-    /**
-     * true if this object has direction dependent tags (e.g. oneway)
-     */
-    public boolean hasDirectionKeys() {
-        return (flags & FLAG_HAS_DIRECTIONS) != 0;
-    }
-
-    public boolean reversedDirection() {
-        return (flags & FLAG_DIRECTION_REVERSED) != 0;
-    }
     /**
      * Replies the name of this primitive. The default implementation replies the value
@@ -1366,4 +1387,16 @@
     }
 
+
+    public abstract BBox getBBox();
+
+    /**
+     * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...)
+     */
+    public abstract void updatePosition();
+
+    /*----------------
+     * OBJECT METHODS
+     *---------------*/
+
     protected String getFlagsAsString() {
         StringBuilder builder = new StringBuilder();
@@ -1400,62 +1433,23 @@
     }
 
-    public abstract BBox getBBox();
-
-    /**
-     * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...)
-     */
-    public abstract void updatePosition();
-
-    /**
-     * Replies the unique primitive id for this primitive
-     *
-     * @return the unique primitive id for this primitive
-     */
-    public PrimitiveId getPrimitiveId() {
-        return new SimplePrimitiveId(getUniqueId(), getType());
-    }
-
-    /**
-     * 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) {
-        if (dataSet != null && incomplete != this.isIncomplete()) {
-            if (incomplete) {
-                dataSet.firePrimitivesRemoved(Collections.singletonList(this), true);
-            } else {
-                dataSet.firePrimitivesAdded(Collections.singletonList(this), true);
-            }
-        }
-        if (incomplete) {
-            flags |= FLAG_INCOMPLETE;
-        } else {
-            flags &= ~FLAG_INCOMPLETE;
-        }
-    }
-
-    public boolean isIncomplete() {
-        return (flags & FLAG_INCOMPLETE) != 0;
-    }
-
-    public boolean isSelected() {
-        return dataSet != null && dataSet.isSelected(this);
-    }
-
-    public void setHighlighted(boolean highlighted) {
-        if (isHighlighted() != highlighted) {
-            if (highlighted) {
-                flags |= FLAG_HIGHLIGHTED;
-            } else {
-                flags &= ~FLAG_HIGHLIGHTED;
-            }
-            if (dataSet != null) {
-                dataSet.fireHighlightingChanged(this);
-            }
-        }
-    }
-
-    public boolean isHighlighted() {
-        return (flags & FLAG_HIGHLIGHTED) != 0;
-    }
+    /**
+     * Equal, if the id (and class) is equal.
+     *
+     * An primitive is equal to its incomplete counter part.
+     */
+    @Override public boolean equals(Object obj) {
+        if (obj instanceof OsmPrimitive)
+            return ((OsmPrimitive)obj).id == id && obj.getClass() == getClass();
+        return false;
+    }
+
+    /**
+     * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0.
+     *
+     * An primitive has the same hashcode as its incomplete counterpart.
+     */
+    @Override public final int hashCode() {
+        return (int)id;
+    }
+
 }
