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

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

fix #4664 - warn when reverting a way with direction defined by tag

  • Property svn:eol-style set to native
File size: 23.9 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 public Iterator<Tag> iterator() {
488 return tags.iterator();
489 }
490
491 /**
492 * Replies the set of keys of this tag collection.
493 *
494 * @return the set of keys of this tag collection
495 */
496 public Set<String> getKeys() {
497 HashSet<String> ret = new HashSet<String>();
498 for (Tag tag: tags) {
499 ret.add(tag.getKey());
500 }
501 return ret;
502 }
503
504 /**
505 * Replies the set of keys which have at least 2 matching tags.
506 *
507 * @return the set of keys which have at least 2 matching tags.
508 */
509 public Set<String> getKeysWithMultipleValues() {
510 HashMap<String, Integer> counters = new HashMap<String, Integer>();
511 for (Tag tag: tags) {
512 Integer v = counters.get(tag.getKey());
513 counters.put(tag.getKey(),(v==null) ? 1 : v+1);
514 }
515 Set<String> ret = new HashSet<String>();
516 for (Entry<String, Integer> e : counters.entrySet()) {
517 if (e.getValue() > 1) {
518 ret.add(e.getKey());
519 }
520 }
521 return ret;
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. Does nothing if tag is null.
527 *
528 * @param tag the tag to set
529 */
530 public void setUniqueForKey(Tag tag) {
531 if (tag == null) return;
532 removeByKey(tag.getKey());
533 add(tag);
534 }
535
536 /**
537 * Sets a unique tag for the key of this tag. All other tags with the same key are
538 * removed from the collection. Assume the empty string for key and value if either
539 * key or value is null.
540 *
541 * @param key the key
542 * @param value the value
543 */
544 public void setUniqueForKey(String key, String value) {
545 Tag tag = new Tag(key, value);
546 setUniqueForKey(tag);
547 }
548
549 /**
550 * Replies the set of values in this tag collection
551 *
552 * @return the set of values
553 */
554 public Set<String> getValues() {
555 HashSet<String> ret = new HashSet<String>();
556 for (Tag tag: tags) {
557 ret.add(tag.getValue());
558 }
559 return ret;
560 }
561
562 /**
563 * Replies the set of values for a given key. Replies an empty collection if there
564 * are no values for the given key.
565 *
566 * @param key the key to look up
567 * @return the set of values for a given key. Replies an empty collection if there
568 * are no values for the given key
569 */
570 public Set<String> getValues(String key) {
571 HashSet<String> ret = new HashSet<String>();
572 if (key == null) return ret;
573 for (Tag tag: tags) {
574 if (tag.matchesKey(key)) {
575 ret.add(tag.getValue());
576 }
577 }
578 return ret;
579 }
580
581 /**
582 * Replies true if for every key there is one tag only, i.e. exactly one value.
583 *
584 * @return
585 */
586 public boolean isApplicableToPrimitive() {
587 return size() == getKeys().size();
588 }
589
590 /**
591 * Applies this tag collection to an {@link OsmPrimitive}. Does nothing if
592 * primitive is null
593 *
594 * @param primitive the primitive
595 * @throws IllegalStateException thrown if this tag collection can't be applied
596 * because there are keys with multiple values
597 */
598 public void applyTo(Tagged primitive) throws IllegalStateException {
599 if (primitive == null) return;
600 if (! isApplicableToPrimitive())
601 throw new IllegalStateException(tr("Tag collection cannot be applied to a primitive because there are keys with multiple values."));
602 for (Tag tag: tags) {
603 if (tag.getValue() == null || tag.getValue().equals("")) {
604 primitive.remove(tag.getKey());
605 } else {
606 primitive.put(tag.getKey(), tag.getValue());
607 }
608 }
609 }
610
611 /**
612 * Applies this tag collection to a collection of {@link OsmPrimitive}s. Does nothing if
613 * primitives is null
614 *
615 * @param primitives the collection of primitives
616 * @throws IllegalStateException thrown if this tag collection can't be applied
617 * because there are keys with multiple values
618 */
619 public void applyTo(Collection<? extends Tagged> primitives) throws IllegalStateException{
620 if (primitives == null) return;
621 if (! isApplicableToPrimitive())
622 throw new IllegalStateException(tr("Tag collection cannot be applied to a primitive because there are keys with multiple values."));
623 for (Tagged primitive: primitives) {
624 applyTo(primitive);
625 }
626 }
627
628 /**
629 * Replaces the tags of an {@link OsmPrimitive} by the tags in this collection . Does nothing if
630 * primitive is null
631 *
632 * @param primitive the primitive
633 * @throws IllegalStateException thrown if this tag collection can't be applied
634 * because there are keys with multiple values
635 */
636 public void replaceTagsOf(Tagged primitive) throws IllegalStateException {
637 if (primitive == null) return;
638 if (! isApplicableToPrimitive())
639 throw new IllegalStateException(tr("Tag collection cannot be applied to a primitive because there are keys with multiple values."));
640 primitive.removeAll();
641 for (Tag tag: tags) {
642 primitive.put(tag.getKey(), tag.getValue());
643 }
644 }
645
646 /**
647 * Replaces the tags of a collection of{@link OsmPrimitive}s by the tags in this collection.
648 * Does nothing if primitives is null
649 *
650 * @param primitives the collection of primitives
651 * @throws IllegalStateException thrown if this tag collection can't be applied
652 * because there are keys with multiple values
653 */
654 public void replaceTagsOf(Collection<? extends Tagged> primitives) throws IllegalStateException {
655 if (primitives == null) return;
656 if (! isApplicableToPrimitive())
657 throw new IllegalStateException(tr("Tag collection cannot be applied to a primitive because there are keys with multiple values."));
658 for (Tagged primitive: primitives) {
659 replaceTagsOf(primitive);
660 }
661 }
662
663 /**
664 * Builds the intersection of this tag collection and another tag collection
665 *
666 * @param other the other tag collection. If null, replies an empty tag collection.
667 * @return the intersection of this tag collection and another tag collection
668 */
669 public TagCollection intersect(TagCollection other) {
670 TagCollection ret = new TagCollection();
671 if (other != null) {
672 for (Tag tag: tags) {
673 if (other.contains(tag)) {
674 ret.add(tag);
675 }
676 }
677 }
678 return ret;
679 }
680
681 /**
682 * Replies the difference of this tag collection and another tag collection
683 *
684 * @param other the other tag collection. May be null.
685 * @return the difference of this tag collection and another tag collection
686 */
687 public TagCollection minus(TagCollection other) {
688 TagCollection ret = new TagCollection(this);
689 if (other != null) {
690 ret.remove(other);
691 }
692 return ret;
693 }
694
695 /**
696 * Replies the union of this tag collection and another tag collection
697 *
698 * @param other the other tag collection. May be null.
699 * @return the union of this tag collection and another tag collection
700 */
701 public TagCollection union(TagCollection other) {
702 TagCollection ret = new TagCollection(this);
703 if (other != null) {
704 ret.add(other);
705 }
706 return ret;
707 }
708
709 public TagCollection emptyTagsForKeysMissingIn(TagCollection other) {
710 TagCollection ret = new TagCollection();
711 for(String key: this.minus(other).getKeys()) {
712 ret.add(new Tag(key));
713 }
714 return ret;
715 }
716
717 /**
718 * Replies the concatenation of all tag values (concatenated by a semicolon)
719 *
720 * @return the concatenation of all tag values
721 */
722 public String getJoinedValues(String key) {
723
724 // See #7201 combining ways screws up the order of ref tags
725 Set<String> originalValues = getValues(key);
726 if (originalValues.size() == 1) {
727 return originalValues.iterator().next();
728 }
729
730 Set<String> values = new LinkedHashSet<String>();
731 Map<String, Collection<String>> originalSplitValues = new LinkedHashMap<String, Collection<String>>();
732 for (String v : originalValues) {
733 List<String> vs = Arrays.asList(v.split(";\\s*"));
734 originalSplitValues.put(v, vs);
735 values.addAll(vs);
736 }
737 values.remove("");
738 // try to retain an already existing key if it contains all needed values (remove this if it causes performance problems)
739 for (Entry<String, Collection<String>> i : originalSplitValues.entrySet()) {
740 if (i.getValue().containsAll(values)) {
741 return i.getKey();
742 }
743 }
744 return Utils.join(";", values);
745 }
746
747 @Override
748 public String toString() {
749 return tags.toString();
750 }
751}
Note: See TracBrowser for help on using the repository browser.