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