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