001    // License: GPL. See LICENSE file for details.
002    package org.openstreetmap.josm.data.validation;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.awt.GridBagConstraints;
007    import java.util.ArrayList;
008    import java.util.Collection;
009    import java.util.List;
010    
011    import javax.swing.JCheckBox;
012    import javax.swing.JPanel;
013    
014    import org.openstreetmap.josm.Main;
015    import org.openstreetmap.josm.command.Command;
016    import org.openstreetmap.josm.command.DeleteCommand;
017    import org.openstreetmap.josm.data.osm.Node;
018    import org.openstreetmap.josm.data.osm.OsmPrimitive;
019    import org.openstreetmap.josm.data.osm.Relation;
020    import org.openstreetmap.josm.data.osm.Way;
021    import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
022    import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
023    import org.openstreetmap.josm.gui.progress.ProgressMonitor;
024    import org.openstreetmap.josm.tools.GBC;
025    
026    /**
027     * Parent class for all validation tests.
028     * <p>
029     * A test is a primitive visitor, so that it can access to all data to be
030     * validated. These primitives are always visited in the same order: nodes
031     * first, then ways.
032     *
033     * @author frsantos
034     */
035    public class Test extends AbstractVisitor
036    {
037        /** Name of the test */
038        protected final String name;
039    
040        /** Description of the test */
041        protected final String description;
042    
043        /** Whether this test is enabled. Enabled by default */
044        public boolean enabled = true;
045    
046        /** The preferences check for validation */
047        protected JCheckBox checkEnabled;
048    
049        /** The preferences check for validation on upload */
050        protected JCheckBox checkBeforeUpload;
051    
052        /** Whether this test must check before upload. Enabled by default */
053        public boolean testBeforeUpload = true;
054    
055        /** Whether this test is performing just before an upload */
056        protected boolean isBeforeUpload;
057    
058        /** The list of errors */
059        protected List<TestError> errors = new ArrayList<TestError>(30);
060    
061        /** Whether the test is run on a partial selection data */
062        protected boolean partialSelection;
063    
064        /** the progress monitor to use */
065        protected ProgressMonitor progressMonitor;
066        /**
067         * Constructor
068         * @param name Name of the test
069         * @param description Description of the test
070         */
071        public Test(String name, String description) {
072            this.name = name;
073            this.description = description;
074        }
075    
076        /**
077         * Constructor
078         * @param name Name of the test
079         */
080        public Test(String name) {
081            this(name, null);
082        }
083    
084        /**
085         * Initializes any global data used this tester.
086         * @throws Exception When cannot initialize the test
087         */
088        public void initialize() throws Exception {
089        }
090    
091        /**
092         * Start the test using a given progress monitor
093         *
094         * @param progressMonitor  the progress monitor
095         */
096        public void startTest(ProgressMonitor progressMonitor) {
097            if (progressMonitor == null) {
098                this.progressMonitor = NullProgressMonitor.INSTANCE;
099            } else {
100                this.progressMonitor = progressMonitor;
101            }
102            this.progressMonitor.beginTask(tr("Running test {0}", name));
103            errors = new ArrayList<TestError>(30);
104        }
105    
106        /**
107         * Flag notifying that this test is run over a partial data selection
108         * @param partialSelection Whether the test is on a partial selection data
109         */
110        public void setPartialSelection(boolean partialSelection) {
111            this.partialSelection = partialSelection;
112        }
113    
114        /**
115         * Gets the validation errors accumulated until this moment.
116         * @return The list of errors
117         */
118        public List<TestError> getErrors() {
119            return errors;
120        }
121    
122        /**
123         * Notification of the end of the test. The tester may perform additional
124         * actions and destroy the used structures.
125         * <p>
126         * If you override this method, don't forget to cleanup {@link #progressMonitor}
127         * (most overrides call {@code super.endTest()} to do this).
128         */
129        public void endTest() {
130            progressMonitor.finishTask();
131            progressMonitor = null;
132        }
133    
134        /**
135         * Visits all primitives to be tested. These primitives are always visited
136         * in the same order: nodes first, then ways.
137         *
138         * @param selection The primitives to be tested
139         */
140        public void visit(Collection<OsmPrimitive> selection) {
141            progressMonitor.setTicksCount(selection.size());
142            for (OsmPrimitive p : selection) {
143                if (p.isUsable()) {
144                    p.visit(this);
145                }
146                progressMonitor.worked(1);
147            }
148        }
149    
150        @Override
151        public void visit(Node n) {}
152    
153        @Override
154        public void visit(Way w) {}
155    
156        @Override
157        public void visit(Relation r) {}
158    
159        /**
160         * Allow the tester to manage its own preferences
161         * @param testPanel The panel to add any preferences component
162         */
163        public void addGui(JPanel testPanel) {
164            checkEnabled = new JCheckBox(name, enabled);
165            checkEnabled.setToolTipText(description);
166            testPanel.add(checkEnabled, GBC.std());
167    
168            GBC a = GBC.eol();
169            a.anchor = GridBagConstraints.EAST;
170            checkBeforeUpload = new JCheckBox();
171            checkBeforeUpload.setSelected(testBeforeUpload);
172            testPanel.add(checkBeforeUpload, a);
173        }
174    
175        /**
176         * Called when the used submits the preferences
177         */
178        public boolean ok() {
179            enabled = checkEnabled.isSelected();
180            testBeforeUpload = checkBeforeUpload.isSelected();
181            return false;
182        }
183    
184        /**
185         * Fixes the error with the appropriate command
186         *
187         * @param testError
188         * @return The command to fix the error
189         */
190        public Command fixError(TestError testError) {
191            return null;
192        }
193    
194        /**
195         * Returns true if the given error can be fixed automatically
196         *
197         * @param testError The error to check if can be fixed
198         * @return true if the error can be fixed
199         */
200        public boolean isFixable(TestError testError) {
201            return false;
202        }
203    
204        /**
205         * Returns true if this plugin must check the uploaded data before uploading
206         * @return true if this plugin must check the uploaded data before uploading
207         */
208        public boolean testBeforeUpload() {
209            return testBeforeUpload;
210        }
211    
212        /**
213         * Sets the flag that marks an upload check
214         * @param isUpload if true, the test is before upload
215         */
216        public void setBeforeUpload(boolean isUpload) {
217            this.isBeforeUpload = isUpload;
218        }
219    
220        public String getName() {
221            return name;
222        }
223    
224        public boolean isCanceled() {
225            return progressMonitor.isCanceled();
226        }
227        
228        /**
229         * Build a Delete command on all primitives that have not yet been deleted manually by user, or by another error fix.
230         * If all primitives have already been deleted, null is returned.
231         * @param primitives The primitives wanted for deletion
232         * @return a Delete command on all primitives that have not yet been deleted, or null otherwise
233         */
234        protected final Command deletePrimitivesIfNeeded(Collection<? extends OsmPrimitive> primitives) {
235            Collection<OsmPrimitive> primitivesToDelete = new ArrayList<OsmPrimitive>();
236            for (OsmPrimitive p : primitives) {
237                if (!p.isDeleted()) {
238                    primitivesToDelete.add(p);
239                }
240            }
241            if (!primitivesToDelete.isEmpty()) {
242                return DeleteCommand.delete(Main.map.mapView.getEditLayer(), primitivesToDelete);
243            } else {
244                return null;
245            }
246        }
247    
248        /**
249         * Determines if the specified primitive denotes a building.
250         * @param p The primitive to be tested
251         * @return True if building key is set and different from no,entrance
252         */
253        protected static final boolean isBuilding(OsmPrimitive p) {
254            String v = p.get("building");
255            return v != null && !v.equals("no") && !v.equals("entrance");
256        }
257    }