Index: /trunk/build.xml
===================================================================
--- /trunk/build.xml	(revision 14255)
+++ /trunk/build.xml	(revision 14256)
@@ -999,5 +999,4 @@
         <jar basedir="${build.dir}" level="${clevel}" destfile="${modules.dir}/josm-cli.jar" includes="org/openstreetmap/josm/cli/**/*.class"/>
         <jar basedir="${build.dir}" level="${clevel}" destfile="${modules.dir}/josm-command.jar" includes="org/openstreetmap/josm/command/**/*.class"/>
-        <jar basedir="${build.dir}" level="${clevel}" destfile="${modules.dir}/josm-corrector.jar" includes="org/openstreetmap/josm/corrector/**/*.class"/>
         <jar basedir="${build.dir}" level="${clevel}" destfile="${modules.dir}/josm-data.jar" includes="org/openstreetmap/josm/data/**/*.class"/>
         <jar basedir="${build.dir}" level="${clevel}" destfile="${modules.dir}/josm-gui.jar" includes="org/openstreetmap/josm/gui/**/*.class"/>
Index: /trunk/src/org/openstreetmap/josm/actions/CombineWayAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/CombineWayAction.java	(revision 14255)
+++ /trunk/src/org/openstreetmap/josm/actions/CombineWayAction.java	(revision 14256)
@@ -19,9 +19,9 @@
 import javax.swing.JOptionPane;
 
+import org.openstreetmap.josm.actions.corrector.ReverseWayTagCorrector;
 import org.openstreetmap.josm.command.ChangeCommand;
 import org.openstreetmap.josm.command.Command;
 import org.openstreetmap.josm.command.DeleteCommand;
 import org.openstreetmap.josm.command.SequenceCommand;
-import org.openstreetmap.josm.corrector.ReverseWayTagCorrector;
 import org.openstreetmap.josm.data.UndoRedoHandler;
 import org.openstreetmap.josm.data.osm.DataSet;
Index: /trunk/src/org/openstreetmap/josm/actions/ReverseWayAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/ReverseWayAction.java	(revision 14255)
+++ /trunk/src/org/openstreetmap/josm/actions/ReverseWayAction.java	(revision 14256)
@@ -16,9 +16,9 @@
 import javax.swing.JOptionPane;
 
+import org.openstreetmap.josm.actions.corrector.ReverseWayNoTagCorrector;
+import org.openstreetmap.josm.actions.corrector.ReverseWayTagCorrector;
 import org.openstreetmap.josm.command.ChangeCommand;
 import org.openstreetmap.josm.command.Command;
 import org.openstreetmap.josm.command.SequenceCommand;
-import org.openstreetmap.josm.corrector.ReverseWayNoTagCorrector;
-import org.openstreetmap.josm.corrector.ReverseWayTagCorrector;
 import org.openstreetmap.josm.data.UndoRedoHandler;
 import org.openstreetmap.josm.data.osm.DataSet;
