source: josm/trunk/src/org/openstreetmap/josm/data/osm/TagCollection.java@ 14302

Last change on this file since 14302 was 14302, checked in by Don-vip, 6 years ago

see #16781 - fix more typos using https://github.com/vlajos/misspell-fixer

  • Property svn:eol-style set to native
File size: 24.7 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.io.Serializable;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.HashMap;
11import java.util.HashSet;
12import java.util.Iterator;
13import java.util.LinkedHashMap;
14import java.util.LinkedHashSet;
15import java.util.List;
16import java.util.Map;
17import java.util.Map.Entry;
18import java.util.Objects;
19import java.util.Set;
20import java.util.regex.Pattern;
21import java.util.stream.Collectors;
22import java.util.stream.Stream;
23
24import org.openstreetmap.josm.tools.Logging;
25import org.openstreetmap.josm.tools.Utils;
26
27/**
28 * TagCollection is a collection of tags which can be used to manipulate
29 * tags managed by {@link org.openstreetmap.josm.data.osm.OsmPrimitive}s.
30 *
31 * A TagCollection can be created:
32 * <ul>
33 * <li>from the tags managed by a specific {@link org.openstreetmap.josm.data.osm.OsmPrimitive}
34 * with {@link #from(org.openstreetmap.josm.data.osm.Tagged)}</li>
35 * <li>from the union of all tags managed by a collection of {@link org.openstreetmap.josm.data.osm.OsmPrimitive}s
36 * with {@link #unionOfAllPrimitives(java.util.Collection)}</li>
37 * <li>from the union of all tags managed by a {@link org.openstreetmap.josm.data.osm.DataSet}
38 * with {@link #unionOfAllPrimitives(org.openstreetmap.josm.data.osm.DataSet)}</li>
39 * <li>from the intersection of all tags managed by a collection of primitives
40 * with {@link #commonToAllPrimitives(java.util.Collection)}</li>
41 * </ul>
42 *
43 * It provides methods to query the collection, like {@link #size()}, {@link #hasTagsFor(String)}, etc.
44 *
45 * Basic set operations allow to create the union, the intersection and the difference
46 * of tag collections, see {@link #union(org.openstreetmap.josm.data.osm.TagCollection)},
47 * {@link #intersect(org.openstreetmap.josm.data.osm.TagCollection)}, and {@link #minus(org.openstreetmap.josm.data.osm.TagCollection)}.
48 *
49 * @since 2008
50 */
51public class TagCollection implements Iterable<Tag>, Serializable {
52
53 private static final long serialVersionUID = 1;
54
55 /**
56 * Creates a tag collection from the tags managed by a specific
57 * {@link org.openstreetmap.josm.data.osm.OsmPrimitive}. If <code>primitive</code> is null, replies
58 * an empty tag collection.
59 *
60 * @param primitive the primitive
61 * @return a tag collection with the tags managed by a specific
62 * {@link org.openstreetmap.josm.data.osm.OsmPrimitive}
63 */
64 public static TagCollection from(Tagged primitive) {
65 TagCollection tags = new TagCollection();
66 if (primitive != null) {
67 for (String key: primitive.keySet()) {
68 tags.add(new Tag(key, primitive.get(key)));
69 }
70 }
71 return tags;
72 }
73
74 /**
75 * Creates a tag collection from a map of key/value-pairs. Replies
76 * an empty tag collection if {@code tags} is null.
77 *
78 * @param tags the key/value-pairs
79 * @return the tag collection
80 */
81 public static TagCollection from(Map<String, String> tags) {
82 TagCollection ret = new TagCollection();
83 if (tags == null) return ret;
84 for (Entry<String, String> entry: tags.entrySet()) {
85 String key = entry.getKey() == null ? "" : entry.getKey();
86 String value = entry.getValue() == null ? "" : entry.getValue();
87 ret.add(new Tag(key, value));
88 }
89 return ret;
90 }
91
92 /**
93 * Creates a tag collection from the union of the tags managed by
94 * a collection of primitives. Replies an empty tag collection,
95 * if <code>primitives</code> is null.
96 *
97 * @param primitives the primitives
98 * @return a tag collection with the union of the tags managed by
99 * a collection of primitives
100 */
101 public static TagCollection unionOfAllPrimitives(Collection<? extends Tagged> primitives) {
102 TagCollection tags = new TagCollection();
103 if (primitives == null) return tags;
104 for (Tagged primitive: primitives) {
105 if (primitive == null) {
106 continue;
107 }
108 tags.add(TagCollection.from(primitive));
109 }
110 return tags;
111 }
112
113 /**
114 * Replies a tag collection with the tags which are common to all primitives in in
115 * <code>primitives</code>. Replies an empty tag collection of <code>primitives</code>
116 * is null.
117 *
118 * @param primitives the primitives
119 * @return a tag collection with the tags which are common to all primitives
120 */
121 public static TagCollection commonToAllPrimitives(Collection<? extends Tagged> primitives) {
122 TagCollection tags = new TagCollection();
123 if (primitives == null || primitives.isEmpty()) return tags;
124 // initialize with the first
125 //
126 tags.add(TagCollection.from(primitives.iterator().next()));
127
128 // intersect with the others
129 //
130 for (Tagged primitive: primitives) {
131 if (primitive == null) {
132 continue;
133 }
134 tags.add(tags.intersect(TagCollection.from(primitive)));
135 }
136 return tags;
137 }
138
139 /**
140 * Replies a tag collection with the union of the tags which are common to all primitives in
141 * the dataset <code>ds</code>. Returns an empty tag collection of <code>ds</code> is null.
142 *
143 * @param ds the dataset
144 * @return a tag collection with the union of the tags which are common to all primitives in
145 * the dataset <code>ds</code>
146 */
147 public static TagCollection unionOfAllPrimitives(DataSet ds) {
148 TagCollection tags = new TagCollection();
149 if (ds == null) return tags;
150 tags.add(TagCollection.unionOfAllPrimitives(ds.allPrimitives()));
151 return tags;
152 }
153
154 private final Map<Tag, Integer> tags = new HashMap<>();
155
156 /**
157 * Creates an empty tag collection.
158 */
159 public TagCollection() {
160 // contents can be set later with add()
161 }
162
163 /**
164 * Creates a clone of the tag collection <code>other</code>. Creats an empty
165 * tag collection if <code>other</code> is null.
166 *
167 * @param other the other collection
168 */
169 public TagCollection(TagCollection other) {
170 if (other != null) {
171 tags.putAll(other.tags);
172 }
173 }
174
175 /**
176 * Creates a tag collection from <code>tags</code>.
177 * @param tags the collection of tags
178 * @since 5724
179 */
180 public TagCollection(Collection<Tag> tags) {
181 add(tags);
182 }
183
184 /**
185 * Replies the number of tags in this tag collection
186 *
187 * @return the number of tags in this tag collection
188 */
189 public int size() {
190 return tags.size();
191 }
192
193 /**
194 * Replies true if this tag collection is empty
195 *
196 * @return true if this tag collection is empty; false, otherwise
197 */
198 public boolean isEmpty() {
199 return size() == 0;
200 }
201
202 /**
203 * Adds a tag to the tag collection. If <code>tag</code> is null, nothing is added.
204 *
205 * @param tag the tag to add
206 */
207 public final void add(Tag tag) {
208 if (tag != null) {
209 tags.merge(tag, 1, (i, j) -> i + j);
210 }
211 }
212
213 /**
214 * Gets the number of this this tag was added to the collection.
215 * @param tag The tag
216 * @return The number of thimes this tag is used in this collection.
217 * @since 10736
218 * @deprecated use {@link #getTagOccurrence}
219 */
220 @Deprecated
221 public int getTagOccurence(Tag tag) {
222 return getTagOccurrence(tag);
223 }
224
225 /**
226 * Gets the number of this this tag was added to the collection.
227 * @param tag The tag
228 * @return The number of thimes this tag is used in this collection.
229 * @since 14301
230 */
231 public int getTagOccurrence(Tag tag) {
232 return tags.getOrDefault(tag, 0);
233 }
234
235 /**
236 * Adds a collection of tags to the tag collection. If <code>tags</code> is null, nothing
237 * is added. null values in the collection are ignored.
238 *
239 * @param tags the collection of tags
240 */
241 public final void add(Collection<Tag> tags) {
242 if (tags == null) return;
243 for (Tag tag: tags) {
244 add(tag);
245 }
246 }
247
248 /**
249 * Adds the tags of another tag collection to this collection. Adds nothing, if
250 * <code>tags</code> is null.
251 *
252 * @param tags the other tag collection
253 */
254 public final void add(TagCollection tags) {
255 if (tags != null) {
256 for (Entry<Tag, Integer> entry : tags.tags.entrySet()) {
257 this.tags.merge(entry.getKey(), entry.getValue(), (i, j) -> i + j);
258 }
259 }
260 }
261
262 /**
263 * Removes a specific tag from the tag collection. Does nothing if <code>tag</code> is
264 * null.
265 *
266 * @param tag the tag to be removed
267 */
268 public void remove(Tag tag) {
269 if (tag == null) return;
270 tags.remove(tag);
271 }
272
273 /**
274 * Removes a collection of tags from the tag collection. Does nothing if <code>tags</code> is
275 * null.
276 *
277 * @param tags the tags to be removed
278 */
279 public void remove(Collection<Tag> tags) {
280 if (tags != null) {
281 tags.stream().forEach(this::remove);
282 }
283 }
284
285 /**
286 * Removes all tags in the tag collection <code>tags</code> from the current tag collection.
287 * Does nothing if <code>tags</code> is null.
288 *
289 * @param tags the tag collection to be removed.
290 */
291 public void remove(TagCollection tags) {
292 if (tags != null) {
293 tags.tags.keySet().stream().forEach(this::remove);
294 }
295 }
296
297 /**
298 * Removes all tags whose keys are equal to <code>key</code>. Does nothing if <code>key</code>
299 * is null.
300 *
301 * @param key the key to be removed
302 */
303 public void removeByKey(String key) {
304 if (key != null) {
305 tags.keySet().removeIf(tag -> tag.matchesKey(key));
306 }
307 }
308
309 /**
310 * Removes all tags whose key is in the collection <code>keys</code>. Does nothing if
311 * <code>keys</code> is null.
312 *
313 * @param keys the collection of keys to be removed
314 */
315 public void removeByKey(Collection<String> keys) {
316 if (keys == null) return;
317 for (String key: keys) {
318 removeByKey(key);
319 }
320 }
321
322 /**
323 * Replies true if the this tag collection contains <code>tag</code>.
324 *
325 * @param tag the tag to look up
326 * @return true if the this tag collection contains <code>tag</code>; false, otherwise
327 */
328 public boolean contains(Tag tag) {
329 return tags.containsKey(tag);
330 }
331
332 /**
333 * Replies true if this tag collection contains all tags in <code>tags</code>. Replies
334 * false, if tags is null.
335 *
336 * @param tags the tags to look up
337 * @return true if this tag collection contains all tags in <code>tags</code>. Replies
338 * false, if tags is null.
339 */
340 public boolean containsAll(Collection<Tag> tags) {
341 if (tags == null) {
342 return false;
343 } else {
344 return this.tags.keySet().containsAll(tags);
345 }
346 }
347
348 /**
349 * Replies true if this tag collection at least one tag for every key in <code>keys</code>.
350 * Replies false, if <code>keys</code> is null. null values in <code>keys</code> are ignored.
351 *
352 * @param keys the keys to lookup
353 * @return true if this tag collection at least one tag for every key in <code>keys</code>.
354 */
355 public boolean containsAllKeys(Collection<String> keys) {
356 if (keys == null) {
357 return false;
358 } else {
359 return keys.stream().filter(Objects::nonNull).allMatch(this::hasTagsFor);
360 }
361 }
362
363 /**
364 * Replies the number of tags with key <code>key</code>
365 *
366 * @param key the key to look up
367 * @return the number of tags with key <code>key</code>, including the empty "" value. 0, if key is null.
368 */
369 public int getNumTagsFor(String key) {
370 return (int) generateStreamForKey(key).count();
371 }
372
373 /**
374 * Replies true if there is at least one tag for the given key.
375 *
376 * @param key the key to look up
377 * @return true if there is at least one tag for the given key. false, if key is null.
378 */
379 public boolean hasTagsFor(String key) {
380 return getNumTagsFor(key) > 0;
381 }
382
383 /**
384 * Replies true it there is at least one tag with a non empty value for key.
385 * Replies false if key is null.
386 *
387 * @param key the key
388 * @return true it there is at least one tag with a non empty value for key.
389 */
390 public boolean hasValuesFor(String key) {
391 return generateStreamForKey(key).anyMatch(t -> !t.getValue().isEmpty());
392 }
393
394 /**
395 * Replies true if there is exactly one tag for <code>key</code> and
396 * if the value of this tag is not empty. Replies false if key is
397 * null.
398 *
399 * @param key the key
400 * @return true if there is exactly one tag for <code>key</code> and
401 * if the value of this tag is not empty
402 */
403 public boolean hasUniqueNonEmptyValue(String key) {
404 return generateStreamForKey(key).filter(t -> !t.getValue().isEmpty()).count() == 1;
405 }
406
407 /**
408 * Replies true if there is a tag with an empty value for <code>key</code>.
409 * Replies false, if key is null.
410 *
411 * @param key the key
412 * @return true if there is a tag with an empty value for <code>key</code>
413 */
414 public boolean hasEmptyValue(String key) {
415 return generateStreamForKey(key).anyMatch(t -> t.getValue().isEmpty());
416 }
417
418 /**
419 * Replies true if there is exactly one tag for <code>key</code> and if
420 * the value for this tag is empty. Replies false if key is null.
421 *
422 * @param key the key
423 * @return true if there is exactly one tag for <code>key</code> and if
424 * the value for this tag is empty
425 */
426 public boolean hasUniqueEmptyValue(String key) {
427 Set<String> values = getValues(key);
428 return values.size() == 1 && values.contains("");
429 }
430
431 /**
432 * Replies a tag collection with the tags for a given key. Replies an empty collection
433 * if key is null.
434 *
435 * @param key the key to look up
436 * @return a tag collection with the tags for a given key. Replies an empty collection
437 * if key is null.
438 */
439 public TagCollection getTagsFor(String key) {
440 TagCollection ret = new TagCollection();
441 generateStreamForKey(key).forEach(ret::add);
442 return ret;
443 }
444
445 /**
446 * Replies a tag collection with all tags whose key is equal to one of the keys in
447 * <code>keys</code>. Replies an empty collection if keys is null.
448 *
449 * @param keys the keys to look up
450 * @return a tag collection with all tags whose key is equal to one of the keys in
451 * <code>keys</code>
452 */
453 public TagCollection getTagsFor(Collection<String> keys) {
454 TagCollection ret = new TagCollection();
455 if (keys == null)
456 return ret;
457 for (String key : keys) {
458 if (key != null) {
459 ret.add(getTagsFor(key));
460 }
461 }
462 return ret;
463 }
464
465 /**
466 * Replies the tags of this tag collection as set
467 *
468 * @return the tags of this tag collection as set
469 */
470 public Set<Tag> asSet() {
471 return new HashSet<>(tags.keySet());
472 }
473
474 /**
475 * Replies the tags of this tag collection as list.
476 * Note that the order of the list is not preserved between method invocations.
477 *
478 * @return the tags of this tag collection as list. There are no dupplicate values.
479 */
480 public List<Tag> asList() {
481 return new ArrayList<>(tags.keySet());
482 }
483
484 /**
485 * Replies an iterator to iterate over the tags in this collection
486 *
487 * @return the iterator
488 */
489 @Override
490 public Iterator<Tag> iterator() {
491 return tags.keySet().iterator();
492 }
493
494 /**
495 * Replies the set of keys of this tag collection.
496 *
497 * @return the set of keys of this tag collection
498 */
499 public Set<String> getKeys() {
500 return generateKeyStream().collect(Collectors.toCollection(HashSet::new));
501 }
502
503 /**
504 * Replies the set of keys which have at least 2 matching tags.
505 *
506 * @return the set of keys which have at least 2 matching tags.
507 */
508 public Set<String> getKeysWithMultipleValues() {
509 HashSet<String> singleKeys = new HashSet<>();
510 return generateKeyStream().filter(key -> !singleKeys.add(key)).collect(Collectors.toSet());
511 }
512
513 /**
514 * Sets a unique tag for the key of this tag. All other tags with the same key are
515 * removed from the collection. Does nothing if tag is null.
516 *
517 * @param tag the tag to set
518 */
519 public void setUniqueForKey(Tag tag) {
520 if (tag == null) return;
521 removeByKey(tag.getKey());
522 add(tag);
523 }
524
525 /**
526 * Sets a unique tag for the key of this tag. All other tags with the same key are
527 * removed from the collection. Assume the empty string for key and value if either
528 * key or value is null.
529 *
530 * @param key the key
531 * @param value the value
532 */
533 public void setUniqueForKey(String key, String value) {
534 Tag tag = new Tag(key, value);
535 setUniqueForKey(tag);
536 }
537
538 /**
539 * Replies the set of values in this tag collection
540 *
541 * @return the set of values
542 */
543 public Set<String> getValues() {
544 return tags.keySet().stream().map(Tag::getValue).collect(Collectors.toSet());
545 }
546
547 /**
548 * Replies the set of values for a given key. Replies an empty collection if there
549 * are no values for the given key.
550 *
551 * @param key the key to look up
552 * @return the set of values for a given key. Replies an empty collection if there
553 * are no values for the given key
554 */
555 public Set<String> getValues(String key) {
556 // null-safe
557 return generateStreamForKey(key).map(Tag::getValue).collect(Collectors.toSet());
558 }
559
560 /**
561 * Replies true if for every key there is one tag only, i.e. exactly one value.
562 *
563 * @return {@code true} if for every key there is one tag only
564 */
565 public boolean isApplicableToPrimitive() {
566 return getKeysWithMultipleValues().isEmpty();
567 }
568
569 /**
570 * Applies this tag collection to an {@link org.openstreetmap.josm.data.osm.OsmPrimitive}. Does nothing if
571 * primitive is null
572 *
573 * @param primitive the primitive
574 * @throws IllegalStateException if this tag collection can't be applied
575 * because there are keys with multiple values
576 */
577 public void applyTo(Tagged primitive) {
578 if (primitive == null) return;
579 ensureApplicableToPrimitive();
580 for (Tag tag: tags.keySet()) {
581 if (tag.getValue() == null || tag.getValue().isEmpty()) {
582 primitive.remove(tag.getKey());
583 } else {
584 primitive.put(tag.getKey(), tag.getValue());
585 }
586 }
587 }
588
589 /**
590 * Applies this tag collection to a collection of {@link org.openstreetmap.josm.data.osm.OsmPrimitive}s. Does nothing if
591 * primitives is null
592 *
593 * @param primitives the collection of primitives
594 * @throws IllegalStateException if this tag collection can't be applied
595 * because there are keys with multiple values
596 */
597 public void applyTo(Collection<? extends Tagged> primitives) {
598 if (primitives == null) return;
599 ensureApplicableToPrimitive();
600 for (Tagged primitive: primitives) {
601 applyTo(primitive);
602 }
603 }
604
605 /**
606 * Replaces the tags of an {@link org.openstreetmap.josm.data.osm.OsmPrimitive} by the tags in this collection . Does nothing if
607 * primitive is null
608 *
609 * @param primitive the primitive
610 * @throws IllegalStateException if this tag collection can't be applied
611 * because there are keys with multiple values
612 */
613 public void replaceTagsOf(Tagged primitive) {
614 if (primitive == null) return;
615 ensureApplicableToPrimitive();
616 primitive.removeAll();
617 for (Tag tag: tags.keySet()) {
618 primitive.put(tag.getKey(), tag.getValue());
619 }
620 }
621
622 /**
623 * Replaces the tags of a collection of{@link org.openstreetmap.josm.data.osm.OsmPrimitive}s by the tags in this collection.
624 * Does nothing if primitives is null
625 *
626 * @param primitives the collection of primitives
627 * @throws IllegalStateException if this tag collection can't be applied
628 * because there are keys with multiple values
629 */
630 public void replaceTagsOf(Collection<? extends Tagged> primitives) {
631 if (primitives == null) return;
632 ensureApplicableToPrimitive();
633 for (Tagged primitive: primitives) {
634 replaceTagsOf(primitive);
635 }
636 }
637
638 private void ensureApplicableToPrimitive() {
639 if (!isApplicableToPrimitive())
640 throw new IllegalStateException(tr("Tag collection cannot be applied to a primitive because there are keys with multiple values."));
641 }
642
643 /**
644 * Builds the intersection of this tag collection and another tag collection
645 *
646 * @param other the other tag collection. If null, replies an empty tag collection.
647 * @return the intersection of this tag collection and another tag collection. All counts are set to 1.
648 */
649 public TagCollection intersect(TagCollection other) {
650 TagCollection ret = new TagCollection();
651 if (other != null) {
652 tags.keySet().stream().filter(other::contains).forEach(ret::add);
653 }
654 return ret;
655 }
656
657 /**
658 * Replies the difference of this tag collection and another tag collection
659 *
660 * @param other the other tag collection. May be null.
661 * @return the difference of this tag collection and another tag collection
662 */
663 public TagCollection minus(TagCollection other) {
664 TagCollection ret = new TagCollection(this);
665 if (other != null) {
666 ret.remove(other);
667 }
668 return ret;
669 }
670
671 /**
672 * Replies the union of this tag collection and another tag collection
673 *
674 * @param other the other tag collection. May be null.
675 * @return the union of this tag collection and another tag collection. The tag count is summed.
676 */
677 public TagCollection union(TagCollection other) {
678 TagCollection ret = new TagCollection(this);
679 if (other != null) {
680 ret.add(other);
681 }
682 return ret;
683 }
684
685 public TagCollection emptyTagsForKeysMissingIn(TagCollection other) {
686 TagCollection ret = new TagCollection();
687 for (String key: this.minus(other).getKeys()) {
688 ret.add(new Tag(key));
689 }
690 return ret;
691 }
692
693 private static final Pattern SPLIT_VALUES_PATTERN = Pattern.compile(";\\s*");
694
695 /**
696 * Replies the concatenation of all tag values (concatenated by a semicolon)
697 * @param key the key to look up
698 *
699 * @return the concatenation of all tag values
700 */
701 public String getJoinedValues(String key) {
702
703 // See #7201 combining ways screws up the order of ref tags
704 Set<String> originalValues = getValues(key);
705 if (originalValues.size() == 1) {
706 return originalValues.iterator().next();
707 }
708
709 Set<String> values = new LinkedHashSet<>();
710 Map<String, Collection<String>> originalSplitValues = new LinkedHashMap<>();
711 for (String v : originalValues) {
712 List<String> vs = Arrays.asList(SPLIT_VALUES_PATTERN.split(v));
713 originalSplitValues.put(v, vs);
714 values.addAll(vs);
715 }
716 values.remove("");
717 // try to retain an already existing key if it contains all needed values (remove this if it causes performance problems)
718 for (Entry<String, Collection<String>> i : originalSplitValues.entrySet()) {
719 if (i.getValue().containsAll(values)) {
720 return i.getKey();
721 }
722 }
723 return Utils.join(";", values);
724 }
725
726 /**
727 * Replies the sum of all numeric tag values. Ignores dupplicates.
728 * @param key the key to look up
729 *
730 * @return the sum of all numeric tag values, as string.
731 * @since 7743
732 */
733 public String getSummedValues(String key) {
734 int result = 0;
735 for (String value : getValues(key)) {
736 try {
737 result += Integer.parseInt(value);
738 } catch (NumberFormatException e) {
739 Logging.trace(e);
740 }
741 }
742 return Integer.toString(result);
743 }
744
745 private Stream<String> generateKeyStream() {
746 return tags.keySet().stream().map(Tag::getKey);
747 }
748
749 /**
750 * Get a stram for the given key.
751 * @param key The key
752 * @return The stream. An empty stream if key is <code>null</code>
753 */
754 private Stream<Tag> generateStreamForKey(String key) {
755 return tags.keySet().stream().filter(e -> e.matchesKey(key));
756 }
757
758 @Override
759 public String toString() {
760 return tags.toString();
761 }
762}
Note: See TracBrowser for help on using the repository browser.