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

Last change on this file since 14925 was 14925, checked in by Klumbumbus, 5 years ago

see #17512 - add osmarender:renderName, osmarender:renderRef and osmarender:rendernames to automatically discarded tags too

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