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