001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.validation.tests;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.util.Collection;
007import java.util.LinkedHashMap;
008import java.util.LinkedList;
009import java.util.List;
010import java.util.Map;
011
012import org.openstreetmap.josm.command.ChangePropertyCommand;
013import org.openstreetmap.josm.command.ChangePropertyKeyCommand;
014import org.openstreetmap.josm.command.Command;
015import org.openstreetmap.josm.command.SequenceCommand;
016import org.openstreetmap.josm.data.osm.Node;
017import org.openstreetmap.josm.data.osm.OsmPrimitive;
018import org.openstreetmap.josm.data.osm.Relation;
019import org.openstreetmap.josm.data.osm.Tag;
020import org.openstreetmap.josm.data.osm.Way;
021import org.openstreetmap.josm.data.validation.Severity;
022import org.openstreetmap.josm.data.validation.Test;
023import org.openstreetmap.josm.data.validation.TestError;
024import org.openstreetmap.josm.tools.Utils;
025
026/**
027 * Checks and corrects deprecated and unnecessary tags.
028 * @since 4442
029 */
030public class DeprecatedTags extends Test {
031
032    private final List<DeprecationCheck> checks = new LinkedList<DeprecationCheck>();
033
034    /**
035     * Constructs a new {@code DeprecatedTags} test.
036     */
037    public DeprecatedTags() {
038        super(tr("Deprecated Tags"), tr("Checks and corrects deprecated tags."));
039        checks.add(new DeprecationCheck(2101).
040                testAndRemove("barrier", "wire_fence").
041                add("barrier", "fence").
042                add("fence_type", "chain_link"));
043        checks.add(new DeprecationCheck(2102).
044                testAndRemove("barrier", "wood_fence").
045                add("barrier", "fence").
046                add("fence_type", "wood"));
047        checks.add(new DeprecationCheck(2103).
048                testAndRemove("highway", "ford").
049                add("ford", "yes"));
050        // from http://wiki.openstreetmap.org/wiki/Deprecated_features
051        checks.add(new DeprecationCheck(2104).
052                test("class").
053                alternative("highway"));
054        checks.add(new DeprecationCheck(2105).
055                testAndRemove("highway", "stile").
056                add("barrier", "stile"));
057        checks.add(new DeprecationCheck(2106).
058                test("highway", "incline").
059                alternative("incline"));
060        checks.add(new DeprecationCheck(2107).
061                test("highway", "incline").
062                alternative("incline"));
063        checks.add(new DeprecationCheck(2108).
064                testAndRemove("highway", "unsurfaced").
065                add("highway", "road").
066                add("incline", "unpaved"));
067        checks.add(new DeprecationCheck(2109).
068                test("landuse", "wood").
069                alternative("landuse", "forest").
070                alternative("natural", "wood"));
071        checks.add(new DeprecationCheck(2110).
072                testAndRemove("natural", "marsh").
073                add("natural", "wetland").
074                add("wetland", "marsh"));
075        checks.add(new DeprecationCheck(2111).
076                test("highway", "byway"));
077        checks.add(new DeprecationCheck(2112).
078                test("power_source").
079                alternative("generator:source"));
080        checks.add(new DeprecationCheck(2113).
081                test("power_rating").
082                alternative("generator:output"));
083        // from http://wiki.openstreetmap.org/wiki/Tag:shop=organic
084        checks.add(new DeprecationCheck(2114).
085                testAndRemove("shop", "organic").
086                add("shop", "supermarket").
087                add("organic", "only"));
088        // from http://wiki.openstreetmap.org/wiki/Key:bicycle_parking
089        checks.add(new DeprecationCheck(2115).
090                testAndRemove("bicycle_parking", "sheffield").
091                add("bicycle_parking", "stands"));
092        // http://wiki.openstreetmap.org/wiki/Tag:emergency=phone
093        checks.add(new DeprecationCheck(2116).
094                testAndRemove("amenity", "emergency_phone").
095                add("emergency", "phone"));
096        // fix #8132 - http://wiki.openstreetmap.org/wiki/Tag:sport=gaelic_football
097        checks.add(new DeprecationCheck(2117).
098                testAndRemove("sport", "gaelic_football").
099                add("sport", "gaelic_games"));
100        // see #8847 / #8961 - http://wiki.openstreetmap.org/wiki/Tag:power=station
101        checks.add(new DeprecationCheck(2118).
102                test("power", "station").
103                alternative("power", "plant").
104                alternative("power", "sub_station"));
105        checks.add(new DeprecationCheck(2119).
106                testAndRemove("generator:method", "dam").
107                add("generator:method", "water-storage"));
108        checks.add(new DeprecationCheck(2120).
109                testAndRemove("generator:method", "pumped-storage").
110                add("generator:method", "water-pumped-storage"));
111        checks.add(new DeprecationCheck(2121).
112                testAndRemove("generator:method", "pumping").
113                add("generator:method", "water-pumped-storage"));
114        // see #8962 - http://wiki.openstreetmap.org/wiki/Key:fence_type
115        checks.add(new DeprecationCheck(2122).
116                test("fence_type", "chain").
117                alternative("barrier", "chain").
118                alternative("fence_type", "chain_link"));
119        // see #9000 - http://wiki.openstreetmap.org/wiki/Key:entrance
120        checks.add(new DeprecationCheck(2123).
121                test("building", "entrance").
122                alternative("entrance"));
123        // see #9213 - Useless tag proposed in internal preset for years
124        checks.add(new DeprecationCheck(2124).
125                testAndRemove("board_type", "board"));
126        // see #8434 - http://wiki.openstreetmap.org/wiki/Proposed_features/monitoring_station
127        checks.add(new DeprecationCheck(2125).
128                testAndRemove("man_made", "measurement_station").
129                add("man_made", "monitoring_station"));
130        checks.add(new DeprecationCheck(2126).
131                testAndRemove("measurement", "water_level").
132                add("monitoring:water_level", "yes"));
133        checks.add(new DeprecationCheck(2127).
134                testAndRemove("measurement", "weather").
135                add("monitoring:weather", "yes"));
136        checks.add(new DeprecationCheck(2128).
137                testAndRemove("measurement", "seismic").
138                add("monitoring:seismic_activity", "yes"));
139        checks.add(new DeprecationCheck(2129).
140                test("monitoring:river_level").
141                changeKey("monitoring:river_level", "monitoring:water_level"));
142        // see #9365 - Useless tag layer=0
143        checks.add(new UnnecessaryTagCheck(2130).
144                testAndRemove("layer", "0"));
145    }
146
147    /**
148     * Visiting call for primitives.
149     * @param p The primitive to inspect.
150     */
151    public void visit(OsmPrimitive p) {
152        for (DeprecationCheck check : checks) {
153            if (check.matchesPrimitive(p)) {
154                errors.add(new DeprecationError(p, check));
155            }
156        }
157    }
158
159    @Override
160    public void visit(Node n) {
161        visit((OsmPrimitive) n);
162    }
163
164    @Override
165    public void visit(Way w) {
166        visit((OsmPrimitive) w);
167    }
168
169    @Override
170    public void visit(Relation r) {
171        visit((OsmPrimitive) r);
172    }
173
174    /**
175     * Represents on deprecation check consisting of a series of {@code test}s,
176     * automatic {@code change}s/{@code keyChange}s (fixes for the deprecated tag),
177     * or a suggestion of tagging {@code alternatives}.
178     */
179    private static class DeprecationCheck {
180
181        private int code;
182        protected final List<Tag> test = new LinkedList<Tag>();
183        protected final List<Tag> change = new LinkedList<Tag>();
184        protected final Map<String, String> keyChange = new LinkedHashMap<String, String>();
185        protected final List<Tag> alternatives = new LinkedList<Tag>();
186
187        /**
188         * Creates a new {@code DeprecationCheck}.
189         * @param code {@link TestError#code}
190         */
191        public DeprecationCheck(int code) {
192            this.code = code;
193        }
194
195        /**
196         * Adds a test criterion which matches primitives with tag {@code key=value}.
197         * @return {@code this}
198         */
199        DeprecationCheck test(String key, String value) {
200            test.add(new Tag(key, value));
201            return this;
202        }
203
204        /**
205         * Adds a test criterion which matches primitives with key {@code key}.
206         * @return {@code this}
207         */
208        DeprecationCheck test(String key) {
209            return test(key, null);
210        }
211
212        /**
213         * Adds an automatic fix which sets/adds the tag {@code key=value}.
214         * @return {@code this}
215         * @see #alternative(String, String)
216         * @see #alternative(String)
217         */
218        DeprecationCheck add(String key, String value) {
219            change.add(new Tag(key, value));
220            return this;
221        }
222
223        /**
224         * Adds an automatic fix which removes the key {@code key}.
225         * @return {@code this}
226         */
227        DeprecationCheck remove(String key) {
228            change.add(new Tag(key));
229            return this;
230        }
231
232        /**
233         * Adds an automatic fix which changes the key {@code oldKey} to {@code newKey}.
234         * @return {@code this}
235         */
236        DeprecationCheck changeKey(String oldKey, String newKey) {
237            keyChange.put(oldKey, newKey);
238            return this;
239        }
240
241        /**
242         * Adds a test criterion which matches primitives with tag {@code key=value},
243         * and an automatic fix which removes the key {@code key}.
244         * Equivalent to {@link #test(String, String)} plus {@link #remove(String)}.
245         * @return {@code this}
246         */
247        DeprecationCheck testAndRemove(String key, String value) {
248            return test(key, value).remove(key);
249        }
250
251        /**
252         * Adds a suggestion to use an alternative tag {@code key=value} instead of the deprecated tag.
253         * This is used for cases where no automatic fix is sensible.
254         * @return {@code this}
255         */
256        DeprecationCheck alternative(String key, String value) {
257            alternatives.add(new Tag(key, value));
258            return this;
259        }
260
261        /**
262         * Adds a suggestion to use an alternative key {@code key} instead of the deprecated tag.
263         * This is used for cases where no automatic fix is sensible.
264         * @return {@code this}
265         */
266        DeprecationCheck alternative(String key) {
267            return alternative(key, null);
268        }
269
270        /**
271         * Tests whether the {@link OsmPrimitive} contains a deprecated tag which is represented by this {@code DeprecationCheck}.
272         * @param p the primitive to test
273         * @return true when the primitive contains a deprecated tag
274         */
275        boolean matchesPrimitive(OsmPrimitive p) {
276            for (Tag tag : test) {
277                String key = tag.getKey();
278                String value = tag.getValue();
279                if (value.isEmpty() && !p.hasKey(key))
280                    return false;
281                if (!value.isEmpty() && !value.equals(p.get(key)))
282                    return false;
283            }
284            return true;
285        }
286
287        /**
288         * Constructs a fix in terms of a {@link Command} for the {@link OsmPrimitive}.
289         * @param p the primitive to construct the fix for
290         * @return the fix
291         */
292        Command fixPrimitive(OsmPrimitive p) {
293            Collection<Command> cmds = new LinkedList<Command>();
294            for (Tag tag : change) {
295                cmds.add(new ChangePropertyCommand(p, tag.getKey(), tag.getValue()));
296            }
297            for (Map.Entry<String, String> i : keyChange.entrySet()) {
298                cmds.add(new ChangePropertyKeyCommand(p, i.getKey(), i.getValue()));
299            }
300            return new SequenceCommand(tr("Deprecation fix of {0}", Utils.join(", ", test)), cmds);
301        }
302
303        /**
304         * Constructs a localized description for this deprecation check.
305         * @return a localized description
306         */
307        String getDescription() {
308            if (alternatives.isEmpty())
309                return tr("{0} is deprecated", Utils.join(", ", test));
310            else
311                return tr("{0} is deprecated, use {1} instead", Utils.join(", ", test), Utils.join(tr(" or "), alternatives));
312        }
313    }
314    
315    private static class UnnecessaryTagCheck extends DeprecationCheck {
316
317        public UnnecessaryTagCheck(int code) {
318            super(code);
319        }
320
321        @Override
322        String getDescription() {
323            return tr("{0} is unnecessary", Utils.join(", ", test));
324        }
325    }
326
327    private class DeprecationError extends TestError {
328
329        private OsmPrimitive p;
330        private DeprecationCheck check;
331
332        public DeprecationError(OsmPrimitive p, DeprecationCheck check) {
333            super(DeprecatedTags.this, Severity.WARNING, check.getDescription(), check.code, p);
334            this.p = p;
335            this.check = check;
336        }
337
338        @Override
339        public boolean isFixable() {
340            return !check.change.isEmpty() || !check.keyChange.isEmpty();
341        }
342
343        @Override
344        public Command getFix() {
345            return check.fixPrimitive(p);
346        }
347    }
348}