001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.corrector;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.GridBagLayout;
007import java.util.ArrayList;
008import java.util.Collection;
009import java.util.Collections;
010import java.util.HashMap;
011import java.util.HashSet;
012import java.util.List;
013import java.util.Map;
014import java.util.Set;
015import java.util.Map.Entry;
016
017import javax.swing.JLabel;
018import javax.swing.JOptionPane;
019import javax.swing.JPanel;
020import javax.swing.JScrollPane;
021
022import org.openstreetmap.josm.Main;
023import org.openstreetmap.josm.command.ChangeCommand;
024import org.openstreetmap.josm.command.ChangeRelationMemberRoleCommand;
025import org.openstreetmap.josm.command.Command;
026import org.openstreetmap.josm.data.osm.Node;
027import org.openstreetmap.josm.data.osm.OsmPrimitive;
028import org.openstreetmap.josm.data.osm.Relation;
029import org.openstreetmap.josm.data.osm.Way;
030import org.openstreetmap.josm.gui.DefaultNameFormatter;
031import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
032import org.openstreetmap.josm.tools.GBC;
033import org.openstreetmap.josm.tools.ImageProvider;
034
035/**
036 * Abstract base class for automatic tag corrections.
037 *
038 * Subclasses call applyCorrections() with maps of the requested
039 * corrections and a dialog is pesented to the user to
040 * confirm these changes.
041 * @param <P> The type of OSM primitive to correct
042 */
043public abstract class TagCorrector<P extends OsmPrimitive> {
044
045    public abstract Collection<Command> execute(P oldprimitive, P primitive) throws UserCancelException;
046
047    private String[] applicationOptions = new String[] {
048            tr("Apply selected changes"),
049            tr("Do not apply changes"),
050            tr("Cancel")
051    };
052
053    protected Collection<Command> applyCorrections(
054            Map<OsmPrimitive, List<TagCorrection>> tagCorrectionsMap,
055            Map<OsmPrimitive, List<RoleCorrection>> roleCorrectionMap,
056            String description) throws UserCancelException {
057
058        if (!tagCorrectionsMap.isEmpty() || !roleCorrectionMap.isEmpty()) {
059            Collection<Command> commands = new ArrayList<>();
060            Map<OsmPrimitive, TagCorrectionTable> tagTableMap = new HashMap<>();
061            Map<OsmPrimitive, RoleCorrectionTable> roleTableMap = new HashMap<>();
062
063            final JPanel p = new JPanel(new GridBagLayout());
064
065            final JMultilineLabel label1 = new JMultilineLabel(description);
066            label1.setMaxWidth(600);
067            p.add(label1, GBC.eop().anchor(GBC.CENTER).fill(GBC.HORIZONTAL));
068
069            final JMultilineLabel label2 = new JMultilineLabel(
070                    tr("Please select which changes you want to apply."));
071            label2.setMaxWidth(600);
072            p.add(label2, GBC.eop().anchor(GBC.CENTER).fill(GBC.HORIZONTAL));
073
074            for (Entry<OsmPrimitive, List<TagCorrection>> entry : tagCorrectionsMap.entrySet()) {
075                final OsmPrimitive primitive = entry.getKey();
076                final List<TagCorrection> tagCorrections = entry.getValue();
077
078                if (tagCorrections.isEmpty()) {
079                    continue;
080                }
081
082                final JLabel propertiesLabel = new JLabel(tr("Tags of "));
083                p.add(propertiesLabel, GBC.std());
084
085                final JLabel primitiveLabel = new JLabel(
086                        primitive.getDisplayName(DefaultNameFormatter.getInstance()) + ":",
087                        ImageProvider.get(primitive.getDisplayType()),
088                        JLabel.LEFT
089                );
090                p.add(primitiveLabel, GBC.eol());
091
092                final TagCorrectionTable table = new TagCorrectionTable(
093                        tagCorrections);
094                final JScrollPane scrollPane = new JScrollPane(table);
095                p.add(scrollPane, GBC.eop().fill(GBC.HORIZONTAL));
096
097                tagTableMap.put(primitive, table);
098            }
099
100            for (Entry<OsmPrimitive, List<RoleCorrection>> entry : roleCorrectionMap.entrySet()) {
101                final OsmPrimitive primitive = entry.getKey();
102                final List<RoleCorrection> roleCorrections = entry.getValue();
103
104                if (roleCorrections.isEmpty()) {
105                    continue;
106                }
107
108                final JLabel rolesLabel = new JLabel(tr("Roles in relations referring to"));
109                p.add(rolesLabel, GBC.std());
110
111                final JLabel primitiveLabel = new JLabel(
112                        primitive.getDisplayName(DefaultNameFormatter.getInstance()),
113                        ImageProvider.get(primitive.getDisplayType()),
114                        JLabel.LEFT
115                );
116                p.add(primitiveLabel, GBC.eol());
117
118                final RoleCorrectionTable table = new RoleCorrectionTable(roleCorrections);
119                final JScrollPane scrollPane = new JScrollPane(table);
120                p.add(scrollPane, GBC.eop().fill(GBC.HORIZONTAL));
121
122                roleTableMap.put(primitive, table);
123            }
124
125            int answer = JOptionPane.showOptionDialog(
126                    Main.parent,
127                    p,
128                    tr("Automatic tag correction"),
129                    JOptionPane.YES_NO_CANCEL_OPTION,
130                    JOptionPane.PLAIN_MESSAGE,
131                    null,
132                    applicationOptions,
133                    applicationOptions[0]
134            );
135
136            if (answer == JOptionPane.YES_OPTION) {
137                for (Entry<OsmPrimitive, List<TagCorrection>> entry : tagCorrectionsMap.entrySet()) {
138                    List<TagCorrection> tagCorrections = entry.getValue();
139                    OsmPrimitive primitive = entry.getKey();
140
141                    // create the clone
142                    OsmPrimitive clone = null;
143                    if (primitive instanceof Way) {
144                        clone = new Way((Way)primitive);
145                    } else if (primitive instanceof Node) {
146                        clone = new Node((Node)primitive);
147                    } else if (primitive instanceof Relation) {
148                        clone = new Relation((Relation)primitive);
149                    } else
150                        throw new AssertionError();
151
152                    // use this structure to remember keys that have been set already so that
153                    // they're not dropped by a later step
154                    Set<String> keysChanged = new HashSet<>();
155
156                    // apply all changes to this clone
157                    for (int i = 0; i < tagCorrections.size(); i++) {
158                        if (tagTableMap.get(primitive).getCorrectionTableModel().getApply(i)) {
159                            TagCorrection tagCorrection = tagCorrections.get(i);
160                            if (tagCorrection.isKeyChanged() && !keysChanged.contains(tagCorrection.oldKey)) {
161                                clone.remove(tagCorrection.oldKey);
162                            }
163                            clone.put(tagCorrection.newKey, tagCorrection.newValue);
164                            keysChanged.add(tagCorrection.newKey);
165                        }
166                    }
167
168                    // save the clone
169                    if (!keysChanged.isEmpty()) {
170                        commands.add(new ChangeCommand(primitive, clone));
171                    }
172                }
173                for (Entry<OsmPrimitive, List<RoleCorrection>> entry : roleCorrectionMap.entrySet()) {
174                    OsmPrimitive primitive = entry.getKey();
175                    List<RoleCorrection> roleCorrections = entry.getValue();
176
177                    for (int i = 0; i < roleCorrections.size(); i++) {
178                        RoleCorrection roleCorrection = roleCorrections.get(i);
179                        if (roleTableMap.get(primitive).getCorrectionTableModel().getApply(i)) {
180                            commands.add(new ChangeRelationMemberRoleCommand(roleCorrection.relation, roleCorrection.position, roleCorrection.newRole));
181                        }
182                    }
183                }
184            } else if (answer != JOptionPane.NO_OPTION)
185                throw new UserCancelException();
186            return commands;
187        }
188
189        return Collections.emptyList();
190    }
191}