001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm;
003
004import java.io.Serializable;
005import java.util.Collection;
006import java.util.Collections;
007import java.util.Map;
008import java.util.Map.Entry;
009import java.util.Objects;
010
011import org.openstreetmap.josm.tools.CheckParameterUtil;
012import org.openstreetmap.josm.tools.Utils;
013
014/**
015 * Tag represents an immutable key/value-pair. Both the key and the value may be empty, but not null.
016 * <p>
017 * It implements the {@link Tagged} interface. However, since instances of this class are immutable,
018 * the modifying methods throw an {@link UnsupportedOperationException}.
019 */
020public class Tag implements Tagged, Entry<String, String>, Serializable {
021
022    private static final long serialVersionUID = 1;
023
024    private final String key;
025    private final String value;
026
027    /**
028     * Create an empty tag whose key and value are empty.
029     */
030    public Tag() {
031        this("", "");
032    }
033
034    /**
035     * Create a tag whose key is <code>key</code> and whose value is
036     * empty.
037     *
038     * @param key the key. If null, it is set to the empty key.
039     */
040    public Tag(String key) {
041        this(key, "");
042    }
043
044    /**
045     * Creates a tag for a key and a value. If key and/or value are null,
046     * the empty value "" is assumed.
047     *
048     * @param key the key
049     * @param value  the value
050     */
051    public Tag(String key, String value) {
052        this.key = key == null ? "" : key;
053        this.value = value == null ? "" : value;
054    }
055
056    /**
057     * Creates clone of the tag <code>tag</code>.
058     *
059     * @param tag the tag.
060     */
061    public Tag(Tag tag) {
062        this(tag.getKey(), tag.getValue());
063    }
064
065    /**
066     * Replies the key of the tag. This is never null.
067     *
068     * @return the key of the tag
069     */
070    @Override
071    public String getKey() {
072        return key;
073    }
074
075    /**
076     * Replies the value of the tag. This is never null.
077     *
078     * @return the value of the tag
079     */
080    @Override
081    public String getValue() {
082        return value;
083    }
084
085    /**
086     * This is not supported by this implementation.
087     * @param value ignored
088     * @return (Does not return)
089     * @throws UnsupportedOperationException always
090     */
091    @Override
092    public String setValue(String value) {
093        throw new UnsupportedOperationException();
094    }
095
096    /**
097     * Replies true if the key of this tag is equal to <code>key</code>.
098     * If <code>key</code> is null, assumes the empty key.
099     *
100     * @param key the key
101     * @return true if the key of this tag is equal to <code>key</code>
102     */
103    public boolean matchesKey(String key) {
104        return this.key.equals(key);
105    }
106
107    @Override
108    public int hashCode() {
109        return Objects.hash(key, value);
110    }
111
112    @Override
113    public boolean equals(Object obj) {
114        if (this == obj) return true;
115        if (obj == null || getClass() != obj.getClass()) return false;
116        Tag tag = (Tag) obj;
117        return Objects.equals(key, tag.key) &&
118                Objects.equals(value, tag.value);
119    }
120
121    /**
122     * This constructs a {@link Tag} by splitting {@code s} on the first equality sign.
123     * @param s the string to convert
124     * @return the constructed tag
125     * @see org.openstreetmap.josm.tools.TextTagParser
126     */
127    public static Tag ofString(String s) {
128        CheckParameterUtil.ensureParameterNotNull(s, "s");
129        final String[] x = s.split("=", 2);
130        if (x.length == 2) {
131            return new Tag(x[0], x[1]);
132        } else {
133            throw new IllegalArgumentException('\'' + s + "' does not contain '='");
134        }
135    }
136
137    @Override
138    public String toString() {
139        return key + '=' + value;
140    }
141
142    /**
143     * Removes leading, trailing, and multiple inner whitespaces from the given string, to be used as a key or value.
144     * @param s The string
145     * @return The string without leading, trailing or multiple inner whitespaces
146     * @since 6699
147     */
148    public static String removeWhiteSpaces(String s) {
149        if (s == null || s.isEmpty()) {
150            return s;
151        }
152        return Utils.strip(s).replaceAll("\\s+", " ");
153    }
154
155    /**
156     * Unsupported.
157     * @param keys ignored
158     * @throws UnsupportedOperationException always
159     */
160    @Override
161    public void setKeys(Map<String, String> keys) {
162        throw new UnsupportedOperationException();
163    }
164
165    @Override
166    public Map<String, String> getKeys() {
167        return Collections.singletonMap(key, value);
168    }
169
170    /**
171     * Unsupported.
172     * @param key ignored
173     * @param value ignored
174     * @throws UnsupportedOperationException always
175     */
176    @Override
177    public void put(String key, String value) {
178        throw new UnsupportedOperationException();
179    }
180
181    @Override
182    public String get(String k) {
183        return key.equals(k) ? value : null;
184    }
185
186    /**
187     * Unsupported.
188     * @param key ignored
189     * @throws UnsupportedOperationException always
190     */
191    @Override
192    public void remove(String key) {
193        throw new UnsupportedOperationException();
194    }
195
196    @Override
197    public boolean hasKeys() {
198        return true;
199    }
200
201    @Override
202    public Collection<String> keySet() {
203        return Collections.singleton(key);
204    }
205
206    /**
207     * Unsupported.
208     * @throws UnsupportedOperationException always
209     */
210    @Override
211    public void removeAll() {
212        throw new UnsupportedOperationException();
213    }
214
215    /**
216     * true if this is a direction dependent tag (e.g. oneway)
217     *
218     * @return {@code true} if this is is a direction dependent tag
219     * @since 10716
220     */
221    public boolean isDirectionKey() {
222        return OsmPrimitive.directionKeys.match(this);
223    }
224
225}