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