001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions.upload; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.util.Collection; 007import java.util.Collections; 008import java.util.HashMap; 009import java.util.LinkedList; 010import java.util.List; 011import java.util.Map; 012import java.util.Map.Entry; 013 014import org.openstreetmap.josm.Main; 015import org.openstreetmap.josm.command.ChangePropertyCommand; 016import org.openstreetmap.josm.command.Command; 017import org.openstreetmap.josm.command.SequenceCommand; 018import org.openstreetmap.josm.data.APIDataSet; 019import org.openstreetmap.josm.data.osm.OsmPrimitive; 020import org.openstreetmap.josm.data.osm.Relation; 021import org.openstreetmap.josm.data.osm.Tag; 022 023/** 024 * Fixes defective data entries for all modified objects before upload 025 * @since 5621 026 */ 027public class FixDataHook implements UploadHook { 028 029 /** 030 * List of checks to run on data 031 */ 032 private final List<FixData> deprecated = new LinkedList<>(); 033 034 /** 035 * Constructor for data initialization 036 */ 037 public FixDataHook() { 038 // CHECKSTYLE.OFF: SingleSpaceSeparator 039 deprecated.add(new FixDataSpace()); 040 deprecated.add(new FixDataKey("color", "colour")); 041 deprecated.add(new FixDataTag("highway", "ford", "ford", "yes")); 042 deprecated.add(new FixDataTag("oneway", "false", "oneway", "no")); 043 deprecated.add(new FixDataTag("oneway", "0", "oneway", "no")); 044 deprecated.add(new FixDataTag("oneway", "true", "oneway", "yes")); 045 deprecated.add(new FixDataTag("oneway", "1", "oneway", "yes")); 046 deprecated.add(new FixDataTag("highway", "stile", "barrier", "stile")); 047 // CHECKSTYLE.ON: SingleSpaceSeparator 048 deprecated.add(new FixData() { 049 @Override 050 public boolean fixKeys(Map<String, String> keys, OsmPrimitive osm) { 051 if (osm instanceof Relation && "multipolygon".equals(keys.get("type")) && "administrative".equals(keys.get("boundary"))) { 052 keys.put("type", "boundary"); 053 return true; 054 } 055 return false; 056 } 057 }); 058 } 059 060 /** 061 * Common set of commands for data fixing 062 */ 063 public interface FixData { 064 /** 065 * Checks if data needs to be fixed and change keys 066 * 067 * @param keys list of keys to be modified 068 * @param osm the object for type validation, don't use keys of it! 069 * @return <code>true</code> if keys have been modified 070 */ 071 boolean fixKeys(Map<String, String> keys, OsmPrimitive osm); 072 } 073 074 /** 075 * Data fix to remove spaces at begin or end of tags 076 */ 077 public static class FixDataSpace implements FixData { 078 @Override 079 public boolean fixKeys(Map<String, String> keys, OsmPrimitive osm) { 080 Map<String, String> newKeys = new HashMap<>(keys); 081 for (Entry<String, String> e : keys.entrySet()) { 082 String v = Tag.removeWhiteSpaces(e.getValue()); 083 String k = Tag.removeWhiteSpaces(e.getKey()); 084 boolean drop = k.isEmpty() || v.isEmpty(); 085 if (!e.getKey().equals(k)) { 086 if (drop || !keys.containsKey(k)) { 087 newKeys.put(e.getKey(), null); 088 if (!drop) 089 newKeys.put(k, v); 090 } 091 } else if (!e.getValue().equals(v)) { 092 newKeys.put(k, v.isEmpty() ? null : v); 093 } else if (drop) { 094 newKeys.put(e.getKey(), null); 095 } 096 } 097 boolean changed = !keys.equals(newKeys); 098 if (changed) { 099 keys.clear(); 100 keys.putAll(newKeys); 101 } 102 return changed; 103 } 104 } 105 106 /** 107 * Data fix to cleanup wrong spelled keys 108 */ 109 public static class FixDataKey implements FixData { 110 /** key of wrong data */ 111 private final String oldKey; 112 /** key of correct data */ 113 private final String newKey; 114 115 /** 116 * Setup key check for wrong spelled keys 117 * 118 * @param oldKey wrong spelled key 119 * @param newKey correct replacement 120 */ 121 public FixDataKey(String oldKey, String newKey) { 122 this.oldKey = oldKey; 123 this.newKey = newKey; 124 } 125 126 @Override 127 public boolean fixKeys(Map<String, String> keys, OsmPrimitive osm) { 128 if (keys.containsKey(oldKey) && !keys.containsKey(newKey)) { 129 keys.put(newKey, keys.get(oldKey)); 130 keys.put(oldKey, null); 131 return true; 132 } else if (keys.containsKey(oldKey) && keys.containsKey(newKey) && keys.get(oldKey).equals(keys.get(newKey))) { 133 keys.put(oldKey, null); 134 return true; 135 } 136 return false; 137 } 138 } 139 140 /** 141 * Data fix to cleanup wrong spelled tags 142 */ 143 public static class FixDataTag implements FixData { 144 /** key of wrong data */ 145 private final String oldKey; 146 /** value of wrong data */ 147 private final String oldValue; 148 /** key of correct data */ 149 private final String newKey; 150 /** value of correct data */ 151 private final String newValue; 152 153 /** 154 * Setup key check for wrong spelled keys 155 * 156 * @param oldKey wrong or old key 157 * @param oldValue wrong or old value 158 * @param newKey correct key replacement 159 * @param newValue correct value replacement 160 */ 161 public FixDataTag(String oldKey, String oldValue, String newKey, String newValue) { 162 this.oldKey = oldKey; 163 this.oldValue = oldValue; 164 this.newKey = newKey; 165 this.newValue = newValue; 166 } 167 168 @Override 169 public boolean fixKeys(Map<String, String> keys, OsmPrimitive osm) { 170 if (oldValue.equals(keys.get(oldKey)) && (newKey.equals(oldKey) 171 || !keys.containsKey(newKey) || keys.get(newKey).equals(newValue))) { 172 keys.put(newKey, newValue); 173 if (!newKey.equals(oldKey)) 174 keys.put(oldKey, null); 175 return true; 176 } 177 return false; 178 } 179 } 180 181 /** 182 * Checks the upload for deprecated or wrong tags. 183 * @param apiDataSet the data to upload 184 */ 185 @Override 186 public boolean checkUpload(APIDataSet apiDataSet) { 187 if (!Main.pref.getBoolean("fix.data.on.upload", true)) 188 return true; 189 190 List<OsmPrimitive> objectsToUpload = apiDataSet.getPrimitives(); 191 Collection<Command> cmds = new LinkedList<>(); 192 193 for (OsmPrimitive osm : objectsToUpload) { 194 Map<String, String> keys = new HashMap<>(osm.getKeys()); 195 if (!keys.isEmpty()) { 196 boolean modified = false; 197 for (FixData fix : deprecated) { 198 if (fix.fixKeys(keys, osm)) 199 modified = true; 200 } 201 if (modified) 202 cmds.add(new ChangePropertyCommand(Collections.singleton(osm), keys)); 203 } 204 } 205 206 if (!cmds.isEmpty()) 207 Main.main.undoRedo.add(new SequenceCommand(tr("Fix deprecated tags"), cmds)); 208 return true; 209 } 210}