001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.corrector;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.util.ArrayList;
007    import java.util.Arrays;
008    import java.util.Collection;
009    import java.util.HashMap;
010    import java.util.List;
011    import java.util.Map;
012    import java.util.regex.Matcher;
013    import java.util.regex.Pattern;
014    
015    import org.openstreetmap.josm.command.Command;
016    import org.openstreetmap.josm.data.osm.OsmPrimitive;
017    import org.openstreetmap.josm.data.osm.OsmUtils;
018    import org.openstreetmap.josm.data.osm.Relation;
019    import org.openstreetmap.josm.data.osm.RelationMember;
020    import org.openstreetmap.josm.data.osm.Way;
021    
022    /**
023     * A ReverseWayTagCorrector handles necessary corrections of tags
024     * when a way is reversed. E.g. oneway=yes needs to be changed
025     * to oneway=-1 and vice versa.
026     *
027     * The Corrector offers the automatic resolution in an dialog
028     * for the user to confirm.
029     */
030    
031    public class ReverseWayTagCorrector extends TagCorrector<Way> {
032    
033        private static class PrefixSuffixSwitcher {
034    
035            private static final String SEPARATOR = "[:_]?";
036    
037            private final String a;
038            private final String b;
039            private final Pattern startPattern;
040            private final Pattern endPattern;
041    
042            public PrefixSuffixSwitcher(String a, String b) {
043                this.a = a;
044                this.b = b;
045                startPattern = Pattern.compile(
046                        "^(" + a + "|" + b + ")(" + SEPARATOR + "|$)",
047                        Pattern.CASE_INSENSITIVE);
048                endPattern = Pattern.compile("^.*" +
049                        SEPARATOR + "(" + a + "|" + b + ")$",
050                        Pattern.CASE_INSENSITIVE);
051            }
052    
053            public String apply(String text) {
054                Matcher m = startPattern.matcher(text);
055                if (!m.lookingAt()) {
056                    m = endPattern.matcher(text);
057                }
058    
059                if (m.lookingAt()) {
060                    String leftRight = m.group(1).toLowerCase();
061    
062                    StringBuilder result = new StringBuilder();
063                    result.append(text.substring(0, m.start(1)));
064                    result.append(leftRight.equals(a) ? b : a);
065                    result.append(text.substring(m.end(1)));
066    
067                    return result.toString();
068                }
069                return text;
070            }
071        }
072    
073        private static PrefixSuffixSwitcher[] prefixSuffixSwitchers =
074            new PrefixSuffixSwitcher[] {
075            new PrefixSuffixSwitcher("left", "right"),
076            new PrefixSuffixSwitcher("forward", "backward"),
077            new PrefixSuffixSwitcher("forwards", "backwards"),
078            new PrefixSuffixSwitcher("up", "down"),
079            new PrefixSuffixSwitcher("east", "west"),
080            new PrefixSuffixSwitcher("north", "south"),
081        };
082    
083        private static ArrayList<String> reversibleTags = new ArrayList<String>(
084                Arrays.asList(new String[] {"oneway", "incline", "direction"}));
085    
086        public static boolean isReversible(Way way) {
087            for (String key : way.keySet()) {
088                if (reversibleTags.contains(key)) return false;
089                for (PrefixSuffixSwitcher prefixSuffixSwitcher : prefixSuffixSwitchers) {
090                    if (!key.equals(prefixSuffixSwitcher.apply(key))) return false;
091                }
092            }
093    
094            return true;
095        }
096    
097        public static List<Way> irreversibleWays(List<Way> ways) {
098            List<Way> newWays = new ArrayList<Way>(ways);
099            for (Way way : ways) {
100                if (isReversible(way)) {
101                    newWays.remove(way);
102                }
103            }
104            return newWays;
105        }
106    
107        public String invertNumber(String value) {
108            Pattern pattern = Pattern.compile("^([+-]?)(\\d.*)$", Pattern.CASE_INSENSITIVE);
109            Matcher matcher = pattern.matcher(value);
110            if (!matcher.matches()) return value;
111            String sign = matcher.group(1);
112            String rest = matcher.group(2);
113            sign = sign.equals("-") ? "" : "-";
114            return sign + rest;
115        }
116    
117        @Override
118        public Collection<Command> execute(Way oldway, Way way) throws UserCancelException {
119            Map<OsmPrimitive, List<TagCorrection>> tagCorrectionsMap =
120                new HashMap<OsmPrimitive, List<TagCorrection>>();
121    
122            ArrayList<TagCorrection> tagCorrections = new ArrayList<TagCorrection>();
123            for (String key : way.keySet()) {
124                String newKey = key;
125                String value = way.get(key);
126                String newValue = value;
127    
128                if (key.equals("oneway")) {
129                    if (OsmUtils.isReversed(value)) {
130                        newValue = OsmUtils.trueval;
131                    } else if (OsmUtils.isTrue(value)) {
132                        newValue = OsmUtils.reverseval;
133                    }
134                } else if (key.equals("incline") || key.equals("direction")) {
135                    PrefixSuffixSwitcher switcher = new PrefixSuffixSwitcher("up", "down");
136                    newValue = switcher.apply(value);
137                    if (newValue.equals(value)) {
138                        newValue = invertNumber(value);
139                    }
140                } else if (!ignoreKeyForPrefixSuffixCorrection(key)) {
141                    for (PrefixSuffixSwitcher prefixSuffixSwitcher : prefixSuffixSwitchers) {
142                        newKey = prefixSuffixSwitcher.apply(key);
143                        if (!key.equals(newKey)) {
144                            break;
145                        }
146                        newValue = prefixSuffixSwitcher.apply(value);
147                        if (!value.equals(newValue)) {
148                            break;
149                        }
150                    }
151                }
152    
153                boolean needsCorrection = !key.equals(newKey);
154                if (way.get(newKey) != null && way.get(newKey).equals(newValue)) {
155                    needsCorrection = false;
156                }
157                if (!value.equals(newValue)) {
158                    needsCorrection = true;
159                }
160    
161                if (needsCorrection) {
162                    tagCorrections.add(new TagCorrection(key, value, newKey, newValue));
163                }
164            }
165            if (!tagCorrections.isEmpty()) {
166                tagCorrectionsMap.put(way, tagCorrections);
167            }
168    
169            Map<OsmPrimitive, List<RoleCorrection>> roleCorrectionMap =
170                new HashMap<OsmPrimitive, List<RoleCorrection>>();
171            ArrayList<RoleCorrection> roleCorrections = new ArrayList<RoleCorrection>();
172    
173            Collection<OsmPrimitive> referrers = oldway.getReferrers();
174            for (OsmPrimitive referrer: referrers) {
175                if (! (referrer instanceof Relation)) {
176                    continue;
177                }
178                Relation relation = (Relation)referrer;
179                int position = 0;
180                for (RelationMember member : relation.getMembers()) {
181                    if (!member.getMember().hasEqualSemanticAttributes(oldway)
182                            || !member.hasRole()) {
183                        position++;
184                        continue;
185                    }
186    
187                    boolean found = false;
188                    String newRole = null;
189                    for (PrefixSuffixSwitcher prefixSuffixSwitcher : prefixSuffixSwitchers) {
190                        newRole = prefixSuffixSwitcher.apply(member.getRole());
191                        if (!newRole.equals(member.getRole())) {
192                            found = true;
193                            break;
194                        }
195                    }
196    
197                    if (found) {
198                        roleCorrections.add(new RoleCorrection(relation, position, member, newRole));
199                    }
200    
201                    position++;
202                }
203            }
204            if (!roleCorrections.isEmpty()) {
205                roleCorrectionMap.put(way, roleCorrections);
206            }
207    
208            return applyCorrections(tagCorrectionsMap, roleCorrectionMap,
209                    tr("When reversing this way, the following changes to properties "
210                            + "of the way and its nodes are suggested in order "
211                            + "to maintain data consistency."));
212        }
213    
214        private static boolean ignoreKeyForPrefixSuffixCorrection(String key) {
215            return key.contains("name") || key.equals("tiger:county")
216                    || key.equalsIgnoreCase("fixme") || key.startsWith("note");
217        }
218    }