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