001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.validation.tests;
003
004import org.openstreetmap.josm.command.ChangePropertyCommand;
005import org.openstreetmap.josm.command.ChangePropertyKeyCommand;
006import org.openstreetmap.josm.command.Command;
007import org.openstreetmap.josm.data.osm.OsmPrimitive;
008import org.openstreetmap.josm.data.osm.Tag;
009import org.openstreetmap.josm.gui.mappaint.Environment;
010import org.openstreetmap.josm.gui.mappaint.mapcss.Expression;
011import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
012import org.openstreetmap.josm.tools.CheckParameterUtil;
013
014/**
015 * Represents a fix to a validation test. The fixing {@link Command} can be obtained by {@link #createCommand(OsmPrimitive, Selector)}.
016 */
017@FunctionalInterface
018interface MapCSSTagCheckerFixCommand {
019    /**
020     * Creates the fixing {@link Command} for the given primitive. The {@code matchingSelector} is used to evaluate placeholders
021     * (cf. {@link MapCSSTagCheckerRule#insertArguments(Selector, String, OsmPrimitive)}).
022     *
023     * @param p                OSM primitive
024     * @param matchingSelector matching selector
025     * @return fix command, or {@code null} if if cannot be created
026     */
027    Command createCommand(OsmPrimitive p, Selector matchingSelector);
028
029    /**
030     * Checks that object is either an {@link Expression} or a {@link String}.
031     *
032     * @param obj object to check
033     * @throws IllegalArgumentException if object is not an {@code Expression} or a {@code String}
034     */
035    static void checkObject(final Object obj) {
036        CheckParameterUtil.ensureThat(obj instanceof Expression || obj instanceof String,
037                () -> "instance of Exception or String expected, but got " + obj);
038    }
039
040    /**
041     * Evaluates given object as {@link Expression} or {@link String} on the matched {@link OsmPrimitive} and {@code matchingSelector}.
042     *
043     * @param obj              object to evaluate ({@link Expression} or {@link String})
044     * @param p                OSM primitive
045     * @param matchingSelector matching selector
046     * @return result string
047     */
048    static String evaluateObject(final Object obj, final OsmPrimitive p, final Selector matchingSelector) {
049        final String s;
050        if (obj instanceof Expression) {
051            s = (String) ((Expression) obj).evaluate(new Environment(p));
052        } else if (obj instanceof String) {
053            s = (String) obj;
054        } else {
055            return null;
056        }
057        return MapCSSTagCheckerRule.insertArguments(matchingSelector, s, p);
058    }
059
060    /**
061     * Creates a fixing command which executes a {@link ChangePropertyCommand} on the specified tag.
062     *
063     * @param obj object to evaluate ({@link Expression} or {@link String})
064     * @return created fix command
065     */
066    static MapCSSTagCheckerFixCommand fixAdd(final Object obj) {
067        checkObject(obj);
068        return new MapCSSTagCheckerFixCommand() {
069            @Override
070            public Command createCommand(OsmPrimitive p, Selector matchingSelector) {
071                final Tag tag = Tag.ofString(MapCSSTagCheckerFixCommand.evaluateObject(obj, p, matchingSelector));
072                return new ChangePropertyCommand(p, tag.getKey(), tag.getValue());
073            }
074
075            @Override
076            public String toString() {
077                return "fixAdd: " + obj;
078            }
079        };
080    }
081
082    /**
083     * Creates a fixing command which executes a {@link ChangePropertyCommand} to delete the specified key.
084     *
085     * @param obj object to evaluate ({@link Expression} or {@link String})
086     * @return created fix command
087     */
088    static MapCSSTagCheckerFixCommand fixRemove(final Object obj) {
089        checkObject(obj);
090        return new MapCSSTagCheckerFixCommand() {
091            @Override
092            public Command createCommand(OsmPrimitive p, Selector matchingSelector) {
093                final String key = MapCSSTagCheckerFixCommand.evaluateObject(obj, p, matchingSelector);
094                return new ChangePropertyCommand(p, key, "");
095            }
096
097            @Override
098            public String toString() {
099                return "fixRemove: " + obj;
100            }
101        };
102    }
103
104    /**
105     * Creates a fixing command which executes a {@link ChangePropertyKeyCommand} on the specified keys
106     *
107     * @param oldKey old key
108     * @param newKey new key
109     * @return created fix command
110     */
111    static MapCSSTagCheckerFixCommand fixChangeKey(final String oldKey, final String newKey) {
112        return new MapCSSTagCheckerFixCommand() {
113            @Override
114            public Command createCommand(OsmPrimitive p, Selector matchingSelector) {
115                return new ChangePropertyKeyCommand(p,
116                        MapCSSTagCheckerRule.insertArguments(matchingSelector, oldKey, p),
117                        MapCSSTagCheckerRule.insertArguments(matchingSelector, newKey, p));
118            }
119
120            @Override
121            public String toString() {
122                return "fixChangeKey: " + oldKey + " => " + newKey;
123            }
124        };
125    }
126}