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

Last change on this file since 2996 was 2996, checked in by jttt, 14 years ago

Fix #4406 tags disapear: merging node with ID:0 and tag to existing node without tags

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