001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.corrector;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005import static org.openstreetmap.josm.tools.I18n.trn;
006
007import java.util.Arrays;
008
009import javax.swing.JOptionPane;
010
011import org.openstreetmap.josm.Main;
012import org.openstreetmap.josm.data.osm.Tag;
013import org.openstreetmap.josm.data.osm.TagCollection;
014import org.openstreetmap.josm.data.osm.Way;
015import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
016import org.openstreetmap.josm.gui.DefaultNameFormatter;
017import org.openstreetmap.josm.tools.Utils;
018
019/**
020 * A ReverseWayNoTagCorrector warns about ways that should not be reversed
021 * because their semantic meaning cannot be preserved in that case.
022 * E.g. natural=coastline, natural=cliff, barrier=retaining_wall cannot be changed.
023 * @see ReverseWayTagCorrector for handling of tags that can be modified (oneway=yes, etc.)
024 * @since 5724
025 */
026public final class ReverseWayNoTagCorrector {
027
028    private ReverseWayNoTagCorrector() {
029        // Hide default constructor for utils classes
030    }
031
032    /**
033     * Tags that imply a semantic meaning from the way direction and cannot be changed.
034     */
035    public static final TagCollection directionalTags = new TagCollection(Arrays.asList(new Tag[]{
036            new Tag("natural", "coastline"),
037            new Tag("natural", "cliff"),
038            new Tag("barrier", "guard_rail"),
039            new Tag("barrier", "kerb"),
040            new Tag("barrier", "retaining_wall"),
041            new Tag("waterway", "stream"),
042            new Tag("waterway", "river"),
043            new Tag("waterway", "ditch"),
044            new Tag("waterway", "drain"),
045            new Tag("waterway", "canal")
046    }));
047
048    /**
049     * Replies the tags that imply a semantic meaning from <code>way</code> direction and cannot be changed.
050     * @param way The way to look for
051     * @return tags that imply a semantic meaning from <code>way</code> direction and cannot be changed
052     */
053    public static final TagCollection getDirectionalTags(Way way) {
054        return directionalTags.intersect(TagCollection.from(way));
055    }
056
057    /**
058     * Tests whether way can be reversed without semantic change.
059     * Looks for tags like natural=cliff, barrier=retaining_wall.
060     * @param way The way to check
061     * @return false if the semantic meaning change if the way is reversed, true otherwise.
062     */
063    public static boolean isReversible(Way way) {
064        return getDirectionalTags(way).isEmpty();
065    }
066
067    protected static String getHTML(TagCollection tags) {
068        if (tags.size() == 1) {
069            return tags.iterator().next().toString();
070        } else if (tags.size() > 1) {
071            return Utils.joinAsHtmlUnorderedList(tags);
072        } else {
073            return "";
074        }
075    }
076
077    protected static boolean confirmReverseWay(Way way, TagCollection tags) {
078        String msg = trn(
079                // Singular, if a single tag is impacted
080                "<html>You are going to reverse the way ''{0}'',"
081                + "<br/> whose semantic meaning of its tag ''{1}'' is defined by its direction.<br/>"
082                + "Do you really want to change the way direction, thus its semantic meaning?</html>",
083                // Plural, if several tags are impacted
084                "<html>You are going to reverse the way ''{0}'',"
085                + "<br/> whose semantic meaning of these tags are defined by its direction:<br/>{1}"
086                + "Do you really want to change the way direction, thus its semantic meaning?</html>",
087                tags.size(),
088                way.getDisplayName(DefaultNameFormatter.getInstance()),
089                getHTML(tags)
090            );
091        int ret = ConditionalOptionPaneUtil.showOptionDialog(
092                "reverse_directional_way",
093                Main.parent,
094                msg,
095                tr("Reverse directional way."),
096                JOptionPane.YES_NO_CANCEL_OPTION,
097                JOptionPane.WARNING_MESSAGE,
098                null,
099                null
100        );
101        switch(ret) {
102            case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION:
103            case JOptionPane.YES_OPTION:
104                return true;
105            default:
106                return false;
107        }
108    }
109
110    /**
111     * Checks the given way can be safely reversed and asks user to confirm the operation if it not the case.
112     * @param way The way to check
113     * @throws UserCancelException If the user cancels the operation
114     */
115    public static void checkAndConfirmReverseWay(Way way) throws UserCancelException {
116        TagCollection tags = getDirectionalTags(way);
117        if (!tags.isEmpty() && !confirmReverseWay(way, tags)) {
118            throw new UserCancelException();
119        }
120    }
121}