Index: /trunk/src/org/openstreetmap/josm/actions/corrector/ReverseWayNoTagCorrector.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/corrector/ReverseWayNoTagCorrector.java	(revision 14256)
+++ /trunk/src/org/openstreetmap/josm/actions/corrector/ReverseWayNoTagCorrector.java	(revision 14256)
@@ -0,0 +1,121 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.actions.corrector;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.data.osm.TagCollection;
+import org.openstreetmap.josm.data.osm.Tagged;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.tools.UserCancelException;
+import org.openstreetmap.josm.tools.Utils;
+
+/**
+ * A ReverseWayNoTagCorrector warns about ways that should not be reversed
+ * because their semantic meaning cannot be preserved in that case.
+ * E.g. natural=coastline, natural=cliff, barrier=retaining_wall cannot be changed.
+ * @see ReverseWayTagCorrector for handling of tags that can be modified (oneway=yes, etc.)
+ * @since 5724
+ */
+public final class ReverseWayNoTagCorrector {
+
+    private ReverseWayNoTagCorrector() {
+        // Hide default constructor for utils classes
+    }
+
+    /**
+     * Tags that imply a semantic meaning from the way direction and cannot be changed.
+     */
+    private static final TagCollection DIRECTIONAL_TAGS = new TagCollection(Arrays.asList(
+            new Tag("natural", "coastline"),
+            new Tag("natural", "cliff"),
+            new Tag("barrier", "guard_rail"),
+            new Tag("barrier", "kerb"),
+            new Tag("barrier", "retaining_wall"),
+            new Tag("man_made", "embankment")
+    ));
+
+    /**
+     * Replies the tags that imply a semantic meaning from <code>way</code> direction and cannot be changed.
+     * @param way The way to look for
+     * @return tags that imply a semantic meaning from <code>way</code> direction and cannot be changed
+     */
+    public static TagCollection getDirectionalTags(Tagged way) {
+        final TagCollection collection = new TagCollection();
+        for (Map.Entry<String, String> entry : way.getKeys().entrySet()) {
+            final Tag tag = new Tag(entry.getKey(), entry.getValue());
+            final boolean isDirectional = DIRECTIONAL_TAGS.contains(tag) || tag.isDirectionKey();
+            if (isDirectional) {
+                final boolean cannotBeCorrected = ReverseWayTagCorrector.getTagCorrections(tag).isEmpty();
+                if (cannotBeCorrected) {
+                    collection.add(tag);
+                }
+            }
+        }
+        return collection;
+    }
+
+    /**
+     * Tests whether way can be reversed without semantic change.
+     * Looks for tags like natural=cliff, barrier=retaining_wall.
+     * @param way The way to check
+     * @return false if the semantic meaning change if the way is reversed, true otherwise.
+     */
+    public static boolean isReversible(Tagged way) {
+        return getDirectionalTags(way).isEmpty();
+    }
+
+    private static boolean confirmReverseWay(Way way, TagCollection tags) {
+        String msg = trn(
+                // Singular, if a single tag is impacted
+                "<html>You are going to reverse the way ''{0}'',"
+                + "<br/> whose semantic meaning of its tag ''{1}'' is defined by its direction.<br/>"
+                + "Do you really want to change the way direction, thus its semantic meaning?</html>",
+                // Plural, if several tags are impacted
+                "<html>You are going to reverse the way ''{0}'',"
+                + "<br/> whose semantic meaning of these tags are defined by its direction:<br/>{1}"
+                + "Do you really want to change the way direction, thus its semantic meaning?</html>",
+                tags.size(),
+                Utils.escapeReservedCharactersHTML(way.getDisplayName(DefaultNameFormatter.getInstance())),
+                Utils.joinAsHtmlUnorderedList(tags)
+            );
+        int ret = ConditionalOptionPaneUtil.showOptionDialog(
+                "reverse_directional_way",
+                MainApplication.getMainFrame(),
+                msg,
+                tr("Reverse directional way."),
+                JOptionPane.YES_NO_CANCEL_OPTION,
+                JOptionPane.WARNING_MESSAGE,
+                null,
+                null
+        );
+        switch(ret) {
+            case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION:
+            case JOptionPane.YES_OPTION:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Checks the given way can be safely reversed and asks user to confirm the operation if it not the case.
+     * @param way The way to check
+     * @throws UserCancelException If the user cancels the operation
+     */
+    public static void checkAndConfirmReverseWay(Way way) throws UserCancelException {
+        TagCollection tags = getDirectionalTags(way);
+        if (!tags.isEmpty() && !confirmReverseWay(way, tags)) {
+            throw new UserCancelException();
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/actions/corrector/ReverseWayTagCorrector.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/corrector/ReverseWayTagCorrector.java	(revision 14256)
+++ /trunk/src/org/openstreetmap/josm/actions/corrector/ReverseWayTagCorrector.java	(revision 14256)
@@ -0,0 +1,311 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.actions.corrector;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.correction.RoleCorrection;
+import org.openstreetmap.josm.data.correction.TagCorrection;
+import org.openstreetmap.josm.data.osm.AbstractPrimitive;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmUtils;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.data.osm.TagCollection;
+import org.openstreetmap.josm.data.osm.Tagged;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.UserCancelException;
+
+/**
+ * A ReverseWayTagCorrector handles necessary corrections of tags
+ * when a way is reversed. E.g. oneway=yes needs to be changed
+ * to oneway=-1 and vice versa.
+ *
+ * The Corrector offers the automatic resolution in an dialog
+ * for the user to confirm.
+ */
+public class ReverseWayTagCorrector extends TagCorrector<Way> {
+
+    private static final String SEPARATOR = "[:_]";
+
+    private static Pattern getPatternFor(String s) {
+        return getPatternFor(s, false);
+    }
+
+    private static Pattern getPatternFor(String s, boolean exactMatch) {
+        if (exactMatch) {
+            return Pattern.compile("(^)(" + s + ")($)");
+        } else {
+            return Pattern.compile("(^|.*" + SEPARATOR + ")(" + s + ")(" + SEPARATOR + ".*|$)",
+                    Pattern.CASE_INSENSITIVE);
+        }
+    }
+
+    private static final Collection<Pattern> IGNORED_KEYS = new ArrayList<>();
+    static {
+        for (String s : AbstractPrimitive.getUninterestingKeys()) {
+            IGNORED_KEYS.add(getPatternFor(s));
+        }
+        for (String s : new String[]{"name", "ref", "tiger:county"}) {
+            IGNORED_KEYS.add(getPatternFor(s, false));
+        }
+        for (String s : new String[]{"tiger:county", "turn:lanes", "change:lanes", "placement"}) {
+            IGNORED_KEYS.add(getPatternFor(s, true));
+        }
+    }
+
+    private interface IStringSwitcher extends Function<String, String> {
+
+        static IStringSwitcher combined(IStringSwitcher... switchers) {
+            return key -> {
+                for (IStringSwitcher switcher : switchers) {
+                    final String newKey = switcher.apply(key);
+                    if (!key.equals(newKey)) {
+                        return newKey;
+                    }
+                }
+                return key;
+            };
+        }
+    }
+
+    private static class StringSwitcher implements IStringSwitcher {
+
+        private final String a;
+        private final String b;
+        private final Pattern pattern;
+
+        StringSwitcher(String a, String b) {
+            this.a = a;
+            this.b = b;
+            this.pattern = getPatternFor(a + '|' + b);
+        }
+
+        @Override
+        public String apply(String text) {
+            Matcher m = pattern.matcher(text);
+
+            if (m.lookingAt()) {
+                String leftRight = m.group(2).toLowerCase(Locale.ENGLISH);
+
+                StringBuilder result = new StringBuilder();
+                result.append(text.substring(0, m.start(2)))
+                      .append(leftRight.equals(a) ? b : a)
+                      .append(text.substring(m.end(2)));
+
+                return result.toString();
+            }
+            return text;
+        }
+    }
+
+    /**
+     * Reverses a given tag.
+     * @since 5787
+     */
+    public static final class TagSwitcher {
+
+        private TagSwitcher() {
+            // Hide implicit public constructor for utility class
+        }
+
+        /**
+         * Reverses a given tag.
+         * @param tag The tag to reverse
+         * @return The reversed tag (is equal to <code>tag</code> if no change is needed)
+         */
+        public static Tag apply(final Tag tag) {
+            return apply(tag.getKey(), tag.getValue());
+        }
+
+        /**
+         * Reverses a given tag (key=value).
+         * @param key The tag key
+         * @param value The tag value
+         * @return The reversed tag (is equal to <code>key=value</code> if no change is needed)
+         */
+        public static Tag apply(final String key, final String value) {
+            String newKey = key;
+            String newValue = value;
+
+            if (key.startsWith("oneway") || key.endsWith("oneway")) {
+                if (OsmUtils.isReversed(value)) {
+                    newValue = OsmUtils.TRUE_VALUE;
+                } else if (OsmUtils.isTrue(value)) {
+                    newValue = OsmUtils.REVERSE_VALUE;
+                }
+                newKey = COMBINED_SWITCHERS.apply(key);
+            } else if (key.startsWith("incline") || key.endsWith("incline")) {
+                newValue = UP_DOWN.apply(value);
+                if (newValue.equals(value)) {
+                    newValue = invertNumber(value);
+                }
+            } else if (key.startsWith("direction") || key.endsWith("direction")) {
+                newValue = COMBINED_SWITCHERS.apply(value);
+            } else if (key.endsWith(":forward") || key.endsWith(":backward")) {
+                // Change key but not left/right value (fix #8518)
+                newKey = FORWARD_BACKWARD.apply(key);
+            } else if (!ignoreKeyForCorrection(key)) {
+                newKey = COMBINED_SWITCHERS.apply(key);
+                newValue = COMBINED_SWITCHERS.apply(value);
+            }
+            return new Tag(newKey, newValue);
+        }
+    }
+
+    private static final StringSwitcher FORWARD_BACKWARD = new StringSwitcher("forward", "backward");
+    private static final StringSwitcher UP_DOWN = new StringSwitcher("up", "down");
+    private static final IStringSwitcher COMBINED_SWITCHERS = IStringSwitcher.combined(
+        new StringSwitcher("left", "right"),
+        new StringSwitcher("forwards", "backwards"),
+        FORWARD_BACKWARD, UP_DOWN
+    );
+
+    /**
+     * Tests whether way can be reversed without semantic change, i.e., whether tags have to be changed.
+     * Looks for keys like oneway, oneway:bicycle, cycleway:right:oneway, left/right.
+     * @param way way to test
+     * @return false if tags should be changed to keep semantic, true otherwise.
+     */
+    public static boolean isReversible(Way way) {
+        for (Tag tag : TagCollection.from(way)) {
+            if (!tag.equals(TagSwitcher.apply(tag))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns the subset of irreversible ways.
+     * @param ways all ways
+     * @return the subset of irreversible ways
+     * @see #isReversible(Way)
+     */
+    public static List<Way> irreversibleWays(List<Way> ways) {
+        List<Way> newWays = new ArrayList<>(ways);
+        for (Way way : ways) {
+            if (isReversible(way)) {
+                newWays.remove(way);
+            }
+        }
+        return newWays;
+    }
+
+    /**
+     * Inverts sign of a numeric value.
+     * @param value numeric value
+     * @return opposite numeric value
+     */
+    public static String invertNumber(String value) {
+        Pattern pattern = Pattern.compile("^([+-]?)(\\d.*)$", Pattern.CASE_INSENSITIVE);
+        Matcher matcher = pattern.matcher(value);
+        if (!matcher.matches()) return value;
+        String sign = matcher.group(1);
+        String rest = matcher.group(2);
+        sign = "-".equals(sign) ? "" : "-";
+        return sign + rest;
+    }
+
+    static List<TagCorrection> getTagCorrections(Tagged way) {
+        List<TagCorrection> tagCorrections = new ArrayList<>();
+        for (Map.Entry<String, String> entry : way.getKeys().entrySet()) {
+            final String key = entry.getKey();
+            final String value = entry.getValue();
+            Tag newTag = TagSwitcher.apply(key, value);
+            String newKey = newTag.getKey();
+            String newValue = newTag.getValue();
+
+            boolean needsCorrection = !key.equals(newKey);
+            if (way.get(newKey) != null && way.get(newKey).equals(newValue)) {
+                needsCorrection = false;
+            }
+            if (!value.equals(newValue)) {
+                needsCorrection = true;
+            }
+
+            if (needsCorrection) {
+                tagCorrections.add(new TagCorrection(key, value, newKey, newValue));
+            }
+        }
+        return tagCorrections;
+    }
+
+    static List<RoleCorrection> getRoleCorrections(Way oldway) {
+        List<RoleCorrection> roleCorrections = new ArrayList<>();
+
+        Collection<OsmPrimitive> referrers = oldway.getReferrers();
+        for (OsmPrimitive referrer: referrers) {
+            if (!(referrer instanceof Relation)) {
+                continue;
+            }
+            Relation relation = (Relation) referrer;
+            int position = 0;
+            for (RelationMember member : relation.getMembers()) {
+                if (!member.getMember().hasEqualSemanticAttributes(oldway)
+                        || !member.hasRole()) {
+                    position++;
+                    continue;
+                }
+
+                final String newRole = COMBINED_SWITCHERS.apply(member.getRole());
+                if (!member.getRole().equals(newRole)) {
+                    roleCorrections.add(new RoleCorrection(relation, position, member, newRole));
+                }
+
+                position++;
+            }
+        }
+        return roleCorrections;
+    }
+
+    static Map<OsmPrimitive, List<TagCorrection>> getTagCorrectionsMap(Way way) {
+        Map<OsmPrimitive, List<TagCorrection>> tagCorrectionsMap = new HashMap<>();
+        List<TagCorrection> tagCorrections = getTagCorrections(way);
+        if (!tagCorrections.isEmpty()) {
+            tagCorrectionsMap.put(way, tagCorrections);
+        }
+        for (Node node : way.getNodes()) {
+            final List<TagCorrection> corrections = getTagCorrections(node);
+            if (!corrections.isEmpty()) {
+                tagCorrectionsMap.put(node, corrections);
+            }
+        }
+        return tagCorrectionsMap;
+    }
+
+    @Override
+    public Collection<Command> execute(Way oldway, Way way) throws UserCancelException {
+        Map<OsmPrimitive, List<TagCorrection>> tagCorrectionsMap = getTagCorrectionsMap(way);
+
+        Map<OsmPrimitive, List<RoleCorrection>> roleCorrectionMap = new HashMap<>();
+        List<RoleCorrection> roleCorrections = getRoleCorrections(oldway);
+        if (!roleCorrections.isEmpty()) {
+            roleCorrectionMap.put(way, roleCorrections);
+        }
+
+        return applyCorrections(oldway.getDataSet(), tagCorrectionsMap, roleCorrectionMap,
+                tr("When reversing this way, the following changes are suggested in order to maintain data consistency."));
+    }
+
+    private static boolean ignoreKeyForCorrection(String key) {
+        for (Pattern ignoredKey : IGNORED_KEYS) {
+            if (ignoredKey.matcher(key).matches()) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/actions/corrector/TagCorrector.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/corrector/TagCorrector.java	(revision 14256)
+++ /trunk/src/org/openstreetmap/josm/actions/corrector/TagCorrector.java	(revision 14256)
@@ -0,0 +1,218 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.actions.corrector;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.GridBagLayout;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+import org.openstreetmap.josm.command.ChangeCommand;
+import org.openstreetmap.josm.command.ChangeRelationMemberRoleCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.correction.RoleCorrection;
+import org.openstreetmap.josm.data.correction.TagCorrection;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.correction.RoleCorrectionTable;
+import org.openstreetmap.josm.gui.correction.TagCorrectionTable;
+import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.UserCancelException;
+
+/**
+ * Abstract base class for automatic tag corrections.
+ *
+ * Subclasses call applyCorrections() with maps of the requested
+ * corrections and a dialog is presented to the user to
+ * confirm these changes.
+ * @param <P> The type of OSM primitive to correct
+ */
+public abstract class TagCorrector<P extends OsmPrimitive> {
+
+    /**
+     * Executes the tag correction.
+     * @param oldprimitive old primitive
+     * @param primitive new primitive
+     * @return A list of commands
+     * @throws UserCancelException If the user canceled
+     * @see #applyCorrections(DataSet, Map, Map, String)
+     */
+    public abstract Collection<Command> execute(P oldprimitive, P primitive) throws UserCancelException;
+
+    private static final String[] APPLICATION_OPTIONS = new String[] {
+            tr("Apply selected changes"),
+            tr("Do not apply changes"),
+            tr("Cancel")
+    };
+
+    /**
+     * Creates the commands to correct the tags. Asks the users about it.
+     * @param dataSet The data set the primitives will be in once the commands are executed
+     * @param tagCorrectionsMap The possible tag corrections
+     * @param roleCorrectionMap The possible role corrections
+     * @param description A description to add to the dialog.
+     * @return A list of commands
+     * @throws UserCancelException If the user canceled
+     */
+    protected Collection<Command> applyCorrections(
+            DataSet dataSet,
+            Map<OsmPrimitive, List<TagCorrection>> tagCorrectionsMap,
+            Map<OsmPrimitive, List<RoleCorrection>> roleCorrectionMap,
+            String description) throws UserCancelException {
+
+        if (!tagCorrectionsMap.isEmpty() || !roleCorrectionMap.isEmpty()) {
+            Collection<Command> commands = new ArrayList<>();
+            Map<OsmPrimitive, TagCorrectionTable> tagTableMap = new HashMap<>();
+            Map<OsmPrimitive, RoleCorrectionTable> roleTableMap = new HashMap<>();
+
+            final JPanel p = new JPanel(new GridBagLayout());
+
+            final JMultilineLabel label1 = new JMultilineLabel(description);
+            label1.setMaxWidth(600);
+            p.add(label1, GBC.eop().anchor(GBC.CENTER).fill(GBC.HORIZONTAL));
+
+            final JMultilineLabel label2 = new JMultilineLabel(
+                    tr("Please select which changes you want to apply."));
+            label2.setMaxWidth(600);
+            p.add(label2, GBC.eop().anchor(GBC.CENTER).fill(GBC.HORIZONTAL));
+
+            for (Entry<OsmPrimitive, List<TagCorrection>> entry : tagCorrectionsMap.entrySet()) {
+                final OsmPrimitive primitive = entry.getKey();
+                final List<TagCorrection> tagCorrections = entry.getValue();
+
+                if (tagCorrections.isEmpty()) {
+                    continue;
+                }
+
+                final JLabel propertiesLabel = new JLabel(tr("Tags of "));
+                p.add(propertiesLabel, GBC.std());
+
+                final JLabel primitiveLabel = new JLabel(
+                        primitive.getDisplayName(DefaultNameFormatter.getInstance()) + ':',
+                        ImageProvider.get(primitive.getDisplayType()),
+                        JLabel.LEFT
+                );
+                p.add(primitiveLabel, GBC.eol());
+
+                final TagCorrectionTable table = new TagCorrectionTable(
+                        tagCorrections);
+                final JScrollPane scrollPane = new JScrollPane(table);
+                p.add(scrollPane, GBC.eop().fill(GBC.HORIZONTAL));
+
+                tagTableMap.put(primitive, table);
+            }
+
+            for (Entry<OsmPrimitive, List<RoleCorrection>> entry : roleCorrectionMap.entrySet()) {
+                final OsmPrimitive primitive = entry.getKey();
+                final List<RoleCorrection> roleCorrections = entry.getValue();
+
+                if (roleCorrections.isEmpty()) {
+                    continue;
+                }
+
+                final JLabel rolesLabel = new JLabel(tr("Roles in relations referring to"));
+                p.add(rolesLabel, GBC.std());
+
+                final JLabel primitiveLabel = new JLabel(
+                        primitive.getDisplayName(DefaultNameFormatter.getInstance()),
+                        ImageProvider.get(primitive.getDisplayType()),
+                        JLabel.LEFT
+                );
+                p.add(primitiveLabel, GBC.eol());
+                rolesLabel.setLabelFor(primitiveLabel);
+
+                final RoleCorrectionTable table = new RoleCorrectionTable(roleCorrections);
+                final JScrollPane scrollPane = new JScrollPane(table);
+                p.add(scrollPane, GBC.eop().fill(GBC.HORIZONTAL));
+                primitiveLabel.setLabelFor(table);
+
+                roleTableMap.put(primitive, table);
+            }
+
+            int answer = JOptionPane.showOptionDialog(
+                    MainApplication.getMainFrame(),
+                    p,
+                    tr("Automatic tag correction"),
+                    JOptionPane.YES_NO_CANCEL_OPTION,
+                    JOptionPane.PLAIN_MESSAGE,
+                    null,
+                    APPLICATION_OPTIONS,
+                    APPLICATION_OPTIONS[0]
+            );
+
+            if (answer == JOptionPane.YES_OPTION) {
+                for (Entry<OsmPrimitive, List<TagCorrection>> entry : tagCorrectionsMap.entrySet()) {
+                    OsmPrimitive primitive = entry.getKey();
+
+                    // create the clone
+                    OsmPrimitive clone;
+                    if (primitive instanceof Way) {
+                        clone = new Way((Way) primitive);
+                    } else if (primitive instanceof Node) {
+                        clone = new Node((Node) primitive);
+                    } else if (primitive instanceof Relation) {
+                        clone = new Relation((Relation) primitive);
+                    } else
+                        throw new AssertionError();
+
+                    // use this structure to remember keys that have been set already so that
+                    // they're not dropped by a later step
+                    Set<String> keysChanged = new HashSet<>();
+
+                    // apply all changes to this clone
+                    List<TagCorrection> tagCorrections = entry.getValue();
+                    for (int i = 0; i < tagCorrections.size(); i++) {
+                        if (tagTableMap.get(primitive).getCorrectionTableModel().getApply(i)) {
+                            TagCorrection tagCorrection = tagCorrections.get(i);
+                            if (tagCorrection.isKeyChanged() && !keysChanged.contains(tagCorrection.oldKey)) {
+                                clone.remove(tagCorrection.oldKey);
+                            }
+                            clone.put(tagCorrection.newKey, tagCorrection.newValue);
+                            keysChanged.add(tagCorrection.newKey);
+                        }
+                    }
+
+                    // save the clone
+                    if (!keysChanged.isEmpty()) {
+                        commands.add(new ChangeCommand(dataSet, primitive, clone));
+                    }
+                }
+                for (Entry<OsmPrimitive, List<RoleCorrection>> entry : roleCorrectionMap.entrySet()) {
+                    OsmPrimitive primitive = entry.getKey();
+                    List<RoleCorrection> roleCorrections = entry.getValue();
+
+                    for (int i = 0; i < roleCorrections.size(); i++) {
+                        RoleCorrection roleCorrection = roleCorrections.get(i);
+                        if (roleTableMap.get(primitive).getCorrectionTableModel().getApply(i)) {
+                            commands.add(new ChangeRelationMemberRoleCommand(dataSet,
+                                    roleCorrection.relation, roleCorrection.position, roleCorrection.newRole));
+                        }
+                    }
+                }
+            } else if (answer != JOptionPane.NO_OPTION)
+                throw new UserCancelException();
+            return commands;
+        }
+
+        return Collections.emptyList();
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/actions/corrector/package-info.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/corrector/package-info.java	(revision 14256)
+++ /trunk/src/org/openstreetmap/josm/actions/corrector/package-info.java	(revision 14256)
@@ -0,0 +1,6 @@
+// License: GPL. For details, see LICENSE file.
+
+/**
+ * Provides the classes for JOSM {@link org.openstreetmap.josm.actions.corrector.TagCorrector tag correctors}.
+ */
+package org.openstreetmap.josm.actions.corrector;
Index: /trunk/test/unit/org/openstreetmap/josm/actions/corrector/ReverseWayNoTagCorrectorTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/actions/corrector/ReverseWayNoTagCorrectorTest.java	(revision 14256)
+++ /trunk/test/unit/org/openstreetmap/josm/actions/corrector/ReverseWayNoTagCorrectorTest.java	(revision 14256)
@@ -0,0 +1,37 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.actions.corrector;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Unit tests of {@link ReverseWayNoTagCorrector} class.
+ */
+public class ReverseWayNoTagCorrectorTest {
+
+    /**
+     * Setup test.
+     */
+    @Rule
+    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+    public JOSMTestRules test = new JOSMTestRules();
+
+    /**
+     * Tests the {@link ReverseWayNoTagCorrector#getDirectionalTags} function
+     */
+    @Test
+    public void testDirectionalTags() {
+        assertEquals(1, ReverseWayNoTagCorrector.getDirectionalTags(new Tag("waterway", "drain")).size());
+        assertEquals(1, ReverseWayNoTagCorrector.getDirectionalTags(new Tag("man_made", "embankment")).size());
+        assertEquals(1, ReverseWayNoTagCorrector.getDirectionalTags(new Tag("aerialway", "drag_lift")).size());
+        assertEquals(0, ReverseWayNoTagCorrector.getDirectionalTags(new Tag("aerialway", "station")).size());
+        assertEquals(0, ReverseWayNoTagCorrector.getDirectionalTags(new Tag("incline", "up")).size());
+        assertEquals(0, ReverseWayNoTagCorrector.getDirectionalTags(new Tag("oneway", "yes")).size());
+    }
+}
Index: /trunk/test/unit/org/openstreetmap/josm/actions/corrector/ReverseWayTagCorrectorTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/actions/corrector/ReverseWayTagCorrectorTest.java	(revision 14256)
+++ /trunk/test/unit/org/openstreetmap/josm/actions/corrector/ReverseWayTagCorrectorTest.java	(revision 14256)
@@ -0,0 +1,133 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.actions.corrector;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.openstreetmap.josm.data.correction.TagCorrection;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmUtils;
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import net.trajano.commons.testing.UtilityClassTestUtil;
+
+/**
+ * Unit tests of {@link ReverseWayTagCorrector} class.
+ */
+public class ReverseWayTagCorrectorTest {
+
+    /**
+     * Setup test.
+     */
+    @Rule
+    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+    public JOSMTestRules test = new JOSMTestRules();
+
+    /**
+     * Tests that {@code ReverseWayTagCorrector.TagSwitcher} satisfies utility class criterias.
+     * @throws ReflectiveOperationException if an error occurs
+     */
+    @Test
+    public void testUtilityClass() throws ReflectiveOperationException {
+        UtilityClassTestUtil.assertUtilityClassWellDefined(ReverseWayTagCorrector.TagSwitcher.class);
+    }
+
+    /**
+     * Test of {@link ReverseWayTagCorrector.TagSwitcher#apply} method.
+     */
+    @Test
+    public void testTagSwitch() {
+        // oneway
+        assertSwitch(new Tag("oneway", "yes"), new Tag("oneway", "-1"));
+        assertSwitch(new Tag("oneway", "true"), new Tag("oneway", "-1"));
+        assertSwitch(new Tag("oneway", "-1"), new Tag("oneway", "yes"));
+        assertSwitch(new Tag("oneway", "no"), new Tag("oneway", "no"));
+        assertSwitch(new Tag("oneway", "something"), new Tag("oneway", "something"));
+        // incline/direction
+        for (String k : new String[]{"incline", "direction"}) {
+            assertSwitch(new Tag(k, "up"), new Tag(k, "down"));
+            assertSwitch(new Tag(k, "down"), new Tag(k, "up"));
+            assertSwitch(new Tag(k, "something"), new Tag(k, "something"));
+        }
+        // direction=forward/backward/...
+        assertSwitch(new Tag("direction", "forward"), new Tag("direction", "backward"));
+        assertSwitch(new Tag("direction", "backward"), new Tag("direction", "forward"));
+        // :left/:right with oneway (see #10977)
+        assertSwitch(new Tag("cycleway:left:oneway", "-1"), new Tag("cycleway:right:oneway", "yes"));
+        // :forward/:backward (see #8518)
+        assertSwitch(new Tag("turn:forward", "right"), new Tag("turn:backward", "right"));
+        assertSwitch(new Tag("change:forward", "not_right"), new Tag("change:backward", "not_right"));
+        assertSwitch(new Tag("placement:forward", "right_of:1"), new Tag("placement:backward", "right_of:1"));
+        assertSwitch(new Tag("turn:lanes:forward", "left|right"), new Tag("turn:lanes:backward", "left|right"));
+        assertSwitch(new Tag("change:lanes:forward", "not_right|only_left"), new Tag("change:lanes:backward", "not_right|only_left"));
+        // keys
+        assertSwitch(new Tag("forward", "something"), new Tag("backward", "something"));
+        assertSwitch(new Tag("backward", "something"), new Tag("forward", "something"));
+        assertSwitch(new Tag("up", "something"), new Tag("down", "something"));
+        assertSwitch(new Tag("down", "something"), new Tag("up", "something"));
+        // values
+        assertSwitch(new Tag("something", "forward"), new Tag("something", "backward"));
+        assertSwitch(new Tag("something", "backward"), new Tag("something", "forward"));
+        assertSwitch(new Tag("something", "up"), new Tag("something", "down"));
+        assertSwitch(new Tag("something", "down"), new Tag("something", "up"));
+        // value[:_]suffix
+        assertSwitch(new Tag("something", "forward:suffix"), new Tag("something", "backward:suffix"));
+        assertSwitch(new Tag("something", "backward_suffix"), new Tag("something", "forward_suffix"));
+        assertSwitch(new Tag("something", "up:suffix"), new Tag("something", "down:suffix"));
+        assertSwitch(new Tag("something", "down_suffix"), new Tag("something", "up_suffix"));
+        // prefix[:_]value
+        assertSwitch(new Tag("something", "prefix:forward"), new Tag("something", "prefix:backward"));
+        assertSwitch(new Tag("something", "prefix_backward"), new Tag("something", "prefix_forward"));
+        assertSwitch(new Tag("something", "prefix:up"), new Tag("something", "prefix:down"));
+        assertSwitch(new Tag("something", "prefix_down"), new Tag("something", "prefix_up"));
+        // prefix[:_]value[:_]suffix
+        assertSwitch(new Tag("something", "prefix:forward:suffix"), new Tag("something", "prefix:backward:suffix"));
+        assertSwitch(new Tag("something", "prefix_backward:suffix"), new Tag("something", "prefix_forward:suffix"));
+        assertSwitch(new Tag("something", "prefix:up_suffix"), new Tag("something", "prefix:down_suffix"));
+        assertSwitch(new Tag("something", "prefix_down_suffix"), new Tag("something", "prefix_up_suffix"));
+        // #8499
+        assertSwitch(new Tag("type", "drawdown"), new Tag("type", "drawdown"));
+    }
+
+    private void assertSwitch(Tag oldTag, Tag newTag) {
+        Assert.assertEquals(ReverseWayTagCorrector.TagSwitcher.apply(oldTag), newTag);
+    }
+
+    private Map<OsmPrimitive, List<TagCorrection>> getTagCorrectionsForWay(String middleNodeTags) {
+        final OsmPrimitive n1 = OsmUtils.createPrimitive("node");
+        final OsmPrimitive n2 = OsmUtils.createPrimitive("node " + middleNodeTags);
+        final OsmPrimitive n3 = OsmUtils.createPrimitive("node");
+        final Way w = new Way();
+        Stream.of(n1, n2, n3).map(Node.class::cast).forEach(w::addNode);
+        return ReverseWayTagCorrector.getTagCorrectionsMap(w);
+    }
+
+    /**
+     * Test tag correction on way nodes
+     */
+    @Test
+    public void testSwitchingWayNodes() {
+        final Map<OsmPrimitive, List<TagCorrection>> tagCorrections = getTagCorrectionsForWay("direction=forward");
+        Assert.assertEquals(1, tagCorrections.size());
+        Assert.assertEquals(Collections.singletonList(new TagCorrection("direction", "forward", "direction", "backward")),
+                tagCorrections.values().iterator().next());
+    }
+
+    /**
+     * Test tag correction on way nodes are not applied for absolute values such as compass cardinal directions
+     */
+    @Test
+    public void testNotSwitchingWayNodes() {
+        Assert.assertEquals(0, getTagCorrectionsForWay("direction=SSW").size());
+        Assert.assertEquals(0, getTagCorrectionsForWay("direction=145").size());
+    }
+}
