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

Last change on this file since 12841 was 12841, checked in by bastiK, 3 months ago

see #15229 - fix deprecations caused by [12840]

  • Property svn:eol-style set to native
File size: 48.5 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.ArrayList;
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.Date;
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.LinkedHashSet;
15import java.util.LinkedList;
16import java.util.List;
17import java.util.Locale;
18import java.util.Map;
19import java.util.Objects;
20import java.util.Set;
21
22import org.openstreetmap.josm.Main;
23import org.openstreetmap.josm.data.osm.search.SearchCompiler;
24import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match;
25import org.openstreetmap.josm.data.osm.search.SearchParseError;
26import org.openstreetmap.josm.data.osm.visitor.OsmPrimitiveVisitor;
27import org.openstreetmap.josm.gui.mappaint.StyleCache;
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 set that are given
40 * by the server environment and not an extendible data stuff.
41 *
42 * @author imi
43 */
44public abstract class OsmPrimitive extends AbstractPrimitive implements Comparable<OsmPrimitive>, 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 volatile 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 | "+
75                "(\"piste:type\"=downhill & -area=yes) | (\"piste:type\"=sled & -area=yes) | (man_made=\"piste:halfpipe\" & -area=yes) | "+
76                "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 sub-collection of {@link OsmPrimitive}s of type <code>type</code> present in
85     * another collection of {@link OsmPrimitive}s. The result collection is a list.
86     *
87     * If <code>list</code> is null, replies an empty list.
88     *
89     * @param <T> type of data (must be one of the {@link OsmPrimitive} types
90     * @param list  the original list
91     * @param type the type to filter for
92     * @return the sub-list of OSM primitives of type <code>type</code>
93     */
94    public static <T extends OsmPrimitive> List<T> getFilteredList(Collection<OsmPrimitive> list, Class<T> type) {
95        if (list == null) return Collections.emptyList();
96        List<T> ret = new LinkedList<>();
97        for (OsmPrimitive p: list) {
98            if (type.isInstance(p)) {
99                ret.add(type.cast(p));
100            }
101        }
102        return ret;
103    }
104
105    /**
106     * Replies the sub-collection of {@link OsmPrimitive}s of type <code>type</code> present in
107     * another collection of {@link OsmPrimitive}s. The result collection is a set.
108     *
109     * If <code>list</code> is null, replies an empty set.
110     *
111     * @param <T> type of data (must be one of the {@link OsmPrimitive} types
112     * @param set  the original collection
113     * @param type the type to filter for
114     * @return the sub-set of OSM primitives of type <code>type</code>
115     */
116    public static <T extends OsmPrimitive> Set<T> getFilteredSet(Collection<OsmPrimitive> set, Class<T> type) {
117        Set<T> ret = new LinkedHashSet<>();
118        if (set != null) {
119            for (OsmPrimitive p: set) {
120                if (type.isInstance(p)) {
121                    ret.add(type.cast(p));
122                }
123            }
124        }
125        return ret;
126    }
127
128    /**
129     * Replies the collection of referring primitives for the primitives in <code>primitives</code>.
130     *
131     * @param primitives the collection of primitives.
132     * @return the collection of referring primitives for the primitives in <code>primitives</code>;
133     * empty set if primitives is null or if there are no referring primitives
134     */
135    public static Set<OsmPrimitive> getReferrer(Collection<? extends OsmPrimitive> primitives) {
136        Set<OsmPrimitive> ret = new HashSet<>();
137        if (primitives == null || primitives.isEmpty()) return ret;
138        for (OsmPrimitive p: primitives) {
139            ret.addAll(p.getReferrers());
140        }
141        return ret;
142    }
143
144    /**
145     * Creates a new primitive for the given id.
146     *
147     * If allowNegativeId is set, provided id can be &lt; 0 and will be set to primitive without any processing.
148     * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or
149     * positive number.
150     *
151     * @param id the id
152     * @param allowNegativeId {@code true} to allow negative id
153     * @throws IllegalArgumentException if id &lt; 0 and allowNegativeId is false
154     */
155    protected OsmPrimitive(long id, boolean allowNegativeId) {
156        if (allowNegativeId) {
157            this.id = id;
158        } else {
159            if (id < 0)
160                throw new IllegalArgumentException(MessageFormat.format("Expected ID >= 0. Got {0}.", id));
161            else if (id == 0) {
162                this.id = generateUniqueId();
163            } else {
164                this.id = id;
165            }
166
167        }
168        this.version = 0;
169        this.setIncomplete(id > 0);
170    }
171
172    /**
173     * Creates a new primitive for the given id and version.
174     *
175     * If allowNegativeId is set, provided id can be &lt; 0 and will be set to primitive without any processing.
176     * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or
177     * positive number.
178     *
179     * If id is not &gt; 0 version is ignored and set to 0.
180     *
181     * @param id the id
182     * @param version the version (positive integer)
183     * @param allowNegativeId {@code true} to allow negative id
184     * @throws IllegalArgumentException if id &lt; 0 and allowNegativeId is false
185     */
186    protected OsmPrimitive(long id, int version, boolean allowNegativeId) {
187        this(id, allowNegativeId);
188        this.version = id > 0 ? version : 0;
189        setIncomplete(id > 0 && version == 0);
190    }
191
192    /*----------
193     * MAPPAINT
194     *--------*/
195    public StyleCache mappaintStyle;
196    private short mappaintCacheIdx;
197
198    /* This should not be called from outside. Fixing the UI to add relevant
199       get/set functions calling this implicitely is preferred, so we can have
200       transparent cache handling in the future. */
201    public void clearCachedStyle() {
202        mappaintStyle = null;
203    }
204
205    /**
206     * Returns mappaint cache index.
207     * @return mappaint cache index
208     */
209    public final short getMappaintCacheIdx() {
210        return mappaintCacheIdx;
211    }
212
213    /**
214     * Sets the mappaint cache index.
215     * @param mappaintCacheIdx mappaint cache index
216     */
217    public final void setMappaintCacheIdx(short mappaintCacheIdx) {
218        this.mappaintCacheIdx = mappaintCacheIdx;
219    }
220
221    /* end of mappaint data */
222
223    /*---------
224     * DATASET
225     *---------*/
226
227    /** the parent dataset */
228    private DataSet dataSet;
229
230    /**
231     * This method should never ever by called from somewhere else than Dataset.addPrimitive or removePrimitive methods
232     * @param dataSet the parent dataset
233     */
234    void setDataset(DataSet dataSet) {
235        if (this.dataSet != null && dataSet != null && this.dataSet != dataSet)
236            throw new DataIntegrityProblemException("Primitive cannot be included in more than one Dataset");
237        this.dataSet = dataSet;
238    }
239
240    /**
241     *
242     * @return DataSet this primitive is part of.
243     */
244    public DataSet getDataSet() {
245        return dataSet;
246    }
247
248    /**
249     * Throws exception if primitive is not part of the dataset
250     */
251    public void checkDataset() {
252        if (dataSet == null)
253            throw new DataIntegrityProblemException("Primitive must be part of the dataset: " + toString());
254    }
255
256    protected boolean writeLock() {
257        if (dataSet != null) {
258            dataSet.beginUpdate();
259            return true;
260        } else
261            return false;
262    }
263
264    protected void writeUnlock(boolean locked) {
265        if (locked) {
266            // It shouldn't be possible for dataset to become null because
267            // method calling setDataset would need write lock which is owned by this thread
268            dataSet.endUpdate();
269        }
270    }
271
272    /**
273     * Sets the id and the version of this primitive if it is known to the OSM API.
274     *
275     * Since we know the id and its version it can't be incomplete anymore. incomplete
276     * is set to false.
277     *
278     * @param id the id. &gt; 0 required
279     * @param version the version &gt; 0 required
280     * @throws IllegalArgumentException if id &lt;= 0
281     * @throws IllegalArgumentException if version &lt;= 0
282     * @throws DataIntegrityProblemException if id is changed and primitive was already added to the dataset
283     */
284    @Override
285    public void setOsmId(long id, int version) {
286        boolean locked = writeLock();
287        try {
288            if (id <= 0)
289                throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
290            if (version <= 0)
291                throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
292            if (dataSet != null && id != this.id) {
293                DataSet datasetCopy = dataSet;
294                // Reindex primitive
295                datasetCopy.removePrimitive(this);
296                this.id = id;
297                datasetCopy.addPrimitive(this);
298            }
299            super.setOsmId(id, version);
300        } finally {
301            writeUnlock(locked);
302        }
303    }
304
305    /**
306     * Clears the metadata, including id and version known to the OSM API.
307     * The id is a new unique id. The version, changeset and timestamp are set to 0.
308     * incomplete and deleted are set to false. It's preferred to use copy constructor with clearMetadata set to true instead
309     *
310     * <strong>Caution</strong>: Do not use this method on primitives which are already added to a {@link DataSet}.
311     *
312     * @throws DataIntegrityProblemException If primitive was already added to the dataset
313     * @since 6140
314     */
315    @Override
316    public void clearOsmMetadata() {
317        if (dataSet != null)
318            throw new DataIntegrityProblemException("Method cannot be called after primitive was added to the dataset");
319        super.clearOsmMetadata();
320    }
321
322    @Override
323    public void setUser(User user) {
324        boolean locked = writeLock();
325        try {
326            super.setUser(user);
327        } finally {
328            writeUnlock(locked);
329        }
330    }
331
332    @Override
333    public void setChangesetId(int changesetId) {
334        boolean locked = writeLock();
335        try {
336            int old = this.changesetId;
337            super.setChangesetId(changesetId);
338            if (dataSet != null) {
339                dataSet.fireChangesetIdChanged(this, old, changesetId);
340            }
341        } finally {
342            writeUnlock(locked);
343        }
344    }
345
346    @Override
347    public void setTimestamp(Date timestamp) {
348        boolean locked = writeLock();
349        try {
350            super.setTimestamp(timestamp);
351        } finally {
352            writeUnlock(locked);
353        }
354    }
355
356
357    /* -------
358    /* FLAGS
359    /* ------*/
360
361    private void updateFlagsNoLock(short flag, boolean value) {
362        super.updateFlags(flag, value);
363    }
364
365    @Override
366    protected final void updateFlags(short flag, boolean value) {
367        boolean locked = writeLock();
368        try {
369            updateFlagsNoLock(flag, value);
370        } finally {
371            writeUnlock(locked);
372        }
373    }
374
375    /**
376     * Make the primitive disabled (e.g.&nbsp;if a filter applies).
377     *
378     * To enable the primitive again, use unsetDisabledState.
379     * @param hidden if the primitive should be completely hidden from view or
380     *             just shown in gray color.
381     * @return true, any flag has changed; false if you try to set the disabled
382     * state to the value that is already preset
383     */
384    public boolean setDisabledState(boolean hidden) {
385        boolean locked = writeLock();
386        try {
387            int oldFlags = flags;
388            updateFlagsNoLock(FLAG_DISABLED, true);
389            updateFlagsNoLock(FLAG_HIDE_IF_DISABLED, hidden);
390            return oldFlags != flags;
391        } finally {
392            writeUnlock(locked);
393        }
394    }
395
396    /**
397     * Remove the disabled flag from the primitive.
398     * Afterwards, the primitive is displayed normally and can be selected again.
399     * @return {@code true} if a change occurred
400     */
401    public boolean unsetDisabledState() {
402        boolean locked = writeLock();
403        try {
404            int oldFlags = flags;
405            updateFlagsNoLock(FLAG_DISABLED, false);
406            updateFlagsNoLock(FLAG_HIDE_IF_DISABLED, false);
407            return oldFlags != flags;
408        } finally {
409            writeUnlock(locked);
410        }
411    }
412
413    /**
414     * Set binary property used internally by the filter mechanism.
415     * @param isExplicit new "disabled type" flag value
416     */
417    public void setDisabledType(boolean isExplicit) {
418        updateFlags(FLAG_DISABLED_TYPE, isExplicit);
419    }
420
421    /**
422     * Set binary property used internally by the filter mechanism.
423     * @param isExplicit new "hidden type" flag value
424     */
425    public void setHiddenType(boolean isExplicit) {
426        updateFlags(FLAG_HIDDEN_TYPE, isExplicit);
427    }
428
429    /**
430     * Replies true, if this primitive is disabled. (E.g. a filter applies)
431     * @return {@code true} if this object has the "disabled" flag enabled
432     */
433    public boolean isDisabled() {
434        return (flags & FLAG_DISABLED) != 0;
435    }
436
437    /**
438     * Replies true, if this primitive is disabled and marked as completely hidden on the map.
439     * @return {@code true} if this object has both the "disabled" and "hide if disabled" flags enabled
440     */
441    public boolean isDisabledAndHidden() {
442        return ((flags & FLAG_DISABLED) != 0) && ((flags & FLAG_HIDE_IF_DISABLED) != 0);
443    }
444
445    /**
446     * Get binary property used internally by the filter mechanism.
447     * @return {@code true} if this object has the "hidden type" flag enabled
448     */
449    public boolean getHiddenType() {
450        return (flags & FLAG_HIDDEN_TYPE) != 0;
451    }
452
453    /**
454     * Get binary property used internally by the filter mechanism.
455     * @return {@code true} if this object has the "disabled type" flag enabled
456     */
457    public boolean getDisabledType() {
458        return (flags & FLAG_DISABLED_TYPE) != 0;
459    }
460
461    /**
462     * Determines if this object is selectable.
463     * <p>
464     * A primitive can be selected if all conditions are met:
465     * <ul>
466     * <li>it is drawable
467     * <li>it is not disabled (greyed out) by a filter.
468     * </ul>
469     * @return {@code true} if this object is selectable
470     */
471    public boolean isSelectable() {
472        // not synchronized -> check disabled twice just to be sure we did not have a race condition.
473        return !isDisabled() && isDrawable() && !isDisabled();
474    }
475
476    /**
477     * Determines if this object is drawable.
478     * <p>
479     * A primitive is complete if all conditions are met:
480     * <ul>
481     * <li>type and id is known
482     * <li>tags are known
483     * <li>it is not deleted
484     * <li>it is not hidden by a filter
485     * <li>for nodes: lat/lon are known
486     * <li>for ways: all nodes are known and complete
487     * <li>for relations: all members are known and complete
488     * </ul>
489     * @return {@code true} if this object is drawable
490     */
491    public boolean isDrawable() {
492        return (flags & (FLAG_DELETED + FLAG_INCOMPLETE + FLAG_HIDE_IF_DISABLED)) == 0;
493    }
494
495    @Override
496    public void setModified(boolean modified) {
497        boolean locked = writeLock();
498        try {
499            super.setModified(modified);
500            if (dataSet != null) {
501                dataSet.firePrimitiveFlagsChanged(this);
502            }
503            clearCachedStyle();
504        } finally {
505            writeUnlock(locked);
506        }
507    }
508
509    @Override
510    public void setVisible(boolean visible) {
511        boolean locked = writeLock();
512        try {
513            super.setVisible(visible);
514            clearCachedStyle();
515        } finally {
516            writeUnlock(locked);
517        }
518    }
519
520    @Override
521    public void setDeleted(boolean deleted) {
522        boolean locked = writeLock();
523        try {
524            super.setDeleted(deleted);
525            if (dataSet != null) {
526                if (deleted) {
527                    dataSet.firePrimitivesRemoved(Collections.singleton(this), false);
528                } else {
529                    dataSet.firePrimitivesAdded(Collections.singleton(this), false);
530                }
531            }
532            clearCachedStyle();
533        } finally {
534            writeUnlock(locked);
535        }
536    }
537
538    @Override
539    protected final void setIncomplete(boolean incomplete) {
540        boolean locked = writeLock();
541        try {
542            if (dataSet != null && incomplete != this.isIncomplete()) {
543                if (incomplete) {
544                    dataSet.firePrimitivesRemoved(Collections.singletonList(this), true);
545                } else {
546                    dataSet.firePrimitivesAdded(Collections.singletonList(this), true);
547                }
548            }
549            super.setIncomplete(incomplete);
550        } finally {
551            writeUnlock(locked);
552        }
553    }
554
555    /**
556     * Determines whether the primitive is selected
557     * @return whether the primitive is selected
558     * @see DataSet#isSelected(OsmPrimitive)
559     */
560    public boolean isSelected() {
561        return dataSet != null && dataSet.isSelected(this);
562    }
563
564    /**
565     * Determines if this primitive is a member of a selected relation.
566     * @return {@code true} if this primitive is a member of a selected relation, {@code false} otherwise
567     */
568    public boolean isMemberOfSelected() {
569        if (referrers == null)
570            return false;
571        if (referrers instanceof OsmPrimitive)
572            return referrers instanceof Relation && ((OsmPrimitive) referrers).isSelected();
573        for (OsmPrimitive ref : (OsmPrimitive[]) referrers) {
574            if (ref instanceof Relation && ref.isSelected())
575                return true;
576        }
577        return false;
578    }
579
580    /**
581     * Determines if this primitive is an outer member of a selected multipolygon relation.
582     * @return {@code true} if this primitive is an outer member of a selected multipolygon relation, {@code false} otherwise
583     * @since 7621
584     */
585    public boolean isOuterMemberOfSelected() {
586        if (referrers == null)
587            return false;
588        if (referrers instanceof OsmPrimitive) {
589            return isOuterMemberOfMultipolygon((OsmPrimitive) referrers);
590        }
591        for (OsmPrimitive ref : (OsmPrimitive[]) referrers) {
592            if (isOuterMemberOfMultipolygon(ref))
593                return true;
594        }
595        return false;
596    }
597
598    private boolean isOuterMemberOfMultipolygon(OsmPrimitive ref) {
599        if (ref instanceof Relation && ref.isSelected() && ((Relation) ref).isMultipolygon()) {
600            for (RelationMember rm : ((Relation) ref).getMembersFor(Collections.singleton(this))) {
601                if ("outer".equals(rm.getRole())) {
602                    return true;
603                }
604            }
605        }
606        return false;
607    }
608
609    /**
610     * Updates the highlight flag for this primitive.
611     * @param highlighted The new highlight flag.
612     */
613    public void setHighlighted(boolean highlighted) {
614        if (isHighlighted() != highlighted) {
615            updateFlags(FLAG_HIGHLIGHTED, highlighted);
616            if (dataSet != null) {
617                dataSet.fireHighlightingChanged();
618            }
619        }
620    }
621
622    /**
623     * Checks if the highlight flag for this primitive was set
624     * @return The highlight flag.
625     */
626    public boolean isHighlighted() {
627        return (flags & FLAG_HIGHLIGHTED) != 0;
628    }
629
630    /*---------------------------------------------------
631     * WORK IN PROGRESS, UNINTERESTING AND DIRECTION KEYS
632     *--------------------------------------------------*/
633
634    private static volatile Collection<String> workinprogress;
635    private static volatile Collection<String> uninteresting;
636    private static volatile Collection<String> discardable;
637
638    /**
639     * Returns a list of "uninteresting" keys that do not make an object
640     * "tagged".  Entries that end with ':' are causing a whole namespace to be considered
641     * "uninteresting".  Only the first level namespace is considered.
642     * Initialized by isUninterestingKey()
643     * @return The list of uninteresting keys.
644     */
645    public static Collection<String> getUninterestingKeys() {
646        if (uninteresting == null) {
647            List<String> l = new LinkedList<>(Arrays.asList(
648                "source", "source_ref", "source:", "comment",
649                "watch", "watch:", "description", "attribution"));
650            l.addAll(getDiscardableKeys());
651            l.addAll(getWorkInProgressKeys());
652            uninteresting = new HashSet<>(Main.pref.getList("tags.uninteresting", l));
653        }
654        return uninteresting;
655    }
656
657    /**
658     * Returns a list of keys which have been deemed uninteresting to the point
659     * that they can be silently removed from data which is being edited.
660     * @return The list of discardable keys.
661     */
662    public static Collection<String> getDiscardableKeys() {
663        if (discardable == null) {
664            discardable = new HashSet<>(Main.pref.getList("tags.discardable",
665                    Arrays.asList(
666                            "created_by",
667                            "converted_by",
668                            "geobase:datasetName",
669                            "geobase:uuid",
670                            "KSJ2:ADS",
671                            "KSJ2:ARE",
672                            "KSJ2:AdminArea",
673                            "KSJ2:COP_label",
674                            "KSJ2:DFD",
675                            "KSJ2:INT",
676                            "KSJ2:INT_label",
677                            "KSJ2:LOC",
678                            "KSJ2:LPN",
679                            "KSJ2:OPC",
680                            "KSJ2:PubFacAdmin",
681                            "KSJ2:RAC",
682                            "KSJ2:RAC_label",
683                            "KSJ2:RIC",
684                            "KSJ2:RIN",
685                            "KSJ2:WSC",
686                            "KSJ2:coordinate",
687                            "KSJ2:curve_id",
688                            "KSJ2:curve_type",
689                            "KSJ2:filename",
690                            "KSJ2:lake_id",
691                            "KSJ2:lat",
692                            "KSJ2:long",
693                            "KSJ2:river_id",
694                            "odbl",
695                            "odbl:note",
696                            "SK53_bulk:load",
697                            "sub_sea:type",
698                            "tiger:source",
699                            "tiger:separated",
700                            "tiger:tlid",
701                            "tiger:upload_uuid",
702                            "yh:LINE_NAME",
703                            "yh:LINE_NUM",
704                            "yh:STRUCTURE",
705                            "yh:TOTYUMONO",
706                            "yh:TYPE",
707                            "yh:WIDTH",
708                            "yh:WIDTH_RANK"
709                        )));
710        }
711        return discardable;
712    }
713
714    /**
715     * Returns a list of "work in progress" keys that do not make an object
716     * "tagged" but "annotated".
717     * @return The list of work in progress keys.
718     * @since 5754
719     */
720    public static Collection<String> getWorkInProgressKeys() {
721        if (workinprogress == null) {
722            workinprogress = new HashSet<>(Main.pref.getList("tags.workinprogress",
723                    Arrays.asList("note", "fixme", "FIXME")));
724        }
725        return workinprogress;
726    }
727
728    /**
729     * Determines if key is considered "uninteresting".
730     * @param key The key to check
731     * @return true if key is considered "uninteresting".
732     */
733    public static boolean isUninterestingKey(String key) {
734        getUninterestingKeys();
735        if (uninteresting.contains(key))
736            return true;
737        int pos = key.indexOf(':');
738        if (pos > 0)
739            return uninteresting.contains(key.substring(0, pos + 1));
740        return false;
741    }
742
743    /**
744     * Returns {@link #getKeys()} for which {@code key} does not fulfill {@link #isUninterestingKey}.
745     * @return A map of interesting tags
746     */
747    public Map<String, String> getInterestingTags() {
748        Map<String, String> result = new HashMap<>();
749        String[] keys = this.keys;
750        if (keys != null) {
751            for (int i = 0; i < keys.length; i += 2) {
752                if (!isUninterestingKey(keys[i])) {
753                    result.put(keys[i], keys[i + 1]);
754                }
755            }
756        }
757        return result;
758    }
759
760    private static Match compileDirectionKeys(String prefName, String defaultValue) throws AssertionError {
761        try {
762            return SearchCompiler.compile(Main.pref.get(prefName, defaultValue));
763        } catch (SearchParseError e) {
764            Logging.log(Logging.LEVEL_ERROR, "Unable to compile pattern for " + prefName + ", trying default pattern:", e);
765        }
766
767        try {
768            return SearchCompiler.compile(defaultValue);
769        } catch (SearchParseError e2) {
770            throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage(), e2);
771        }
772    }
773
774    private void updateTagged() {
775        for (String key: keySet()) {
776            // 'area' is not really uninteresting (putting it in that list may have unpredictable side effects)
777            // but it's clearly not enough to consider an object as tagged (see #9261)
778            if (!isUninterestingKey(key) && !"area".equals(key)) {
779                updateFlagsNoLock(FLAG_TAGGED, true);
780                return;
781            }
782        }
783        updateFlagsNoLock(FLAG_TAGGED, false);
784    }
785
786    private void updateAnnotated() {
787        for (String key: keySet()) {
788            if (getWorkInProgressKeys().contains(key)) {
789                updateFlagsNoLock(FLAG_ANNOTATED, true);
790                return;
791            }
792        }
793        updateFlagsNoLock(FLAG_ANNOTATED, false);
794    }
795
796    /**
797     * Determines if this object is considered "tagged". To be "tagged", an object
798     * must have one or more "interesting" tags. "created_by" and "source"
799     * are typically considered "uninteresting" and do not make an object
800     * "tagged".
801     * @return true if this object is considered "tagged"
802     */
803    public boolean isTagged() {
804        return (flags & FLAG_TAGGED) != 0;
805    }
806
807    /**
808     * Determines if this object is considered "annotated". To be "annotated", an object
809     * must have one or more "work in progress" tags, such as "note" or "fixme".
810     * @return true if this object is considered "annotated"
811     * @since 5754
812     */
813    public boolean isAnnotated() {
814        return (flags & FLAG_ANNOTATED) != 0;
815    }
816
817    private void updateDirectionFlags() {
818        boolean hasDirections = false;
819        boolean directionReversed = false;
820        if (reversedDirectionKeys.match(this)) {
821            hasDirections = true;
822            directionReversed = true;
823        }
824        if (directionKeys.match(this)) {
825            hasDirections = true;
826        }
827
828        updateFlagsNoLock(FLAG_DIRECTION_REVERSED, directionReversed);
829        updateFlagsNoLock(FLAG_HAS_DIRECTIONS, hasDirections);
830    }
831
832    /**
833     * true if this object has direction dependent tags (e.g. oneway)
834     * @return {@code true} if this object has direction dependent tags
835     */
836    public boolean hasDirectionKeys() {
837        return (flags & FLAG_HAS_DIRECTIONS) != 0;
838    }
839
840    /**
841     * true if this object has the "reversed diretion" flag enabled
842     * @return {@code true} if this object has the "reversed diretion" flag enabled
843     */
844    public boolean reversedDirection() {
845        return (flags & FLAG_DIRECTION_REVERSED) != 0;
846    }
847
848    /*------------
849     * Keys handling
850     ------------*/
851
852    @Override
853    public final void setKeys(TagMap keys) {
854        boolean locked = writeLock();
855        try {
856            super.setKeys(keys);
857        } finally {
858            writeUnlock(locked);
859        }
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    /**
1018     * Gets a list of all primitives in the current dataset that reference this primitive.
1019     * @return The referrers
1020     */
1021    public final List<OsmPrimitive> getReferrers() {
1022        return getReferrers(false);
1023    }
1024
1025    /**
1026     * <p>Visits {@code org.openstreetmap.josm.data.osm.visitor.Visitor} for all referrers.</p>
1027     *
1028     * @param visitor the visitor. Ignored, if null.
1029     * @deprecated use {@link #visitReferrers(OsmPrimitiveVisitor)}
1030     */
1031    @Deprecated
1032    public void visitReferrers(org.openstreetmap.josm.data.osm.visitor.Visitor visitor) {
1033        visitReferrers((OsmPrimitiveVisitor) visitor);
1034    }
1035
1036    /**
1037     * <p>Visits {@code visitor} for all referrers.</p>
1038     *
1039     * @param visitor the visitor. Ignored, if null.
1040     * @since 12809
1041     */
1042    public void visitReferrers(OsmPrimitiveVisitor visitor) {
1043        if (visitor == null) return;
1044        if (this.referrers == null)
1045            return;
1046        else if (this.referrers instanceof OsmPrimitive) {
1047            OsmPrimitive ref = (OsmPrimitive) this.referrers;
1048            if (ref.dataSet == dataSet) {
1049                ref.accept(visitor);
1050            }
1051        } else if (this.referrers instanceof OsmPrimitive[]) {
1052            OsmPrimitive[] refs = (OsmPrimitive[]) this.referrers;
1053            for (OsmPrimitive ref: refs) {
1054                if (ref.dataSet == dataSet) {
1055                    ref.accept(visitor);
1056                }
1057            }
1058        }
1059    }
1060
1061    /**
1062      Return true, if this primitive is referred by at least n ways
1063      @param n Minimal number of ways to return true. Must be positive
1064     * @return {@code true} if this primitive is referred by at least n ways
1065     */
1066    public final boolean isReferredByWays(int n) {
1067        // Count only referrers that are members of the same dataset (primitive can have some fake references, for example
1068        // when way is cloned
1069        Object referrers = this.referrers;
1070        if (referrers == null) return false;
1071        checkDataset();
1072        if (referrers instanceof OsmPrimitive)
1073            return n <= 1 && referrers instanceof Way && ((OsmPrimitive) referrers).dataSet == dataSet;
1074        else {
1075            int counter = 0;
1076            for (OsmPrimitive o : (OsmPrimitive[]) referrers) {
1077                if (dataSet == o.dataSet && o instanceof Way && ++counter >= n)
1078                    return true;
1079            }
1080            return false;
1081        }
1082    }
1083
1084    /*-----------------
1085     * OTHER METHODS
1086     *----------------*/
1087
1088    /**
1089     * Implementation of the visitor scheme. Subclasses have to call the correct
1090     * visitor function.
1091     * @param visitor The visitor from which the visit() function must be called.
1092     * @deprecated will be removed along with {@link org.openstreetmap.josm.data.osm.visitor.Visitor}
1093     */
1094    @Deprecated
1095    public abstract void accept(org.openstreetmap.josm.data.osm.visitor.Visitor visitor);
1096
1097    /**
1098     * Implementation of the visitor scheme. Subclasses have to call the correct
1099     * visitor function.
1100     * @param visitor The visitor from which the visit() function must be called.
1101     * @since 12809
1102     */
1103    public abstract void accept(OsmPrimitiveVisitor visitor);
1104
1105    /**
1106     * Get and write all attributes from the parameter. Does not fire any listener, so
1107     * use this only in the data initializing phase
1108     * @param other other primitive
1109     */
1110    public void cloneFrom(OsmPrimitive other) {
1111        // write lock is provided by subclasses
1112        if (id != other.id && dataSet != null)
1113            throw new DataIntegrityProblemException("Osm id cannot be changed after primitive was added to the dataset");
1114
1115        super.cloneFrom(other);
1116        clearCachedStyle();
1117    }
1118
1119    /**
1120     * Merges the technical and semantical attributes from <code>other</code> onto this.
1121     *
1122     * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
1123     * have an assigned OSM id, the IDs have to be the same.
1124     *
1125     * @param other the other primitive. Must not be null.
1126     * @throws IllegalArgumentException if other is null.
1127     * @throws DataIntegrityProblemException if either this is new and other is not, or other is new and this is not
1128     * @throws DataIntegrityProblemException if other isn't new and other.getId() != this.getId()
1129     */
1130    public void mergeFrom(OsmPrimitive other) {
1131        boolean locked = writeLock();
1132        try {
1133            CheckParameterUtil.ensureParameterNotNull(other, "other");
1134            if (other.isNew() ^ isNew())
1135                throw new DataIntegrityProblemException(
1136                        tr("Cannot merge because either of the participating primitives is new and the other is not"));
1137            if (!other.isNew() && other.getId() != id)
1138                throw new DataIntegrityProblemException(
1139                        tr("Cannot merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId()));
1140
1141            setKeys(other.hasKeys() ? other.getKeys() : null);
1142            timestamp = other.timestamp;
1143            version = other.version;
1144            setIncomplete(other.isIncomplete());
1145            flags = other.flags;
1146            user = other.user;
1147            changesetId = other.changesetId;
1148        } finally {
1149            writeUnlock(locked);
1150        }
1151    }
1152
1153    /**
1154     * Replies true if other isn't null and has the same interesting tags (key/value-pairs) as this.
1155     *
1156     * @param other the other object primitive
1157     * @return true if other isn't null and has the same interesting tags (key/value-pairs) as this.
1158     */
1159    public boolean hasSameInterestingTags(OsmPrimitive other) {
1160        return (keys == null && other.keys == null)
1161                || getInterestingTags().equals(other.getInterestingTags());
1162    }
1163
1164    /**
1165     * Replies true if this primitive and other are equal with respect to their semantic attributes.
1166     * <ol>
1167     *   <li>equal id</li>
1168     *   <li>both are complete or both are incomplete</li>
1169     *   <li>both have the same tags</li>
1170     * </ol>
1171     * @param other other primitive to compare
1172     * @return true if this primitive and other are equal with respect to their semantic attributes.
1173     */
1174    public final boolean hasEqualSemanticAttributes(OsmPrimitive other) {
1175        return hasEqualSemanticAttributes(other, true);
1176    }
1177
1178    boolean hasEqualSemanticFlags(final OsmPrimitive other) {
1179        if (!isNew() && id != other.id)
1180            return false;
1181        return !(isIncomplete() ^ other.isIncomplete()); // exclusive or operator for performance (see #7159)
1182    }
1183
1184    boolean hasEqualSemanticAttributes(final OsmPrimitive other, final boolean testInterestingTagsOnly) {
1185        return hasEqualSemanticFlags(other)
1186                && (testInterestingTagsOnly ? hasSameInterestingTags(other) : getKeys().equals(other.getKeys()));
1187    }
1188
1189    /**
1190     * Replies true if this primitive and other are equal with respect to their technical attributes.
1191     * The attributes:
1192     * <ol>
1193     *   <li>deleted</li>
1194     *   <li>modified</li>
1195     *   <li>timestamp</li>
1196     *   <li>version</li>
1197     *   <li>visible</li>
1198     *   <li>user</li>
1199     * </ol>
1200     * have to be equal
1201     * @param other the other primitive
1202     * @return true if this primitive and other are equal with respect to their technical attributes
1203     */
1204    public boolean hasEqualTechnicalAttributes(OsmPrimitive other) {
1205        // CHECKSTYLE.OFF: BooleanExpressionComplexity
1206        return other != null
1207            && timestamp == other.timestamp
1208            && version == other.version
1209            && changesetId == other.changesetId
1210            && isDeleted() == other.isDeleted()
1211            && isModified() == other.isModified()
1212            && isVisible() == other.isVisible()
1213            && Objects.equals(user, other.user);
1214        // CHECKSTYLE.ON: BooleanExpressionComplexity
1215    }
1216
1217    /**
1218     * Loads (clone) this primitive from provided PrimitiveData
1219     * @param data The object which should be cloned
1220     */
1221    public void load(PrimitiveData data) {
1222        // Write lock is provided by subclasses
1223        setKeys(data.hasKeys() ? data.getKeys() : null);
1224        setRawTimestamp(data.getRawTimestamp());
1225        user = data.getUser();
1226        setChangesetId(data.getChangesetId());
1227        setDeleted(data.isDeleted());
1228        setModified(data.isModified());
1229        setIncomplete(data.isIncomplete());
1230        version = data.getVersion();
1231    }
1232
1233    /**
1234     * Save parameters of this primitive to the transport object
1235     * @return The saved object data
1236     */
1237    public abstract PrimitiveData save();
1238
1239    /**
1240     * Save common parameters of primitives to the transport object
1241     * @param data The object to save the data into
1242     */
1243    protected void saveCommonAttributes(PrimitiveData data) {
1244        data.setId(id);
1245        data.setKeys(hasKeys() ? getKeys() : null);
1246        data.setRawTimestamp(getRawTimestamp());
1247        data.setUser(user);
1248        data.setDeleted(isDeleted());
1249        data.setModified(isModified());
1250        data.setVisible(isVisible());
1251        data.setIncomplete(isIncomplete());
1252        data.setChangesetId(changesetId);
1253        data.setVersion(version);
1254    }
1255
1256    /**
1257     * Fetch the bounding box of the primitive
1258     * @return Bounding box of the object
1259     */
1260    public abstract BBox getBBox();
1261
1262    /**
1263     * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...)
1264     */
1265    public abstract void updatePosition();
1266
1267    /*----------------
1268     * OBJECT METHODS
1269     *---------------*/
1270
1271    @Override
1272    protected String getFlagsAsString() {
1273        StringBuilder builder = new StringBuilder(super.getFlagsAsString());
1274
1275        if (isDisabled()) {
1276            if (isDisabledAndHidden()) {
1277                builder.append('h');
1278            } else {
1279                builder.append('d');
1280            }
1281        }
1282        if (isTagged()) {
1283            builder.append('T');
1284        }
1285        if (hasDirectionKeys()) {
1286            if (reversedDirection()) {
1287                builder.append('<');
1288            } else {
1289                builder.append('>');
1290            }
1291        }
1292        return builder.toString();
1293    }
1294
1295    /**
1296     * Equal, if the id (and class) is equal.
1297     *
1298     * An primitive is equal to its incomplete counter part.
1299     */
1300    @Override
1301    public boolean equals(Object obj) {
1302        if (this == obj) {
1303            return true;
1304        } else if (obj == null || getClass() != obj.getClass()) {
1305            return false;
1306        } else {
1307            OsmPrimitive that = (OsmPrimitive) obj;
1308            return id == that.id;
1309        }
1310    }
1311
1312    /**
1313     * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0.
1314     *
1315     * An primitive has the same hashcode as its incomplete counterpart.
1316     */
1317    @Override
1318    public int hashCode() {
1319        return Long.hashCode(id);
1320    }
1321
1322    /**
1323     * Replies the display name of a primitive formatted by <code>formatter</code>
1324     * @param formatter formatter to use
1325     *
1326     * @return the display name
1327     */
1328    public abstract String getDisplayName(NameFormatter formatter);
1329
1330    @Override
1331    public Collection<String> getTemplateKeys() {
1332        Collection<String> keySet = keySet();
1333        List<String> result = new ArrayList<>(keySet.size() + 2);
1334        result.add(SPECIAL_VALUE_ID);
1335        result.add(SPECIAL_VALUE_LOCAL_NAME);
1336        result.addAll(keySet);
1337        return result;
1338    }
1339
1340    @Override
1341    public Object getTemplateValue(String name, boolean special) {
1342        if (special) {
1343            String lc = name.toLowerCase(Locale.ENGLISH);
1344            if (SPECIAL_VALUE_ID.equals(lc))
1345                return getId();
1346            else if (SPECIAL_VALUE_LOCAL_NAME.equals(lc))
1347                return getLocalName();
1348            else
1349                return null;
1350
1351        } else
1352            return getIgnoreCase(name);
1353    }
1354
1355    @Override
1356    public boolean evaluateCondition(Match condition) {
1357        return condition.match(this);
1358    }
1359
1360    /**
1361     * Replies the set of referring relations
1362     * @param primitives primitives to fetch relations from
1363     *
1364     * @return the set of referring relations
1365     */
1366    public static Set<Relation> getParentRelations(Collection<? extends OsmPrimitive> primitives) {
1367        Set<Relation> ret = new HashSet<>();
1368        for (OsmPrimitive w : primitives) {
1369            ret.addAll(OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class));
1370        }
1371        return ret;
1372    }
1373
1374    /**
1375     * Determines if this primitive has tags denoting an area.
1376     * @return {@code true} if this primitive has tags denoting an area, {@code false} otherwise.
1377     * @since 6491
1378     */
1379    public final boolean hasAreaTags() {
1380        return hasKey("landuse", "amenity", "building", "building:part")
1381                || hasTag("area", OsmUtils.TRUE_VALUE)
1382                || hasTag("waterway", "riverbank")
1383                || hasTagDifferent("leisure", "picnic_table", "slipway", "firepit")
1384                || hasTag("natural", "water", "wood", "scrub", "wetland", "grassland", "heath", "rock", "bare_rock",
1385                                     "sand", "beach", "scree", "bay", "glacier", "shingle", "fell", "reef", "stone",
1386                                     "mud", "landslide", "sinkhole", "crevasse", "desert");
1387    }
1388
1389    /**
1390     * Determines if this primitive semantically concerns an area.
1391     * @return {@code true} if this primitive semantically concerns an area, according to its type, geometry and tags, {@code false} otherwise.
1392     * @since 6491
1393     */
1394    public abstract boolean concernsArea();
1395
1396    /**
1397     * Tests if this primitive lies outside of the downloaded area of its {@link DataSet}.
1398     * @return {@code true} if this primitive lies outside of the downloaded area
1399     */
1400    public abstract boolean isOutsideDownloadArea();
1401
1402    /**
1403     * Determines if this object is a relation and behaves as a multipolygon.
1404     * @return {@code true} if it is a real mutlipolygon or a boundary relation
1405     * @since 10716
1406     */
1407    public boolean isMultipolygon() {
1408        return false;
1409    }
1410
1411    /**
1412     * If necessary, extend the bbox to contain this primitive
1413     * @param box a bbox instance
1414     * @param visited a set of visited members  or null
1415     * @since 11269
1416     */
1417    protected abstract void addToBBox(BBox box, Set<PrimitiveId> visited);
1418}
Note: See TracBrowser for help on using the repository browser.