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.JOptionPane; 019import javax.swing.JPanel; 020import javax.swing.JScrollPane; 021 022import org.openstreetmap.josm.command.ChangeCommand; 023import org.openstreetmap.josm.command.ChangeRelationMemberRoleCommand; 024import org.openstreetmap.josm.command.Command; 025import org.openstreetmap.josm.data.correction.RoleCorrection; 026import org.openstreetmap.josm.data.correction.TagCorrection; 027import org.openstreetmap.josm.data.osm.DataSet; 028import org.openstreetmap.josm.data.osm.DefaultNameFormatter; 029import org.openstreetmap.josm.data.osm.Node; 030import org.openstreetmap.josm.data.osm.OsmPrimitive; 031import org.openstreetmap.josm.data.osm.Relation; 032import org.openstreetmap.josm.data.osm.Way; 033import org.openstreetmap.josm.gui.MainApplication; 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 MainApplication.getMainFrame(), 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}