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

Last change on this file since 4746 was 4746, checked in by jttt, 12 years ago

Fix #7201 combining ways screws up the order of ref tags

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