001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm;
003
004import java.util.Arrays;
005import java.util.Collection;
006import java.util.Map;
007import java.util.Objects;
008import java.util.stream.Collectors;
009import java.util.stream.Stream;
010
011import org.openstreetmap.josm.tools.Utils;
012
013/**
014 * Objects implement Tagged if they provide a map of key/value pairs.
015 *
016 * @since 2115
017 */
018// FIXME: better naming? setTags(), getTags(), getKeys() instead of keySet() ?
019//
020public interface Tagged {
021
022    /**
023     * The maximum tag length allowed by OSM API
024     * @since 13414
025     */
026    int MAX_TAG_LENGTH = 255;
027
028    /**
029     * Sets the map of key/value pairs
030     *
031     * @param keys the map of key value pairs. If null, reset to the empty map.
032     */
033    void setKeys(Map<String, String> keys);
034
035    /**
036     * Replies the map of key/value pairs. Never null, but may be the empty map.
037     *
038     * @return the map of key/value pairs
039     */
040    Map<String, String> getKeys();
041
042    /**
043     * Calls the visitor for every key/value pair.
044     *
045     * @param visitor The visitor to call.
046     * @see #getKeys()
047     * @since 13668
048     */
049    default void visitKeys(KeyValueVisitor visitor) {
050        getKeys().forEach((k, v) -> visitor.visitKeyValue(this, k, v));
051    }
052
053    /**
054     * Sets a key/value pairs
055     *
056     * @param key the key
057     * @param value the value. If null, removes the key/value pair.
058     */
059    void put(String key, String value);
060
061    /**
062     * Sets a key/value pairs
063     *
064     * @param tag The tag to set.
065     * @since 10736
066     */
067    default void put(Tag tag) {
068        put(tag.getKey(), tag.getValue());
069    }
070
071    /**
072     * Replies the value of the given key; null, if there is no value for this key
073     *
074     * @param key the key
075     * @return the value
076     */
077    String get(String key);
078
079    /**
080     * Removes a given key/value pair
081     *
082     * @param key the key
083     */
084    void remove(String key);
085
086    /**
087     * Replies true, if there is at least one key/value pair; false, otherwise
088     *
089     * @return true, if there is at least one key/value pair; false, otherwise
090     */
091    boolean hasKeys();
092
093    /**
094     * Replies true if there is a tag with key <code>key</code>.
095     * The value could however be empty. See {@link #hasTag(String)} to check for non-empty tags.
096     *
097     * @param key the key
098     * @return true, if there is a tag with key <code>key</code>
099     * @see #hasTag(String)
100     * @since 11608
101     */
102    default boolean hasKey(String key) {
103        return get(key) != null;
104    }
105
106    /**
107     * Replies true if there is a non-empty tag with key <code>key</code>.
108     *
109     * @param key the key
110     * @return true, if there is a non-empty tag with key <code>key</code>
111     * @see Tagged#hasKey(String)
112     * @since 13430
113     */
114    default boolean hasTag(String key) {
115        return !Utils.isEmpty(get(key));
116    }
117
118    /**
119     * Tests whether this primitive contains a tag consisting of {@code key} and {@code value}.
120     * @param key the key forming the tag.
121     * @param value value forming the tag.
122     * @return true if primitive contains a tag consisting of {@code key} and {@code value}.
123     * @since 13668
124     */
125    default boolean hasTag(String key, String value) {
126        return Objects.equals(value, get(key));
127    }
128
129    /**
130     * Tests whether this primitive contains a tag consisting of {@code key} and any of {@code values}.
131     * @param key the key forming the tag.
132     * @param values one or many values forming the tag.
133     * @return true if primitive contains a tag consisting of {@code key} and any of {@code values}.
134     * @since 13668
135     */
136    default boolean hasTag(String key, String... values) {
137        return hasTag(key, Arrays.asList(values));
138    }
139
140    /**
141     * Tests whether this primitive contains a tag consisting of {@code key} and any of {@code values}.
142     * @param key the key forming the tag.
143     * @param values one or many values forming the tag.
144     * @return true if primitive contains a tag consisting of {@code key} and any of {@code values}.
145     * @since 13668
146     */
147    default boolean hasTag(String key, Collection<String> values) {
148        String v = get(key);
149        return v != null && values.contains(v);
150    }
151
152    /**
153     * Tests whether this primitive contains a tag consisting of {@code key} and a value different from {@code value}.
154     * @param key the key forming the tag.
155     * @param value value not forming the tag.
156     * @return true if primitive contains a tag consisting of {@code key} and a value different from {@code value}.
157     * @since 13668
158     */
159    default boolean hasTagDifferent(String key, String value) {
160        String v = get(key);
161        return v != null && !v.equals(value);
162    }
163
164    /**
165     * Tests whether this primitive contains a tag consisting of {@code key} and none of {@code values}.
166     * @param key the key forming the tag.
167     * @param values one or many values forming the tag.
168     * @return true if primitive contains a tag consisting of {@code key} and none of {@code values}.
169     * @since 13668
170     */
171    default boolean hasTagDifferent(String key, String... values) {
172        return hasTagDifferent(key, Arrays.asList(values));
173    }
174
175    /**
176     * Tests whether this primitive contains a tag consisting of {@code key} and none of {@code values}.
177     * @param key the key forming the tag.
178     * @param values one or many values forming the tag.
179     * @return true if primitive contains a tag consisting of {@code key} and none of {@code values}.
180     * @since 13668
181     */
182    default boolean hasTagDifferent(String key, Collection<String> values) {
183        String v = get(key);
184        return v != null && !values.contains(v);
185    }
186
187    /**
188     * Replies the set of keys
189     *
190     * @return the set of keys
191     * @see #keys()
192     */
193    Collection<String> keySet();
194
195    /**
196     * Replies the keys as stream
197     *
198     * @return the keys as stream
199     * @see #keySet()
200     * @since 17584
201     */
202    default Stream<String> keys() {
203        return keySet().stream();
204    }
205
206    /**
207     * Gets the number of keys
208     * @return The number of keys set for this tagged object.
209     * @since 13625
210     */
211    int getNumKeys();
212
213    /**
214     * Removes all tags
215     */
216    void removeAll();
217
218    /**
219     * Returns true if the {@code key} corresponds to an OSM true value.
220     * @param key OSM key
221     * @return {@code true} if the {@code key} corresponds to an OSM true value
222     * @see OsmUtils#isTrue(String)
223     */
224    default boolean isKeyTrue(String key) {
225        return OsmUtils.isTrue(get(key));
226    }
227
228    /**
229     * Returns true if the {@code key} corresponds to an OSM false value.
230     * @param key OSM key
231     * @return {@code true} if the {@code key} corresponds to an OSM false value
232     * @see OsmUtils#isFalse(String)
233     */
234    default boolean isKeyFalse(String key) {
235        return OsmUtils.isFalse(get(key));
236    }
237
238    /**
239     * Returns a Tagged instance for the given tag collection
240     * @param tags the tag collection
241     * @return a Tagged instance for the given tag collection
242     */
243    static Tagged ofTags(Collection<Tag> tags) {
244        return ofMap(tags.stream().collect(Collectors.toMap(Tag::getKey, Tag::getValue, (a, b) -> a)));
245    }
246
247    /**
248     * Returns a Tagged instance for the given tag map
249     * @param tags the tag map
250     * @return a Tagged instance for the given tag map
251     */
252    static Tagged ofMap(Map<String, String> tags) {
253        return new Tagged() {
254
255            @Override
256            public String get(String key) {
257                return tags.get(key);
258            }
259
260            @Override
261            public Map<String, String> getKeys() {
262                return tags;
263            }
264
265            @Override
266            public Collection<String> keySet() {
267                return tags.keySet();
268            }
269
270            @Override
271            public void put(String key, String value) {
272                tags.put(key, value);
273            }
274
275            @Override
276            public void setKeys(Map<String, String> keys) {
277                tags.putAll(keys);
278            }
279
280            @Override
281            public boolean hasKeys() {
282                return !tags.isEmpty();
283            }
284
285            @Override
286            public int getNumKeys() {
287                return tags.size();
288            }
289
290            @Override
291            public void remove(String key) {
292                tags.remove(key);
293            }
294
295            @Override
296            public void removeAll() {
297                tags.clear();
298            }
299        };
300    }
301}