001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.validation;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.util.ArrayList;
007import java.util.Collection;
008import java.util.List;
009
010import javax.swing.JOptionPane;
011
012import org.openstreetmap.josm.data.osm.OsmPrimitive;
013import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
014import org.openstreetmap.josm.gui.MainApplication;
015import org.openstreetmap.josm.gui.MapFrame;
016import org.openstreetmap.josm.gui.Notification;
017import org.openstreetmap.josm.gui.PleaseWaitRunnable;
018import org.openstreetmap.josm.gui.layer.ValidatorLayer;
019import org.openstreetmap.josm.gui.progress.ProgressMonitor;
020import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor;
021import org.openstreetmap.josm.gui.util.GuiHelper;
022import org.openstreetmap.josm.tools.Utils;
023
024/**
025 * Asynchronous task for running a collection of tests against a collection of primitives
026 */
027public class ValidationTask extends PleaseWaitRunnable {
028    private Collection<Test> tests;
029    private final Collection<OsmPrimitive> validatedPrimitives;
030    private final Collection<OsmPrimitive> formerValidatedPrimitives;
031    private boolean canceled;
032    private List<TestError> errors;
033
034    /**
035     * Constructs a new {@code ValidationTask}
036     *
037     * @param tests                     the tests to run
038     * @param validatedPrimitives       the collection of primitives to validate.
039     * @param formerValidatedPrimitives the last collection of primitives being validates. May be null.
040     */
041    public ValidationTask(Collection<Test> tests,
042                          Collection<OsmPrimitive> validatedPrimitives,
043                          Collection<OsmPrimitive> formerValidatedPrimitives) {
044        this(new PleaseWaitProgressMonitor(tr("Validating")), tests, validatedPrimitives, formerValidatedPrimitives);
045    }
046
047    protected ValidationTask(ProgressMonitor progressMonitor,
048                             Collection<Test> tests,
049                             Collection<OsmPrimitive> validatedPrimitives,
050                             Collection<OsmPrimitive> formerValidatedPrimitives) {
051        super(tr("Validating"), progressMonitor, false /*don't ignore exceptions */);
052        this.validatedPrimitives = validatedPrimitives;
053        this.formerValidatedPrimitives = formerValidatedPrimitives;
054        this.tests = tests;
055    }
056
057    @Override
058    protected void cancel() {
059        this.canceled = true;
060    }
061
062    @Override
063    protected void finish() {
064        if (canceled) return;
065
066        // update GUI on Swing EDT
067        GuiHelper.runInEDT(() -> {
068            MapFrame map = MainApplication.getMap();
069            map.validatorDialog.unfurlDialog();
070            map.validatorDialog.tree.setErrors(errors);
071            //FIXME: nicer way to find / invalidate the corresponding error layer
072            MainApplication.getLayerManager().getLayersOfType(ValidatorLayer.class).forEach(ValidatorLayer::invalidate);
073            if (!errors.isEmpty()) {
074                OsmValidator.initializeErrorLayer();
075            }
076        });
077    }
078
079    @Override
080    protected void realRun() {
081        if (Utils.isEmpty(tests))
082            return;
083        errors = new ArrayList<>();
084        getProgressMonitor().setTicksCount(tests.size() * validatedPrimitives.size());
085        int testCounter = 0;
086        for (Test test : tests) {
087            if (canceled)
088                return;
089            testCounter++;
090            getProgressMonitor().setCustomText(tr("Test {0}/{1}: Starting {2}", testCounter, tests.size(), test.getName()));
091            test.setBeforeUpload(false);
092            test.setPartialSelection(formerValidatedPrimitives != null);
093            test.startTest(getProgressMonitor().createSubTaskMonitor(validatedPrimitives.size(), false));
094            test.visit(validatedPrimitives);
095            test.endTest();
096            errors.addAll(test.getErrors());
097            test.clear();
098        }
099        tests = null;
100        if (Boolean.TRUE.equals(ValidatorPrefHelper.PREF_USE_IGNORE.get())) {
101            getProgressMonitor().setCustomText("");
102            getProgressMonitor().subTask(tr("Updating ignored errors ..."));
103            for (TestError error : errors) {
104                if (canceled) return;
105                error.updateIgnored();
106            }
107        }
108
109        if (errors.stream().anyMatch(e -> e.getPrimitives().stream().anyMatch(OsmPrimitive::isDisabledAndHidden))) {
110            final String msg = "<b>" + tr("Validation results contain elements hidden by a filter.") + "</b><br/>"
111                    + tr("Please review active filters to see the hidden results.");
112            GuiHelper.runInEDT(() -> new Notification(msg)
113                    .setDuration(Notification.TIME_LONG)
114                    .setIcon(JOptionPane.WARNING_MESSAGE)
115                    .setHelpTopic("Dialog/Validator")
116                    .show());
117        }
118    }
119
120    /**
121     * Gets the validation errors accumulated until this moment.
122     * @return The list of errors
123     */
124    public List<TestError> getErrors() {
125        return errors;
126    }
127}