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

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

move hasSameInterestingTags() implementation from AbstractPrimitive to IPrimitive

  • Property svn:eol-style set to native
File size: 26.8 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    protected int timestamp;
196
197    /**
198     * Get and write all attributes from the parameter. Does not fire any listener, so
199     * use this only in the data initializing phase
200     * @param other the primitive to clone data from
201     */
202    public void cloneFrom(AbstractPrimitive other) {
203        setKeys(other.getKeys());
204        id = other.id;
205        if (id <= 0) {
206            // reset version and changeset id
207            version = 0;
208            changesetId = 0;
209        }
210        timestamp = other.timestamp;
211        if (id > 0) {
212            version = other.version;
213        }
214        flags = other.flags;
215        user = other.user;
216        if (id > 0 && other.changesetId > 0) {
217            // #4208: sometimes we cloned from other with id < 0 *and*
218            // an assigned changeset id. Don't know why yet. For primitives
219            // with id < 0 we don't propagate the changeset id any more.
220            //
221            setChangesetId(other.changesetId);
222        }
223    }
224
225    @Override
226    public int getVersion() {
227        return version;
228    }
229
230    @Override
231    public long getId() {
232        long id = this.id;
233        return id >= 0 ? id : 0;
234    }
235
236    /**
237     * Gets a unique id representing this object.
238     *
239     * @return Osm id if primitive already exists on the server. Unique negative value if primitive is new
240     */
241    @Override
242    public long getUniqueId() {
243        return id;
244    }
245
246    /**
247     * Determines if this primitive is new.
248     * @return {@code true} if this primitive is new (not yet uploaded the server, id &lt;= 0)
249     */
250    @Override
251    public boolean isNew() {
252        return id <= 0;
253    }
254
255    @Override
256    public boolean isNewOrUndeleted() {
257        return isNew() || ((flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0);
258    }
259
260    @Override
261    public void setOsmId(long id, int version) {
262        if (id <= 0)
263            throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
264        if (version <= 0)
265            throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
266        this.id = id;
267        this.version = version;
268        this.setIncomplete(false);
269    }
270
271    /**
272     * Clears the metadata, including id and version known to the OSM API.
273     * The id is a new unique id. The version, changeset and timestamp are set to 0.
274     * incomplete and deleted are set to false. It's preferred to use copy constructor with clearMetadata set to true instead
275     * of calling this method.
276     * @since 6140
277     */
278    public void clearOsmMetadata() {
279        // Not part of dataset - no lock necessary
280        this.id = generateUniqueId();
281        this.version = 0;
282        this.user = null;
283        this.changesetId = 0; // reset changeset id on a new object
284        this.timestamp = 0;
285        this.setIncomplete(false);
286        this.setDeleted(false);
287        this.setVisible(true);
288    }
289
290    @Override
291    public User getUser() {
292        return user;
293    }
294
295    @Override
296    public void setUser(User user) {
297        this.user = user;
298    }
299
300    @Override
301    public int getChangesetId() {
302        return changesetId;
303    }
304
305    @Override
306    public void setChangesetId(int changesetId) {
307        if (this.changesetId == changesetId)
308            return;
309        if (changesetId < 0)
310            throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' >= 0 expected, got {1}", "changesetId", changesetId));
311        if (changesetId > 0 && isNew())
312            throw new IllegalStateException(tr("Cannot assign a changesetId > 0 to a new primitive. Value of changesetId is {0}", changesetId));
313
314        this.changesetId = changesetId;
315    }
316
317    @Override
318    public void setTimestamp(Date timestamp) {
319        this.timestamp = (int) TimeUnit.MILLISECONDS.toSeconds(timestamp.getTime());
320    }
321
322    @Override
323    public void setRawTimestamp(int timestamp) {
324        this.timestamp = timestamp;
325    }
326
327    @Override
328    public Date getTimestamp() {
329        return new Date(TimeUnit.SECONDS.toMillis(timestamp));
330    }
331
332    @Override
333    public int getRawTimestamp() {
334        return timestamp;
335    }
336
337    @Override
338    public boolean isTimestampEmpty() {
339        return timestamp == 0;
340    }
341
342    /* -------
343    /* FLAGS
344    /* ------*/
345
346    protected void updateFlags(short flag, boolean value) {
347        if (value) {
348            flags |= flag;
349        } else {
350            flags &= (short) ~flag;
351        }
352    }
353
354    @Override
355    public void setModified(boolean modified) {
356        updateFlags(FLAG_MODIFIED, modified);
357    }
358
359    @Override
360    public boolean isModified() {
361        return (flags & FLAG_MODIFIED) != 0;
362    }
363
364    @Override
365    public boolean isDeleted() {
366        return (flags & FLAG_DELETED) != 0;
367    }
368
369    @Override
370    public boolean isUndeleted() {
371        return (flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0;
372    }
373
374    @Override
375    public boolean isUsable() {
376        return (flags & (FLAG_DELETED + FLAG_INCOMPLETE)) == 0;
377    }
378
379    @Override
380    public boolean isVisible() {
381        return (flags & FLAG_VISIBLE) != 0;
382    }
383
384    @Override
385    public void setVisible(boolean visible) {
386        if (!visible && isNew())
387            throw new IllegalStateException(tr("A primitive with ID = 0 cannot be invisible."));
388        updateFlags(FLAG_VISIBLE, visible);
389    }
390
391    @Override
392    public void setDeleted(boolean deleted) {
393        updateFlags(FLAG_DELETED, deleted);
394        setModified(deleted ^ !isVisible());
395    }
396
397    /**
398     * If set to true, this object is incomplete, which means only the id
399     * and type is known (type is the objects instance class)
400     * @param incomplete incomplete flag value
401     */
402    protected void setIncomplete(boolean incomplete) {
403        updateFlags(FLAG_INCOMPLETE, incomplete);
404    }
405
406    @Override
407    public boolean isIncomplete() {
408        return (flags & FLAG_INCOMPLETE) != 0;
409    }
410
411    protected String getFlagsAsString() {
412        StringBuilder builder = new StringBuilder();
413
414        if (isIncomplete()) {
415            builder.append('I');
416        }
417        if (isModified()) {
418            builder.append('M');
419        }
420        if (isVisible()) {
421            builder.append('V');
422        }
423        if (isDeleted()) {
424            builder.append('D');
425        }
426        return builder.toString();
427    }
428
429    /*------------
430     * Keys handling
431     ------------*/
432
433    /**
434     * The key/value list for this primitive.
435     * <p>
436     * Note that the keys field is synchronized using RCU.
437     * Writes to it are not synchronized by this object, the writers have to synchronize writes themselves.
438     * <p>
439     * In short this means that you should not rely on this variable being the same value when read again and your should always
440     * copy it on writes.
441     * <p>
442     * Further reading:
443     * <ul>
444     * <li>{@link java.util.concurrent.CopyOnWriteArrayList}</li>
445     * <li> <a href="http://stackoverflow.com/questions/2950871/how-can-copyonwritearraylist-be-thread-safe">
446     *     http://stackoverflow.com/questions/2950871/how-can-copyonwritearraylist-be-thread-safe</a></li>
447     * <li> <a href="https://en.wikipedia.org/wiki/Read-copy-update">
448     *     https://en.wikipedia.org/wiki/Read-copy-update</a> (mind that we have a Garbage collector,
449     *     {@code rcu_assign_pointer} and {@code rcu_dereference} are ensured by the {@code volatile} keyword)</li>
450     * </ul>
451     */
452    protected volatile String[] keys;
453
454    /**
455     * Replies the map of key/value pairs. Never replies null. The map can be empty, though.
456     *
457     * @return tags of this primitive. Changes made in returned map are not mapped
458     * back to the primitive, use setKeys() to modify the keys
459     * @see #visitKeys(KeyValueVisitor)
460     */
461    @Override
462    public TagMap getKeys() {
463        return new TagMap(keys);
464    }
465
466    @Override
467    public void visitKeys(KeyValueVisitor visitor) {
468        final String[] keys = this.keys;
469        if (keys != null) {
470            for (int i = 0; i < keys.length; i += 2) {
471                visitor.visitKeyValue(this, keys[i], keys[i + 1]);
472            }
473        }
474    }
475
476    /**
477     * Sets the keys of this primitives to the key/value pairs in <code>keys</code>.
478     * Old key/value pairs are removed.
479     * If <code>keys</code> is null, clears existing key/value pairs.
480     * <p>
481     * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
482     * from multiple threads.
483     *
484     * @param keys the key/value pairs to set. If null, removes all existing key/value pairs.
485     */
486    @Override
487    public void setKeys(Map<String, String> keys) {
488        Map<String, String> originalKeys = getKeys();
489        if (keys == null || keys.isEmpty()) {
490            this.keys = null;
491            keysChangedImpl(originalKeys);
492            return;
493        }
494        String[] newKeys = new String[keys.size() * 2];
495        int index = 0;
496        for (Entry<String, String> entry:keys.entrySet()) {
497            newKeys[index++] = entry.getKey();
498            newKeys[index++] = entry.getValue();
499        }
500        this.keys = newKeys;
501        keysChangedImpl(originalKeys);
502    }
503
504    /**
505     * Copy the keys from a TagMap.
506     * @param keys The new key map.
507     */
508    public void setKeys(TagMap keys) {
509        Map<String, String> originalKeys = getKeys();
510        if (keys == null) {
511            this.keys = null;
512        } else {
513            String[] arr = keys.getTagsArray();
514            if (arr.length == 0) {
515                this.keys = null;
516            } else {
517                this.keys = arr;
518            }
519        }
520        keysChangedImpl(originalKeys);
521    }
522
523    /**
524     * Set the given value to the given key. If key is null, does nothing. If value is null,
525     * removes the key and behaves like {@link #remove(String)}.
526     * <p>
527     * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
528     * from multiple threads.
529     *
530     * @param key  The key, for which the value is to be set. Can be null or empty, does nothing in this case.
531     * @param value The value for the key. If null, removes the respective key/value pair.
532     *
533     * @see #remove(String)
534     */
535    @Override
536    public void put(String key, String value) {
537        Map<String, String> originalKeys = getKeys();
538        if (key == null || Utils.isStripEmpty(key))
539            return;
540        else if (value == null) {
541            remove(key);
542        } else if (keys == null) {
543            keys = new String[] {key, value};
544            keysChangedImpl(originalKeys);
545        } else {
546            int keyIndex = indexOfKey(keys, key);
547            int tagArrayLength = keys.length;
548            if (keyIndex < 0) {
549                keyIndex = tagArrayLength;
550                tagArrayLength += 2;
551            }
552
553            // Do not try to optimize this array creation if the key already exists.
554            // We would need to convert the keys array to be an AtomicReferenceArray
555            // Or we would at least need a volatile write after the array was modified to
556            // ensure that changes are visible by other threads.
557            String[] newKeys = Arrays.copyOf(keys, tagArrayLength);
558            newKeys[keyIndex] = key;
559            newKeys[keyIndex + 1] = value;
560            keys = newKeys;
561            keysChangedImpl(originalKeys);
562        }
563    }
564
565    /**
566     * Scans a key/value array for a given key.
567     * @param keys The key array. It is not modified. It may be null to indicate an emtpy array.
568     * @param key The key to search for.
569     * @return The position of that key in the keys array - which is always a multiple of 2 - or -1 if it was not found.
570     */
571    private static int indexOfKey(String[] keys, String key) {
572        if (keys == null) {
573            return -1;
574        }
575        for (int i = 0; i < keys.length; i += 2) {
576            if (keys[i].equals(key)) {
577                return i;
578            }
579        }
580        return -1;
581    }
582
583    /**
584     * Remove the given key from the list
585     * <p>
586     * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
587     * from multiple threads.
588     *
589     * @param key  the key to be removed. Ignored, if key is null.
590     */
591    @Override
592    public void remove(String key) {
593        if (key == null || keys == null) return;
594        if (!hasKey(key))
595            return;
596        Map<String, String> originalKeys = getKeys();
597        if (keys.length == 2) {
598            keys = null;
599            keysChangedImpl(originalKeys);
600            return;
601        }
602        String[] newKeys = new String[keys.length - 2];
603        int j = 0;
604        for (int i = 0; i < keys.length; i += 2) {
605            if (!keys[i].equals(key)) {
606                newKeys[j++] = keys[i];
607                newKeys[j++] = keys[i+1];
608            }
609        }
610        keys = newKeys;
611        keysChangedImpl(originalKeys);
612    }
613
614    /**
615     * Removes all keys from this primitive.
616     * <p>
617     * Note that this method, like all methods that modify keys, is not synchronized and may lead to data corruption when being used
618     * from multiple threads.
619     */
620    @Override
621    public void removeAll() {
622        if (keys != null) {
623            Map<String, String> originalKeys = getKeys();
624            keys = null;
625            keysChangedImpl(originalKeys);
626        }
627    }
628
629    /**
630     * Replies the value for key <code>key</code>. Replies null, if <code>key</code> is null.
631     * Replies null, if there is no value for the given key.
632     *
633     * @param key the key. Can be null, replies null in this case.
634     * @return the value for key <code>key</code>.
635     */
636    @Override
637    public final String get(String key) {
638        String[] keys = this.keys;
639        if (key == null)
640            return null;
641        if (keys == null)
642            return null;
643        for (int i = 0; i < keys.length; i += 2) {
644            if (keys[i].equals(key)) return keys[i+1];
645        }
646        return null;
647    }
648
649    /**
650     * Gets a key ignoring the case of the key
651     * @param key The key to get
652     * @return The value for a key that matches the given key ignoring case.
653     */
654    public final String getIgnoreCase(String key) {
655        String[] keys = this.keys;
656        if (key == null)
657            return null;
658        if (keys == null)
659            return null;
660        for (int i = 0; i < keys.length; i += 2) {
661            if (keys[i].equalsIgnoreCase(key)) return keys[i+1];
662        }
663        return null;
664    }
665
666    @Override
667    public final int getNumKeys() {
668        String[] keys = this.keys;
669        return keys == null ? 0 : keys.length / 2;
670    }
671
672    @Override
673    public final Collection<String> keySet() {
674        final String[] keys = this.keys;
675        if (keys == null) {
676            return Collections.emptySet();
677        }
678        if (keys.length == 1) {
679            return Collections.singleton(keys[0]);
680        }
681
682        final Set<String> result = new HashSet<>(Utils.hashMapInitialCapacity(keys.length / 2));
683        for (int i = 0; i < keys.length; i += 2) {
684            result.add(keys[i]);
685        }
686        return result;
687    }
688
689    /**
690     * Replies true, if the map of key/value pairs of this primitive is not empty.
691     *
692     * @return true, if the map of key/value pairs of this primitive is not empty; false otherwise
693     */
694    @Override
695    public final boolean hasKeys() {
696        return keys != null;
697    }
698
699    /**
700     * Replies true if this primitive has a tag with key <code>key</code>.
701     *
702     * @param key the key
703     * @return true, if this primitive has a tag with key <code>key</code>
704     */
705    @Override
706    public boolean hasKey(String key) {
707        return key != null && indexOfKey(keys, key) >= 0;
708    }
709
710    /**
711     * Replies true if this primitive has a tag any of the <code>keys</code>.
712     *
713     * @param keys the keys
714     * @return true, if this primitive has a tag with any of the <code>keys</code>
715     * @since 11587
716     */
717    public boolean hasKey(String... keys) {
718        return keys != null && Arrays.stream(keys).anyMatch(this::hasKey);
719    }
720
721    /**
722     * What to do, when the tags have changed by one of the tag-changing methods.
723     * @param originalKeys original tags
724     */
725    protected abstract void keysChangedImpl(Map<String, String> originalKeys);
726
727    /*-------------------------------------
728     * WORK IN PROGRESS, UNINTERESTING KEYS
729     *-------------------------------------*/
730
731    private static volatile Collection<String> workinprogress;
732    private static volatile Collection<String> uninteresting;
733    private static volatile Collection<String> discardable;
734
735    /**
736     * Returns a list of "uninteresting" keys that do not make an object
737     * "tagged".  Entries that end with ':' are causing a whole namespace to be considered
738     * "uninteresting".  Only the first level namespace is considered.
739     * Initialized by isUninterestingKey()
740     * @return The list of uninteresting keys.
741     */
742    public static Collection<String> getUninterestingKeys() {
743        if (uninteresting == null) {
744            List<String> l = new LinkedList<>(Arrays.asList(
745                "source", "source_ref", "source:", "comment",
746                "watch", "watch:", "description", "attribution"));
747            l.addAll(getDiscardableKeys());
748            l.addAll(getWorkInProgressKeys());
749            uninteresting = new HashSet<>(Config.getPref().getList("tags.uninteresting", l));
750        }
751        return uninteresting;
752    }
753
754    /**
755     * Returns a list of keys which have been deemed uninteresting to the point
756     * that they can be silently removed from data which is being edited.
757     * @return The list of discardable keys.
758     */
759    public static Collection<String> getDiscardableKeys() {
760        if (discardable == null) {
761            discardable = new HashSet<>(Config.getPref().getList("tags.discardable",
762                    Arrays.asList(
763                            "created_by",
764                            "converted_by",
765                            "geobase:datasetName",
766                            "geobase:uuid",
767                            "KSJ2:ADS",
768                            "KSJ2:ARE",
769                            "KSJ2:AdminArea",
770                            "KSJ2:COP_label",
771                            "KSJ2:DFD",
772                            "KSJ2:INT",
773                            "KSJ2:INT_label",
774                            "KSJ2:LOC",
775                            "KSJ2:LPN",
776                            "KSJ2:OPC",
777                            "KSJ2:PubFacAdmin",
778                            "KSJ2:RAC",
779                            "KSJ2:RAC_label",
780                            "KSJ2:RIC",
781                            "KSJ2:RIN",
782                            "KSJ2:WSC",
783                            "KSJ2:coordinate",
784                            "KSJ2:curve_id",
785                            "KSJ2:curve_type",
786                            "KSJ2:filename",
787                            "KSJ2:lake_id",
788                            "KSJ2:lat",
789                            "KSJ2:long",
790                            "KSJ2:river_id",
791                            "odbl",
792                            "odbl:note",
793                            "SK53_bulk:load",
794                            "sub_sea:type",
795                            "tiger:source",
796                            "tiger:separated",
797                            "tiger:tlid",
798                            "tiger:upload_uuid",
799                            "yh:LINE_NAME",
800                            "yh:LINE_NUM",
801                            "yh:STRUCTURE",
802                            "yh:TOTYUMONO",
803                            "yh:TYPE",
804                            "yh:WIDTH",
805                            "yh:WIDTH_RANK"
806                        )));
807        }
808        return discardable;
809    }
810
811    /**
812     * Returns a list of "work in progress" keys that do not make an object
813     * "tagged" but "annotated".
814     * @return The list of work in progress keys.
815     * @since 5754
816     */
817    public static Collection<String> getWorkInProgressKeys() {
818        if (workinprogress == null) {
819            workinprogress = new HashSet<>(Config.getPref().getList("tags.workinprogress",
820                    Arrays.asList("note", "fixme", "FIXME")));
821        }
822        return workinprogress;
823    }
824
825    /**
826     * Determines if key is considered "uninteresting".
827     * @param key The key to check
828     * @return true if key is considered "uninteresting".
829     */
830    public static boolean isUninterestingKey(String key) {
831        getUninterestingKeys();
832        if (uninteresting.contains(key))
833            return true;
834        int pos = key.indexOf(':');
835        if (pos > 0)
836            return uninteresting.contains(key.substring(0, pos + 1));
837        return false;
838    }
839
840    @Override
841    public Map<String, String> getInterestingTags() {
842        Map<String, String> result = new HashMap<>();
843        String[] keys = this.keys;
844        if (keys != null) {
845            for (int i = 0; i < keys.length; i += 2) {
846                if (!isUninterestingKey(keys[i])) {
847                    result.put(keys[i], keys[i + 1]);
848                }
849            }
850        }
851        return result;
852    }
853}
Note: See TracBrowser for help on using the repository browser.