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