source: josm/trunk/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java @ 14434

Last change on this file since 14434 was 14434, checked in by Don-vip, 4 months ago

fix #16995 - fix timestamp in GPX exports (patch by cmuelle8) + use Java 8 unsigned int API

  • Property svn:eol-style set to native
File size: 27.0 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.HashMap;
12import java.util.HashSet;
13import java.util.LinkedList;
14import java.util.List;
15import java.util.Map;
16import java.util.Map.Entry;
17import java.util.Set;
18import java.util.concurrent.TimeUnit;
19import java.util.concurrent.atomic.AtomicLong;
20
21import org.openstreetmap.josm.spi.preferences.Config;
22import org.openstreetmap.josm.tools.Utils;
23
24/**
25 * Abstract class to represent common features of the datatypes primitives.
26 *
27 * @since 4099
28 */
29public abstract class AbstractPrimitive implements IPrimitive {
30
31    private static final AtomicLong idCounter = new AtomicLong(0);
32
33    /**
34     * Generates a new primitive unique id.
35     * @return new primitive unique (negative) id
36     */
37    static long generateUniqueId() {
38        return idCounter.decrementAndGet();
39    }
40
41    /**
42     * Returns the current primitive unique id.
43     * @return the current primitive unique (negative) id (last generated)
44     * @since 12536
45     */
46    public static long currentUniqueId() {
47        return idCounter.get();
48    }
49
50    /**
51     * Advances the current primitive unique id to skip a range of values.
52     * @param newId new unique id
53     * @throws IllegalArgumentException if newId is greater than current unique id
54     * @since 12536
55     */
56    public static void advanceUniqueId(long newId) {
57        if (newId > currentUniqueId()) {
58            throw new IllegalArgumentException("Cannot modify the id counter backwards");
59        }
60        idCounter.set(newId);
61    }
62
63    /**
64     * This flag shows, that the properties have been changed by the user
65     * and on upload the object will be send to the server.
66     */
67    protected static final short FLAG_MODIFIED = 1 << 0;
68
69    /**
70     * This flag is false, if the object is marked
71     * as deleted on the server.
72     */
73    protected static final short FLAG_VISIBLE = 1 << 1;
74
75    /**
76     * An object that was deleted by the user.
77     * Deleted objects are usually hidden on the map and a request
78     * for deletion will be send to the server on upload.
79     * An object usually cannot be deleted if it has non-deleted
80     * objects still referring to it.
81     */
82    protected static final short FLAG_DELETED = 1 << 2;
83
84    /**
85     * A primitive is incomplete if we know its id and type, but nothing more.
86     * Typically some members of a relation are incomplete until they are
87     * fetched from the server.
88     */
89    protected static final short FLAG_INCOMPLETE = 1 << 3;
90
91    /**
92     * An object can be disabled by the filter mechanism.
93     * Then it will show in a shade of gray on the map or it is completely
94     * hidden from the view.
95     * Disabled objects usually cannot be selected or modified
96     * while the filter is active.
97     */
98    protected static final short FLAG_DISABLED = 1 << 4;
99
100    /**
101     * This flag is only relevant if an object is disabled by the
102     * filter mechanism (i.e.&nbsp;FLAG_DISABLED is set).
103     * Then it indicates, whether it is completely hidden or
104     * just shown in gray color.
105     *
106     * When the primitive is not disabled, this flag should be
107     * unset as well (for efficient access).
108     */
109    protected static final short FLAG_HIDE_IF_DISABLED = 1 << 5;
110
111    /**
112     * Flag used internally by the filter mechanism.
113     */
114    protected static final short FLAG_DISABLED_TYPE = 1 << 6;
115
116    /**
117     * Flag used internally by the filter mechanism.
118     */
119    protected static final short FLAG_HIDDEN_TYPE = 1 << 7;
120
121    /**
122     * This flag is set if the primitive is a way and
123     * according to the tags, the direction of the way is important.
124     * (e.g. one way street.)
125     */
126    protected static final short FLAG_HAS_DIRECTIONS = 1 << 8;
127
128    /**
129     * If the primitive is tagged.
130     * Some trivial tags like source=* are ignored here.
131     */
132    protected static final short FLAG_TAGGED = 1 << 9;
133
134    /**
135     * This flag is only relevant if FLAG_HAS_DIRECTIONS is set.
136     * It shows, that direction of the arrows should be reversed.
137     * (E.g. oneway=-1.)
138     */
139    protected static final short FLAG_DIRECTION_REVERSED = 1 << 10;
140
141    /**
142     * When hovering over ways and nodes in add mode, the
143     * "target" objects are visually highlighted. This flag indicates
144     * that the primitive is currently highlighted.
145     */
146    protected static final short FLAG_HIGHLIGHTED = 1 << 11;
147
148    /**
149     * If the primitive is annotated with a tag such as note, fixme, etc.
150     * Match the "work in progress" tags in default map style.
151     */
152    protected static final short FLAG_ANNOTATED = 1 << 12;
153
154    /**
155     * Determines if the primitive is preserved from the filter mechanism.
156     */
157    protected static final short FLAG_PRESERVED = 1 << 13;
158
159    /**
160     * Put several boolean flags to one short int field to save memory.
161     * Other bits of this field are used in subclasses.
162     */
163    protected volatile short flags = FLAG_VISIBLE;   // visible per default
164
165    /*-------------------
166     * OTHER PROPERTIES
167     *-------------------*/
168
169    /**
170     * Unique identifier in OSM. This is used to identify objects on the server.
171     * An id of 0 means an unknown id. The object has not been uploaded yet to
172     * know what id it will get.
173     */
174    protected long id;
175
176    /**
177     * User that last modified this primitive, as specified by the server.
178     * Never changed by JOSM.
179     */
180    protected User user;
181
182    /**
183     * Contains the version number as returned by the API. Needed to
184     * ensure update consistency
185     */
186    protected int version;
187
188    /**
189     * The id of the changeset this primitive was last uploaded to.
190     * 0 if it wasn't uploaded to a changeset yet of if the changeset
191     * id isn't known.
192     */
193    protected int changesetId;
194
195    /**
196     * A time value, measured in seconds from the epoch, or in other words,
197     * a number of seconds that have passed since 1970-01-01T00:00:00Z
198     */
199    protected int timestamp;
200
201    /**
202     * Get and write all attributes from the parameter. Does not fire any listener, so
203     * use this only in the data initializing phase
204     * @param other the primitive to clone data from
205     */
206    public void cloneFrom(AbstractPrimitive other) {
207        setKeys(other.getKeys());
208        id = other.id;
209        if (id <= 0) {
210            // reset version and changeset id
211            version = 0;
212            changesetId = 0;
213        }
214        timestamp = other.timestamp;
215        if (id > 0) {
216            version = other.version;
217        }
218        flags = other.flags;
219        user = other.user;
220        if (id > 0 && other.changesetId > 0) {
221            // #4208: sometimes we cloned from other with id < 0 *and*
222            // an assigned changeset id. Don't know why yet. For primitives
223            // with id < 0 we don't propagate the changeset id any more.
224            //
225            setChangesetId(other.changesetId);
226        }
227    }
228
229    @Override
230    public int getVersion() {
231        return version;
232    }
233
234    @Override
235    public long getId() {
236        long id = this.id;
237        return id >= 0 ? id : 0;
238    }
239
240    /**
241     * Gets a unique id representing this object.
242     *
243     * @return Osm id if primitive already exists on the server. Unique negative value if primitive is new
244     */
245    @Override
246    public long getUniqueId() {
247        return id;
248    }
249
250    /**
251     * Determines if this primitive is new.
252     * @return {@code true} if this primitive is new (not yet uploaded the server, id &lt;= 0)
253     */
254    @Override
255    public boolean isNew() {
256        return id <= 0;
257    }
258
259    @Override
260    public boolean isNewOrUndeleted() {
261        return isNew() || ((flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0);
262    }
263
264    @Override
265    public void setOsmId(long id, int version) {
266        if (id <= 0)
267            throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
268        if (version <= 0)
269            throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
270        this.id = id;
271        this.version = version;
272        this.setIncomplete(false);
273    }
274
275    /**
276     * Clears the metadata, including id and version known to the OSM API.
277     * The id is a new unique id. The version, changeset and timestamp are set to 0.
278     * incomplete and deleted are set to false. It's preferred to use copy constructor with clearMetadata set to true instead
279     * of calling this method.
280     * @since 6140
281     */
282    public void clearOsmMetadata() {
283        // Not part of dataset - no lock necessary
284        this.id = generateUniqueId();
285        this.version = 0;
286        this.user = null;
287        this.changesetId = 0; // reset changeset id on a new object
288        this.timestamp = 0;
289        this.setIncomplete(false);
290        this.setDeleted(false);
291        this.setVisible(true);
292    }
293
294    @Override
295    public User getUser() {
296        return user;
297    }
298
299    @Override
300    public void setUser(User user) {
301        this.user = user;
302    }
303
304    @Override
305    public int getChangesetId() {
306        return changesetId;
307    }
308
309    @Override
310    public void setChangesetId(int changesetId) {
311        if (this.changesetId == changesetId)
312            return;
313        if (changesetId < 0)
314            throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' >= 0 expected, got {1}", "changesetId", changesetId));
315        if (changesetId > 0 && isNew())
316            throw new IllegalStateException(tr("Cannot assign a changesetId > 0 to a new primitive. Value of changesetId is {0}", changesetId));
317
318        this.changesetId = changesetId;
319    }
320
321    @Override
322    public void setTimestamp(Date timestamp) {
323        this.timestamp = (int) TimeUnit.MILLISECONDS.toSeconds(timestamp.getTime());
324    }
325
326    @Override
327    public void setRawTimestamp(int timestamp) {
328        this.timestamp = timestamp;
329    }
330
331    @Override
332    public Date getTimestamp() {
333        return new Date(TimeUnit.SECONDS.toMillis(Integer.toUnsignedLong(timestamp)));
334    }
335
336    @Override
337    public int getRawTimestamp() {
338        return timestamp;
339    }
340
341    @Override
342    public boolean isTimestampEmpty() {
343        return timestamp == 0;
344    }
345
346    /* -------
347    /* FLAGS
348    /* ------*/
349
350    protected void updateFlags(short flag, boolean value) {
351        if (value) {
352            flags |= flag;
353        } else {
354            flags &= (short) ~flag;
355        }
356    }
357
358    @Override
359    public void setModified(boolean modified) {
360        updateFlags(FLAG_MODIFIED, modified);
361    }
362
363    @Override
364    public boolean isModified() {
365        return (flags & FLAG_MODIFIED) != 0;
366    }
367
368    @Override
369    public boolean isDeleted() {
370        return (flags & FLAG_DELETED) != 0;
371    }
372
373    @Override
374    public boolean isUndeleted() {
375        return (flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0;
376    }
377
378    @Override
379    public boolean isUsable() {
380        return (flags & (FLAG_DELETED + FLAG_INCOMPLETE)) == 0;
381    }
382
383    @Override
384    public boolean isVisible() {
385        return (flags & FLAG_VISIBLE) != 0;
386    }
387
388    @Override
389    public void setVisible(boolean visible) {
390        if (!visible && isNew())
391            throw new IllegalStateException(tr("A primitive with ID = 0 cannot be invisible."));
392        updateFlags(FLAG_VISIBLE, visible);
393    }
394
395    @Override
396    public void setDeleted(boolean deleted) {
397        updateFlags(FLAG_DELETED, deleted);
398        setModified(deleted ^ !isVisible());
399    }
400
401    /**
402     * If set to true, this object is incomplete, which means only the id
403     * and type is known (type is the objects instance class)
404     * @param incomplete incomplete flag value
405     */
406    protected void setIncomplete(boolean incomplete) {
407        updateFlags(FLAG_INCOMPLETE, incomplete);
408    }
409
410    @Override
411    public boolean isIncomplete() {
412        return (flags & FLAG_INCOMPLETE) != 0;
413    }
414
415    protected String getFlagsAsString() {
416        StringBuilder builder = new StringBuilder();
417
418        if (isIncomplete()) {
419            builder.append('I');
420        }
421        if (isModified()) {
422            builder.append('M');
423        }
424        if (isVisible()) {
425            builder.append('V');
426        }
427        if (isDeleted()) {
428            builder.append('D');
429        }
430        return builder.toString();
431    }
432
433    /*------------
434     * Keys handling
435     ------------*/
436
437    /**
438     * The key/value list for this primitive.
439     * <p>
440     * Note that the keys field is synchronized using RCU.
441     * Writes to it are not synchronized by this object, the writers have to synchronize writes themselves.
442     * <p>
443     * In short this means that you should not rely on this variable being the same value when read again and your should always
444     * copy it on writes.
445     * <p>
446     * Further reading:
447     * <ul>
448     * <li>{@link java.util.concurrent.CopyOnWriteArrayList}</li>
449     * <li> <a href="http://stackoverflow.com/questions/2950871/how-can-copyonwritearraylist-be-thread-safe">
450     *     http://stackoverflow.com/questions/2950871/how-can-copyonwritearraylist-be-thread-safe</a></li>
451     * <li> <a href="https://en.wikipedia.org/wiki/Read-copy-update">
452     *     https://en.wikipedia.org/wiki/Read-copy-update</a> (mind that we have a Garbage collector,
453     *     {@code rcu_assign_pointer} and {@code rcu_dereference} are ensured by the {@code volatile} keyword)</li>
454     * </ul>
455     */
456    protected volatile String[] keys;
457
458    /**
459     * Replies the map of key/value pairs. Never replies null. The map can be empty, though.
460     *
461     * @return tags of this primitive. Changes made in returned map are not mapped
462     * back to the primitive, use setKeys() to modify the keys
463     * @see #visitKeys(KeyValueVisitor)
464     */
465    @Override
466    public TagMap getKeys() {
467        return new TagMap(keys);
468    }
469
470    @Override
471    public void visitKeys(KeyValueVisitor visitor) {
472        final String[] keys = this.keys;
473        if (keys != null) {
474            for (int i = 0; i < keys.length; i += 2) {
475                visitor.visitKeyValue(this, keys[i], keys[i + 1]);
476            }
477        }
478    }
479
480    /**
481     * Sets the keys of this primitives to the key/value pairs in <code>keys</code>.
482     * Old key/value pairs are removed.
483     * If <code>keys</code> is null, clears existing key/value pairs.
484     * <p>
485     * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
486     * from multiple threads.
487     *
488     * @param keys the key/value pairs to set. If null, removes all existing key/value pairs.
489     */
490    @Override
491    public void setKeys(Map<String, String> keys) {
492        Map<String, String> originalKeys = getKeys();
493        if (keys == null || keys.isEmpty()) {
494            this.keys = null;
495            keysChangedImpl(originalKeys);
496            return;
497        }
498        String[] newKeys = new String[keys.size() * 2];
499        int index = 0;
500        for (Entry<String, String> entry:keys.entrySet()) {
501            newKeys[index++] = entry.getKey();
502            newKeys[index++] = entry.getValue();
503        }
504        this.keys = newKeys;
505        keysChangedImpl(originalKeys);
506    }
507
508    /**
509     * Copy the keys from a TagMap.
510     * @param keys The new key map.
511     */
512    public void setKeys(TagMap keys) {
513        Map<String, String> originalKeys = getKeys();
514        if (keys == null) {
515            this.keys = null;
516        } else {
517            String[] arr = keys.getTagsArray();
518            if (arr.length == 0) {
519                this.keys = null;
520            } else {
521                this.keys = arr;
522            }
523        }
524        keysChangedImpl(originalKeys);
525    }
526
527    /**
528     * Set the given value to the given key. If key is null, does nothing. If value is null,
529     * removes the key and behaves like {@link #remove(String)}.
530     * <p>
531     * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
532     * from multiple threads.
533     *
534     * @param key  The key, for which the value is to be set. Can be null or empty, does nothing in this case.
535     * @param value The value for the key. If null, removes the respective key/value pair.
536     *
537     * @see #remove(String)
538     */
539    @Override
540    public void put(String key, String value) {
541        Map<String, String> originalKeys = getKeys();
542        if (key == null || Utils.isStripEmpty(key))
543            return;
544        else if (value == null) {
545            remove(key);
546        } else if (keys == null) {
547            keys = new String[] {key, value};
548            keysChangedImpl(originalKeys);
549        } else {
550            int keyIndex = indexOfKey(keys, key);
551            int tagArrayLength = keys.length;
552            if (keyIndex < 0) {
553                keyIndex = tagArrayLength;
554                tagArrayLength += 2;
555            }
556
557            // Do not try to optimize this array creation if the key already exists.
558            // We would need to convert the keys array to be an AtomicReferenceArray
559            // Or we would at least need a volatile write after the array was modified to
560            // ensure that changes are visible by other threads.
561            String[] newKeys = Arrays.copyOf(keys, tagArrayLength);
562            newKeys[keyIndex] = key;
563            newKeys[keyIndex + 1] = value;
564            keys = newKeys;
565            keysChangedImpl(originalKeys);
566        }
567    }
568
569    /**
570     * Scans a key/value array for a given key.
571     * @param keys The key array. It is not modified. It may be null to indicate an emtpy array.
572     * @param key The key to search for.
573     * @return The position of that key in the keys array - which is always a multiple of 2 - or -1 if it was not found.
574     */
575    private static int indexOfKey(String[] keys, String key) {
576        if (keys == null) {
577            return -1;
578        }
579        for (int i = 0; i < keys.length; i += 2) {
580            if (keys[i].equals(key)) {
581                return i;
582            }
583        }
584        return -1;
585    }
586
587    /**
588     * Remove the given key from the list
589     * <p>
590     * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
591     * from multiple threads.
592     *
593     * @param key  the key to be removed. Ignored, if key is null.
594     */
595    @Override
596    public void remove(String key) {
597        if (key == null || keys == null) return;
598        if (!hasKey(key))
599            return;
600        Map<String, String> originalKeys = getKeys();
601        if (keys.length == 2) {
602            keys = null;
603            keysChangedImpl(originalKeys);
604            return;
605        }
606        String[] newKeys = new String[keys.length - 2];
607        int j = 0;
608        for (int i = 0; i < keys.length; i += 2) {
609            if (!keys[i].equals(key)) {
610                newKeys[j++] = keys[i];
611                newKeys[j++] = keys[i+1];
612            }
613        }
614        keys = newKeys;
615        keysChangedImpl(originalKeys);
616    }
617
618    /**
619     * Removes all keys from this primitive.
620     * <p>
621     * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
622     * from multiple threads.
623     */
624    @Override
625    public void removeAll() {
626        if (keys != null) {
627            Map<String, String> originalKeys = getKeys();
628            keys = null;
629            keysChangedImpl(originalKeys);
630        }
631    }
632
633    /**
634     * Replies the value for key <code>key</code>. Replies null, if <code>key</code> is null.
635     * Replies null, if there is no value for the given key.
636     *
637     * @param key the key. Can be null, replies null in this case.
638     * @return the value for key <code>key</code>.
639     */
640    @Override
641    public final String get(String key) {
642        String[] keys = this.keys;
643        if (key == null)
644            return null;
645        if (keys == null)
646            return null;
647        for (int i = 0; i < keys.length; i += 2) {
648            if (keys[i].equals(key)) return keys[i+1];
649        }
650        return null;
651    }
652
653    /**
654     * Gets a key ignoring the case of the key
655     * @param key The key to get
656     * @return The value for a key that matches the given key ignoring case.
657     */
658    public final String getIgnoreCase(String key) {
659        String[] keys = this.keys;
660        if (key == null)
661            return null;
662        if (keys == null)
663            return null;
664        for (int i = 0; i < keys.length; i += 2) {
665            if (keys[i].equalsIgnoreCase(key)) return keys[i+1];
666        }
667        return null;
668    }
669
670    @Override
671    public final int getNumKeys() {
672        String[] keys = this.keys;
673        return keys == null ? 0 : keys.length / 2;
674    }
675
676    @Override
677    public final Collection<String> keySet() {
678        final String[] keys = this.keys;
679        if (keys == null) {
680            return Collections.emptySet();
681        }
682        if (keys.length == 1) {
683            return Collections.singleton(keys[0]);
684        }
685
686        final Set<String> result = new HashSet<>(Utils.hashMapInitialCapacity(keys.length / 2));
687        for (int i = 0; i < keys.length; i += 2) {
688            result.add(keys[i]);
689        }
690        return result;
691    }
692
693    /**
694     * Replies true, if the map of key/value pairs of this primitive is not empty.
695     *
696     * @return true, if the map of key/value pairs of this primitive is not empty; false otherwise
697     */
698    @Override
699    public final boolean hasKeys() {
700        return keys != null;
701    }
702
703    /**
704     * Replies true if this primitive has a tag with key <code>key</code>.
705     *
706     * @param key the key
707     * @return true, if this primitive has a tag with key <code>key</code>
708     */
709    @Override
710    public boolean hasKey(String key) {
711        return key != null && indexOfKey(keys, key) >= 0;
712    }
713
714    /**
715     * Replies true if this primitive has a tag any of the <code>keys</code>.
716     *
717     * @param keys the keys
718     * @return true, if this primitive has a tag with any of the <code>keys</code>
719     * @since 11587
720     */
721    public boolean hasKey(String... keys) {
722        return keys != null && Arrays.stream(keys).anyMatch(this::hasKey);
723    }
724
725    /**
726     * What to do, when the tags have changed by one of the tag-changing methods.
727     * @param originalKeys original tags
728     */
729    protected abstract void keysChangedImpl(Map<String, String> originalKeys);
730
731    /*-------------------------------------
732     * WORK IN PROGRESS, UNINTERESTING KEYS
733     *-------------------------------------*/
734
735    private static volatile Collection<String> workinprogress;
736    private static volatile Collection<String> uninteresting;
737    private static volatile Collection<String> discardable;
738
739    /**
740     * Returns a list of "uninteresting" keys that do not make an object
741     * "tagged".  Entries that end with ':' are causing a whole namespace to be considered
742     * "uninteresting".  Only the first level namespace is considered.
743     * Initialized by isUninterestingKey()
744     * @return The list of uninteresting keys.
745     */
746    public static Collection<String> getUninterestingKeys() {
747        if (uninteresting == null) {
748            List<String> l = new LinkedList<>(Arrays.asList(
749                "source", "source_ref", "source:", "comment",
750                "watch", "watch:", "description", "attribution"));
751            l.addAll(getDiscardableKeys());
752            l.addAll(getWorkInProgressKeys());
753            uninteresting = new HashSet<>(Config.getPref().getList("tags.uninteresting", l));
754        }
755        return uninteresting;
756    }
757
758    /**
759     * Returns a list of keys which have been deemed uninteresting to the point
760     * that they can be silently removed from data which is being edited.
761     * @return The list of discardable keys.
762     */
763    public static Collection<String> getDiscardableKeys() {
764        if (discardable == null) {
765            discardable = new HashSet<>(Config.getPref().getList("tags.discardable",
766                    Arrays.asList(
767                            "created_by",
768                            "converted_by",
769                            "geobase:datasetName",
770                            "geobase:uuid",
771                            "KSJ2:ADS",
772                            "KSJ2:ARE",
773                            "KSJ2:AdminArea",
774                            "KSJ2:COP_label",
775                            "KSJ2:DFD",
776                            "KSJ2:INT",
777                            "KSJ2:INT_label",
778                            "KSJ2:LOC",
779                            "KSJ2:LPN",
780                            "KSJ2:OPC",
781                            "KSJ2:PubFacAdmin",
782                            "KSJ2:RAC",
783                            "KSJ2:RAC_label",
784                            "KSJ2:RIC",
785                            "KSJ2:RIN",
786                            "KSJ2:WSC",
787                            "KSJ2:coordinate",
788                            "KSJ2:curve_id",
789                            "KSJ2:curve_type",
790                            "KSJ2:filename",
791                            "KSJ2:lake_id",
792                            "KSJ2:lat",
793                            "KSJ2:long",
794                            "KSJ2:river_id",
795                            "odbl",
796                            "odbl:note",
797                            "SK53_bulk:load",
798                            "sub_sea:type",
799                            "tiger:source",
800                            "tiger:separated",
801                            "tiger:tlid",
802                            "tiger:upload_uuid",
803                            "yh:LINE_NAME",
804                            "yh:LINE_NUM",
805                            "yh:STRUCTURE",
806                            "yh:TOTYUMONO",
807                            "yh:TYPE",
808                            "yh:WIDTH",
809                            "yh:WIDTH_RANK"
810                        )));
811        }
812        return discardable;
813    }
814
815    /**
816     * Returns a list of "work in progress" keys that do not make an object
817     * "tagged" but "annotated".
818     * @return The list of work in progress keys.
819     * @since 5754
820     */
821    public static Collection<String> getWorkInProgressKeys() {
822        if (workinprogress == null) {
823            workinprogress = new HashSet<>(Config.getPref().getList("tags.workinprogress",
824                    Arrays.asList("note", "fixme", "FIXME")));
825        }
826        return workinprogress;
827    }
828
829    /**
830     * Determines if key is considered "uninteresting".
831     * @param key The key to check
832     * @return true if key is considered "uninteresting".
833     */
834    public static boolean isUninterestingKey(String key) {
835        getUninterestingKeys();
836        if (uninteresting.contains(key))
837            return true;
838        int pos = key.indexOf(':');
839        if (pos > 0)
840            return uninteresting.contains(key.substring(0, pos + 1));
841        return false;
842    }
843
844    @Override
845    public Map<String, String> getInterestingTags() {
846        Map<String, String> result = new HashMap<>();
847        String[] keys = this.keys;
848        if (keys != null) {
849            for (int i = 0; i < keys.length; i += 2) {
850                if (!isUninterestingKey(keys[i])) {
851                    result.put(keys[i], keys[i + 1]);
852                }
853            }
854        }
855        return result;
856    }
857}
Note: See TracBrowser for help on using the repository browser.