001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions.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.data.osm.DefaultNameFormatter; 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.MainApplication; 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 DIRECTIONAL_TAGS = new TagCollection(Arrays.asList( 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 = DIRECTIONAL_TAGS.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 Utils.escapeReservedCharactersHTML(way.getDisplayName(DefaultNameFormatter.getInstance())), 089 Utils.joinAsHtmlUnorderedList(tags) 090 ); 091 int ret = ConditionalOptionPaneUtil.showOptionDialog( 092 "reverse_directional_way", 093 MainApplication.getMainFrame(), 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}