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