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