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

Last change on this file since 2070 was 2070, checked in by Gubaer, 15 years ago

new: rewrite of CombineWay action
new: conflict resolution dialog for CombineWay, including conflicts for different relation memberships
cleanup: cleanup in OsmReader, reduces memory footprint and reduces parsing time
cleanup: made most of the public fields in OsmPrimitive @deprecated, added accessors and changed the code
cleanup: replaced usages of @deprecated constructors for ExtendedDialog
fixed #3208: Combine ways brokes relation order

WARNING: this changeset touches a lot of code all over the code base. "latest" might become slightly unstable in the next days. Also experience incompatibility issues with plugins in the next few days.

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