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

Last change on this file since 17787 was 17787, checked in by simon04, 3 years ago

fix #20741 - Various code simplifications (patch by gaben)

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