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}