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

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

remove extra whitespaces

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