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, 5 years 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
RevLine 
[5927]1// License: GPL. For details, see LICENSE file.
[4099]2package org.openstreetmap.josm.data.osm;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.text.MessageFormat;
[4682]7import java.util.Arrays;
[4099]8import java.util.Collection;
9import java.util.Collections;
10import java.util.Date;
[13809]11import java.util.HashMap;
[4099]12import java.util.HashSet;
[13809]13import java.util.LinkedList;
14import java.util.List;
[4099]15import java.util.Map;
16import java.util.Map.Entry;
17import java.util.Set;
[11288]18import java.util.concurrent.TimeUnit;
[4099]19import java.util.concurrent.atomic.AtomicLong;
20
[13809]21import org.openstreetmap.josm.spi.preferences.Config;
[7783]22import org.openstreetmap.josm.tools.Utils;
23
[5417]24/**
[13637]25 * Abstract class to represent common features of the datatypes primitives.
26 *
27 * @since 4099
28 */
[4099]29public abstract class AbstractPrimitive implements IPrimitive {
[4431]30
[12542]31 private static final AtomicLong idCounter = new AtomicLong(0);
[4099]32
[12536]33 /**
34 * Generates a new primitive unique id.
35 * @return new primitive unique (negative) id
36 */
[4099]37 static long generateUniqueId() {
[12542]38 return idCounter.decrementAndGet();
[4099]39 }
40
41 /**
[12536]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() {
[12542]47 return idCounter.get();
[12536]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 }
[12542]60 idCounter.set(newId);
[12536]61 }
62
63 /**
[4099]64 * This flag shows, that the properties have been changed by the user
[5251]65 * and on upload the object will be send to the server.
[4099]66 */
[11369]67 protected static final short FLAG_MODIFIED = 1 << 0;
[4099]68
69 /**
[5251]70 * This flag is false, if the object is marked
[4099]71 * as deleted on the server.
72 */
[11369]73 protected static final short FLAG_VISIBLE = 1 << 1;
[4099]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 */
[11369]82 protected static final short FLAG_DELETED = 1 << 2;
[4099]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 */
[11369]89 protected static final short FLAG_INCOMPLETE = 1 << 3;
[4099]90
91 /**
[11373]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 /**
[13309]155 * Determines if the primitive is preserved from the filter mechanism.
156 */
157 protected static final short FLAG_PRESERVED = 1 << 13;
158
159 /**
[5251]160 * Put several boolean flags to one short int field to save memory.
[4099]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 */
[8840]174 protected long id;
[4099]175
176 /**
177 * User that last modified this primitive, as specified by the server.
178 * Never changed by JOSM.
179 */
[8840]180 protected User user;
[4099]181
182 /**
183 * Contains the version number as returned by the API. Needed to
184 * ensure update consistency
185 */
[8840]186 protected int version;
[4099]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
[14434]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 */
[4099]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
[5417]204 * @param other the primitive to clone data from
[4099]205 */
206 public void cloneFrom(AbstractPrimitive other) {
207 setKeys(other.getKeys());
208 id = other.id;
[8510]209 if (id <= 0) {
[4099]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;
[8510]219 user = other.user;
[4099]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;
[8510]237 return id >= 0 ? id : 0;
[4099]238 }
239
240 /**
[5417]241 * Gets a unique id representing this object.
[4099]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 /**
[9460]251 * Determines if this primitive is new.
252 * @return {@code true} if this primitive is new (not yet uploaded the server, id &lt;= 0)
[4099]253 */
254 @Override
255 public boolean isNew() {
256 return id <= 0;
257 }
258
[4100]259 @Override
[4099]260 public boolean isNewOrUndeleted() {
[10583]261 return isNew() || ((flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0);
[4099]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 /**
[6140]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
[4099]279 * of calling this method.
[6140]280 * @since 6140
[4099]281 */
[6140]282 public void clearOsmMetadata() {
[4099]283 // Not part of dataset - no lock necessary
284 this.id = generateUniqueId();
285 this.version = 0;
[4150]286 this.user = null;
[4099]287 this.changesetId = 0; // reset changeset id on a new object
[6140]288 this.timestamp = 0;
[4099]289 this.setIncomplete(false);
[6140]290 this.setDeleted(false);
291 this.setVisible(true);
[4099]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
[8291]310 public void setChangesetId(int changesetId) {
[4099]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));
[11452]315 if (changesetId > 0 && isNew())
[4099]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
[4100]321 @Override
[4099]322 public void setTimestamp(Date timestamp) {
[11288]323 this.timestamp = (int) TimeUnit.MILLISECONDS.toSeconds(timestamp.getTime());
[4099]324 }
325
[8565]326 @Override
327 public void setRawTimestamp(int timestamp) {
328 this.timestamp = timestamp;
329 }
330
[4099]331 @Override
332 public Date getTimestamp() {
[14434]333 return new Date(TimeUnit.SECONDS.toMillis(Integer.toUnsignedLong(timestamp)));
[4099]334 }
335
336 @Override
[8565]337 public int getRawTimestamp() {
338 return timestamp;
339 }
340
341 @Override
[4099]342 public boolean isTimestampEmpty() {
343 return timestamp == 0;
344 }
345
346 /* -------
347 /* FLAGS
348 /* ------*/
349
[11294]350 protected void updateFlags(short flag, boolean value) {
[4099]351 if (value) {
352 flags |= flag;
353 } else {
[11940]354 flags &= (short) ~flag;
[4099]355 }
356 }
[4431]357
[4099]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
[9460]373 @Override
[4099]374 public boolean isUndeleted() {
375 return (flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0;
376 }
377
[9460]378 @Override
[4099]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 }
[4431]387
[4099]388 @Override
[8291]389 public void setVisible(boolean visible) {
[11452]390 if (!visible && isNew())
[4099]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)
[9243]404 * @param incomplete incomplete flag value
[4099]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 }
[4431]414
[4099]415 protected String getFlagsAsString() {
416 StringBuilder builder = new StringBuilder();
417
418 if (isIncomplete()) {
[8390]419 builder.append('I');
[4099]420 }
421 if (isModified()) {
[8390]422 builder.append('M');
[4099]423 }
424 if (isVisible()) {
[8390]425 builder.append('V');
[4099]426 }
427 if (isDeleted()) {
[8390]428 builder.append('D');
[4099]429 }
430 return builder.toString();
431 }
432
433 /*------------
434 * Keys handling
435 ------------*/
436
437 /**
438 * The key/value list for this primitive.
[9267]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>
[4099]455 */
[9267]456 protected volatile String[] keys;
[4099]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
[8740]463 * @see #visitKeys(KeyValueVisitor)
[4099]464 */
465 @Override
[9649]466 public TagMap getKeys() {
467 return new TagMap(keys);
[4099]468 }
469
[13668]470 @Override
[8740]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) {
[8742]475 visitor.visitKeyValue(this, keys[i], keys[i + 1]);
[8740]476 }
477 }
478 }
479
480 /**
[4099]481 * Sets the keys of this primitives to the key/value pairs in <code>keys</code>.
[4103]482 * Old key/value pairs are removed.
483 * If <code>keys</code> is null, clears existing key/value pairs.
[9267]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.
[4099]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 /**
[9649]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();
[9656]514 if (keys == null) {
[9649]515 this.keys = null;
516 } else {
[9656]517 String[] arr = keys.getTagsArray();
518 if (arr.length == 0) {
519 this.keys = null;
520 } else {
521 this.keys = arr;
522 }
[9649]523 }
524 keysChangedImpl(originalKeys);
525 }
526
527 /**
[4099]528 * Set the given value to the given key. If key is null, does nothing. If value is null,
[5266]529 * removes the key and behaves like {@link #remove(String)}.
[9267]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.
[4099]533 *
[7783]534 * @param key The key, for which the value is to be set. Can be null or empty, does nothing in this case.
[4099]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();
[11435]542 if (key == null || Utils.isStripEmpty(key))
[4099]543 return;
544 else if (value == null) {
545 remove(key);
[8510]546 } else if (keys == null) {
[4099]547 keys = new String[] {key, value};
548 keysChangedImpl(originalKeys);
549 } else {
[9267]550 int keyIndex = indexOfKey(keys, key);
551 int tagArrayLength = keys.length;
552 if (keyIndex < 0) {
553 keyIndex = tagArrayLength;
554 tagArrayLength += 2;
[4099]555 }
[9267]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;
[4099]564 keys = newKeys;
565 keysChangedImpl(originalKeys);
566 }
567 }
[4431]568
[4099]569 /**
[9267]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 /**
[4099]588 * Remove the given key from the list
[9267]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.
[4099]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];
[8510]607 int j = 0;
608 for (int i = 0; i < keys.length; i += 2) {
[4099]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.
[9267]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.
[4099]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;
[8510]647 for (int i = 0; i < keys.length; i += 2) {
[4099]648 if (keys[i].equals(key)) return keys[i+1];
649 }
650 return null;
651 }
652
[6579]653 /**
[12190]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 */
[4431]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;
[8510]664 for (int i = 0; i < keys.length; i += 2) {
[4431]665 if (keys[i].equalsIgnoreCase(key)) return keys[i+1];
666 }
667 return null;
668 }
669
[13625]670 @Override
[7237]671 public final int getNumKeys() {
[9267]672 String[] keys = this.keys;
[7237]673 return keys == null ? 0 : keys.length / 2;
674 }
[7509]675
[4099]676 @Override
677 public final Collection<String> keySet() {
[8574]678 final String[] keys = this.keys;
[8582]679 if (keys == null) {
[4099]680 return Collections.emptySet();
[8574]681 }
[8582]682 if (keys.length == 1) {
[8574]683 return Collections.singleton(keys[0]);
684 }
685
686 final Set<String> result = new HashSet<>(Utils.hashMapInitialCapacity(keys.length / 2));
[8510]687 for (int i = 0; i < keys.length; i += 2) {
[4099]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 *
[11608]696 * @return true, if the map of key/value pairs of this primitive is not empty; false otherwise
[4099]697 */
698 @Override
699 public final boolean hasKeys() {
700 return keys != null;
701 }
702
703 /**
[5417]704 * Replies true if this primitive has a tag with key <code>key</code>.
[4099]705 *
706 * @param key the key
[11608]707 * @return true, if this primitive has a tag with key <code>key</code>
[4099]708 */
[11608]709 @Override
[4099]710 public boolean hasKey(String key) {
[9267]711 return key != null && indexOfKey(keys, key) >= 0;
[4099]712 }
713
714 /**
[11587]715 * Replies true if this primitive has a tag any of the <code>keys</code>.
716 *
717 * @param keys the keys
[11608]718 * @return true, if this primitive has a tag with any of the <code>keys</code>
[11587]719 * @since 11587
720 */
[11747]721 public boolean hasKey(String... keys) {
[11587]722 return keys != null && Arrays.stream(keys).anyMatch(this::hasKey);
723 }
724
725 /**
[4099]726 * What to do, when the tags have changed by one of the tag-changing methods.
[9243]727 * @param originalKeys original tags
[4099]728 */
[6889]729 protected abstract void keysChangedImpl(Map<String, String> originalKeys);
[4431]730
[13809]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 }
[4099]857}
Note: See TracBrowser for help on using the repository browser.