001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.event.ActionEvent;
007import java.awt.event.KeyEvent;
008import java.io.IOException;
009import java.util.ArrayList;
010import java.util.Collection;
011import java.util.List;
012
013import org.openstreetmap.josm.Main;
014import org.openstreetmap.josm.data.osm.OsmPrimitive;
015import org.openstreetmap.josm.data.validation.OsmValidator;
016import org.openstreetmap.josm.data.validation.Test;
017import org.openstreetmap.josm.data.validation.TestError;
018import org.openstreetmap.josm.data.validation.util.AggregatePrimitivesVisitor;
019import org.openstreetmap.josm.gui.PleaseWaitRunnable;
020import org.openstreetmap.josm.gui.preferences.validator.ValidatorPreference;
021import org.openstreetmap.josm.gui.util.GuiHelper;
022import org.openstreetmap.josm.io.OsmTransferException;
023import org.openstreetmap.josm.tools.Shortcut;
024import org.xml.sax.SAXException;
025
026/**
027 * The action that does the validate thing.
028 * <p>
029 * This action iterates through all active tests and give them the data, so that
030 * each one can test it.
031 *
032 * @author frsantos
033 */
034public class ValidateAction extends JosmAction {
035
036    /** Last selection used to validate */
037    private transient Collection<OsmPrimitive> lastSelection;
038
039    /**
040     * Constructor
041     */
042    public ValidateAction() {
043        super(tr("Validation"), "dialogs/validator", tr("Performs the data validation"),
044                Shortcut.registerShortcut("tools:validate", tr("Tool: {0}", tr("Validation")),
045                        KeyEvent.VK_V, Shortcut.SHIFT), true);
046    }
047
048    @Override
049    public void actionPerformed(ActionEvent ev) {
050        doValidate(true);
051    }
052
053    /**
054     * Does the validation.
055     * <p>
056     * If getSelectedItems is true, the selected items (or all items, if no one
057     * is selected) are validated. If it is false, last selected items are
058     * revalidated
059     *
060     * @param getSelectedItems If selected or last selected items must be validated
061     */
062    public void doValidate(boolean getSelectedItems) {
063        if (Main.map == null || !Main.map.isVisible())
064            return;
065
066        OsmValidator.initializeTests();
067        OsmValidator.initializeErrorLayer();
068
069        Collection<Test> tests = OsmValidator.getEnabledTests(false);
070        if (tests.isEmpty())
071            return;
072
073        Collection<OsmPrimitive> selection;
074        if (getSelectedItems) {
075            selection = Main.main.getCurrentDataSet().getAllSelected();
076            if (selection.isEmpty()) {
077                selection = Main.main.getCurrentDataSet().allNonDeletedPrimitives();
078                lastSelection = null;
079            } else {
080                AggregatePrimitivesVisitor v = new AggregatePrimitivesVisitor();
081                selection = v.visit(selection);
082                lastSelection = selection;
083            }
084        } else {
085            if (lastSelection == null) {
086                selection = Main.main.getCurrentDataSet().allNonDeletedPrimitives();
087            } else {
088                selection = lastSelection;
089            }
090        }
091
092        ValidationTask task = new ValidationTask(tests, selection, lastSelection);
093        Main.worker.submit(task);
094    }
095
096    @Override
097    public void updateEnabledState() {
098        setEnabled(getEditLayer() != null);
099    }
100
101    @Override
102    public void destroy() {
103        // Hack - this action should stay forever because it could be added to toolbar
104        // Do not call super.destroy() here
105    }
106
107    /**
108     * Asynchronous task for running a collection of tests against a collection
109     * of primitives
110     *
111     */
112    static class ValidationTask extends PleaseWaitRunnable {
113        private Collection<Test> tests;
114        private Collection<OsmPrimitive> validatedPrimitives;
115        private Collection<OsmPrimitive> formerValidatedPrimitives;
116        private boolean canceled;
117        private List<TestError> errors;
118
119        /**
120         *
121         * @param tests  the tests to run
122         * @param validatedPrimitives the collection of primitives to validate.
123         * @param formerValidatedPrimitives the last collection of primitives being validates. May be null.
124         */
125        ValidationTask(Collection<Test> tests, Collection<OsmPrimitive> validatedPrimitives,
126                Collection<OsmPrimitive> formerValidatedPrimitives) {
127            super(tr("Validating"), false /*don't ignore exceptions */);
128            this.validatedPrimitives  = validatedPrimitives;
129            this.formerValidatedPrimitives = formerValidatedPrimitives;
130            this.tests = tests;
131        }
132
133        @Override
134        protected void cancel() {
135            this.canceled = true;
136        }
137
138        @Override
139        protected void finish() {
140            if (canceled) return;
141
142            // update GUI on Swing EDT
143            //
144            GuiHelper.runInEDT(new Runnable()  {
145                @Override
146                public void run() {
147                    Main.map.validatorDialog.tree.setErrors(errors);
148                    Main.map.validatorDialog.unfurlDialog();
149                    Main.main.getCurrentDataSet().fireSelectionChanged();
150                }
151            });
152        }
153
154        @Override
155        protected void realRun() throws SAXException, IOException,
156        OsmTransferException {
157            if (tests == null || tests.isEmpty())
158                return;
159            errors = new ArrayList<>(200);
160            getProgressMonitor().setTicksCount(tests.size() * validatedPrimitives.size());
161            int testCounter = 0;
162            for (Test test : tests) {
163                if (canceled)
164                    return;
165                testCounter++;
166                getProgressMonitor().setCustomText(tr("Test {0}/{1}: Starting {2}", testCounter, tests.size(), test.getName()));
167                test.setPartialSelection(formerValidatedPrimitives != null);
168                test.startTest(getProgressMonitor().createSubTaskMonitor(validatedPrimitives.size(), false));
169                test.visit(validatedPrimitives);
170                test.endTest();
171                errors.addAll(test.getErrors());
172            }
173            tests = null;
174            if (Main.pref.getBoolean(ValidatorPreference.PREF_USE_IGNORE, true)) {
175                getProgressMonitor().subTask(tr("Updating ignored errors ..."));
176                for (TestError error : errors) {
177                    if (canceled) return;
178                    List<String> s = new ArrayList<>();
179                    s.add(error.getIgnoreState());
180                    s.add(error.getIgnoreGroup());
181                    s.add(error.getIgnoreSubGroup());
182                    for (String state : s) {
183                        if (state != null && OsmValidator.hasIgnoredError(state)) {
184                            error.setIgnored(true);
185                        }
186                    }
187                }
188            }
189        }
190    }
191}