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