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 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 public 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 if(!e.getKey().equals(k)) { 083 boolean drop = k.isEmpty() || v.isEmpty(); 084 if(drop || !keys.containsKey(k)) { 085 newKeys.remove(e.getKey()); 086 if(!drop) 087 newKeys.put(k, v); 088 } 089 } else if(!e.getValue().equals(v)) { 090 if(v.isEmpty()) 091 newKeys.remove(k); 092 else 093 newKeys.put(k, v); 094 } 095 } 096 boolean changed = !keys.equals(newKeys); 097 if (changed) { 098 keys.clear(); 099 keys.putAll(newKeys); 100 } 101 return changed; 102 } 103 } 104 105 /** 106 * Data fix to cleanup wrong spelled keys 107 */ 108 public static class FixDataKey implements FixData { 109 /** key of wrong data */ 110 String oldKey; 111 /** key of correct data */ 112 String newKey; 113 114 /** 115 * Setup key check for wrong spelled keys 116 * 117 * @param oldKey wrong spelled key 118 * @param newKey correct replacement 119 */ 120 public FixDataKey(String oldKey, String newKey) { 121 this.oldKey = oldKey; 122 this.newKey = newKey; 123 } 124 125 @Override 126 public boolean fixKeys(Map<String, String> keys, OsmPrimitive osm) { 127 if(keys.containsKey(oldKey) && !keys.containsKey(newKey)) { 128 keys.put(newKey, keys.get(oldKey)); 129 keys.remove(oldKey); 130 return true; 131 } 132 return false; 133 } 134 } 135 136 /** 137 * Data fix to cleanup wrong spelled tags 138 */ 139 public static class FixDataTag implements FixData { 140 /** key of wrong data */ 141 String oldKey; 142 /** value of wrong data */ 143 String oldValue; 144 /** key of correct data */ 145 String newKey; 146 /** value of correct data */ 147 String newValue; 148 149 /** 150 * Setup key check for wrong spelled keys 151 * 152 * @param oldKey wrong or old key 153 * @param oldValue wrong or old value 154 * @param newKey correct key replacement 155 * @param newValue correct value replacement 156 */ 157 public FixDataTag(String oldKey, String oldValue, String newKey, String newValue) { 158 this.oldKey = oldKey; 159 this.oldValue = oldValue; 160 this.newKey = newKey; 161 this.newValue = newValue; 162 } 163 164 @Override 165 public boolean fixKeys(Map<String, String> keys, OsmPrimitive osm) { 166 if(oldValue.equals(keys.get(oldKey)) && (newKey.equals(oldKey) || !keys.containsKey(newKey))) { 167 keys.put(newKey, newValue); 168 if(!newKey.equals(oldKey)) 169 keys.remove(oldKey); 170 return true; 171 } 172 return false; 173 } 174 } 175 176 /** 177 * Checks the upload for deprecated or wrong tags. 178 * @param apiDataSet the data to upload 179 */ 180 @Override 181 public boolean checkUpload(APIDataSet apiDataSet) { 182 if(!Main.pref.getBoolean("fix.data.on.upload", true)) 183 return true; 184 185 List<OsmPrimitive> objectsToUpload = apiDataSet.getPrimitives(); 186 Collection<Command> cmds = new LinkedList<>(); 187 188 for (OsmPrimitive osm : objectsToUpload) { 189 Map<String, String> keys = osm.getKeys(); 190 if(!keys.isEmpty()) { 191 boolean modified = false; 192 for (FixData fix : deprecated) { 193 if(fix.fixKeys(keys, osm)) 194 modified = true; 195 } 196 if(modified) 197 cmds.add(new ChangePropertyCommand(Collections.singleton(osm), new HashMap<>(keys))); 198 } 199 } 200 201 if(!cmds.isEmpty()) 202 Main.main.undoRedo.add(new SequenceCommand(tr("Fix deprecated tags"), cmds)); 203 return true; 204 } 205}