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