001// License: GPL. See LICENSE file for details.
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 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(ev, 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 ev The event
061     * @param getSelectedItems If selected or last selected items must be validated
062     */
063    public void doValidate(ActionEvent ev, boolean getSelectedItems) {
064        if (Main.map == null || !Main.map.isVisible())
065            return;
066
067        OsmValidator.initializeTests();
068        OsmValidator.initializeErrorLayer();
069
070        Collection<Test> tests = OsmValidator.getEnabledTests(false);
071        if (tests.isEmpty())
072            return;
073
074        Collection<OsmPrimitive> selection;
075        if (getSelectedItems) {
076            selection = Main.main.getCurrentDataSet().getAllSelected();
077            if (selection.isEmpty()) {
078                selection = Main.main.getCurrentDataSet().allNonDeletedPrimitives();
079                lastSelection = null;
080            } else {
081                AggregatePrimitivesVisitor v = new AggregatePrimitivesVisitor();
082                selection = v.visit(selection);
083                lastSelection = selection;
084            }
085        } else {
086            if (lastSelection == null) {
087                selection = Main.main.getCurrentDataSet().allNonDeletedPrimitives();
088            } else {
089                selection = lastSelection;
090            }
091        }
092
093        ValidationTask task = new ValidationTask(tests, selection, lastSelection);
094        Main.worker.submit(task);
095    }
096
097    @Override
098    public void updateEnabledState() {
099        setEnabled(getEditLayer() != null);
100    }
101
102    @Override
103    public void destroy() {
104        // Hack - this action should stay forever because it could be added to toolbar
105        // Do not call super.destroy() here
106    }
107
108    /**
109     * Asynchronous task for running a collection of tests against a collection
110     * of primitives
111     *
112     */
113    static class ValidationTask extends PleaseWaitRunnable {
114        private Collection<Test> tests;
115        private Collection<OsmPrimitive> validatedPrimitives;
116        private Collection<OsmPrimitive> formerValidatedPrimitives;
117        private boolean canceled;
118        private List<TestError> errors;
119
120        /**
121         *
122         * @param tests  the tests to run
123         * @param validatedPrimitives the collection of primitives to validate.
124         * @param formerValidatedPrimitives the last collection of primitives being validates. May be null.
125         */
126        public ValidationTask(Collection<Test> tests, Collection<OsmPrimitive> validatedPrimitives, 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}