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

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

fix squid:S1319 - Declarations should use Java collection interfaces rather than specific implementation classes

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