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