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

Last change on this file since 6084 was 6084, checked in by bastiK, 11 years ago

see #8902 - add missing @Override annotations (patch by shinigami)

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