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