source: josm/trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java

Last change on this file was 19062, checked in by GerdP, 7 weeks ago

fix #21881: Add a check for loops in directional waterways
Patch by gaben, slightly modified

  • implements Tarjan algorithm to find strongly connected components
  • new preference validator.CycleDetector.directionalWaterways contains the list of waterway values which should be checked
  • Property svn:eol-style set to native
File size: 27.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.validation;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.GraphicsEnvironment;
7import java.io.File;
8import java.io.FileNotFoundException;
9import java.io.IOException;
10import java.nio.charset.StandardCharsets;
11import java.nio.file.Files;
12import java.nio.file.Path;
13import java.nio.file.Paths;
14import java.util.ArrayList;
15import java.util.Arrays;
16import java.util.Collection;
17import java.util.Collections;
18import java.util.EnumMap;
19import java.util.Enumeration;
20import java.util.HashMap;
21import java.util.Iterator;
22import java.util.List;
23import java.util.Map;
24import java.util.Map.Entry;
25import java.util.SortedMap;
26import java.util.TreeMap;
27import java.util.TreeSet;
28import java.util.function.Predicate;
29import java.util.regex.Pattern;
30import java.util.stream.Collectors;
31
32import javax.swing.JOptionPane;
33import javax.swing.JTree;
34import javax.swing.tree.DefaultMutableTreeNode;
35import javax.swing.tree.TreeModel;
36import javax.swing.tree.TreeNode;
37
38import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
39import org.openstreetmap.josm.data.projection.ProjectionRegistry;
40import org.openstreetmap.josm.data.validation.tests.Addresses;
41import org.openstreetmap.josm.data.validation.tests.ApiCapabilitiesTest;
42import org.openstreetmap.josm.data.validation.tests.BarriersEntrances;
43import org.openstreetmap.josm.data.validation.tests.Coastlines;
44import org.openstreetmap.josm.data.validation.tests.ConditionalKeys;
45import org.openstreetmap.josm.data.validation.tests.ConnectivityRelations;
46import org.openstreetmap.josm.data.validation.tests.CrossingWays;
47import org.openstreetmap.josm.data.validation.tests.CycleDetector;
48import org.openstreetmap.josm.data.validation.tests.DirectionNodes;
49import org.openstreetmap.josm.data.validation.tests.DuplicateNode;
50import org.openstreetmap.josm.data.validation.tests.DuplicateRelation;
51import org.openstreetmap.josm.data.validation.tests.DuplicateWay;
52import org.openstreetmap.josm.data.validation.tests.DuplicatedWayNodes;
53import org.openstreetmap.josm.data.validation.tests.Highways;
54import org.openstreetmap.josm.data.validation.tests.InternetTags;
55import org.openstreetmap.josm.data.validation.tests.Lanes;
56import org.openstreetmap.josm.data.validation.tests.LongSegment;
57import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker;
58import org.openstreetmap.josm.data.validation.tests.MultipolygonTest;
59import org.openstreetmap.josm.data.validation.tests.NameMismatch;
60import org.openstreetmap.josm.data.validation.tests.OpeningHourTest;
61import org.openstreetmap.josm.data.validation.tests.OverlappingWays;
62import org.openstreetmap.josm.data.validation.tests.PowerLines;
63import org.openstreetmap.josm.data.validation.tests.PublicTransportRouteTest;
64import org.openstreetmap.josm.data.validation.tests.RelationChecker;
65import org.openstreetmap.josm.data.validation.tests.RightAngleBuildingTest;
66import org.openstreetmap.josm.data.validation.tests.SelfIntersectingWay;
67import org.openstreetmap.josm.data.validation.tests.SharpAngles;
68import org.openstreetmap.josm.data.validation.tests.SimilarNamedWays;
69import org.openstreetmap.josm.data.validation.tests.TagChecker;
70import org.openstreetmap.josm.data.validation.tests.TurnrestrictionTest;
71import org.openstreetmap.josm.data.validation.tests.UnclosedWays;
72import org.openstreetmap.josm.data.validation.tests.UnconnectedWays;
73import org.openstreetmap.josm.data.validation.tests.UntaggedNode;
74import org.openstreetmap.josm.data.validation.tests.UntaggedWay;
75import org.openstreetmap.josm.data.validation.tests.WayConnectedToArea;
76import org.openstreetmap.josm.data.validation.tests.WronglyOrderedWays;
77import org.openstreetmap.josm.gui.MainApplication;
78import org.openstreetmap.josm.gui.layer.ValidatorLayer;
79import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
80import org.openstreetmap.josm.gui.util.GuiHelper;
81import org.openstreetmap.josm.spi.preferences.Config;
82import org.openstreetmap.josm.tools.AlphanumComparator;
83import org.openstreetmap.josm.tools.Logging;
84import org.openstreetmap.josm.tools.Stopwatch;
85import org.openstreetmap.josm.tools.Utils;
86
87/**
88 * A OSM data validator.
89 *
90 * @author Francisco R. Santos <frsantos@gmail.com>
91 */
92public final class OsmValidator {
93
94 private OsmValidator() {
95 // Hide default constructor for utilities classes
96 }
97
98 private static volatile ValidatorLayer errorLayer;
99
100 /** Grid detail, multiplier of east,north values for valuable cell sizing */
101 private static double griddetail;
102
103 private static final SortedMap<String, String> ignoredErrors = new TreeMap<>();
104 /**
105 * All registered tests
106 */
107 private static final Collection<Class<? extends Test>> allTests = new ArrayList<>();
108 private static final Map<String, Test> allTestsMap = new HashMap<>();
109
110 /**
111 * All available tests in core
112 */
113 @SuppressWarnings("unchecked")
114 private static final Class<Test>[] CORE_TEST_CLASSES = new Class[] {// NOPMD
115 /* FIXME - unique error numbers for tests aren't properly unique - ignoring will not work as expected */
116 /* Error codes are class.getName().hashCode() + "_" + oldCode. There should almost never be a collision. */
117 DuplicateNode.class, // ID 1 .. 99
118 OverlappingWays.class, // ID 101 .. 199
119 UntaggedNode.class, // ID 201 .. 299
120 UntaggedWay.class, // ID 301 .. 399
121 SelfIntersectingWay.class, // ID 401 .. 499
122 DuplicatedWayNodes.class, // ID 501 .. 599
123 CrossingWays.Ways.class, // ID 601 .. 699
124 CrossingWays.Boundaries.class, // ID 601 .. 699
125 CrossingWays.SelfCrossing.class, // ID 601 .. 699
126 SimilarNamedWays.class, // ID 701 .. 799
127 Coastlines.class, // ID 901 .. 999
128 WronglyOrderedWays.class, // ID 1001 .. 1099
129 UnclosedWays.class, // ID 1101 .. 1199
130 TagChecker.class, // ID 1201 .. 1299
131 UnconnectedWays.UnconnectedHighways.class, // ID 1301 .. 1399
132 UnconnectedWays.UnconnectedRailways.class, // ID 1301 .. 1399
133 UnconnectedWays.UnconnectedWaterways.class, // ID 1301 .. 1399
134 UnconnectedWays.UnconnectedNaturalOrLanduse.class, // ID 1301 .. 1399
135 UnconnectedWays.UnconnectedPower.class, // ID 1301 .. 1399
136 DuplicateWay.class, // ID 1401 .. 1499
137 NameMismatch.class, // ID 1501 .. 1599
138 MultipolygonTest.class, // ID 1601 .. 1699
139 RelationChecker.class, // ID 1701 .. 1799
140 TurnrestrictionTest.class, // ID 1801 .. 1899
141 DuplicateRelation.class, // ID 1901 .. 1999
142 WayConnectedToArea.class, // ID 2301 .. 2399
143 PowerLines.class, // ID 2501 .. 2599
144 Addresses.class, // ID 2601 .. 2699
145 Highways.class, // ID 2701 .. 2799
146 BarriersEntrances.class, // ID 2801 .. 2899
147 OpeningHourTest.class, // 2901 .. 2999
148 MapCSSTagChecker.class, // 3000 .. 3099
149 Lanes.class, // 3100 .. 3199
150 ConditionalKeys.class, // 3200 .. 3299
151 InternetTags.class, // 3300 .. 3399
152 ApiCapabilitiesTest.class, // 3400 .. 3499
153 LongSegment.class, // 3500 .. 3599
154 PublicTransportRouteTest.class, // 3600 .. 3699
155 // 3700 .. 3799 is automatically removed since it clashed with pt_assistant.
156 SharpAngles.class, // 3800 .. 3899
157 ConnectivityRelations.class, // 3900 .. 3999
158 DirectionNodes.class, // 4000 .. 4099
159 RightAngleBuildingTest.class, // 4100 .. 4199
160 CycleDetector.class, // 4200 .. 4299
161 };
162
163 /**
164 * Adds a test to the list of available tests
165 * @param testClass The test class
166 */
167 public static void addTest(Class<? extends Test> testClass) {
168 allTests.add(testClass);
169 try {
170 allTestsMap.put(testClass.getName(), testClass.getConstructor().newInstance());
171 } catch (ReflectiveOperationException e) {
172 Logging.error(e);
173 }
174 }
175
176 /**
177 * Removes a test from the list of available tests. This will not remove
178 * core tests.
179 *
180 * @param testClass The test class
181 * @return {@code true} if the test was removed (see {@link Collection#remove})
182 * @since 15603
183 */
184 public static boolean removeTest(Class<? extends Test> testClass) {
185 boolean removed = false;
186 if (!Arrays.asList(CORE_TEST_CLASSES).contains(testClass)) {
187 removed = allTests.remove(testClass);
188 allTestsMap.remove(testClass.getName());
189 }
190 return removed;
191 }
192
193 static {
194 for (Class<? extends Test> testClass : CORE_TEST_CLASSES) {
195 addTest(testClass);
196 }
197 }
198
199 /**
200 * Initializes {@code OsmValidator}.
201 */
202 public static void initialize() {
203 initializeGridDetail();
204 loadIgnoredErrors();
205 }
206
207 /**
208 * Returns the validator directory.
209 *
210 * @return The validator directory
211 */
212 public static String getValidatorDir() {
213 File dir = new File(Config.getDirs().getUserDataDirectory(true), "validator");
214 try {
215 return dir.getAbsolutePath();
216 } catch (SecurityException e) {
217 Logging.log(Logging.LEVEL_ERROR, null, e);
218 return dir.getPath();
219 }
220 }
221
222 private static void loadIgnoredErrors() {
223 ignoredErrors.clear();
224 if (Boolean.TRUE.equals(ValidatorPrefHelper.PREF_USE_IGNORE.get())) {
225 Config.getPref().getListOfMaps(ValidatorPrefHelper.PREF_IGNORELIST).forEach(ignoredErrors::putAll);
226 Path path = Paths.get(getValidatorDir()).resolve("ignorederrors");
227 try {
228 if (path.toFile().exists()) {
229 try {
230 TreeSet<String> treeSet = new TreeSet<>(Files.readAllLines(path, StandardCharsets.UTF_8));
231 treeSet.forEach(ignore -> ignoredErrors.putIfAbsent(ignore, ""));
232 removeLegacyEntries(true);
233
234 saveIgnoredErrors();
235 Files.deleteIfExists(path);
236
237 } catch (FileNotFoundException e) {
238 Logging.debug(Logging.getErrorMessage(e));
239 } catch (IOException e) {
240 Logging.error(e);
241 }
242 }
243 } catch (SecurityException e) {
244 Logging.log(Logging.LEVEL_ERROR, "Unable to load ignored errors", e);
245 }
246 removeLegacyEntries(Config.getPref().get(ValidatorPrefHelper.PREF_IGNORELIST_FORMAT).isEmpty());
247 }
248 }
249
250 private static void removeLegacyEntries(boolean force) {
251 // see #19053:
252 boolean wasChanged = removeLegacyEntry(force, true, "3000");
253 // see #18230 (pt_assistant, RightAngleBuildingTest)
254 wasChanged |= removeLegacyEntry(force, false, "3701");
255
256 if (wasChanged) {
257 saveIgnoredErrors();
258 }
259 }
260
261 private static boolean removeLegacyEntry(boolean force, boolean keep, String prefix) {
262 boolean wasChanged = false;
263 if (force) {
264 Iterator<Entry<String, String>> iter = ignoredErrors.entrySet().iterator();
265 while (iter.hasNext()) {
266 Entry<String, String> entry = iter.next();
267 if (entry.getKey().startsWith(prefix + "_")) {
268 Logging.warn(tr("Cannot handle ignore list entry {0}", entry));
269 iter.remove();
270 wasChanged = true;
271 }
272 }
273 }
274 String legacyEntry = ignoredErrors.remove(prefix);
275 if (keep && legacyEntry != null) {
276 if (!legacyEntry.isEmpty()) {
277 addIgnoredError(prefix + "_" + legacyEntry, legacyEntry);
278 }
279 wasChanged = true;
280 }
281 return wasChanged;
282 }
283
284 /**
285 * Adds an ignored error
286 * @param s The ignore group / sub group name
287 * @see TestError#getIgnoreGroup()
288 * @see TestError#getIgnoreSubGroup()
289 */
290 public static void addIgnoredError(String s) {
291 addIgnoredError(s, "");
292 }
293
294 /**
295 * Adds an ignored error
296 * @param s The ignore group / sub group name
297 * @param description What the error actually is
298 * @see TestError#getIgnoreGroup()
299 * @see TestError#getIgnoreSubGroup()
300 */
301 public static void addIgnoredError(String s, String description) {
302 if (description == null) description = "";
303 ignoredErrors.put(s, description);
304 }
305
306 /**
307 * Make sure that we don't keep single entries for a "group ignore".
308 */
309 static void cleanupIgnoredErrors() {
310 if (ignoredErrors.size() > 1) {
311 List<String> toRemove = new ArrayList<>();
312
313 Iterator<Entry<String, String>> iter = ignoredErrors.entrySet().iterator();
314 String lastKey = iter.next().getKey();
315 while (iter.hasNext()) {
316 String currKey = iter.next().getKey();
317 if (currKey.startsWith(lastKey) && sameCode(currKey, lastKey)) {
318 toRemove.add(currKey);
319 } else {
320 lastKey = currKey;
321 }
322 }
323 toRemove.forEach(ignoredErrors::remove);
324 }
325
326 Map<String, String> tmap = buildIgnore(buildJTreeList());
327 if (!tmap.isEmpty()) {
328 ignoredErrors.clear();
329 ignoredErrors.putAll(tmap);
330 }
331 }
332
333 private static boolean sameCode(String key1, String key2) {
334 return extractCodeFromIgnoreKey(key1).equals(extractCodeFromIgnoreKey(key2));
335 }
336
337 /**
338 * Extract the leading digits building the code for the error key.
339 * @param key the error key
340 * @return the leading digits
341 */
342 private static String extractCodeFromIgnoreKey(String key) {
343 int lenCode = 0;
344
345 for (int i = 0; i < key.length(); i++) {
346 if (key.charAt(i) >= '0' && key.charAt(i) <= '9') {
347 lenCode++;
348 } else {
349 break;
350 }
351 }
352 return key.substring(0, lenCode);
353 }
354
355 /**
356 * Check if a error should be ignored
357 * @param s The ignore group / sub group name
358 * @return <code>true</code> to ignore that error
359 */
360 public static boolean hasIgnoredError(String s) {
361 return ignoredErrors.containsKey(s);
362 }
363
364 /**
365 * Get the list of all ignored errors
366 * @return The <code>Collection&lt;String&gt;</code> of errors that are ignored
367 */
368 public static SortedMap<String, String> getIgnoredErrors() {
369 return ignoredErrors;
370 }
371
372 /**
373 * Build a JTree with a list
374 * @return &lt;type&gt;list as a {@code JTree}
375 */
376 public static JTree buildJTreeList() {
377 DefaultMutableTreeNode root = new DefaultMutableTreeNode(tr("Ignore list"));
378 final Pattern elemId1Pattern = Pattern.compile(":([rwn])_");
379 final Pattern elemId2Pattern = Pattern.compile("^[0-9]+$");
380 for (Entry<String, String> e: ignoredErrors.entrySet()) {
381 String key = e.getKey();
382 // key starts with a code, it maybe followed by a string (eg. a MapCSS rule) and
383 // optionally with a list of one or more OSM element IDs
384 String description = e.getValue();
385
386 ArrayList<String> ignoredElementList = new ArrayList<>();
387 String[] osmobjects = elemId1Pattern.split(key, -1);
388 for (int i = 1; i < osmobjects.length; i++) {
389 String osmid = osmobjects[i];
390 if (elemId2Pattern.matcher(osmid).matches()) {
391 osmid = '_' + osmid;
392 int index = key.indexOf(osmid);
393 if (index < key.lastIndexOf(']')) continue;
394 char type = key.charAt(index - 1);
395 ignoredElementList.add(type + osmid);
396 }
397 }
398 for (String osmignore : ignoredElementList) {
399 key = key.replace(':' + osmignore, "");
400 }
401
402 DefaultMutableTreeNode trunk;
403 DefaultMutableTreeNode branch;
404
405 if (!Utils.isEmpty(description)) {
406 trunk = inTree(root, description);
407 branch = inTree(trunk, key);
408 trunk.add(branch);
409 } else {
410 trunk = inTree(root, key);
411 branch = trunk;
412 }
413 if (!ignoredElementList.isEmpty()) {
414 String item;
415 if (ignoredElementList.size() == 1) {
416 item = ignoredElementList.iterator().next();
417 } else {
418 // combination of two or more objects, keep them together
419 item = ignoredElementList.toString(); // [ID1, ID2, ..., IDn]
420 }
421 branch.add(new DefaultMutableTreeNode(item));
422 }
423 root.add(trunk);
424 }
425 return new JTree(root);
426 }
427
428 private static DefaultMutableTreeNode inTree(DefaultMutableTreeNode root, String name) {
429 @SuppressWarnings("unchecked")
430 Enumeration<TreeNode> trunks = root.children();
431 while (trunks.hasMoreElements()) {
432 TreeNode ttrunk = trunks.nextElement();
433 if (ttrunk instanceof DefaultMutableTreeNode) {
434 DefaultMutableTreeNode trunk = (DefaultMutableTreeNode) ttrunk;
435 if (name.equals(trunk.getUserObject())) {
436 return trunk;
437 }
438 }
439 }
440 return new DefaultMutableTreeNode(name);
441 }
442
443 /**
444 * Build a {@code HashMap} from a tree of ignored errors
445 * @param tree The JTree of ignored errors
446 * @return A {@code HashMap} of the ignored errors for comparison
447 */
448 public static Map<String, String> buildIgnore(JTree tree) {
449 TreeModel model = tree.getModel();
450 DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
451 return buildIgnore(model, root);
452 }
453
454 private static Map<String, String> buildIgnore(TreeModel model, DefaultMutableTreeNode node) {
455 HashMap<String, String> rHashMap = new HashMap<>();
456
457 for (int i = 0; i < model.getChildCount(node); i++) {
458 DefaultMutableTreeNode child = (DefaultMutableTreeNode) model.getChild(node, i);
459 if (model.getChildCount(child) == 0) {
460 // create an entry for the error list
461 String key = node.getUserObject().toString();
462 String description;
463
464 if (!model.getRoot().equals(node)) {
465 description = ((DefaultMutableTreeNode) node.getParent()).getUserObject().toString();
466 } else {
467 description = key; // we get here when reading old file ignorederrors
468 }
469 if (tr("Ignore list").equals(description))
470 description = "";
471 if (!key.matches("^-?[0-9]+(_.*|$)")) {
472 description = key;
473 key = "";
474 }
475
476 String item = child.getUserObject().toString();
477 String entry = null;
478 if (item.matches("^\\[([rwn])_.*")) {
479 // list of elements (produced with list.toString() method)
480 entry = key + ":" + item.substring(1, item.lastIndexOf(']')).replace(", ", ":");
481 } else if (item.matches("^([rwn])_.*")) {
482 // single element
483 entry = key + ":" + item;
484 } else if (item.matches("^-?[0-9]+(_.*|)$")) {
485 // no element ids
486 entry = item;
487 }
488 if (entry != null) {
489 rHashMap.put(entry, description);
490 } else {
491 Logging.warn("ignored unexpected item in validator ignore list management dialog:'" + item + "'");
492 }
493 } else {
494 rHashMap.putAll(buildIgnore(model, child));
495 }
496 }
497 return rHashMap;
498 }
499
500 /**
501 * Reset the error list by deleting {@code validator.ignorelist}
502 */
503 public static void resetErrorList() {
504 saveIgnoredErrors();
505 Config.getPref().putListOfMaps(ValidatorPrefHelper.PREF_IGNORELIST, null);
506 OsmValidator.initialize();
507 }
508
509 /**
510 * Saves the names of the ignored errors to a preference
511 */
512 public static void saveIgnoredErrors() {
513 List<Map<String, String>> list = new ArrayList<>();
514 cleanupIgnoredErrors();
515 ignoredErrors.remove("3000"); // see #19053
516 ignoredErrors.remove("3701"); // see #18230
517 list.add(ignoredErrors);
518 int i = 0;
519 while (i < list.size()) {
520 if (Utils.isEmpty(list.get(i))) {
521 list.remove(i);
522 continue;
523 }
524 i++;
525 }
526 if (list.isEmpty()) list = null;
527 Config.getPref().putListOfMaps(ValidatorPrefHelper.PREF_IGNORELIST, list);
528 Config.getPref().put(ValidatorPrefHelper.PREF_IGNORELIST_FORMAT, "2");
529 }
530
531 /**
532 * Initializes error layer.
533 */
534 public static synchronized void initializeErrorLayer() {
535 if (errorLayer == null && Boolean.TRUE.equals(ValidatorPrefHelper.PREF_LAYER.get())) {
536 errorLayer = new ValidatorLayer();
537 MainApplication.getLayerManager().addLayer(errorLayer);
538 }
539 }
540
541 /**
542 * Resets error layer.
543 * @since 11852
544 */
545 public static synchronized void resetErrorLayer() {
546 errorLayer = null;
547 }
548
549 /**
550 * Gets a map from simple names to all tests.
551 * @return A map of all tests, indexed and sorted by the name of their Java class
552 */
553 public static SortedMap<String, Test> getAllTestsMap() {
554 applyPrefs(allTestsMap, false);
555 applyPrefs(allTestsMap, true);
556 return new TreeMap<>(allTestsMap);
557 }
558
559 /**
560 * Returns the instance of the given test class.
561 * @param <T> testClass type
562 * @param testClass The class of test to retrieve
563 * @return the instance of the given test class, if any, or {@code null}
564 * @since 6670
565 */
566 @SuppressWarnings("unchecked")
567 public static <T extends Test> T getTest(Class<T> testClass) {
568 if (testClass == null) {
569 return null;
570 }
571 return (T) allTestsMap.get(testClass.getName());
572 }
573
574 private static void applyPrefs(Map<String, Test> tests, boolean beforeUpload) {
575 for (String testName : Config.getPref().getList(beforeUpload
576 ? ValidatorPrefHelper.PREF_SKIP_TESTS_BEFORE_UPLOAD : ValidatorPrefHelper.PREF_SKIP_TESTS)) {
577 Test test = tests.get(testName);
578 if (test != null) {
579 if (beforeUpload) {
580 test.testBeforeUpload = false;
581 } else {
582 test.enabled = false;
583 }
584 }
585 }
586 }
587
588 /**
589 * Gets all tests that are possible
590 * @return The tests
591 */
592 public static Collection<Test> getTests() {
593 return getAllTestsMap().values();
594 }
595
596 /**
597 * Gets all tests that are run
598 * @param beforeUpload To get the ones that are run before upload
599 * @return The tests
600 */
601 public static Collection<Test> getEnabledTests(boolean beforeUpload) {
602 Collection<Test> enabledTests = getTests();
603 for (Test t : new ArrayList<>(enabledTests)) {
604 if (beforeUpload ? t.testBeforeUpload : t.enabled) {
605 continue;
606 }
607 enabledTests.remove(t);
608 }
609 return enabledTests;
610 }
611
612 /**
613 * Gets the list of all available test classes
614 *
615 * @return A collection of the test classes
616 */
617 public static Collection<Class<? extends Test>> getAllAvailableTestClasses() {
618 return Collections.unmodifiableCollection(allTests);
619 }
620
621 /**
622 * Initialize grid details based on the current projection system. Values based on
623 * the original value fixed for EPSG:4326 (10000) using heuristics (that is, test&amp;error
624 * until most bugs were discovered while keeping the processing time reasonable)
625 */
626 public static void initializeGridDetail() {
627 String code = ProjectionRegistry.getProjection().toCode();
628 if (Arrays.asList(ProjectionPreference.wgs84.allCodes()).contains(code)) {
629 OsmValidator.griddetail = 10_000;
630 } else if (Arrays.asList(ProjectionPreference.mercator.allCodes()).contains(code)) {
631 OsmValidator.griddetail = 0.01;
632 } else if (Arrays.asList(ProjectionPreference.lambert.allCodes()).contains(code)) {
633 OsmValidator.griddetail = 0.1;
634 } else {
635 OsmValidator.griddetail = 1.0;
636 }
637 }
638
639 /**
640 * Returns grid detail, multiplier of east,north values for valuable cell sizing
641 * @return grid detail
642 * @since 11852
643 */
644 public static double getGridDetail() {
645 return griddetail;
646 }
647
648 private static boolean testsInitialized;
649
650 /**
651 * Initializes all tests if this operation hasn't been performed already.
652 */
653 public static synchronized void initializeTests() {
654 if (!testsInitialized) {
655 final String message = "Initializing validator tests";
656 Logging.debug(message);
657 final Stopwatch stopwatch = Stopwatch.createStarted();
658 initializeTests(getTests());
659 testsInitialized = true;
660 Logging.debug(stopwatch.toString("Initializing validator tests"));
661 }
662 }
663
664 /**
665 * Initializes all tests
666 * @param allTests The tests to initialize
667 */
668 public static void initializeTests(Collection<? extends Test> allTests) {
669 for (Test test : allTests) {
670 try {
671 if (test.enabled) {
672 test.initialize();
673 }
674 } catch (Exception e) { // NOPMD
675 String message = tr("Error initializing test {0}:\n {1}", test.getClass().getSimpleName(), e);
676 Logging.error(message);
677 if (!GraphicsEnvironment.isHeadless()) {
678 GuiHelper.runInEDT(() ->
679 JOptionPane.showMessageDialog(MainApplication.getMainFrame(), message, tr("Error"), JOptionPane.ERROR_MESSAGE)
680 );
681 }
682 }
683 }
684 }
685
686 /**
687 * Groups the given collection of errors by severity, then message, then description.
688 * @param errors list of errors to group
689 * @param filterToUse optional filter
690 * @return collection of errors grouped by severity, then message, then description
691 * @since 12667
692 */
693 public static Map<Severity, Map<String, Map<String, List<TestError>>>> getErrorsBySeverityMessageDescription(
694 Collection<TestError> errors, Predicate<? super TestError> filterToUse) {
695 return errors.stream().filter(filterToUse).collect(
696 Collectors.groupingBy(TestError::getSeverity, () -> new EnumMap<>(Severity.class),
697 Collectors.groupingBy(TestError::getMessage, () -> new TreeMap<>(AlphanumComparator.getInstance()),
698 Collectors.groupingBy(e -> e.getDescription() == null ? "" : e.getDescription(),
699 () -> new TreeMap<>(AlphanumComparator.getInstance()),
700 Collectors.toList()
701 ))));
702 }
703
704 /**
705 * For unit tests
706 */
707 static void clearIgnoredErrors() {
708 ignoredErrors.clear();
709 }
710}
Note: See TracBrowser for help on using the repository browser.