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