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.List;
013import java.util.Locale;
014import java.util.Map;
015import java.util.Objects;
016import java.util.Set;
017import java.util.function.Consumer;
018import java.util.stream.Collectors;
019import java.util.stream.Stream;
020
021import org.openstreetmap.josm.data.osm.search.SearchCompiler;
022import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match;
023import org.openstreetmap.josm.data.osm.search.SearchParseError;
024import org.openstreetmap.josm.data.osm.visitor.OsmPrimitiveVisitor;
025import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
026import org.openstreetmap.josm.gui.mappaint.StyleCache;
027import org.openstreetmap.josm.spi.preferences.Config;
028import org.openstreetmap.josm.tools.CheckParameterUtil;
029import org.openstreetmap.josm.tools.Logging;
030import org.openstreetmap.josm.tools.Utils;
031import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider;
032
033/**
034 * The base class for OSM objects ({@link Node}, {@link Way}, {@link Relation}).
035 *
036 * It can be created, deleted and uploaded to the OSM-Server.
037 *
038 * Although OsmPrimitive is designed as a base class, it is not to be meant to subclass
039 * it by any other than from the package {@link org.openstreetmap.josm.data.osm}. The available primitives are a fixed set that are given
040 * by the server environment and not an extendible data stuff.
041 *
042 * @author imi
043 */
044public abstract class OsmPrimitive extends AbstractPrimitive implements TemplateEngineDataProvider {
045    private static final String SPECIAL_VALUE_ID = "id";
046    private static final String SPECIAL_VALUE_LOCAL_NAME = "localname";
047
048    /**
049     * A tagged way that matches this pattern has a direction.
050     * @see #FLAG_HAS_DIRECTIONS
051     */
052    static volatile Match directionKeys;
053
054    /**
055     * A tagged way that matches this pattern has a direction that is reversed.
056     * <p>
057     * This pattern should be a subset of {@link #directionKeys}
058     * @see #FLAG_DIRECTION_REVERSED
059     */
060    private static volatile Match reversedDirectionKeys;
061
062    static {
063        String reversedDirectionDefault = "oneway=\"-1\"";
064
065        String directionDefault = "oneway? | "+
066                "(aerialway=chair_lift & -oneway=no) | "+
067                "(aerialway=rope_tow & -oneway=no) | "+
068                "(aerialway=magic_carpet & -oneway=no) | "+
069                "(aerialway=zip_line & -oneway=no) | "+
070                "(aerialway=drag_lift & -oneway=no) | "+
071                "(aerialway=t-bar & -oneway=no) | "+
072                "(aerialway=j-bar & -oneway=no) | "+
073                "(aerialway=platter & -oneway=no) | "+
074                "waterway=stream | waterway=river | waterway=ditch | waterway=drain | waterway=tidal_channel | "+
075                "(\"piste:type\"=downhill & -area=yes) | (\"piste:type\"=sled & -area=yes) | (man_made=\"piste:halfpipe\" & -area=yes) | "+
076                "junction=circular | junction=roundabout | (highway=motorway & -oneway=no & -oneway=reversible) | "+
077                "(highway=motorway_link & -oneway=no & -oneway=reversible)";
078
079        reversedDirectionKeys = compileDirectionKeys("tags.reversed_direction", reversedDirectionDefault);
080        directionKeys = compileDirectionKeys("tags.direction", directionDefault);
081    }
082
083    /**
084     * Replies the collection of referring primitives for the primitives in <code>primitives</code>.
085     *
086     * @param primitives the collection of primitives.
087     * @return the collection of referring primitives for the primitives in <code>primitives</code>;
088     * empty set if primitives is null or if there are no referring primitives
089     */
090    public static Set<OsmPrimitive> getReferrer(Collection<? extends OsmPrimitive> primitives) {
091        return (primitives != null ? primitives.stream() : Stream.<OsmPrimitive>empty())
092                .flatMap(p -> p.referrers(OsmPrimitive.class))
093                .collect(Collectors.toSet());
094    }
095
096    /**
097     * Creates a new primitive for the given id.
098     *
099     * If allowNegativeId is set, provided id can be &lt; 0 and will be set to primitive without any processing.
100     * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or
101     * positive number.
102     *
103     * @param id the id
104     * @param allowNegativeId {@code true} to allow negative id
105     * @throws IllegalArgumentException if id &lt; 0 and allowNegativeId is false
106     */
107    protected OsmPrimitive(long id, boolean allowNegativeId) {
108        if (allowNegativeId) {
109            this.id = id;
110        } else {
111            if (id < 0)
112                throw new IllegalArgumentException(MessageFormat.format("Expected ID >= 0. Got {0}.", id));
113            else if (id == 0) {
114                this.id = generateUniqueId();
115            } else {
116                this.id = id;
117            }
118
119        }
120        this.version = 0;
121        this.setIncomplete(id > 0);
122    }
123
124    /**
125     * Creates a new primitive for the given id and version.
126     *
127     * If allowNegativeId is set, provided id can be &lt; 0 and will be set to primitive without any processing.
128     * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or
129     * positive number.
130     *
131     * If id is not &gt; 0 version is ignored and set to 0.
132     *
133     * @param id the id
134     * @param version the version (positive integer)
135     * @param allowNegativeId {@code true} to allow negative id
136     * @throws IllegalArgumentException if id &lt; 0 and allowNegativeId is false
137     */
138    protected OsmPrimitive(long id, int version, boolean allowNegativeId) {
139        this(id, allowNegativeId);
140        this.version = id > 0 ? version : 0;
141        setIncomplete(id > 0 && version == 0);
142    }
143
144    /*----------
145     * MAPPAINT
146     *--------*/
147    private StyleCache mappaintStyle;
148    private short mappaintCacheIdx;
149
150    @Override
151    public final StyleCache getCachedStyle() {
152        return mappaintStyle;
153    }
154
155    @Override
156    public final void setCachedStyle(StyleCache mappaintStyle) {
157        this.mappaintStyle = mappaintStyle;
158    }
159
160    @Override
161    public final boolean isCachedStyleUpToDate() {
162        return mappaintStyle != null && mappaintCacheIdx == dataSet.getMappaintCacheIndex();
163    }
164
165    @Override
166    public final void declareCachedStyleUpToDate() {
167        this.mappaintCacheIdx = dataSet.getMappaintCacheIndex();
168    }
169
170    /* end of mappaint data */
171
172    /*---------
173     * DATASET
174     *---------*/
175
176    /** the parent dataset */
177    private DataSet dataSet;
178
179    /**
180     * This method should never ever by called from somewhere else than Dataset.addPrimitive or removePrimitive methods
181     * @param dataSet the parent dataset
182     */
183    void setDataset(DataSet dataSet) {
184        if (this.dataSet != null && dataSet != null && this.dataSet != dataSet)
185            throw new DataIntegrityProblemException("Primitive cannot be included in more than one Dataset");
186        this.dataSet = dataSet;
187    }
188
189    @Override
190    public DataSet getDataSet() {
191        return dataSet;
192    }
193
194    /**
195     * Throws exception if primitive is not part of the dataset
196     */
197    public void checkDataset() {
198        if (dataSet == null)
199            throw new DataIntegrityProblemException("Primitive must be part of the dataset: " + toString());
200    }
201
202    /**
203     * Throws exception if primitive is in a read-only dataset
204     */
205    protected final void checkDatasetNotReadOnly() {
206        if (dataSet != null && dataSet.isLocked())
207            throw new DataIntegrityProblemException("Primitive cannot be modified in read-only dataset: " + toString());
208    }
209
210    protected boolean writeLock() {
211        if (dataSet != null) {
212            dataSet.beginUpdate();
213            return true;
214        } else
215            return false;
216    }
217
218    protected void writeUnlock(boolean locked) {
219        if (locked && dataSet != null) {
220            // It shouldn't be possible for dataset to become null because
221            // method calling setDataset would need write lock which is owned by this thread
222            dataSet.endUpdate();
223        }
224    }
225
226    /**
227     * Sets the id and the version of this primitive if it is known to the OSM API.
228     *
229     * Since we know the id and its version it can't be incomplete anymore. incomplete
230     * is set to false.
231     *
232     * @param id the id. &gt; 0 required
233     * @param version the version &gt; 0 required
234     * @throws IllegalArgumentException if id &lt;= 0
235     * @throws IllegalArgumentException if version &lt;= 0
236     * @throws DataIntegrityProblemException if id is changed and primitive was already added to the dataset
237     */
238    @Override
239    public void setOsmId(long id, int version) {
240        checkDatasetNotReadOnly();
241        boolean locked = writeLock();
242        try {
243            if (id <= 0)
244                throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
245            if (version <= 0)
246                throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
247            if (dataSet != null && id != this.id) {
248                DataSet datasetCopy = dataSet;
249                // Reindex primitive
250                datasetCopy.removePrimitive(this);
251                this.id = id;
252                datasetCopy.addPrimitive(this);
253            }
254            super.setOsmId(id, version);
255        } finally {
256            writeUnlock(locked);
257        }
258    }
259
260    /**
261     * Clears the metadata, including id and version known to the OSM API.
262     * The id is a new unique id. The version, changeset and timestamp are set to 0.
263     * incomplete and deleted are set to false. It's preferred to use copy constructor with clearMetadata set to true instead
264     *
265     * <strong>Caution</strong>: Do not use this method on primitives which are already added to a {@link DataSet}.
266     *
267     * @throws DataIntegrityProblemException If primitive was already added to the dataset
268     * @since 6140
269     */
270    @Override
271    public void clearOsmMetadata() {
272        if (dataSet != null)
273            throw new DataIntegrityProblemException("Method cannot be called after primitive was added to the dataset");
274        super.clearOsmMetadata();
275    }
276
277    @Override
278    public void setUser(User user) {
279        checkDatasetNotReadOnly();
280        boolean locked = writeLock();
281        try {
282            super.setUser(user);
283        } finally {
284            writeUnlock(locked);
285        }
286    }
287
288    @Override
289    public void setChangesetId(int changesetId) {
290        checkDatasetNotReadOnly();
291        boolean locked = writeLock();
292        try {
293            int old = this.changesetId;
294            super.setChangesetId(changesetId);
295            if (dataSet != null) {
296                dataSet.fireChangesetIdChanged(this, old, changesetId);
297            }
298        } finally {
299            writeUnlock(locked);
300        }
301    }
302
303    @Override
304    public void setTimestamp(Date timestamp) {
305        checkDatasetNotReadOnly();
306        boolean locked = writeLock();
307        try {
308            super.setTimestamp(timestamp);
309        } finally {
310            writeUnlock(locked);
311        }
312    }
313
314    /* -------
315    /* FLAGS
316    /* ------*/
317
318    private void updateFlagsNoLock(short flag, boolean value) {
319        super.updateFlags(flag, value);
320    }
321
322    @Override
323    protected final void updateFlags(short flag, boolean value) {
324        boolean locked = writeLock();
325        try {
326            updateFlagsNoLock(flag, value);
327        } finally {
328            writeUnlock(locked);
329        }
330    }
331
332    /**
333     * Make the primitive disabled (e.g.&nbsp;if a filter applies).
334     *
335     * To enable the primitive again, use unsetDisabledState.
336     * @param hidden if the primitive should be completely hidden from view or
337     *             just shown in gray color.
338     * @return true, any flag has changed; false if you try to set the disabled
339     * state to the value that is already preset
340     */
341    public boolean setDisabledState(boolean hidden) {
342        boolean locked = writeLock();
343        try {
344            int oldFlags = flags;
345            updateFlagsNoLock(FLAG_DISABLED, true);
346            updateFlagsNoLock(FLAG_HIDE_IF_DISABLED, hidden);
347            return oldFlags != flags;
348        } finally {
349            writeUnlock(locked);
350        }
351    }
352
353    /**
354     * Remove the disabled flag from the primitive.
355     * Afterwards, the primitive is displayed normally and can be selected again.
356     * @return {@code true} if a change occurred
357     */
358    public boolean unsetDisabledState() {
359        boolean locked = writeLock();
360        try {
361            int oldFlags = flags;
362            updateFlagsNoLock(FLAG_DISABLED, false);
363            updateFlagsNoLock(FLAG_HIDE_IF_DISABLED, false);
364            return oldFlags != flags;
365        } finally {
366            writeUnlock(locked);
367        }
368    }
369
370    /**
371     * Set binary property used internally by the filter mechanism.
372     * @param isExplicit new "disabled type" flag value
373     */
374    public void setDisabledType(boolean isExplicit) {
375        updateFlags(FLAG_DISABLED_TYPE, isExplicit);
376    }
377
378    /**
379     * Set binary property used internally by the filter mechanism.
380     * @param isExplicit new "hidden type" flag value
381     */
382    public void setHiddenType(boolean isExplicit) {
383        updateFlags(FLAG_HIDDEN_TYPE, isExplicit);
384    }
385
386    /**
387     * Set binary property used internally by the filter mechanism.
388     * @param isPreserved new "preserved" flag value
389     * @since 13309
390     */
391    public void setPreserved(boolean isPreserved) {
392        updateFlags(FLAG_PRESERVED, isPreserved);
393    }
394
395    @Override
396    public boolean isDisabled() {
397        return (flags & FLAG_DISABLED) != 0;
398    }
399
400    @Override
401    public boolean isDisabledAndHidden() {
402        return ((flags & FLAG_DISABLED) != 0) && ((flags & FLAG_HIDE_IF_DISABLED) != 0);
403    }
404
405    /**
406     * Get binary property used internally by the filter mechanism.
407     * @return {@code true} if this object has the "hidden type" flag enabled
408     */
409    public boolean getHiddenType() {
410        return (flags & FLAG_HIDDEN_TYPE) != 0;
411    }
412
413    /**
414     * Get binary property used internally by the filter mechanism.
415     * @return {@code true} if this object has the "disabled type" flag enabled
416     */
417    public boolean getDisabledType() {
418        return (flags & FLAG_DISABLED_TYPE) != 0;
419    }
420
421    @Override
422    public boolean isPreserved() {
423        return (flags & FLAG_PRESERVED) != 0;
424    }
425
426    @Override
427    public boolean isSelectable() {
428        // not synchronized -> check disabled twice just to be sure we did not have a race condition.
429        return !isDisabled() && isDrawable() && !isDisabled();
430    }
431
432    @Override
433    public boolean isDrawable() {
434        return (flags & (FLAG_DELETED + FLAG_INCOMPLETE + FLAG_HIDE_IF_DISABLED)) == 0;
435    }
436
437    @Override
438    public void setModified(boolean modified) {
439        checkDatasetNotReadOnly();
440        boolean locked = writeLock();
441        try {
442            super.setModified(modified);
443            if (dataSet != null) {
444                dataSet.firePrimitiveFlagsChanged(this);
445            }
446            clearCachedStyle();
447        } finally {
448            writeUnlock(locked);
449        }
450    }
451
452    @Override
453    public void setVisible(boolean visible) {
454        checkDatasetNotReadOnly();
455        boolean locked = writeLock();
456        try {
457            super.setVisible(visible);
458            clearCachedStyle();
459        } finally {
460            writeUnlock(locked);
461        }
462    }
463
464    @Override
465    public void setDeleted(boolean deleted) {
466        checkDatasetNotReadOnly();
467        boolean locked = writeLock();
468        try {
469            super.setDeleted(deleted);
470            if (dataSet != null) {
471                if (deleted) {
472                    dataSet.firePrimitivesRemoved(Collections.singleton(this), false);
473                } else {
474                    dataSet.firePrimitivesAdded(Collections.singleton(this), false);
475                }
476            }
477            clearCachedStyle();
478        } finally {
479            writeUnlock(locked);
480        }
481    }
482
483    @Override
484    protected final void setIncomplete(boolean incomplete) {
485        checkDatasetNotReadOnly();
486        boolean locked = writeLock();
487        try {
488            if (dataSet != null && incomplete != this.isIncomplete()) {
489                if (incomplete) {
490                    dataSet.firePrimitivesRemoved(Collections.singletonList(this), true);
491                } else {
492                    dataSet.firePrimitivesAdded(Collections.singletonList(this), true);
493                }
494            }
495            super.setIncomplete(incomplete);
496        } finally {
497            writeUnlock(locked);
498        }
499    }
500
501    @Override
502    public boolean isSelected() {
503        return dataSet != null && dataSet.isSelected(this);
504    }
505
506    @Override
507    public boolean isMemberOfSelected() {
508        if (referrers == null)
509            return false;
510        if (referrers instanceof OsmPrimitive)
511            return referrers instanceof Relation && ((OsmPrimitive) referrers).isSelected();
512        for (OsmPrimitive ref : (OsmPrimitive[]) referrers) {
513            if (ref instanceof Relation && ref.isSelected())
514                return true;
515        }
516        return false;
517    }
518
519    @Override
520    public boolean isOuterMemberOfSelected() {
521        if (referrers == null)
522            return false;
523        if (referrers instanceof OsmPrimitive) {
524            return isOuterMemberOfMultipolygon((OsmPrimitive) referrers);
525        }
526        for (OsmPrimitive ref : (OsmPrimitive[]) referrers) {
527            if (isOuterMemberOfMultipolygon(ref))
528                return true;
529        }
530        return false;
531    }
532
533    private boolean isOuterMemberOfMultipolygon(OsmPrimitive ref) {
534        if (ref instanceof Relation && ref.isSelected() && ((Relation) ref).isMultipolygon()) {
535            for (RelationMember rm : ((Relation) ref).getMembersFor(Collections.singleton(this))) {
536                if ("outer".equals(rm.getRole())) {
537                    return true;
538                }
539            }
540        }
541        return false;
542    }
543
544    @Override
545    public void setHighlighted(boolean highlighted) {
546        if (isHighlighted() != highlighted) {
547            updateFlags(FLAG_HIGHLIGHTED, highlighted);
548            if (dataSet != null) {
549                dataSet.fireHighlightingChanged();
550            }
551        }
552    }
553
554    @Override
555    public boolean isHighlighted() {
556        return (flags & FLAG_HIGHLIGHTED) != 0;
557    }
558
559    /*---------------
560     * DIRECTION KEYS
561     *---------------*/
562
563    private static Match compileDirectionKeys(String prefName, String defaultValue) throws AssertionError {
564        try {
565            return SearchCompiler.compile(Config.getPref().get(prefName, defaultValue));
566        } catch (SearchParseError e) {
567            Logging.log(Logging.LEVEL_ERROR, "Unable to compile pattern for " + prefName + ", trying default pattern:", e);
568        }
569
570        try {
571            return SearchCompiler.compile(defaultValue);
572        } catch (SearchParseError e2) {
573            throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage(), e2);
574        }
575    }
576
577    private void updateTagged() {
578        for (String key: keySet()) {
579            // 'area' is not really uninteresting (putting it in that list may have unpredictable side effects)
580            // but it's clearly not enough to consider an object as tagged (see #9261)
581            if (!isUninterestingKey(key) && !"area".equals(key)) {
582                updateFlagsNoLock(FLAG_TAGGED, true);
583                return;
584            }
585        }
586        updateFlagsNoLock(FLAG_TAGGED, false);
587    }
588
589    private void updateAnnotated() {
590        for (String key: keySet()) {
591            if (getWorkInProgressKeys().contains(key)) {
592                updateFlagsNoLock(FLAG_ANNOTATED, true);
593                return;
594            }
595        }
596        updateFlagsNoLock(FLAG_ANNOTATED, false);
597    }
598
599    @Override
600    public boolean isTagged() {
601        return (flags & FLAG_TAGGED) != 0;
602    }
603
604    @Override
605    public boolean isAnnotated() {
606        return (flags & FLAG_ANNOTATED) != 0;
607    }
608
609    private void updateDirectionFlags() {
610        boolean hasDirections = false;
611        boolean directionReversed = false;
612        if (reversedDirectionKeys.match(this)) {
613            hasDirections = true;
614            directionReversed = true;
615        }
616        if (directionKeys.match(this)) {
617            hasDirections = true;
618        }
619
620        updateFlagsNoLock(FLAG_DIRECTION_REVERSED, directionReversed);
621        updateFlagsNoLock(FLAG_HAS_DIRECTIONS, hasDirections);
622    }
623
624    @Override
625    public boolean hasDirectionKeys() {
626        return (flags & FLAG_HAS_DIRECTIONS) != 0;
627    }
628
629    @Override
630    public boolean reversedDirection() {
631        return (flags & FLAG_DIRECTION_REVERSED) != 0;
632    }
633
634    /*------------
635     * Keys handling
636     ------------*/
637
638    @Override
639    public final void setKeys(TagMap keys) {
640        checkDatasetNotReadOnly();
641        boolean locked = writeLock();
642        try {
643            super.setKeys(keys);
644        } finally {
645            writeUnlock(locked);
646        }
647    }
648
649    @Override
650    public final void setKeys(Map<String, String> keys) {
651        checkDatasetNotReadOnly();
652        boolean locked = writeLock();
653        try {
654            super.setKeys(keys);
655        } finally {
656            writeUnlock(locked);
657        }
658    }
659
660    @Override
661    public final void put(String key, String value) {
662        checkDatasetNotReadOnly();
663        boolean locked = writeLock();
664        try {
665            super.put(key, value);
666        } finally {
667            writeUnlock(locked);
668        }
669    }
670
671    @Override
672    public final void remove(String key) {
673        checkDatasetNotReadOnly();
674        boolean locked = writeLock();
675        try {
676            super.remove(key);
677        } finally {
678            writeUnlock(locked);
679        }
680    }
681
682    @Override
683    public final void removeAll() {
684        checkDatasetNotReadOnly();
685        boolean locked = writeLock();
686        try {
687            super.removeAll();
688        } finally {
689            writeUnlock(locked);
690        }
691    }
692
693    @Override
694    protected void keysChangedImpl(Map<String, String> originalKeys) {
695        clearCachedStyle();
696        if (dataSet != null) {
697            for (OsmPrimitive ref : getReferrers()) {
698                ref.clearCachedStyle();
699            }
700        }
701        updateDirectionFlags();
702        updateTagged();
703        updateAnnotated();
704        if (dataSet != null) {
705            dataSet.fireTagsChanged(this, originalKeys);
706        }
707    }
708
709    /*------------
710     * Referrers
711     ------------*/
712
713    private Object referrers;
714
715    /**
716     * Add new referrer. If referrer is already included then no action is taken
717     * @param referrer The referrer to add
718     */
719    protected void addReferrer(OsmPrimitive referrer) {
720        checkDatasetNotReadOnly();
721        if (referrers == null) {
722            referrers = referrer;
723        } else if (referrers instanceof OsmPrimitive) {
724            if (referrers != referrer) {
725                referrers = new OsmPrimitive[] {(OsmPrimitive) referrers, referrer};
726            }
727        } else {
728            for (OsmPrimitive primitive:(OsmPrimitive[]) referrers) {
729                if (primitive == referrer)
730                    return;
731            }
732            referrers = Utils.addInArrayCopy((OsmPrimitive[]) referrers, referrer);
733        }
734    }
735
736    /**
737     * Remove referrer. No action is taken if referrer is not registered
738     * @param referrer The referrer to remove
739     */
740    protected void removeReferrer(OsmPrimitive referrer) {
741        checkDatasetNotReadOnly();
742        if (referrers instanceof OsmPrimitive) {
743            if (referrers == referrer) {
744                referrers = null;
745            }
746        } else if (referrers instanceof OsmPrimitive[]) {
747            OsmPrimitive[] orig = (OsmPrimitive[]) referrers;
748            int idx = -1;
749            for (int i = 0; i < orig.length; i++) {
750                if (orig[i] == referrer) {
751                    idx = i;
752                    break;
753                }
754            }
755            if (idx == -1)
756                return;
757
758            if (orig.length == 2) {
759                referrers = orig[1-idx]; // idx is either 0 or 1, take the other
760            } else { // downsize the array
761                OsmPrimitive[] smaller = new OsmPrimitive[orig.length-1];
762                System.arraycopy(orig, 0, smaller, 0, idx);
763                System.arraycopy(orig, idx+1, smaller, idx, smaller.length-idx);
764                referrers = smaller;
765            }
766        }
767    }
768
769    private <T extends OsmPrimitive> Stream<T> referrers(boolean allowWithoutDataset, Class<T> filter) {
770        // Returns only referrers that are members of the same dataset (primitive can have some fake references, for example
771        // when way is cloned
772
773        if (dataSet == null && allowWithoutDataset) {
774            return Stream.empty();
775        }
776        checkDataset();
777        if (referrers == null) {
778            return Stream.empty();
779        }
780        final Stream<OsmPrimitive> stream = referrers instanceof OsmPrimitive // NOPMD
781                ? Stream.of((OsmPrimitive) referrers)
782                : Arrays.stream((OsmPrimitive[]) referrers);
783        return stream
784                .filter(p -> p.dataSet == dataSet)
785                .filter(filter::isInstance)
786                .map(filter::cast);
787    }
788
789    /**
790     * Gets all primitives in the current dataset that reference this primitive.
791     * @param filter restrict primitives to subclasses
792     * @param <T> type of primitives
793     * @return the referrers as Stream
794     * @since 14654
795     */
796    public final <T extends OsmPrimitive> Stream<T> referrers(Class<T> filter) {
797        return referrers(false, filter);
798    }
799
800    @Override
801    public final List<OsmPrimitive> getReferrers(boolean allowWithoutDataset) {
802        return referrers(allowWithoutDataset, OsmPrimitive.class)
803                .collect(Collectors.toList());
804    }
805
806    @Override
807    public final List<OsmPrimitive> getReferrers() {
808        return getReferrers(false);
809    }
810
811    /**
812     * <p>Visits {@code visitor} for all referrers.</p>
813     *
814     * @param visitor the visitor. Ignored, if null.
815     * @since 12809
816     */
817    public void visitReferrers(OsmPrimitiveVisitor visitor) {
818        if (visitor != null)
819            doVisitReferrers(o -> o.accept(visitor));
820    }
821
822    @Override
823    public void visitReferrers(PrimitiveVisitor visitor) {
824        if (visitor != null)
825            doVisitReferrers(o -> o.accept(visitor));
826    }
827
828    private void doVisitReferrers(Consumer<OsmPrimitive> visitor) {
829        if (this.referrers == null)
830            return;
831        else if (this.referrers instanceof OsmPrimitive) {
832            OsmPrimitive ref = (OsmPrimitive) this.referrers;
833            if (ref.dataSet == dataSet) {
834                visitor.accept(ref);
835            }
836        } else if (this.referrers instanceof OsmPrimitive[]) {
837            OsmPrimitive[] refs = (OsmPrimitive[]) this.referrers;
838            for (OsmPrimitive ref: refs) {
839                if (ref.dataSet == dataSet) {
840                    visitor.accept(ref);
841                }
842            }
843        }
844    }
845
846    /**
847     * Return true, if this primitive is a node referred by at least n ways
848     * @param n Minimal number of ways to return true. Must be positive
849     * @return {@code true} if this primitive is referred by at least n ways
850     */
851    protected final boolean isNodeReferredByWays(int n) {
852        // Count only referrers that are members of the same dataset (primitive can have some fake references, for example
853        // when way is cloned
854        if (referrers == null) return false;
855        checkDataset();
856        if (referrers instanceof OsmPrimitive)
857            return n <= 1 && referrers instanceof Way && ((OsmPrimitive) referrers).dataSet == dataSet;
858        else {
859            int counter = 0;
860            for (OsmPrimitive o : (OsmPrimitive[]) referrers) {
861                if (dataSet == o.dataSet && o instanceof Way && ++counter >= n)
862                    return true;
863            }
864            return false;
865        }
866    }
867
868    /*-----------------
869     * OTHER METHODS
870     *----------------*/
871
872    /**
873     * Implementation of the visitor scheme. Subclasses have to call the correct
874     * visitor function.
875     * @param visitor The visitor from which the visit() function must be called.
876     * @since 12809
877     */
878    public abstract void accept(OsmPrimitiveVisitor visitor);
879
880    /**
881     * Get and write all attributes from the parameter. Does not fire any listener, so
882     * use this only in the data initializing phase
883     * @param other other primitive
884     */
885    public void cloneFrom(OsmPrimitive other) {
886        // write lock is provided by subclasses
887        if (id != other.id && dataSet != null)
888            throw new DataIntegrityProblemException("Osm id cannot be changed after primitive was added to the dataset");
889
890        super.cloneFrom(other);
891        clearCachedStyle();
892    }
893
894    /**
895     * Merges the technical and semantical attributes from <code>other</code> onto this.
896     *
897     * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
898     * have an assigned OSM id, the IDs have to be the same.
899     *
900     * @param other the other primitive. Must not be null.
901     * @throws IllegalArgumentException if other is null.
902     * @throws DataIntegrityProblemException if either this is new and other is not, or other is new and this is not
903     * @throws DataIntegrityProblemException if other isn't new and other.getId() != this.getId()
904     */
905    public void mergeFrom(OsmPrimitive other) {
906        checkDatasetNotReadOnly();
907        boolean locked = writeLock();
908        try {
909            CheckParameterUtil.ensureParameterNotNull(other, "other");
910            if (other.isNew() ^ isNew())
911                throw new DataIntegrityProblemException(
912                        tr("Cannot merge because either of the participating primitives is new and the other is not"));
913            if (!other.isNew() && other.getId() != id)
914                throw new DataIntegrityProblemException(
915                        tr("Cannot merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId()));
916
917            setKeys(other.hasKeys() ? other.getKeys() : null);
918            timestamp = other.timestamp;
919            version = other.version;
920            setIncomplete(other.isIncomplete());
921            flags = other.flags;
922            user = other.user;
923            changesetId = other.changesetId;
924        } finally {
925            writeUnlock(locked);
926        }
927    }
928
929    /**
930     * Replies true if this primitive and other are equal with respect to their semantic attributes.
931     * <ol>
932     *   <li>equal id</li>
933     *   <li>both are complete or both are incomplete</li>
934     *   <li>both have the same tags</li>
935     * </ol>
936     * @param other other primitive to compare
937     * @return true if this primitive and other are equal with respect to their semantic attributes.
938     */
939    public final boolean hasEqualSemanticAttributes(OsmPrimitive other) {
940        return hasEqualSemanticAttributes(other, true);
941    }
942
943    boolean hasEqualSemanticFlags(final OsmPrimitive other) {
944        if (!isNew() && id != other.id)
945            return false;
946        return !(isIncomplete() ^ other.isIncomplete()); // exclusive or operator for performance (see #7159)
947    }
948
949    boolean hasEqualSemanticAttributes(final OsmPrimitive other, final boolean testInterestingTagsOnly) {
950        return hasEqualSemanticFlags(other)
951                && (testInterestingTagsOnly ? hasSameInterestingTags(other) : getKeys().equals(other.getKeys()));
952    }
953
954    /**
955     * Replies true if this primitive and other are equal with respect to their technical attributes.
956     * The attributes:
957     * <ol>
958     *   <li>deleted</li>
959     *   <li>modified</li>
960     *   <li>timestamp</li>
961     *   <li>version</li>
962     *   <li>visible</li>
963     *   <li>user</li>
964     * </ol>
965     * have to be equal
966     * @param other the other primitive
967     * @return true if this primitive and other are equal with respect to their technical attributes
968     */
969    public boolean hasEqualTechnicalAttributes(OsmPrimitive other) {
970        // CHECKSTYLE.OFF: BooleanExpressionComplexity
971        return other != null
972            && timestamp == other.timestamp
973            && version == other.version
974            && changesetId == other.changesetId
975            && isDeleted() == other.isDeleted()
976            && isModified() == other.isModified()
977            && isVisible() == other.isVisible()
978            && Objects.equals(user, other.user);
979        // CHECKSTYLE.ON: BooleanExpressionComplexity
980    }
981
982    /**
983     * Loads (clone) this primitive from provided PrimitiveData
984     * @param data The object which should be cloned
985     */
986    public void load(PrimitiveData data) {
987        checkDatasetNotReadOnly();
988        // Write lock is provided by subclasses
989        setKeys(data.hasKeys() ? data.getKeys() : null);
990        setRawTimestamp(data.getRawTimestamp());
991        user = data.getUser();
992        setChangesetId(data.getChangesetId());
993        setDeleted(data.isDeleted());
994        setModified(data.isModified());
995        setVisible(data.isVisible());
996        setIncomplete(data.isIncomplete());
997        version = data.getVersion();
998    }
999
1000    /**
1001     * Save parameters of this primitive to the transport object
1002     * @return The saved object data
1003     */
1004    public abstract PrimitiveData save();
1005
1006    /**
1007     * Save common parameters of primitives to the transport object
1008     * @param data The object to save the data into
1009     */
1010    protected void saveCommonAttributes(PrimitiveData data) {
1011        data.setId(id);
1012        data.setKeys(hasKeys() ? getKeys() : null);
1013        data.setRawTimestamp(getRawTimestamp());
1014        data.setUser(user);
1015        data.setDeleted(isDeleted());
1016        data.setModified(isModified());
1017        data.setVisible(isVisible());
1018        data.setIncomplete(isIncomplete());
1019        data.setChangesetId(changesetId);
1020        data.setVersion(version);
1021    }
1022
1023    /**
1024     * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...)
1025     */
1026    public abstract void updatePosition();
1027
1028    /*----------------
1029     * OBJECT METHODS
1030     *---------------*/
1031
1032    @Override
1033    protected String getFlagsAsString() {
1034        StringBuilder builder = new StringBuilder(super.getFlagsAsString());
1035
1036        if (isDisabled()) {
1037            if (isDisabledAndHidden()) {
1038                builder.append('h');
1039            } else {
1040                builder.append('d');
1041            }
1042        }
1043        if (isTagged()) {
1044            builder.append('T');
1045        }
1046        if (hasDirectionKeys()) {
1047            if (reversedDirection()) {
1048                builder.append('<');
1049            } else {
1050                builder.append('>');
1051            }
1052        }
1053        return builder.toString();
1054    }
1055
1056    /**
1057     * Equal, if the id (and class) is equal.
1058     *
1059     * An primitive is equal to its incomplete counter part.
1060     */
1061    @Override
1062    public boolean equals(Object obj) {
1063        if (this == obj) {
1064            return true;
1065        } else if (obj == null || getClass() != obj.getClass()) {
1066            return false;
1067        } else {
1068            OsmPrimitive that = (OsmPrimitive) obj;
1069            return id == that.id;
1070        }
1071    }
1072
1073    /**
1074     * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0.
1075     *
1076     * An primitive has the same hashcode as its incomplete counterpart.
1077     */
1078    @Override
1079    public int hashCode() {
1080        return Long.hashCode(id);
1081    }
1082
1083    @Override
1084    public Collection<String> getTemplateKeys() {
1085        Collection<String> keySet = keySet();
1086        List<String> result = new ArrayList<>(keySet.size() + 2);
1087        result.add(SPECIAL_VALUE_ID);
1088        result.add(SPECIAL_VALUE_LOCAL_NAME);
1089        result.addAll(keySet);
1090        return result;
1091    }
1092
1093    @Override
1094    public Object getTemplateValue(String name, boolean special) {
1095        if (special) {
1096            String lc = name.toLowerCase(Locale.ENGLISH);
1097            if (SPECIAL_VALUE_ID.equals(lc))
1098                return getId();
1099            else if (SPECIAL_VALUE_LOCAL_NAME.equals(lc))
1100                return getLocalName();
1101            else
1102                return null;
1103
1104        } else
1105            return getIgnoreCase(name);
1106    }
1107
1108    @Override
1109    public boolean evaluateCondition(Match condition) {
1110        return condition.match(this);
1111    }
1112
1113    /**
1114     * Replies the set of referring relations
1115     * @param primitives primitives to fetch relations from
1116     *
1117     * @return the set of referring relations
1118     */
1119    public static Set<Relation> getParentRelations(Collection<? extends OsmPrimitive> primitives) {
1120        return primitives.stream()
1121                .flatMap(p -> p.referrers(Relation.class))
1122                .collect(Collectors.toSet());
1123    }
1124
1125    /**
1126     * Determines if this primitive has tags denoting an area.
1127     * @return {@code true} if this primitive has tags denoting an area, {@code false} otherwise.
1128     * @since 6491
1129     */
1130    public final boolean hasAreaTags() {
1131        return hasKey("landuse", "amenity", "building", "building:part")
1132                || hasTag("area", OsmUtils.TRUE_VALUE)
1133                || hasTag("waterway", "riverbank")
1134                || hasTagDifferent("leisure", "picnic_table", "slipway", "firepit")
1135                || hasTag("natural", "water", "wood", "scrub", "wetland", "grassland", "heath", "rock", "bare_rock",
1136                                     "sand", "beach", "scree", "bay", "glacier", "shingle", "fell", "reef", "stone",
1137                                     "mud", "landslide", "sinkhole", "crevasse", "desert");
1138    }
1139
1140    /**
1141     * Determines if this primitive semantically concerns an area.
1142     * @return {@code true} if this primitive semantically concerns an area, according to its type, geometry and tags, {@code false} otherwise.
1143     * @since 6491
1144     */
1145    public abstract boolean concernsArea();
1146
1147    /**
1148     * Tests if this primitive lies outside of the downloaded area of its {@link DataSet}.
1149     * @return {@code true} if this primitive lies outside of the downloaded area
1150     */
1151    public abstract boolean isOutsideDownloadArea();
1152
1153    /**
1154     * If necessary, extend the bbox to contain this primitive
1155     * @param box a bbox instance
1156     * @param visited a set of visited members  or null
1157     * @since 11269
1158     */
1159    protected abstract void addToBBox(BBox box, Set<PrimitiveId> visited);
1160}