001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.text.MessageFormat;
007import java.util.ArrayList;
008import java.util.Arrays;
009import java.util.Collection;
010import java.util.Collections;
011import java.util.Date;
012import java.util.HashSet;
013import java.util.LinkedHashSet;
014import java.util.LinkedList;
015import java.util.List;
016import java.util.Map;
017import java.util.Set;
018
019import org.openstreetmap.josm.Main;
020import org.openstreetmap.josm.actions.search.SearchCompiler;
021import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
022import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
023import org.openstreetmap.josm.data.osm.visitor.Visitor;
024import org.openstreetmap.josm.gui.mappaint.StyleCache;
025import org.openstreetmap.josm.tools.CheckParameterUtil;
026import org.openstreetmap.josm.tools.Predicate;
027import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider;
028
029
030/**
031 * The base class for OSM objects ({@link Node}, {@link Way}, {@link Relation}).
032 *
033 * It can be created, deleted and uploaded to the OSM-Server.
034 *
035 * Although OsmPrimitive is designed as a base class, it is not to be meant to subclass
036 * it by any other than from the package {@link org.openstreetmap.josm.data.osm}. The available primitives are a fixed set that are given
037 * by the server environment and not an extendible data stuff.
038 *
039 * @author imi
040 */
041abstract public class OsmPrimitive extends AbstractPrimitive implements Comparable<OsmPrimitive>, TemplateEngineDataProvider {
042    private static final String SPECIAL_VALUE_ID = "id";
043    private static final String SPECIAL_VALUE_LOCAL_NAME = "localname";
044
045
046    /**
047     * An object can be disabled by the filter mechanism.
048     * Then it will show in a shade of gray on the map or it is completely
049     * hidden from the view.
050     * Disabled objects usually cannot be selected or modified
051     * while the filter is active.
052     */
053    protected static final int FLAG_DISABLED = 1 << 4;
054
055    /**
056     * This flag is only relevant if an object is disabled by the
057     * filter mechanism (i.e.&nbsp;FLAG_DISABLED is set).
058     * Then it indicates, whether it is completely hidden or
059     * just shown in gray color.
060     *
061     * When the primitive is not disabled, this flag should be
062     * unset as well (for efficient access).
063     */
064    protected static final int FLAG_HIDE_IF_DISABLED = 1 << 5;
065
066    /**
067     * Flag used internally by the filter mechanism.
068     */
069    protected static final int FLAG_DISABLED_TYPE = 1 << 6;
070
071    /**
072     * Flag used internally by the filter mechanism.
073     */
074    protected static final int FLAG_HIDDEN_TYPE = 1 << 7;
075
076    /**
077     * This flag is set if the primitive is a way and
078     * according to the tags, the direction of the way is important.
079     * (e.g. one way street.)
080     */
081    protected static final int FLAG_HAS_DIRECTIONS = 1 << 8;
082
083    /**
084     * If the primitive is tagged.
085     * Some trivial tags like source=* are ignored here.
086     */
087    protected static final int FLAG_TAGGED = 1 << 9;
088
089    /**
090     * This flag is only relevant if FLAG_HAS_DIRECTIONS is set.
091     * It shows, that direction of the arrows should be reversed.
092     * (E.g. oneway=-1.)
093     */
094    protected static final int FLAG_DIRECTION_REVERSED = 1 << 10;
095
096    /**
097     * When hovering over ways and nodes in add mode, the
098     * "target" objects are visually highlighted. This flag indicates
099     * that the primitive is currently highlighted.
100     */
101    protected static final int FLAG_HIGHLIGHTED = 1 << 11;
102
103    /**
104     * If the primitive is annotated with a tag such as note, fixme, etc.
105     * Match the "work in progress" tags in default elemstyles.xml.
106     */
107    protected static final int FLAG_ANNOTATED = 1 << 12;
108
109    /**
110     * Replies the sub-collection of {@link OsmPrimitive}s of type <code>type</code> present in
111     * another collection of {@link OsmPrimitive}s. The result collection is a list.
112     *
113     * If <code>list</code> is null, replies an empty list.
114     *
115     * @param <T>
116     * @param list  the original list
117     * @param type the type to filter for
118     * @return the sub-list of OSM primitives of type <code>type</code>
119     */
120    static public <T extends OsmPrimitive>  List<T> getFilteredList(Collection<OsmPrimitive> list, Class<T> type) {
121        if (list == null) return Collections.emptyList();
122        List<T> ret = new LinkedList<T>();
123        for(OsmPrimitive p: list) {
124            if (type.isInstance(p)) {
125                ret.add(type.cast(p));
126            }
127        }
128        return ret;
129    }
130
131    /**
132     * Replies the sub-collection of {@link OsmPrimitive}s of type <code>type</code> present in
133     * another collection of {@link OsmPrimitive}s. The result collection is a set.
134     *
135     * If <code>list</code> is null, replies an empty set.
136     *
137     * @param <T> type of data (must be one of the {@link OsmPrimitive} types
138     * @param set  the original collection
139     * @param type the type to filter for
140     * @return the sub-set of OSM primitives of type <code>type</code>
141     */
142    static public <T extends OsmPrimitive> Set<T> getFilteredSet(Collection<OsmPrimitive> set, Class<T> type) {
143        Set<T> ret = new LinkedHashSet<T>();
144        if (set != null) {
145            for(OsmPrimitive p: set) {
146                if (type.isInstance(p)) {
147                    ret.add(type.cast(p));
148                }
149            }
150        }
151        return ret;
152    }
153
154    /**
155     * Replies the collection of referring primitives for the primitives in <code>primitives</code>.
156     *
157     * @param primitives the collection of primitives.
158     * @return the collection of referring primitives for the primitives in <code>primitives</code>;
159     * empty set if primitives is null or if there are no referring primitives
160     */
161    static public Set<OsmPrimitive> getReferrer(Collection<? extends OsmPrimitive> primitives) {
162        HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
163        if (primitives == null || primitives.isEmpty()) return ret;
164        for (OsmPrimitive p: primitives) {
165            ret.addAll(p.getReferrers());
166        }
167        return ret;
168    }
169
170    /**
171     * Some predicates, that describe conditions on primitives.
172     */
173    public static final Predicate<OsmPrimitive> isUsablePredicate = new Predicate<OsmPrimitive>() {
174        @Override public boolean evaluate(OsmPrimitive primitive) {
175            return primitive.isUsable();
176        }
177    };
178
179    public static final Predicate<OsmPrimitive> isSelectablePredicate = new Predicate<OsmPrimitive>() {
180        @Override public boolean evaluate(OsmPrimitive primitive) {
181            return primitive.isSelectable();
182        }
183    };
184
185    public static final Predicate<OsmPrimitive> nonDeletedPredicate = new Predicate<OsmPrimitive>() {
186        @Override public boolean evaluate(OsmPrimitive primitive) {
187            return !primitive.isDeleted();
188        }
189    };
190
191    public static final Predicate<OsmPrimitive> nonDeletedCompletePredicate = new Predicate<OsmPrimitive>() {
192        @Override public boolean evaluate(OsmPrimitive primitive) {
193            return !primitive.isDeleted() && !primitive.isIncomplete();
194        }
195    };
196
197    public static final Predicate<OsmPrimitive> nonDeletedPhysicalPredicate = new Predicate<OsmPrimitive>() {
198        @Override public boolean evaluate(OsmPrimitive primitive) {
199            return !primitive.isDeleted() && !primitive.isIncomplete() && !(primitive instanceof Relation);
200        }
201    };
202
203    public static final Predicate<OsmPrimitive> modifiedPredicate = new Predicate<OsmPrimitive>() {
204        @Override public boolean evaluate(OsmPrimitive primitive) {
205            return primitive.isModified();
206        }
207    };
208
209    public static final Predicate<OsmPrimitive> nodePredicate = new Predicate<OsmPrimitive>() {
210        @Override public boolean evaluate(OsmPrimitive primitive) {
211            return primitive.getClass() == Node.class;
212        }
213    };
214
215    public static final Predicate<OsmPrimitive> wayPredicate = new Predicate<OsmPrimitive>() {
216        @Override public boolean evaluate(OsmPrimitive primitive) {
217            return primitive.getClass() == Way.class;
218        }
219    };
220
221    public static final Predicate<OsmPrimitive> relationPredicate = new Predicate<OsmPrimitive>() {
222        @Override public boolean evaluate(OsmPrimitive primitive) {
223            return primitive.getClass() == Relation.class;
224        }
225    };
226
227    public static final Predicate<OsmPrimitive> multipolygonPredicate = new Predicate<OsmPrimitive>() {
228        @Override public boolean evaluate(OsmPrimitive primitive) {
229            return primitive.getClass() == Relation.class && ((Relation) primitive).isMultipolygon();
230        }
231    };
232
233    public static final Predicate<OsmPrimitive> allPredicate = new Predicate<OsmPrimitive>() {
234        @Override public boolean evaluate(OsmPrimitive primitive) {
235            return true;
236        }
237    };
238
239    /**
240     * Creates a new primitive for the given id.
241     *
242     * If allowNegativeId is set, provided id can be < 0 and will be set to primitive without any processing.
243     * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or
244     * positive number.
245     *
246     * @param id the id
247     * @param allowNegativeId
248     * @throws IllegalArgumentException thrown if id < 0 and allowNegativeId is false
249     */
250    protected OsmPrimitive(long id, boolean allowNegativeId) throws IllegalArgumentException {
251        if (allowNegativeId) {
252            this.id = id;
253        } else {
254            if (id < 0)
255                throw new IllegalArgumentException(MessageFormat.format("Expected ID >= 0. Got {0}.", id));
256            else if (id == 0) {
257                this.id = generateUniqueId();
258            } else {
259                this.id = id;
260            }
261
262        }
263        this.version = 0;
264        this.setIncomplete(id > 0);
265    }
266
267    /**
268     * Creates a new primitive for the given id and version.
269     *
270     * If allowNegativeId is set, provided id can be < 0 and will be set to primitive without any processing.
271     * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or
272     * positive number.
273     *
274     * If id is not > 0 version is ignored and set to 0.
275     *
276     * @param id
277     * @param version
278     * @param allowNegativeId
279     * @throws IllegalArgumentException thrown if id < 0 and allowNegativeId is false
280     */
281    protected OsmPrimitive(long id, int version, boolean allowNegativeId) throws IllegalArgumentException {
282        this(id, allowNegativeId);
283        this.version = (id > 0 ? version : 0);
284        setIncomplete(id > 0 && version == 0);
285    }
286
287
288    /*----------
289     * MAPPAINT
290     *--------*/
291    public StyleCache mappaintStyle = null;
292    public int mappaintCacheIdx;
293
294    /* This should not be called from outside. Fixing the UI to add relevant
295       get/set functions calling this implicitely is preferred, so we can have
296       transparent cache handling in the future. */
297    public void clearCachedStyle()
298    {
299        mappaintStyle = null;
300    }
301    /* end of mappaint data */
302
303    /*---------
304     * DATASET
305     *---------*/
306
307    /** the parent dataset */
308    private DataSet dataSet;
309
310    /**
311     * This method should never ever by called from somewhere else than Dataset.addPrimitive or removePrimitive methods
312     * @param dataSet
313     */
314    void setDataset(DataSet dataSet) {
315        if (this.dataSet != null && dataSet != null && this.dataSet != dataSet)
316            throw new DataIntegrityProblemException("Primitive cannot be included in more than one Dataset");
317        this.dataSet = dataSet;
318    }
319
320    /**
321     *
322     * @return DataSet this primitive is part of.
323     */
324    public DataSet getDataSet() {
325        return dataSet;
326    }
327
328    /**
329     * Throws exception if primitive is not part of the dataset
330     */
331    public void checkDataset() {
332        if (dataSet == null)
333            throw new DataIntegrityProblemException("Primitive must be part of the dataset: " + toString());
334    }
335
336    protected boolean writeLock() {
337        if (dataSet != null) {
338            dataSet.beginUpdate();
339            return true;
340        } else
341            return false;
342    }
343
344    protected void writeUnlock(boolean locked) {
345        if (locked) {
346            // It shouldn't be possible for dataset to become null because method calling setDataset would need write lock which is owned by this thread
347            dataSet.endUpdate();
348        }
349    }
350
351    /**
352     * Sets the id and the version of this primitive if it is known to the OSM API.
353     *
354     * Since we know the id and its version it can't be incomplete anymore. incomplete
355     * is set to false.
356     *
357     * @param id the id. > 0 required
358     * @param version the version > 0 required
359     * @throws IllegalArgumentException thrown if id <= 0
360     * @throws IllegalArgumentException thrown if version <= 0
361     * @throws DataIntegrityProblemException If id is changed and primitive was already added to the dataset
362     */
363    @Override
364    public void setOsmId(long id, int version) {
365        boolean locked = writeLock();
366        try {
367            if (id <= 0)
368                throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
369            if (version <= 0)
370                throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
371            if (dataSet != null && id != this.id) {
372                DataSet datasetCopy = dataSet;
373                // Reindex primitive
374                datasetCopy.removePrimitive(this);
375                this.id = id;
376                datasetCopy.addPrimitive(this);
377            }
378            super.setOsmId(id, version);
379        } finally {
380            writeUnlock(locked);
381        }
382    }
383
384    /**
385     * Clears the metadata, including id and version known to the OSM API.
386     * The id is a new unique id. The version, changeset and timestamp are set to 0.
387     * incomplete and deleted are set to false. It's preferred to use copy constructor with clearMetadata set to true instead
388     *
389     * <strong>Caution</strong>: Do not use this method on primitives which are already added to a {@link DataSet}.
390     *
391     * @throws DataIntegrityProblemException If primitive was already added to the dataset
392     * @since 6140
393     */
394    @Override
395    public void clearOsmMetadata() {
396        if (dataSet != null)
397            throw new DataIntegrityProblemException("Method cannot be called after primitive was added to the dataset");
398        super.clearOsmMetadata();
399    }
400
401    @Override
402    public void setUser(User user) {
403        boolean locked = writeLock();
404        try {
405            super.setUser(user);
406        } finally {
407            writeUnlock(locked);
408        }
409    }
410
411    @Override
412    public void setChangesetId(int changesetId) throws IllegalStateException, IllegalArgumentException {
413        boolean locked = writeLock();
414        try {
415            int old = this.changesetId;
416            super.setChangesetId(changesetId);
417            if (dataSet != null) {
418                dataSet.fireChangesetIdChanged(this, old, changesetId);
419            }
420        } finally {
421            writeUnlock(locked);
422        }
423    }
424
425    @Override
426    public void setTimestamp(Date timestamp) {
427        boolean locked = writeLock();
428        try {
429            super.setTimestamp(timestamp);
430        } finally {
431            writeUnlock(locked);
432        }
433    }
434
435
436    /* -------
437    /* FLAGS
438    /* ------*/
439
440    private void updateFlagsNoLock (int flag, boolean value) {
441        super.updateFlags(flag, value);
442    }
443
444    @Override
445    protected final void updateFlags(int flag, boolean value) {
446        boolean locked = writeLock();
447        try {
448            updateFlagsNoLock(flag, value);
449        } finally {
450            writeUnlock(locked);
451        }
452    }
453
454    /**
455     * Make the primitive disabled (e.g.&nbsp;if a filter applies).
456     *
457     * To enable the primitive again, use unsetDisabledState.
458     * @param hidden if the primitive should be completely hidden from view or
459     *             just shown in gray color.
460     * @return true, any flag has changed; false if you try to set the disabled
461     * state to the value that is already preset
462     */
463    public boolean setDisabledState(boolean hidden) {
464        boolean locked = writeLock();
465        try {
466            int oldFlags = flags;
467            updateFlagsNoLock(FLAG_DISABLED, true);
468            updateFlagsNoLock(FLAG_HIDE_IF_DISABLED, hidden);
469            return oldFlags != flags;
470        } finally {
471            writeUnlock(locked);
472        }
473    }
474
475    /**
476     * Remove the disabled flag from the primitive.
477     * Afterwards, the primitive is displayed normally and can be selected
478     * again.
479     */
480    public boolean unsetDisabledState() {
481        boolean locked = writeLock();
482        try {
483            int oldFlags = flags;
484            updateFlagsNoLock(FLAG_DISABLED + FLAG_HIDE_IF_DISABLED, false);
485            return oldFlags != flags;
486        } finally {
487            writeUnlock(locked);
488        }
489    }
490
491    /**
492     * Set binary property used internally by the filter mechanism.
493     */
494    public void setDisabledType(boolean isExplicit) {
495        updateFlags(FLAG_DISABLED_TYPE, isExplicit);
496    }
497
498    /**
499     * Set binary property used internally by the filter mechanism.
500     */
501    public void setHiddenType(boolean isExplicit) {
502        updateFlags(FLAG_HIDDEN_TYPE, isExplicit);
503    }
504
505    /**
506     * Replies true, if this primitive is disabled. (E.g. a filter
507     * applies)
508     */
509    public boolean isDisabled() {
510        return (flags & FLAG_DISABLED) != 0;
511    }
512
513    /**
514     * Replies true, if this primitive is disabled and marked as
515     * completely hidden on the map.
516     */
517    public boolean isDisabledAndHidden() {
518        return (((flags & FLAG_DISABLED) != 0) && ((flags & FLAG_HIDE_IF_DISABLED) != 0));
519    }
520
521    /**
522     * Get binary property used internally by the filter mechanism.
523     */
524    public boolean getHiddenType() {
525        return (flags & FLAG_HIDDEN_TYPE) != 0;
526    }
527
528    /**
529     * Get binary property used internally by the filter mechanism.
530     */
531    public boolean getDisabledType() {
532        return (flags & FLAG_DISABLED_TYPE) != 0;
533    }
534
535    public boolean isSelectable() {
536        return (flags & (FLAG_DELETED + FLAG_INCOMPLETE + FLAG_DISABLED + FLAG_HIDE_IF_DISABLED)) == 0;
537    }
538
539    public boolean isDrawable() {
540        return (flags & (FLAG_DELETED + FLAG_INCOMPLETE + FLAG_HIDE_IF_DISABLED)) == 0;
541    }
542
543    @Override
544    public void setVisible(boolean visible) throws IllegalStateException {
545        boolean locked = writeLock();
546        try {
547            super.setVisible(visible);
548        } finally {
549            writeUnlock(locked);
550        }
551    }
552
553    @Override
554    public void setDeleted(boolean deleted) {
555        boolean locked = writeLock();
556        try {
557            super.setDeleted(deleted);
558            if (dataSet != null) {
559                if (deleted) {
560                    dataSet.firePrimitivesRemoved(Collections.singleton(this), false);
561                } else {
562                    dataSet.firePrimitivesAdded(Collections.singleton(this), false);
563                }
564            }
565        } finally {
566            writeUnlock(locked);
567        }
568    }
569
570    @Override
571    protected void setIncomplete(boolean incomplete) {
572        boolean locked = writeLock();
573        try {
574            if (dataSet != null && incomplete != this.isIncomplete()) {
575                if (incomplete) {
576                    dataSet.firePrimitivesRemoved(Collections.singletonList(this), true);
577                } else {
578                    dataSet.firePrimitivesAdded(Collections.singletonList(this), true);
579                }
580            }
581            super.setIncomplete(incomplete);
582        }  finally {
583            writeUnlock(locked);
584        }
585    }
586
587    public boolean isSelected() {
588        return dataSet != null && dataSet.isSelected(this);
589    }
590
591    public boolean isMemberOfSelected() {
592        if (referrers == null)
593            return false;
594        if (referrers instanceof OsmPrimitive)
595            return referrers instanceof Relation && ((OsmPrimitive) referrers).isSelected();
596        for (OsmPrimitive ref : (OsmPrimitive[]) referrers) {
597            if (ref instanceof Relation && ref.isSelected())
598                return true;
599        }
600        return false;
601    }
602
603    public void setHighlighted(boolean highlighted) {
604        if (isHighlighted() != highlighted) {
605            updateFlags(FLAG_HIGHLIGHTED, highlighted);
606            if (dataSet != null) {
607                dataSet.fireHighlightingChanged(this);
608            }
609        }
610    }
611
612    public boolean isHighlighted() {
613        return (flags & FLAG_HIGHLIGHTED) != 0;
614    }
615
616    /*---------------------------------------------------
617     * WORK IN PROGRESS, UNINTERESTING AND DIRECTION KEYS
618     *--------------------------------------------------*/
619
620    private static volatile Collection<String> workinprogress = null;
621    private static volatile Collection<String> uninteresting = null;
622    private static volatile Collection<String> discardable = null;
623
624    /**
625     * Returns a list of "uninteresting" keys that do not make an object
626     * "tagged".  Entries that end with ':' are causing a whole namespace to be considered
627     * "uninteresting".  Only the first level namespace is considered.
628     * Initialized by isUninterestingKey()
629     * @return The list of uninteresting keys.
630     */
631    public static Collection<String> getUninterestingKeys() {
632        if (uninteresting == null) {
633            LinkedList<String> l = new LinkedList<String>(Arrays.asList(
634                "source", "source_ref", "source:", "comment",
635                "converted_by", "watch", "watch:",
636                "description", "attribution"));
637            l.addAll(getDiscardableKeys());
638            l.addAll(getWorkInProgressKeys());
639            uninteresting = Main.pref.getCollection("tags.uninteresting", l);
640        }
641        return uninteresting;
642    }
643
644    /**
645     * Returns a list of keys which have been deemed uninteresting to the point
646     * that they can be silently removed from data which is being edited.
647     * @return The list of discardable keys.
648     */
649    public static Collection<String> getDiscardableKeys() {
650        if (discardable == null) {
651            discardable = Main.pref.getCollection("tags.discardable",
652                    Arrays.asList(
653                            "created_by",
654                            "geobase:datasetName",
655                            "geobase:uuid",
656                            "KSJ2:ADS",
657                            "KSJ2:ARE",
658                            "KSJ2:AdminArea",
659                            "KSJ2:COP_label",
660                            "KSJ2:DFD",
661                            "KSJ2:INT",
662                            "KSJ2:INT_label",
663                            "KSJ2:LOC",
664                            "KSJ2:LPN",
665                            "KSJ2:OPC",
666                            "KSJ2:PubFacAdmin",
667                            "KSJ2:RAC",
668                            "KSJ2:RAC_label",
669                            "KSJ2:RIC",
670                            "KSJ2:RIN",
671                            "KSJ2:WSC",
672                            "KSJ2:coordinate",
673                            "KSJ2:curve_id",
674                            "KSJ2:curve_type",
675                            "KSJ2:filename",
676                            "KSJ2:lake_id",
677                            "KSJ2:lat",
678                            "KSJ2:long",
679                            "KSJ2:river_id",
680                            "odbl",
681                            "odbl:note",
682                            "SK53_bulk:load",
683                            "sub_sea:type",
684                            "tiger:source",
685                            "tiger:separated",
686                            "tiger:tlid",
687                            "tiger:upload_uuid",
688                            "yh:LINE_NAME",
689                            "yh:LINE_NUM",
690                            "yh:STRUCTURE",
691                            "yh:TOTYUMONO",
692                            "yh:TYPE",
693                            "yh:WIDTH_RANK"
694                        ));
695        }
696        return discardable;
697    }
698
699    /**
700     * Returns a list of "work in progress" keys that do not make an object
701     * "tagged" but "annotated".
702     * @return The list of work in progress keys.
703     * @since 5754
704     */
705    public static Collection<String> getWorkInProgressKeys() {
706        if (workinprogress == null) {
707            workinprogress = Main.pref.getCollection("tags.workinprogress",
708                    Arrays.asList("note", "fixme", "FIXME"));
709        }
710        return workinprogress;
711    }
712
713    /**
714     * Determines if key is considered "uninteresting".
715     * @param key The key to check
716     * @return true if key is considered "uninteresting".
717     */
718    public static boolean isUninterestingKey(String key) {
719        getUninterestingKeys();
720        if (uninteresting.contains(key))
721            return true;
722        int pos = key.indexOf(':');
723        if (pos > 0)
724            return uninteresting.contains(key.substring(0, pos + 1));
725        return false;
726    }
727
728    private static volatile Match directionKeys = null;
729    private static volatile Match reversedDirectionKeys = null;
730
731    /**
732     * Contains a list of direction-dependent keys that make an object
733     * direction dependent.
734     * Initialized by checkDirectionTagged()
735     */
736    static {
737        String reversedDirectionDefault = "oneway=\"-1\"";
738
739        String directionDefault = "oneway? | aerialway=* | "+
740                "waterway=stream | waterway=river | waterway=canal | waterway=drain | waterway=rapids | "+
741                "\"piste:type\"=downhill | \"piste:type\"=sled | man_made=\"piste:halfpipe\" | "+
742                "junction=roundabout | (highway=motorway_link & -oneway=no)";
743
744        try {
745            reversedDirectionKeys = SearchCompiler.compile(Main.pref.get("tags.reversed_direction", reversedDirectionDefault), false, false);
746        } catch (ParseError e) {
747            Main.error("Unable to compile pattern for tags.reversed_direction, trying default pattern: " + e.getMessage());
748
749            try {
750                reversedDirectionKeys = SearchCompiler.compile(reversedDirectionDefault, false, false);
751            } catch (ParseError e2) {
752                throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage());
753            }
754        }
755        try {
756            directionKeys = SearchCompiler.compile(Main.pref.get("tags.direction", directionDefault), false, false);
757        } catch (ParseError e) {
758            Main.error("Unable to compile pattern for tags.direction, trying default pattern: " + e.getMessage());
759
760            try {
761                directionKeys = SearchCompiler.compile(directionDefault, false, false);
762            } catch (ParseError e2) {
763                throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage());
764            }
765        }
766    }
767
768    private void updateTagged() {
769        if (keys != null) {
770            for (String key: keySet()) {
771                // 'area' is not really uninteresting (putting it in that list may have unpredictable side effects)
772                // but it's clearly not enough to consider an object as tagged (see #9261)
773                if (!isUninterestingKey(key) && !"area".equals(key)) {
774                    updateFlagsNoLock(FLAG_TAGGED, true);
775                    return;
776                }
777            }
778        }
779        updateFlagsNoLock(FLAG_TAGGED, false);
780    }
781
782    private void updateAnnotated() {
783        if (keys != null) {
784            for (String key: keySet()) {
785                if (getWorkInProgressKeys().contains(key)) {
786                    updateFlagsNoLock(FLAG_ANNOTATED, true);
787                    return;
788                }
789            }
790        }
791        updateFlagsNoLock(FLAG_ANNOTATED, false);
792    }
793
794    /**
795     * Determines if this object is considered "tagged". To be "tagged", an object
796     * must have one or more "interesting" tags. "created_by" and "source"
797     * are typically considered "uninteresting" and do not make an object
798     * "tagged".
799     * @return true if this object is considered "tagged"
800     */
801    public boolean isTagged() {
802        return (flags & FLAG_TAGGED) != 0;
803    }
804
805    /**
806     * Determines if this object is considered "annotated". To be "annotated", an object
807     * must have one or more "work in progress" tags, such as "note" or "fixme".
808     * @return true if this object is considered "annotated"
809     * @since 5754
810     */
811    public boolean isAnnotated() {
812        return (flags & FLAG_ANNOTATED) != 0;
813    }
814
815    private void updateDirectionFlags() {
816        boolean hasDirections = false;
817        boolean directionReversed = false;
818        if (reversedDirectionKeys.match(this)) {
819            hasDirections = true;
820            directionReversed = true;
821        }
822        if (directionKeys.match(this)) {
823            hasDirections = true;
824        }
825
826        updateFlagsNoLock(FLAG_DIRECTION_REVERSED, directionReversed);
827        updateFlagsNoLock(FLAG_HAS_DIRECTIONS, hasDirections);
828    }
829
830    /**
831     * true if this object has direction dependent tags (e.g. oneway)
832     */
833    public boolean hasDirectionKeys() {
834        return (flags & FLAG_HAS_DIRECTIONS) != 0;
835    }
836
837    public boolean reversedDirection() {
838        return (flags & FLAG_DIRECTION_REVERSED) != 0;
839    }
840
841    /*------------
842     * Keys handling
843     ------------*/
844
845    @Override
846    public final void setKeys(Map<String, String> keys) {
847        boolean locked = writeLock();
848        try {
849            super.setKeys(keys);
850        } finally {
851            writeUnlock(locked);
852        }
853    }
854
855    @Override
856    public final void put(String key, String value) {
857        boolean locked = writeLock();
858        try {
859            super.put(key, value);
860        } finally {
861            writeUnlock(locked);
862        }
863    }
864
865    @Override
866    public final void remove(String key) {
867        boolean locked = writeLock();
868        try {
869            super.remove(key);
870        } finally {
871            writeUnlock(locked);
872        }
873    }
874
875    @Override
876    public final void removeAll() {
877        boolean locked = writeLock();
878        try {
879            super.removeAll();
880        } finally {
881            writeUnlock(locked);
882        }
883    }
884
885    @Override
886    protected void keysChangedImpl(Map<String, String> originalKeys) {
887        clearCachedStyle();
888        if (dataSet != null) {
889            for (OsmPrimitive ref : getReferrers()) {
890                ref.clearCachedStyle();
891            }
892        }
893        updateDirectionFlags();
894        updateTagged();
895        updateAnnotated();
896        if (dataSet != null) {
897            dataSet.fireTagsChanged(this, originalKeys);
898        }
899    }
900
901    /*------------
902     * Referrers
903     ------------*/
904
905    private Object referrers;
906
907    /**
908     * Add new referrer. If referrer is already included then no action is taken
909     * @param referrer
910     */
911    protected void addReferrer(OsmPrimitive referrer) {
912        // Based on methods from josm-ng
913        if (referrers == null) {
914            referrers = referrer;
915        } else if (referrers instanceof OsmPrimitive) {
916            if (referrers != referrer) {
917                referrers = new OsmPrimitive[] { (OsmPrimitive)referrers, referrer };
918            }
919        } else {
920            for (OsmPrimitive primitive:(OsmPrimitive[])referrers) {
921                if (primitive == referrer)
922                    return;
923            }
924            OsmPrimitive[] orig = (OsmPrimitive[])referrers;
925            OsmPrimitive[] bigger = new OsmPrimitive[orig.length+1];
926            System.arraycopy(orig, 0, bigger, 0, orig.length);
927            bigger[orig.length] = referrer;
928            referrers = bigger;
929        }
930    }
931
932    /**
933     * Remove referrer. No action is taken if referrer is not registered
934     * @param referrer
935     */
936    protected void removeReferrer(OsmPrimitive referrer) {
937        // Based on methods from josm-ng
938        if (referrers instanceof OsmPrimitive) {
939            if (referrers == referrer) {
940                referrers = null;
941            }
942        } else if (referrers instanceof OsmPrimitive[]) {
943            OsmPrimitive[] orig = (OsmPrimitive[])referrers;
944            int idx = -1;
945            for (int i=0; i<orig.length; i++) {
946                if (orig[i] == referrer) {
947                    idx = i;
948                    break;
949                }
950            }
951            if (idx == -1)
952                return;
953
954            if (orig.length == 2) {
955                referrers = orig[1-idx]; // idx is either 0 or 1, take the other
956            } else { // downsize the array
957                OsmPrimitive[] smaller = new OsmPrimitive[orig.length-1];
958                System.arraycopy(orig, 0, smaller, 0, idx);
959                System.arraycopy(orig, idx+1, smaller, idx, smaller.length-idx);
960                referrers = smaller;
961            }
962        }
963    }
964    /**
965     * Find primitives that reference this primitive. Returns only primitives that are included in the same
966     * dataset as this primitive. <br>
967     *
968     * For example following code will add wnew as referer to all nodes of existingWay, but this method will
969     * not return wnew because it's not part of the dataset <br>
970     *
971     * <code>Way wnew = new Way(existingWay)</code>
972     *
973     * @param allowWithoutDataset If true, method will return empty list if primitive is not part of the dataset. If false,
974     * exception will be thrown in this case
975     *
976     * @return a collection of all primitives that reference this primitive.
977     */
978
979    public final List<OsmPrimitive> getReferrers(boolean allowWithoutDataset) {
980        // Method copied from OsmPrimitive in josm-ng
981        // Returns only referrers that are members of the same dataset (primitive can have some fake references, for example
982        // when way is cloned
983
984        if (dataSet == null && allowWithoutDataset)
985            return Collections.emptyList();
986
987        checkDataset();
988        Object referrers = this.referrers;
989        List<OsmPrimitive> result = new ArrayList<OsmPrimitive>();
990        if (referrers != null) {
991            if (referrers instanceof OsmPrimitive) {
992                OsmPrimitive ref = (OsmPrimitive)referrers;
993                if (ref.dataSet == dataSet) {
994                    result.add(ref);
995                }
996            } else {
997                for (OsmPrimitive o:(OsmPrimitive[])referrers) {
998                    if (dataSet == o.dataSet) {
999                        result.add(o);
1000                    }
1001                }
1002            }
1003        }
1004        return result;
1005    }
1006
1007    public final List<OsmPrimitive> getReferrers() {
1008        return getReferrers(false);
1009    }
1010
1011    /**
1012     * <p>Visits {@code visitor} for all referrers.</p>
1013     *
1014     * @param visitor the visitor. Ignored, if null.
1015     */
1016    public void visitReferrers(Visitor visitor){
1017        if (visitor == null) return;
1018        if (this.referrers == null)
1019            return;
1020        else if (this.referrers instanceof OsmPrimitive) {
1021            OsmPrimitive ref = (OsmPrimitive) this.referrers;
1022            if (ref.dataSet == dataSet) {
1023                ref.accept(visitor);
1024            }
1025        } else if (this.referrers instanceof OsmPrimitive[]) {
1026            OsmPrimitive[] refs = (OsmPrimitive[]) this.referrers;
1027            for (OsmPrimitive ref: refs) {
1028                if (ref.dataSet == dataSet) {
1029                    ref.accept(visitor);
1030                }
1031            }
1032        }
1033    }
1034
1035    /**
1036      Return true, if this primitive is referred by at least n ways
1037      @param n Minimal number of ways to return true. Must be positive
1038     */
1039    public final boolean isReferredByWays(int n) {
1040        // Count only referrers that are members of the same dataset (primitive can have some fake references, for example
1041        // when way is cloned
1042        Object referrers = this.referrers;
1043        if (referrers == null) return false;
1044        checkDataset();
1045        if (referrers instanceof OsmPrimitive)
1046            return n<=1 && referrers instanceof Way && ((OsmPrimitive)referrers).dataSet == dataSet;
1047        else {
1048            int counter=0;
1049            for (OsmPrimitive o : (OsmPrimitive[])referrers) {
1050                if (dataSet == o.dataSet && o instanceof Way) {
1051                    if (++counter >= n)
1052                        return true;
1053                }
1054            }
1055            return false;
1056        }
1057    }
1058
1059
1060    /*-----------------
1061     * OTHER METHODS
1062     *----------------*/
1063
1064    /**
1065     * Implementation of the visitor scheme. Subclasses have to call the correct
1066     * visitor function.
1067     * @param visitor The visitor from which the visit() function must be called.
1068     */
1069    abstract public void accept(Visitor visitor);
1070
1071    /**
1072     * Get and write all attributes from the parameter. Does not fire any listener, so
1073     * use this only in the data initializing phase
1074     */
1075    public void cloneFrom(OsmPrimitive other) {
1076        // write lock is provided by subclasses
1077        if (id != other.id && dataSet != null)
1078            throw new DataIntegrityProblemException("Osm id cannot be changed after primitive was added to the dataset");
1079
1080        super.cloneFrom(other);
1081        clearCachedStyle();
1082    }
1083
1084    /**
1085     * Merges the technical and semantical attributes from <code>other</code> onto this.
1086     *
1087     * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
1088     * have an assigend OSM id, the IDs have to be the same.
1089     *
1090     * @param other the other primitive. Must not be null.
1091     * @throws IllegalArgumentException thrown if other is null.
1092     * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not
1093     * @throws DataIntegrityProblemException thrown if other isn't new and other.getId() != this.getId()
1094     */
1095    public void mergeFrom(OsmPrimitive other) {
1096        boolean locked = writeLock();
1097        try {
1098            CheckParameterUtil.ensureParameterNotNull(other, "other");
1099            if (other.isNew() ^ isNew())
1100                throw new DataIntegrityProblemException(tr("Cannot merge because either of the participating primitives is new and the other is not"));
1101            if (! other.isNew() && other.getId() != id)
1102                throw new DataIntegrityProblemException(tr("Cannot merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId()));
1103
1104            setKeys(other.getKeys());
1105            timestamp = other.timestamp;
1106            version = other.version;
1107            setIncomplete(other.isIncomplete());
1108            flags = other.flags;
1109            user= other.user;
1110            changesetId = other.changesetId;
1111        } finally {
1112            writeUnlock(locked);
1113        }
1114    }
1115
1116    /**
1117     * Replies true if this primitive and other are equal with respect to their
1118     * semantic attributes.
1119     * <ol>
1120     *   <li>equal id</ol>
1121     *   <li>both are complete or both are incomplete</li>
1122     *   <li>both have the same tags</li>
1123     * </ol>
1124     * @param other
1125     * @return true if this primitive and other are equal with respect to their
1126     * semantic attributes.
1127     */
1128    public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
1129        if (!isNew() &&  id != other.id)
1130            return false;
1131        if (isIncomplete() ^ other.isIncomplete()) // exclusive or operator for performance (see #7159)
1132            return false;
1133        // can't do an equals check on the internal keys array because it is not ordered
1134        //
1135        return hasSameTags(other);
1136    }
1137
1138    /**
1139     * Replies true if this primitive and other are equal with respect to their
1140     * technical attributes. The attributes:
1141     * <ol>
1142     *   <li>deleted</ol>
1143     *   <li>modified</ol>
1144     *   <li>timestamp</ol>
1145     *   <li>version</ol>
1146     *   <li>visible</ol>
1147     *   <li>user</ol>
1148     * </ol>
1149     * have to be equal
1150     * @param other the other primitive
1151     * @return true if this primitive and other are equal with respect to their
1152     * technical attributes
1153     */
1154    public boolean hasEqualTechnicalAttributes(OsmPrimitive other) {
1155        if (other == null) return false;
1156
1157        return
1158                isDeleted() == other.isDeleted()
1159                && isModified() == other.isModified()
1160                && timestamp == other.timestamp
1161                && version == other.version
1162                && isVisible() == other.isVisible()
1163                && (user == null ? other.user==null : user==other.user)
1164                && changesetId == other.changesetId;
1165    }
1166
1167    /**
1168     * Loads (clone) this primitive from provided PrimitiveData
1169     * @param data The object which should be cloned
1170     */
1171    public void load(PrimitiveData data) {
1172        // Write lock is provided by subclasses
1173        setKeys(data.getKeys());
1174        setTimestamp(data.getTimestamp());
1175        user = data.getUser();
1176        setChangesetId(data.getChangesetId());
1177        setDeleted(data.isDeleted());
1178        setModified(data.isModified());
1179        setIncomplete(data.isIncomplete());
1180        version = data.getVersion();
1181    }
1182
1183    /**
1184     * Save parameters of this primitive to the transport object
1185     * @return The saved object data
1186     */
1187    public abstract PrimitiveData save();
1188
1189    /**
1190     * Save common parameters of primitives to the transport object
1191     * @param data The object to save the data into
1192     */
1193    protected void saveCommonAttributes(PrimitiveData data) {
1194        data.setId(id);
1195        data.setKeys(getKeys());
1196        data.setTimestamp(getTimestamp());
1197        data.setUser(user);
1198        data.setDeleted(isDeleted());
1199        data.setModified(isModified());
1200        data.setVisible(isVisible());
1201        data.setIncomplete(isIncomplete());
1202        data.setChangesetId(changesetId);
1203        data.setVersion(version);
1204    }
1205
1206    /**
1207     * Fetch the bounding box of the primitive
1208     * @return Bounding box of the object
1209     */
1210    public abstract BBox getBBox();
1211
1212    /**
1213     * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...)
1214     */
1215    public abstract void updatePosition();
1216
1217    /*----------------
1218     * OBJECT METHODS
1219     *---------------*/
1220
1221    @Override
1222    protected String getFlagsAsString() {
1223        StringBuilder builder = new StringBuilder(super.getFlagsAsString());
1224
1225        if (isDisabled()) {
1226            if (isDisabledAndHidden()) {
1227                builder.append("h");
1228            } else {
1229                builder.append("d");
1230            }
1231        }
1232        if (isTagged()) {
1233            builder.append("T");
1234        }
1235        if (hasDirectionKeys()) {
1236            if (reversedDirection()) {
1237                builder.append("<");
1238            } else {
1239                builder.append(">");
1240            }
1241        }
1242        return builder.toString();
1243    }
1244
1245    /**
1246     * Equal, if the id (and class) is equal.
1247     *
1248     * An primitive is equal to its incomplete counter part.
1249     */
1250    @Override public boolean equals(Object obj) {
1251        if (obj instanceof OsmPrimitive)
1252            return ((OsmPrimitive)obj).id == id && obj.getClass() == getClass();
1253        return false;
1254    }
1255
1256    /**
1257     * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0.
1258     *
1259     * An primitive has the same hashcode as its incomplete counterpart.
1260     */
1261    @Override public final int hashCode() {
1262        return (int)id;
1263    }
1264
1265    /**
1266     * Replies the display name of a primitive formatted by <code>formatter</code>
1267     *
1268     * @return the display name
1269     */
1270    public abstract String getDisplayName(NameFormatter formatter);
1271
1272    @Override
1273    public Collection<String> getTemplateKeys() {
1274        Collection<String> keySet = keySet();
1275        List<String> result = new ArrayList<String>(keySet.size() + 2);
1276        result.add(SPECIAL_VALUE_ID);
1277        result.add(SPECIAL_VALUE_LOCAL_NAME);
1278        result.addAll(keySet);
1279        return result;
1280    }
1281
1282    @Override
1283    public Object getTemplateValue(String name, boolean special) {
1284        if (special) {
1285            String lc = name.toLowerCase();
1286            if (SPECIAL_VALUE_ID.equals(lc))
1287                return getId();
1288            else if (SPECIAL_VALUE_LOCAL_NAME.equals(lc))
1289                return getLocalName();
1290            else
1291                return null;
1292
1293        } else
1294            return getIgnoreCase(name);
1295    }
1296
1297    @Override
1298    public boolean evaluateCondition(Match condition) {
1299        return condition.match(this);
1300    }
1301
1302    /**
1303     * Replies the set of referring relations
1304     *
1305     * @return the set of referring relations
1306     */
1307    public static Set<Relation> getParentRelations(Collection<? extends OsmPrimitive> primitives) {
1308        HashSet<Relation> ret = new HashSet<Relation>();
1309        for (OsmPrimitive w : primitives) {
1310            ret.addAll(OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class));
1311        }
1312        return ret;
1313    }
1314
1315    /**
1316     * Determines if this primitive has tags denoting an area.
1317     * @return {@code true} if this primitive has tags denoting an area, {@code false} otherwise.
1318     * @since 6491
1319     */
1320    public final boolean hasAreaTags() {
1321        return hasKey("landuse")
1322                || "yes".equals(get("area"))
1323                || "riverbank".equals(get("waterway"))
1324                || hasKey("natural")
1325                || hasKey("amenity")
1326                || hasKey("leisure")
1327                || hasKey("building")
1328                || hasKey("building:part");
1329    }
1330
1331    /**
1332     * Determines if this primitive semantically concerns an area.
1333     * @return {@code true} if this primitive semantically concerns an area, according to its type, geometry and tags, {@code false} otherwise.
1334     * @since 6491
1335     */
1336    public abstract boolean concernsArea();
1337}