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

Last change on this file since 3166 was 3137, checked in by Gubaer, 14 years ago

added a few utility methods

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