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

Last change on this file since 16643 was 16643, checked in by simon04, 4 years ago

see #19334 - https://errorprone.info/bugpattern/StringSplitter

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