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