source: josm/trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java

Last change on this file was 18516, checked in by taylor.smock, 7 months ago

Move OsmPrimitive isDrawable to AbstractPrimitive

This should have no effect on binary compatibility (according to
japi-compliance-checker), and allows filters to work properly on
non-OsmPrimitive objects (like vector primitives).

  • Property svn:eol-style set to native
File size: 36.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.text.MessageFormat;
7import java.util.Arrays;
8import java.util.Collection;
9import java.util.Collections;
10import java.util.Date;
11import java.util.List;
12import java.util.Locale;
13import java.util.Map;
14import java.util.Objects;
15import java.util.Set;
16import java.util.function.Consumer;
17import java.util.stream.Collectors;
18import java.util.stream.IntStream;
19import java.util.stream.Stream;
20
21import org.openstreetmap.josm.data.osm.search.SearchCompiler;
22import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match;
23import org.openstreetmap.josm.data.osm.search.SearchParseError;
24import org.openstreetmap.josm.data.osm.visitor.OsmPrimitiveVisitor;
25import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
26import org.openstreetmap.josm.gui.mappaint.StyleCache;
27import org.openstreetmap.josm.spi.preferences.Config;
28import org.openstreetmap.josm.tools.CheckParameterUtil;
29import org.openstreetmap.josm.tools.Logging;
30import org.openstreetmap.josm.tools.Utils;
31import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider;
32
33/**
34 * The base class for OSM objects ({@link Node}, {@link Way}, {@link Relation}).
35 *
36 * It can be created, deleted and uploaded to the OSM-Server.
37 *
38 * Although OsmPrimitive is designed as a base class, it is not to be meant to subclass
39 * it by any other than from the package {@link org.openstreetmap.josm.data.osm}. The available primitives are a fixed
40 * set that are given by the server environment and not an extendable data stuff.
41 *
42 * @author imi
43 */
44public abstract class OsmPrimitive extends AbstractPrimitive implements TemplateEngineDataProvider {
45    private static final String SPECIAL_VALUE_ID = "id";
46    private static final String SPECIAL_VALUE_LOCAL_NAME = "localname";
47
48    /**
49     * A tagged way that matches this pattern has a direction.
50     * @see #FLAG_HAS_DIRECTIONS
51     */
52    static volatile Match directionKeys;
53
54    /**
55     * A tagged way that matches this pattern has a direction that is reversed.
56     * <p>
57     * This pattern should be a subset of {@link #directionKeys}
58     * @see #FLAG_DIRECTION_REVERSED
59     */
60    private static final Match reversedDirectionKeys;
61
62    static {
63        String reversedDirectionDefault = "oneway=\"-1\"";
64
65        String directionDefault = "oneway? | "+
66                "(aerialway=chair_lift & -oneway=no) | "+
67                "(aerialway=rope_tow & -oneway=no) | "+
68                "(aerialway=magic_carpet & -oneway=no) | "+
69                "(aerialway=zip_line & -oneway=no) | "+
70                "(aerialway=drag_lift & -oneway=no) | "+
71                "(aerialway=t-bar & -oneway=no) | "+
72                "(aerialway=j-bar & -oneway=no) | "+
73                "(aerialway=platter & -oneway=no) | "+
74                "waterway=stream | waterway=river | waterway=ditch | waterway=drain | waterway=tidal_channel | "+
75                "(\"piste:type\"=downhill & -area=yes) | (\"piste:type\"=sled & -area=yes) | (man_made=\"piste:halfpipe\" & -area=yes) | "+
76                "(junction=circular & -oneway=no) | junction=roundabout | (highway=motorway & -oneway=no & -oneway=reversible) | "+
77                "(highway=motorway_link & -oneway=no & -oneway=reversible)";
78
79        reversedDirectionKeys = compileDirectionKeys("tags.reversed_direction", reversedDirectionDefault);
80        directionKeys = compileDirectionKeys("tags.direction", directionDefault);
81    }
82
83    /**
84     * Replies the collection of referring primitives for the primitives in <code>primitives</code>.
85     *
86     * @param primitives the collection of primitives.
87     * @return the collection of referring primitives for the primitives in <code>primitives</code>;
88     * empty set if primitives is null or if there are no referring primitives
89     */
90    public static Set<OsmPrimitive> getReferrer(Collection<? extends OsmPrimitive> primitives) {
91        return (primitives != null ? primitives.stream() : Stream.<OsmPrimitive>empty())
92                .flatMap(p -> p.referrers(OsmPrimitive.class))
93                .collect(Collectors.toSet());
94    }
95
96    /**
97     * Creates a new primitive for the given id.
98     *
99     * 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 = getIdGenerator().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
149    @Override
150    public final StyleCache getCachedStyle() {
151        return mappaintStyle;
152    }
153
154    @Override
155    public final void setCachedStyle(StyleCache mappaintStyle) {
156        this.mappaintStyle = mappaintStyle;
157    }
158
159    @Override
160    public final boolean isCachedStyleUpToDate() {
161        return mappaintStyle != null && mappaintCacheIdx == dataSet.getMappaintCacheIndex();
162    }
163
164    @Override
165    public final void declareCachedStyleUpToDate() {
166        this.mappaintCacheIdx = dataSet.getMappaintCacheIndex();
167    }
168
169    /* end of mappaint data */
170
171    /*---------
172     * DATASET
173     *---------*/
174
175    /** the parent dataset */
176    private DataSet dataSet;
177
178    /**
179     * This method should never ever by called from somewhere else than Dataset.addPrimitive or removePrimitive methods
180     * @param dataSet the parent dataset
181     */
182    void setDataset(DataSet dataSet) {
183        if (this.dataSet != null && dataSet != null && this.dataSet != dataSet)
184            throw new DataIntegrityProblemException("Primitive cannot be included in more than one Dataset");
185        this.dataSet = dataSet;
186    }
187
188    @Override
189    public DataSet getDataSet() {
190        return dataSet;
191    }
192
193    /**
194     * Throws exception if primitive is not part of the dataset
195     */
196    public void checkDataset() {
197        if (dataSet == null)
198            throw new DataIntegrityProblemException("Primitive must be part of the dataset: " + toString());
199    }
200
201    /**
202     * Throws exception if primitive is in a read-only dataset
203     */
204    protected final void checkDatasetNotReadOnly() {
205        if (dataSet != null && dataSet.isLocked())
206            throw new DataIntegrityProblemException("Primitive cannot be modified in read-only dataset: " + toString());
207    }
208
209    protected boolean writeLock() {
210        if (dataSet != null) {
211            dataSet.beginUpdate();
212            return true;
213        } else
214            return false;
215    }
216
217    protected void writeUnlock(boolean locked) {
218        if (locked && dataSet != null) {
219            // It shouldn't be possible for dataset to become null because
220            // method calling setDataset would need write lock which is owned by this thread
221            dataSet.endUpdate();
222        }
223    }
224
225    /**
226     * Sets the id and the version of this primitive if it is known to the OSM API.
227     *
228     * Since we know the id and its version it can't be incomplete anymore. incomplete
229     * is set to false.
230     *
231     * @param id the id. &gt; 0 required
232     * @param version the version &gt; 0 required
233     * @throws IllegalArgumentException if id &lt;= 0
234     * @throws IllegalArgumentException if version &lt;= 0
235     * @throws DataIntegrityProblemException if id is changed and primitive was already added to the dataset
236     */
237    @Override
238    public void setOsmId(long id, int version) {
239        checkDatasetNotReadOnly();
240        boolean locked = writeLock();
241        try {
242            if (id <= 0)
243                throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
244            if (version <= 0)
245                throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
246            if (dataSet != null && id != this.id) {
247                DataSet datasetCopy = dataSet;
248                // Reindex primitive
249                datasetCopy.removePrimitive(this);
250                this.id = id;
251                datasetCopy.addPrimitive(this);
252            }
253            super.setOsmId(id, version);
254        } finally {
255            writeUnlock(locked);
256        }
257    }
258
259    /**
260     * Clears the metadata, including id and version known to the OSM API.
261     * The id is a new unique id. The version, changeset and timestamp are set to 0.
262     * incomplete and deleted are set to false. It's preferred to use copy constructor with clearMetadata set to true instead
263     *
264     * <strong>Caution</strong>: Do not use this method on primitives which are already added to a {@link DataSet}.
265     *
266     * @throws DataIntegrityProblemException If primitive was already added to the dataset
267     * @since 6140
268     */
269    @Override
270    public void clearOsmMetadata() {
271        if (dataSet != null)
272            throw new DataIntegrityProblemException("Method cannot be called after primitive was added to the dataset");
273        super.clearOsmMetadata();
274    }
275
276    @Override
277    public void setUser(User user) {
278        checkDatasetNotReadOnly();
279        boolean locked = writeLock();
280        try {
281            super.setUser(user);
282        } finally {
283            writeUnlock(locked);
284        }
285    }
286
287    @Override
288    public void setChangesetId(int changesetId) {
289        checkDatasetNotReadOnly();
290        boolean locked = writeLock();
291        try {
292            int old = this.changesetId;
293            super.setChangesetId(changesetId);
294            if (dataSet != null) {
295                dataSet.fireChangesetIdChanged(this, old, changesetId);
296            }
297        } finally {
298            writeUnlock(locked);
299        }
300    }
301
302    @Deprecated
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    @Override
333    public boolean setDisabledState(boolean hidden) {
334        boolean locked = writeLock();
335        try {
336            return super.setDisabledState(hidden);
337        } finally {
338            writeUnlock(locked);
339        }
340    }
341
342    /**
343     * Remove the disabled flag from the primitive.
344     * Afterwards, the primitive is displayed normally and can be selected again.
345     * @return {@code true} if a change occurred
346     */
347    @Override
348    public boolean unsetDisabledState() {
349        boolean locked = writeLock();
350        try {
351            return super.unsetDisabledState();
352        } finally {
353            writeUnlock(locked);
354        }
355    }
356
357    /**
358     * Set binary property used internally by the filter mechanism.
359     * @param isPreserved new "preserved" flag value
360     * @since 13309
361     */
362    public void setPreserved(boolean isPreserved) {
363        updateFlags(FLAG_PRESERVED, isPreserved);
364    }
365
366    @Override
367    public boolean isDisabled() {
368        return (flags & FLAG_DISABLED) != 0;
369    }
370
371    @Override
372    public boolean isDisabledAndHidden() {
373        return ((flags & FLAG_DISABLED) != 0) && ((flags & FLAG_HIDE_IF_DISABLED) != 0);
374    }
375
376    @Override
377    public boolean isPreserved() {
378        return (flags & FLAG_PRESERVED) != 0;
379    }
380
381    @Override
382    public boolean isSelectable() {
383        // not synchronized -> check disabled twice just to be sure we did not have a race condition.
384        return !isDisabled() && isDrawable() && !isDisabled();
385    }
386
387    @Override
388    public void setModified(boolean modified) {
389        checkDatasetNotReadOnly();
390        boolean locked = writeLock();
391        try {
392            super.setModified(modified);
393            if (dataSet != null) {
394                dataSet.firePrimitiveFlagsChanged(this);
395            }
396            clearCachedStyle();
397        } finally {
398            writeUnlock(locked);
399        }
400    }
401
402    @Override
403    public void setVisible(boolean visible) {
404        checkDatasetNotReadOnly();
405        boolean locked = writeLock();
406        try {
407            super.setVisible(visible);
408            clearCachedStyle();
409        } finally {
410            writeUnlock(locked);
411        }
412    }
413
414    @Override
415    public void setDeleted(boolean deleted) {
416        checkDatasetNotReadOnly();
417        boolean locked = writeLock();
418        try {
419            super.setDeleted(deleted);
420            if (dataSet != null) {
421                if (deleted) {
422                    dataSet.firePrimitivesRemoved(Collections.singleton(this), false);
423                } else {
424                    dataSet.firePrimitivesAdded(Collections.singleton(this), false);
425                }
426            }
427            clearCachedStyle();
428        } finally {
429            writeUnlock(locked);
430        }
431    }
432
433    @Override
434    protected final void setIncomplete(boolean incomplete) {
435        checkDatasetNotReadOnly();
436        boolean locked = writeLock();
437        try {
438            if (dataSet != null && incomplete != this.isIncomplete()) {
439                if (incomplete) {
440                    dataSet.firePrimitivesRemoved(Collections.singletonList(this), true);
441                } else {
442                    dataSet.firePrimitivesAdded(Collections.singletonList(this), true);
443                }
444            }
445            super.setIncomplete(incomplete);
446        } finally {
447            writeUnlock(locked);
448        }
449    }
450
451    @Override
452    public boolean isSelected() {
453        return dataSet != null && dataSet.isSelected(this);
454    }
455
456    @Override
457    public boolean isMemberOfSelected() {
458        if (referrers == null)
459            return false;
460        if (referrers instanceof OsmPrimitive)
461            return referrers instanceof Relation && ((OsmPrimitive) referrers).isSelected();
462        return Arrays.stream((OsmPrimitive[]) referrers)
463                .anyMatch(ref -> ref instanceof Relation && ref.isSelected());
464    }
465
466    @Override
467    public boolean isOuterMemberOfSelected() {
468        if (referrers == null)
469            return false;
470        if (referrers instanceof OsmPrimitive) {
471            return isOuterMemberOfMultipolygon((OsmPrimitive) referrers);
472        }
473        return Arrays.stream((OsmPrimitive[]) referrers)
474                .anyMatch(this::isOuterMemberOfMultipolygon);
475    }
476
477    private boolean isOuterMemberOfMultipolygon(OsmPrimitive ref) {
478        return ref instanceof Relation
479                && ref.isSelected()
480                && ((Relation) ref).isMultipolygon()
481                && ((Relation) ref).getMembersFor(Collections.singleton(this)).stream()
482                    .anyMatch(rm -> "outer".equals(rm.getRole()));
483    }
484
485    @Override
486    public void setHighlighted(boolean highlighted) {
487        if (isHighlighted() != highlighted) {
488            updateFlags(FLAG_HIGHLIGHTED, highlighted);
489            if (dataSet != null) {
490                dataSet.fireHighlightingChanged();
491            }
492        }
493    }
494
495    @Override
496    public boolean isHighlighted() {
497        return (flags & FLAG_HIGHLIGHTED) != 0;
498    }
499
500    /*---------------
501     * DIRECTION KEYS
502     *---------------*/
503
504    private static Match compileDirectionKeys(String prefName, String defaultValue) throws AssertionError {
505        try {
506            return SearchCompiler.compile(Config.getPref().get(prefName, defaultValue));
507        } catch (SearchParseError e) {
508            Logging.log(Logging.LEVEL_ERROR, "Unable to compile pattern for " + prefName + ", trying default pattern:", e);
509        }
510
511        try {
512            return SearchCompiler.compile(defaultValue);
513        } catch (SearchParseError e2) {
514            throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage(), e2);
515        }
516    }
517
518    private void updateTagged() {
519        // 'area' is not really uninteresting (putting it in that list may have unpredictable side effects)
520        // but it's clearly not enough to consider an object as tagged (see #9261)
521        updateFlagsNoLock(FLAG_TAGGED, hasKeys() && keys()
522                .anyMatch(key -> !isUninterestingKey(key) && !"area".equals(key)));
523    }
524
525    private void updateAnnotated() {
526        updateFlagsNoLock(FLAG_ANNOTATED, hasKeys() && getWorkInProgressKeys().stream().anyMatch(this::hasKey));
527    }
528
529    @Override
530    public boolean isTagged() {
531        return (flags & FLAG_TAGGED) != 0;
532    }
533
534    @Override
535    public boolean isAnnotated() {
536        return (flags & FLAG_ANNOTATED) != 0;
537    }
538
539    protected void updateDirectionFlags() {
540        boolean hasDirections = false;
541        boolean directionReversed = false;
542        if (reversedDirectionKeys.match(this)) {
543            hasDirections = true;
544            directionReversed = true;
545        }
546        if (directionKeys.match(this)) {
547            hasDirections = true;
548        }
549
550        updateFlagsNoLock(FLAG_DIRECTION_REVERSED, directionReversed);
551        updateFlagsNoLock(FLAG_HAS_DIRECTIONS, hasDirections);
552    }
553
554    @Override
555    public boolean hasDirectionKeys() {
556        return (flags & FLAG_HAS_DIRECTIONS) != 0;
557    }
558
559    @Override
560    public boolean reversedDirection() {
561        return (flags & FLAG_DIRECTION_REVERSED) != 0;
562    }
563
564    /*------------
565     * Keys handling
566     ------------*/
567
568    @Override
569    public final void setKeys(TagMap keys) {
570        checkDatasetNotReadOnly();
571        boolean locked = writeLock();
572        try {
573            super.setKeys(keys);
574        } finally {
575            writeUnlock(locked);
576        }
577    }
578
579    @Override
580    public final void setKeys(Map<String, String> keys) {
581        checkDatasetNotReadOnly();
582        boolean locked = writeLock();
583        try {
584            super.setKeys(keys);
585        } finally {
586            writeUnlock(locked);
587        }
588    }
589
590    @Override
591    public final void put(String key, String value) {
592        checkDatasetNotReadOnly();
593        boolean locked = writeLock();
594        try {
595            super.put(key, value);
596        } finally {
597            writeUnlock(locked);
598        }
599    }
600
601    @Override
602    public final void remove(String key) {
603        checkDatasetNotReadOnly();
604        boolean locked = writeLock();
605        try {
606            super.remove(key);
607        } finally {
608            writeUnlock(locked);
609        }
610    }
611
612    @Override
613    public final void removeAll() {
614        checkDatasetNotReadOnly();
615        boolean locked = writeLock();
616        try {
617            super.removeAll();
618        } finally {
619            writeUnlock(locked);
620        }
621    }
622
623    @Override
624    protected void keysChangedImpl(Map<String, String> originalKeys) {
625        clearCachedStyle();
626        if (dataSet != null) {
627            for (OsmPrimitive ref : getReferrers()) {
628                ref.clearCachedStyle();
629            }
630        }
631        updateDirectionFlags();
632        updateTagged();
633        updateAnnotated();
634        if (dataSet != null) {
635            dataSet.fireTagsChanged(this, originalKeys);
636        }
637    }
638
639    /*------------
640     * Referrers
641     ------------*/
642
643    private Object referrers;
644
645    /**
646     * Add new referrer. If referrer is already included then no action is taken
647     * @param referrer The referrer to add
648     */
649    protected void addReferrer(OsmPrimitive referrer) {
650        checkDatasetNotReadOnly();
651        if (referrers == null) {
652            referrers = referrer;
653        } else if (referrers instanceof OsmPrimitive) {
654            if (referrers != referrer) {
655                referrers = new OsmPrimitive[] {(OsmPrimitive) referrers, referrer};
656            }
657        } else {
658            for (OsmPrimitive primitive:(OsmPrimitive[]) referrers) {
659                if (primitive == referrer)
660                    return;
661            }
662            referrers = Utils.addInArrayCopy((OsmPrimitive[]) referrers, referrer);
663        }
664    }
665
666    /**
667     * Remove referrer. No action is taken if referrer is not registered
668     * @param referrer The referrer to remove
669     */
670    protected void removeReferrer(OsmPrimitive referrer) {
671        checkDatasetNotReadOnly();
672        if (referrers instanceof OsmPrimitive) {
673            if (referrers == referrer) {
674                referrers = null;
675            }
676        } else if (referrers instanceof OsmPrimitive[]) {
677            OsmPrimitive[] orig = (OsmPrimitive[]) referrers;
678            int idx = IntStream.range(0, orig.length)
679                    .filter(i -> orig[i] == referrer)
680                    .findFirst().orElse(-1);
681            if (idx == -1)
682                return;
683
684            if (orig.length == 2) {
685                referrers = orig[1-idx]; // idx is either 0 or 1, take the other
686            } else { // downsize the array
687                OsmPrimitive[] smaller = new OsmPrimitive[orig.length-1];
688                System.arraycopy(orig, 0, smaller, 0, idx);
689                System.arraycopy(orig, idx+1, smaller, idx, smaller.length-idx);
690                referrers = smaller;
691            }
692        }
693    }
694
695    private <T extends OsmPrimitive> Stream<T> referrers(boolean allowWithoutDataset, Class<T> filter) {
696        // Returns only referrers that are members of the same dataset (primitive can have some fake references, for example
697        // when way is cloned
698
699        if (dataSet == null && allowWithoutDataset) {
700            return Stream.empty();
701        }
702        checkDataset();
703        if (referrers == null) {
704            return Stream.empty();
705        }
706        final Stream<OsmPrimitive> stream = referrers instanceof OsmPrimitive // NOPMD
707                ? Stream.of((OsmPrimitive) referrers)
708                : Arrays.stream((OsmPrimitive[]) referrers);
709        return stream
710                .filter(p -> p.dataSet == dataSet)
711                .filter(filter::isInstance)
712                .map(filter::cast);
713    }
714
715    /**
716     * Gets all primitives in the current dataset that reference this primitive.
717     * @param filter restrict primitives to subclasses
718     * @param <T> type of primitives
719     * @return the referrers as Stream
720     * @since 14654
721     */
722    public final <T extends OsmPrimitive> Stream<T> referrers(Class<T> filter) {
723        return referrers(false, filter);
724    }
725
726    @Override
727    public final List<OsmPrimitive> getReferrers(boolean allowWithoutDataset) {
728        return referrers(allowWithoutDataset, OsmPrimitive.class)
729                .collect(Collectors.toList());
730    }
731
732    @Override
733    public final List<OsmPrimitive> getReferrers() {
734        return getReferrers(false);
735    }
736
737    /**
738     * <p>Visits {@code visitor} for all referrers.</p>
739     *
740     * @param visitor the visitor. Ignored, if null.
741     * @since 12809
742     */
743    public void visitReferrers(OsmPrimitiveVisitor visitor) {
744        if (visitor != null)
745            doVisitReferrers(o -> o.accept(visitor));
746    }
747
748    @Override
749    public void visitReferrers(PrimitiveVisitor visitor) {
750        if (visitor != null)
751            doVisitReferrers(o -> o.accept(visitor));
752    }
753
754    private void doVisitReferrers(Consumer<OsmPrimitive> visitor) {
755        if (this.referrers == null)
756            return;
757        else if (this.referrers instanceof OsmPrimitive) {
758            OsmPrimitive ref = (OsmPrimitive) this.referrers;
759            if (ref.dataSet == dataSet) {
760                visitor.accept(ref);
761            }
762        } else if (this.referrers instanceof OsmPrimitive[]) {
763            OsmPrimitive[] refs = (OsmPrimitive[]) this.referrers;
764            for (OsmPrimitive ref: refs) {
765                if (ref.dataSet == dataSet) {
766                    visitor.accept(ref);
767                }
768            }
769        }
770    }
771
772    /**
773     * Return true, if this primitive is a node referred by at least n ways
774     * @param n Minimal number of ways to return true. Must be positive
775     * @return {@code true} if this primitive is referred by at least n ways
776     */
777    protected final boolean isNodeReferredByWays(int n) {
778        // Count only referrers that are members of the same dataset (primitive can have some fake references, for example
779        // when way is cloned
780        if (referrers == null) return false;
781        checkDataset();
782        if (referrers instanceof OsmPrimitive)
783            return n <= 1 && referrers instanceof Way && ((OsmPrimitive) referrers).dataSet == dataSet;
784        else {
785            int counter = 0;
786            for (OsmPrimitive o : (OsmPrimitive[]) referrers) {
787                if (dataSet == o.dataSet && o instanceof Way && ++counter >= n)
788                    return true;
789            }
790            return false;
791        }
792    }
793
794    /*-----------------
795     * OTHER METHODS
796     *----------------*/
797
798    /**
799     * Implementation of the visitor scheme. Subclasses have to call the correct
800     * visitor function.
801     * @param visitor The visitor from which the visit() function must be called.
802     * @since 12809
803     */
804    public abstract void accept(OsmPrimitiveVisitor visitor);
805
806    /**
807     * Get and write all attributes from the parameter. Does not fire any listener, so
808     * use this only in the data initializing phase
809     * @param other other primitive
810     */
811    public final void cloneFrom(OsmPrimitive other) {
812        cloneFrom(other, true);
813    }
814
815    /**
816     * Get and write all attributes from the parameter. Does not fire any listener, so
817     * use this only in the data initializing phase
818     * @param other other primitive
819     * @param copyChildren whether to copy child primitives too
820     * @since 16212
821     */
822    protected void cloneFrom(OsmPrimitive other, boolean copyChildren) {
823        // write lock is provided by subclasses
824        if (id != other.id && dataSet != null)
825            throw new DataIntegrityProblemException("Osm id cannot be changed after primitive was added to the dataset");
826
827        super.cloneFrom(other);
828        clearCachedStyle();
829    }
830
831    /**
832     * Merges the technical and semantic attributes from <code>other</code> onto this.
833     *
834     * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
835     * have an assigned OSM id, the IDs have to be the same.
836     *
837     * @param other the other primitive. Must not be null.
838     * @throws IllegalArgumentException if other is null.
839     * @throws DataIntegrityProblemException if either this is new and other is not, or other is new and this is not
840     * @throws DataIntegrityProblemException if other isn't new and other.getId() != this.getId()
841     */
842    public void mergeFrom(OsmPrimitive other) {
843        checkDatasetNotReadOnly();
844        boolean locked = writeLock();
845        try {
846            CheckParameterUtil.ensureParameterNotNull(other, "other");
847            if (other.isNew() ^ isNew())
848                throw new DataIntegrityProblemException(
849                        tr("Cannot merge because either of the participating primitives is new and the other is not"));
850            if (!other.isNew() && other.getId() != id)
851                throw new DataIntegrityProblemException(
852                        tr("Cannot merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId()));
853
854            setKeys(other.hasKeys() ? other.getKeys() : null);
855            timestamp = other.timestamp;
856            version = other.version;
857            setIncomplete(other.isIncomplete());
858            flags = other.flags;
859            user = other.user;
860            changesetId = other.changesetId;
861        } finally {
862            writeUnlock(locked);
863        }
864    }
865
866    /**
867     * Replies true if this primitive and other are equal with respect to their semantic attributes.
868     * <ol>
869     *   <li>equal id</li>
870     *   <li>both are complete or both are incomplete</li>
871     *   <li>both have the same tags</li>
872     * </ol>
873     * @param other other primitive to compare
874     * @return true if this primitive and other are equal with respect to their semantic attributes.
875     */
876    public final boolean hasEqualSemanticAttributes(OsmPrimitive other) {
877        return hasEqualSemanticAttributes(other, false);
878    }
879
880    boolean hasEqualSemanticFlags(final OsmPrimitive other) {
881        if (!isNew() && id != other.id)
882            return false;
883        return !(isIncomplete() ^ other.isIncomplete()); // exclusive or operator for performance (see #7159)
884    }
885
886    boolean hasEqualSemanticAttributes(final OsmPrimitive other, final boolean testInterestingTagsOnly) {
887        return hasEqualSemanticFlags(other)
888                && (testInterestingTagsOnly ? hasSameInterestingTags(other) : getKeys().equals(other.getKeys()));
889    }
890
891    /**
892     * Replies true if this primitive and other are equal with respect to their technical attributes.
893     * The attributes:
894     * <ol>
895     *   <li>deleted</li>
896     *   <li>modified</li>
897     *   <li>timestamp</li>
898     *   <li>version</li>
899     *   <li>visible</li>
900     *   <li>user</li>
901     * </ol>
902     * have to be equal
903     * @param other the other primitive
904     * @return true if this primitive and other are equal with respect to their technical attributes
905     */
906    public boolean hasEqualTechnicalAttributes(OsmPrimitive other) {
907        // CHECKSTYLE.OFF: BooleanExpressionComplexity
908        return other != null
909            && timestamp == other.timestamp
910            && version == other.version
911            && changesetId == other.changesetId
912            && isDeleted() == other.isDeleted()
913            && isModified() == other.isModified()
914            && isVisible() == other.isVisible()
915            && Objects.equals(user, other.user);
916        // CHECKSTYLE.ON: BooleanExpressionComplexity
917    }
918
919    /**
920     * Loads (clone) this primitive from provided PrimitiveData
921     * @param data The object which should be cloned
922     */
923    public void load(PrimitiveData data) {
924        checkDatasetNotReadOnly();
925        // Write lock is provided by subclasses
926        setKeys(data.hasKeys() ? data.getKeys() : null);
927        setRawTimestamp(data.getRawTimestamp());
928        user = data.getUser();
929        setChangesetId(data.getChangesetId());
930        setDeleted(data.isDeleted());
931        setModified(data.isModified());
932        setVisible(data.isVisible());
933        setIncomplete(data.isIncomplete());
934        version = data.getVersion();
935    }
936
937    /**
938     * Save parameters of this primitive to the transport object
939     * @return The saved object data
940     */
941    public abstract PrimitiveData save();
942
943    /**
944     * Save common parameters of primitives to the transport object
945     * @param data The object to save the data into
946     */
947    protected void saveCommonAttributes(PrimitiveData data) {
948        data.setId(id);
949        data.setKeys(hasKeys() ? getKeys() : null);
950        data.setRawTimestamp(getRawTimestamp());
951        data.setUser(user);
952        data.setDeleted(isDeleted());
953        data.setModified(isModified());
954        data.setVisible(isVisible());
955        data.setIncomplete(isIncomplete());
956        data.setChangesetId(changesetId);
957        data.setVersion(version);
958    }
959
960    /**
961     * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...)
962     */
963    public abstract void updatePosition();
964
965    /*----------------
966     * OBJECT METHODS
967     *---------------*/
968
969    @Override
970    protected String getFlagsAsString() {
971        StringBuilder builder = new StringBuilder(super.getFlagsAsString());
972
973        if (isDisabled()) {
974            if (isDisabledAndHidden()) {
975                builder.append('h');
976            } else {
977                builder.append('d');
978            }
979        }
980        if (isTagged()) {
981            builder.append('T');
982        }
983        if (hasDirectionKeys()) {
984            if (reversedDirection()) {
985                builder.append('<');
986            } else {
987                builder.append('>');
988            }
989        }
990        return builder.toString();
991    }
992
993    /**
994     * Equal, if the id (and class) is equal.
995     *
996     * An primitive is equal to its incomplete counter part.
997     */
998    @Override
999    public boolean equals(Object obj) {
1000        if (this == obj) {
1001            return true;
1002        } else if (obj == null || getClass() != obj.getClass()) {
1003            return false;
1004        } else {
1005            OsmPrimitive that = (OsmPrimitive) obj;
1006            return id == that.id;
1007        }
1008    }
1009
1010    /**
1011     * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0.
1012     *
1013     * An primitive has the same hashcode as its incomplete counterpart.
1014     */
1015    @Override
1016    public int hashCode() {
1017        return Long.hashCode(id);
1018    }
1019
1020    @Override
1021    public Collection<String> getTemplateKeys() {
1022        return Stream.concat(Stream.of(SPECIAL_VALUE_ID, SPECIAL_VALUE_LOCAL_NAME), keys())
1023                .collect(Collectors.toList());
1024    }
1025
1026    @Override
1027    public Object getTemplateValue(String name, boolean special) {
1028        if (special) {
1029            String lc = name.toLowerCase(Locale.ENGLISH);
1030            if (SPECIAL_VALUE_ID.equals(lc))
1031                return getId();
1032            else if (SPECIAL_VALUE_LOCAL_NAME.equals(lc))
1033                return getLocalName();
1034            else
1035                return null;
1036
1037        } else
1038            return getIgnoreCase(name);
1039    }
1040
1041    @Override
1042    public boolean evaluateCondition(Match condition) {
1043        return condition.match(this);
1044    }
1045
1046    /**
1047     * Replies the set of referring relations
1048     * @param primitives primitives to fetch relations from
1049     *
1050     * @return the set of referring relations
1051     */
1052    public static Set<Relation> getParentRelations(Collection<? extends OsmPrimitive> primitives) {
1053        return primitives.stream()
1054                .flatMap(p -> p.referrers(Relation.class))
1055                .collect(Collectors.toSet());
1056    }
1057
1058    /**
1059     * Determines if this primitive has tags denoting an area.
1060     * @return {@code true} if this primitive has tags denoting an area, {@code false} otherwise.
1061     * @since 6491
1062     */
1063    public final boolean hasAreaTags() {
1064        return hasKey("building", "landuse", "amenity", "shop", "building:part", "boundary", "historic", "place", "area:highway")
1065                || hasTag("area", OsmUtils.TRUE_VALUE)
1066                || hasTag("waterway", "riverbank")
1067                || hasTag("highway", "rest_area", "services", "platform")
1068                || hasTag("railway", "platform")
1069                || hasTagDifferent("leisure", "picnic_table", "slipway", "firepit")
1070                || hasTag("natural", "water", "wood", "scrub", "wetland", "grassland", "heath", "rock", "bare_rock",
1071                                     "sand", "beach", "scree", "bay", "glacier", "shingle", "fell", "reef", "stone",
1072                                     "mud", "landslide", "sinkhole", "crevasse", "desert")
1073                || hasTag("aeroway", "aerodrome");
1074    }
1075
1076    /**
1077     * Determines if this primitive semantically concerns an area.
1078     * @return {@code true} if this primitive semantically concerns an area, according to its type, geometry and tags, {@code false} otherwise.
1079     * @since 6491
1080     */
1081    public abstract boolean concernsArea();
1082
1083    /**
1084     * Tests if this primitive lies outside of the downloaded area of its {@link DataSet}.
1085     * @return {@code true} if this primitive lies outside of the downloaded area
1086     */
1087    public abstract boolean isOutsideDownloadArea();
1088
1089    /**
1090     * If necessary, extend the bbox to contain this primitive
1091     * @param box a bbox instance
1092     * @param visited a set of visited members  or null
1093     * @since 11269
1094     */
1095    protected abstract void addToBBox(BBox box, Set<PrimitiveId> visited);
1096}
Note: See TracBrowser for help on using the repository browser.