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