001// License: GPL. See LICENSE file for details. 002package org.openstreetmap.josm.data.validation; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.io.BufferedReader; 007import java.io.File; 008import java.io.FileNotFoundException; 009import java.io.FileReader; 010import java.io.FileWriter; 011import java.io.IOException; 012import java.io.PrintWriter; 013import java.util.ArrayList; 014import java.util.Arrays; 015import java.util.Collection; 016import java.util.HashMap; 017import java.util.Map; 018import java.util.TreeSet; 019import java.util.regex.Matcher; 020import java.util.regex.Pattern; 021 022import javax.swing.JOptionPane; 023 024import org.openstreetmap.josm.Main; 025import org.openstreetmap.josm.actions.ValidateAction; 026import org.openstreetmap.josm.data.validation.tests.Addresses; 027import org.openstreetmap.josm.data.validation.tests.BarriersEntrances; 028import org.openstreetmap.josm.data.validation.tests.BuildingInBuilding; 029import org.openstreetmap.josm.data.validation.tests.Coastlines; 030import org.openstreetmap.josm.data.validation.tests.CrossingWays; 031import org.openstreetmap.josm.data.validation.tests.DeprecatedTags; 032import org.openstreetmap.josm.data.validation.tests.DuplicateNode; 033import org.openstreetmap.josm.data.validation.tests.DuplicateRelation; 034import org.openstreetmap.josm.data.validation.tests.DuplicateWay; 035import org.openstreetmap.josm.data.validation.tests.DuplicatedWayNodes; 036import org.openstreetmap.josm.data.validation.tests.Highways; 037import org.openstreetmap.josm.data.validation.tests.MultipolygonTest; 038import org.openstreetmap.josm.data.validation.tests.NameMismatch; 039import org.openstreetmap.josm.data.validation.tests.NodesDuplicatingWayTags; 040import org.openstreetmap.josm.data.validation.tests.NodesWithSameName; 041import org.openstreetmap.josm.data.validation.tests.OpeningHourTest; 042import org.openstreetmap.josm.data.validation.tests.OverlappingAreas; 043import org.openstreetmap.josm.data.validation.tests.OverlappingWays; 044import org.openstreetmap.josm.data.validation.tests.PowerLines; 045import org.openstreetmap.josm.data.validation.tests.RelationChecker; 046import org.openstreetmap.josm.data.validation.tests.SelfIntersectingWay; 047import org.openstreetmap.josm.data.validation.tests.SimilarNamedWays; 048import org.openstreetmap.josm.data.validation.tests.TagChecker; 049import org.openstreetmap.josm.data.validation.tests.TurnrestrictionTest; 050import org.openstreetmap.josm.data.validation.tests.UnclosedWays; 051import org.openstreetmap.josm.data.validation.tests.UnconnectedWays; 052import org.openstreetmap.josm.data.validation.tests.UntaggedNode; 053import org.openstreetmap.josm.data.validation.tests.UntaggedWay; 054import org.openstreetmap.josm.data.validation.tests.WayConnectedToArea; 055import org.openstreetmap.josm.data.validation.tests.WronglyOrderedWays; 056import org.openstreetmap.josm.gui.MapView.LayerChangeListener; 057import org.openstreetmap.josm.gui.layer.Layer; 058import org.openstreetmap.josm.gui.layer.OsmDataLayer; 059import org.openstreetmap.josm.gui.layer.ValidatorLayer; 060import org.openstreetmap.josm.gui.preferences.ValidatorPreference; 061import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference; 062import org.openstreetmap.josm.tools.Utils; 063 064/** 065 * A OSM data validator. 066 * 067 * @author Francisco R. Santos <frsantos@gmail.com> 068 */ 069public class OsmValidator implements LayerChangeListener { 070 071 public static ValidatorLayer errorLayer = null; 072 073 /** The validate action */ 074 public ValidateAction validateAction = new ValidateAction(); 075 076 /** Grid detail, multiplier of east,north values for valuable cell sizing */ 077 public static double griddetail; 078 079 public static final Collection<String> ignoredErrors = new TreeSet<String>(); 080 081 /** 082 * All available tests 083 * TODO: is there any way to find out automatically all available tests? 084 */ 085 @SuppressWarnings("unchecked") 086 private static final Class<Test>[] allAvailableTests = new Class[] { 087 DuplicateNode.class, // ID 1 .. 99 088 OverlappingWays.class, // ID 101 .. 199 089 UntaggedNode.class, // ID 201 .. 299 090 UntaggedWay.class, // ID 301 .. 399 091 SelfIntersectingWay.class, // ID 401 .. 499 092 DuplicatedWayNodes.class, // ID 501 .. 599 093 CrossingWays.class, // ID 601 .. 699 094 SimilarNamedWays.class, // ID 701 .. 799 095 NodesWithSameName.class, // ID 801 .. 899 096 Coastlines.class, // ID 901 .. 999 097 WronglyOrderedWays.class, // ID 1001 .. 1099 098 UnclosedWays.class, // ID 1101 .. 1199 099 TagChecker.class, // ID 1201 .. 1299 100 UnconnectedWays.class, // ID 1301 .. 1399 101 DuplicateWay.class, // ID 1401 .. 1499 102 NameMismatch.class, // ID 1501 .. 1599 103 MultipolygonTest.class, // ID 1601 .. 1699 104 RelationChecker.class, // ID 1701 .. 1799 105 TurnrestrictionTest.class, // ID 1801 .. 1899 106 DuplicateRelation.class, // ID 1901 .. 1999 107 BuildingInBuilding.class, // ID 2001 .. 2099 108 DeprecatedTags.class, // ID 2101 .. 2199 109 OverlappingAreas.class, // ID 2201 .. 2299 110 WayConnectedToArea.class, // ID 2301 .. 2399 111 NodesDuplicatingWayTags.class, // ID 2401 .. 2499 112 PowerLines.class, // ID 2501 .. 2599 113 Addresses.class, // ID 2601 .. 2699 114 Highways.class, // ID 2701 .. 2799 115 BarriersEntrances.class, // ID 2801 .. 2899 116 OpeningHourTest.class // 2901 .. 2999 117 }; 118 119 private static Map<String, Test> allTestsMap; 120 static { 121 allTestsMap = new HashMap<String, Test>(); 122 for (Class<Test> testClass : allAvailableTests) { 123 try { 124 allTestsMap.put(testClass.getSimpleName(), testClass.newInstance()); 125 } catch (Exception e) { 126 Main.error(e); 127 continue; 128 } 129 } 130 } 131 132 /** 133 * Constructs a new {@code OsmValidator}. 134 */ 135 public OsmValidator() { 136 checkValidatorDir(); 137 initializeGridDetail(); 138 initializeTests(getTests()); 139 loadIgnoredErrors(); //FIXME: load only when needed 140 } 141 142 /** 143 * Returns the plugin's directory of the plugin 144 * 145 * @return The directory of the plugin 146 */ 147 public static String getValidatorDir() { 148 return Main.pref.getPreferencesDir() + "validator/"; 149 } 150 151 /** 152 * Check if plugin directory exists (store ignored errors file) 153 */ 154 private void checkValidatorDir() { 155 try { 156 File pathDir = new File(getValidatorDir()); 157 if (!pathDir.exists()) { 158 pathDir.mkdirs(); 159 } 160 } catch (Exception e){ 161 e.printStackTrace(); 162 } 163 } 164 165 private void loadIgnoredErrors() { 166 ignoredErrors.clear(); 167 if (Main.pref.getBoolean(ValidatorPreference.PREF_USE_IGNORE, true)) { 168 BufferedReader in = null; 169 try { 170 in = new BufferedReader(new FileReader(getValidatorDir() + "ignorederrors")); 171 for (String line = in.readLine(); line != null; line = in.readLine()) { 172 ignoredErrors.add(line); 173 } 174 } catch (final FileNotFoundException e) { 175 // Ignore 176 } catch (final IOException e) { 177 e.printStackTrace(); 178 } finally { 179 Utils.close(in); 180 } 181 } 182 } 183 184 public static void addIgnoredError(String s) { 185 ignoredErrors.add(s); 186 } 187 188 public static boolean hasIgnoredError(String s) { 189 return ignoredErrors.contains(s); 190 } 191 192 public static void saveIgnoredErrors() { 193 PrintWriter out = null; 194 try { 195 out = new PrintWriter(new FileWriter(getValidatorDir() + "ignorederrors"), false); 196 for (String e : ignoredErrors) { 197 out.println(e); 198 } 199 } catch (IOException e) { 200 e.printStackTrace(); 201 } finally { 202 Utils.close(out); 203 } 204 } 205 206 public static void initializeErrorLayer() { 207 if (!Main.pref.getBoolean(ValidatorPreference.PREF_LAYER, true)) 208 return; 209 if (errorLayer == null) { 210 errorLayer = new ValidatorLayer(); 211 Main.main.addLayer(errorLayer); 212 } 213 } 214 215 /** 216 * Gets a map from simple names to all tests. 217 * @return A map of all tests, indexed by the simple name of their Java class 218 */ 219 public static Map<String, Test> getAllTestsMap() { 220 applyPrefs(allTestsMap, false); 221 applyPrefs(allTestsMap, true); 222 return new HashMap<String, Test>(allTestsMap); 223 } 224 225 private static void applyPrefs(Map<String, Test> tests, boolean beforeUpload) { 226 Pattern regexp = Pattern.compile("(\\w+)=(true|false),?"); 227 Matcher m = regexp.matcher(Main.pref.get(beforeUpload ? ValidatorPreference.PREF_TESTS_BEFORE_UPLOAD 228 : ValidatorPreference.PREF_TESTS)); 229 int pos = 0; 230 while (m.find(pos)) { 231 String testName = m.group(1); 232 Test test = tests.get(testName); 233 if (test != null) { 234 boolean enabled = Boolean.valueOf(m.group(2)); 235 if (beforeUpload) { 236 test.testBeforeUpload = enabled; 237 } else { 238 test.enabled = enabled; 239 } 240 } 241 pos = m.end(); 242 } 243 } 244 245 public static Collection<Test> getTests() { 246 return getAllTestsMap().values(); 247 } 248 249 public static Collection<Test> getEnabledTests(boolean beforeUpload) { 250 Collection<Test> enabledTests = getTests(); 251 for (Test t : new ArrayList<Test>(enabledTests)) { 252 if (beforeUpload ? t.testBeforeUpload : t.enabled) { 253 continue; 254 } 255 enabledTests.remove(t); 256 } 257 return enabledTests; 258 } 259 260 /** 261 * Gets the list of all available test classes 262 * 263 * @return An array of the test classes 264 */ 265 public static Class<Test>[] getAllAvailableTests() { 266 return Arrays.copyOf(allAvailableTests, allAvailableTests.length); 267 } 268 269 /** 270 * Initialize grid details based on current projection system. Values based on 271 * the original value fixed for EPSG:4326 (10000) using heuristics (that is, test&error 272 * until most bugs were discovered while keeping the processing time reasonable) 273 */ 274 public void initializeGridDetail() { 275 String code = Main.getProjection().toCode(); 276 if (Arrays.asList(ProjectionPreference.wgs84.allCodes()).contains(code)) { 277 OsmValidator.griddetail = 10000; 278 } else if (Arrays.asList(ProjectionPreference.mercator.allCodes()).contains(code)) { 279 OsmValidator.griddetail = 0.01; 280 } else if (Arrays.asList(ProjectionPreference.lambert.allCodes()).contains(code)) { 281 OsmValidator.griddetail = 0.1; 282 } else { 283 OsmValidator.griddetail = 1.0; 284 } 285 } 286 287 /** 288 * Initializes all tests 289 * @param allTests The tests to initialize 290 */ 291 public static void initializeTests(Collection<Test> allTests) { 292 for (Test test : allTests) { 293 try { 294 if (test.enabled) { 295 test.initialize(); 296 } 297 } catch (Exception e) { 298 e.printStackTrace(); 299 JOptionPane.showMessageDialog(Main.parent, 300 tr("Error initializing test {0}:\n {1}", test.getClass() 301 .getSimpleName(), e), 302 tr("Error"), 303 JOptionPane.ERROR_MESSAGE); 304 } 305 } 306 } 307 308 /* -------------------------------------------------------------------------- */ 309 /* interface LayerChangeListener */ 310 /* -------------------------------------------------------------------------- */ 311 @Override 312 public void activeLayerChange(Layer oldLayer, Layer newLayer) { 313 } 314 315 @Override 316 public void layerAdded(Layer newLayer) { 317 } 318 319 @Override 320 public void layerRemoved(Layer oldLayer) { 321 if (oldLayer == errorLayer) { 322 errorLayer = null; 323 return; 324 } 325 if (Main.map.mapView.getLayersOfType(OsmDataLayer.class).isEmpty()) { 326 if (errorLayer != null) { 327 Main.main.removeLayer(errorLayer); 328 } 329 } 330 } 331}