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;
008import java.util.Map;
009
010import javax.swing.JOptionPane;
011
012import org.openstreetmap.josm.Main;
013import org.openstreetmap.josm.data.osm.Tag;
014import org.openstreetmap.josm.data.osm.TagCollection;
015import org.openstreetmap.josm.data.osm.Tagged;
016import org.openstreetmap.josm.data.osm.Way;
017import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
018import org.openstreetmap.josm.gui.DefaultNameFormatter;
019import org.openstreetmap.josm.tools.UserCancelException;
020import org.openstreetmap.josm.tools.Utils;
021
022/**
023 * A ReverseWayNoTagCorrector warns about ways that should not be reversed
024 * because their semantic meaning cannot be preserved in that case.
025 * E.g. natural=coastline, natural=cliff, barrier=retaining_wall cannot be changed.
026 * @see ReverseWayTagCorrector for handling of tags that can be modified (oneway=yes, etc.)
027 * @since 5724
028 */
029public final class ReverseWayNoTagCorrector {
030
031    private ReverseWayNoTagCorrector() {
032        // Hide default constructor for utils classes
033    }
034
035    /**
036     * Tags that imply a semantic meaning from the way direction and cannot be changed.
037     */
038    private static final TagCollection directionalTags = new TagCollection(Arrays.asList(new Tag[]{
039            new Tag("natural", "coastline"),
040            new Tag("natural", "cliff"),
041            new Tag("barrier", "guard_rail"),
042            new Tag("barrier", "kerb"),
043            new Tag("barrier", "retaining_wall"),
044            new Tag("man_made", "embankment"),
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 TagCollection getDirectionalTags(Tagged way) {
053        final TagCollection collection = new TagCollection();
054        for (Map.Entry<String, String> entry : way.getKeys().entrySet()) {
055            final Tag tag = new Tag(entry.getKey(), entry.getValue());
056            final boolean isDirectional = directionalTags.contains(tag) || tag.isDirectionKey();
057            if (isDirectional) {
058                final boolean cannotBeCorrected = ReverseWayTagCorrector.getTagCorrections(tag).isEmpty();
059                if (cannotBeCorrected) {
060                    collection.add(tag);
061                }
062            }
063        }
064        return collection;
065    }
066
067    /**
068     * Tests whether way can be reversed without semantic change.
069     * Looks for tags like natural=cliff, barrier=retaining_wall.
070     * @param way The way to check
071     * @return false if the semantic meaning change if the way is reversed, true otherwise.
072     */
073    public static boolean isReversible(Tagged way) {
074        return getDirectionalTags(way).isEmpty();
075    }
076
077    private 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                Utils.joinAsHtmlUnorderedList(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}