source: josm/trunk/src/org/openstreetmap/josm/data/validation/Test.java @ 13671

Last change on this file since 13671 was 13671, checked in by Don-vip, 10 months ago

fix #16188 - Detect crossing of residential areas (patch by marxin, modified)

  • Property svn:eol-style set to native
File size: 11.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.validation;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.GridBagConstraints;
7import java.util.ArrayList;
8import java.util.Collection;
9import java.util.List;
10import java.util.Objects;
11import java.util.Optional;
12import java.util.function.Predicate;
13
14import javax.swing.JCheckBox;
15import javax.swing.JPanel;
16
17import org.openstreetmap.josm.command.Command;
18import org.openstreetmap.josm.command.DeleteCommand;
19import org.openstreetmap.josm.data.osm.Node;
20import org.openstreetmap.josm.data.osm.OsmPrimitive;
21import org.openstreetmap.josm.data.osm.Relation;
22import org.openstreetmap.josm.data.osm.Way;
23import org.openstreetmap.josm.data.osm.search.SearchCompiler.InDataSourceArea;
24import org.openstreetmap.josm.data.osm.search.SearchCompiler.NotOutsideDataSourceArea;
25import org.openstreetmap.josm.data.osm.visitor.OsmPrimitiveVisitor;
26import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
27import org.openstreetmap.josm.gui.progress.ProgressMonitor;
28import org.openstreetmap.josm.tools.GBC;
29import org.openstreetmap.josm.tools.Logging;
30import org.openstreetmap.josm.tools.Utils;
31
32/**
33 * Parent class for all validation tests.
34 * <p>
35 * A test is a primitive visitor, so that it can access to all data to be
36 * validated. These primitives are always visited in the same order: nodes
37 * first, then ways.
38 *
39 * @author frsantos
40 */
41public class Test implements OsmPrimitiveVisitor, Comparable<Test> {
42
43    protected static final Predicate<OsmPrimitive> IN_DOWNLOADED_AREA = new NotOutsideDataSourceArea();
44    protected static final Predicate<OsmPrimitive> IN_DOWNLOADED_AREA_STRICT = new InDataSourceArea(true);
45
46    /** Name of the test */
47    protected final String name;
48
49    /** Description of the test */
50    protected final String description;
51
52    /** Whether this test is enabled. Enabled by default */
53    public boolean enabled = true;
54
55    /** The preferences check for validation */
56    protected JCheckBox checkEnabled;
57
58    /** The preferences check for validation on upload */
59    protected JCheckBox checkBeforeUpload;
60
61    /** Whether this test must check before upload. Enabled by default */
62    public boolean testBeforeUpload = true;
63
64    /** Whether this test is performing just before an upload */
65    protected boolean isBeforeUpload;
66
67    /** The list of errors */
68    protected List<TestError> errors = new ArrayList<>(30);
69
70    /** Whether the test is run on a partial selection data */
71    protected boolean partialSelection;
72
73    /** the progress monitor to use */
74    protected ProgressMonitor progressMonitor;
75
76    /** the start time to compute elapsed time when test finishes */
77    protected long startTime;
78
79    /**
80     * Constructor
81     * @param name Name of the test
82     * @param description Description of the test
83     */
84    public Test(String name, String description) {
85        this.name = name;
86        this.description = description;
87    }
88
89    /**
90     * Constructor
91     * @param name Name of the test
92     */
93    public Test(String name) {
94        this(name, null);
95    }
96
97    /**
98     * A test that forwards all primitives to {@link #check(OsmPrimitive)}.
99     */
100    public abstract static class TagTest extends Test {
101        /**
102         * Constructs a new {@code TagTest} with given name and description.
103         * @param name The test name
104         * @param description The test description
105         */
106        public TagTest(String name, String description) {
107            super(name, description);
108        }
109
110        /**
111         * Constructs a new {@code TagTest} with given name.
112         * @param name The test name
113         */
114        public TagTest(String name) {
115            super(name);
116        }
117
118        /**
119         * Checks the tags of the given primitive.
120         * @param p The primitive to test
121         */
122        public abstract void check(OsmPrimitive p);
123
124        @Override
125        public void visit(Node n) {
126            check(n);
127        }
128
129        @Override
130        public void visit(Way w) {
131            check(w);
132        }
133
134        @Override
135        public void visit(Relation r) {
136            check(r);
137        }
138    }
139
140    /**
141     * Initializes any global data used this tester.
142     * @throws Exception When cannot initialize the test
143     */
144    public void initialize() throws Exception {
145        this.startTime = -1;
146    }
147
148    /**
149     * Start the test using a given progress monitor
150     *
151     * @param progressMonitor  the progress monitor
152     */
153    public void startTest(ProgressMonitor progressMonitor) {
154        this.progressMonitor = Optional.ofNullable(progressMonitor).orElse(NullProgressMonitor.INSTANCE);
155        String startMessage = tr("Running test {0}", name);
156        this.progressMonitor.beginTask(startMessage);
157        Logging.debug(startMessage);
158        this.errors = new ArrayList<>(30);
159        this.startTime = System.currentTimeMillis();
160    }
161
162    /**
163     * Flag notifying that this test is run over a partial data selection
164     * @param partialSelection Whether the test is on a partial selection data
165     */
166    public void setPartialSelection(boolean partialSelection) {
167        this.partialSelection = partialSelection;
168    }
169
170    /**
171     * Gets the validation errors accumulated until this moment.
172     * @return The list of errors
173     */
174    public List<TestError> getErrors() {
175        return errors;
176    }
177
178    /**
179     * Notification of the end of the test. The tester may perform additional
180     * actions and destroy the used structures.
181     * <p>
182     * If you override this method, don't forget to cleanup {@code progressMonitor}
183     * (most overrides call {@code super.endTest()} to do this).
184     */
185    public void endTest() {
186        progressMonitor.finishTask();
187        progressMonitor = null;
188        if (startTime > 0) {
189            // fix #11567 where elapsedTime is < 0
190            long elapsedTime = Math.max(0, System.currentTimeMillis() - startTime);
191            Logging.debug(tr("Test ''{0}'' completed in {1}", getName(), Utils.getDurationString(elapsedTime)));
192        }
193    }
194
195    /**
196     * Visits all primitives to be tested. These primitives are always visited
197     * in the same order: nodes first, then ways.
198     *
199     * @param selection The primitives to be tested
200     */
201    public void visit(Collection<OsmPrimitive> selection) {
202        if (progressMonitor != null) {
203            progressMonitor.setTicksCount(selection.size());
204        }
205        for (OsmPrimitive p : selection) {
206            if (isCanceled()) {
207                break;
208            }
209            if (isPrimitiveUsable(p)) {
210                p.accept(this);
211            }
212            if (progressMonitor != null) {
213                progressMonitor.worked(1);
214            }
215        }
216    }
217
218    /**
219     * Determines if the primitive is usable for tests.
220     * @param p The primitive
221     * @return {@code true} if the primitive can be tested, {@code false} otherwise
222     */
223    public boolean isPrimitiveUsable(OsmPrimitive p) {
224        return p.isUsable() && (!(p instanceof Way) || (((Way) p).getNodesCount() > 1)); // test only Ways with at least 2 nodes
225    }
226
227    @Override
228    public void visit(Node n) {
229        // To be overridden in subclasses
230    }
231
232    @Override
233    public void visit(Way w) {
234        // To be overridden in subclasses
235    }
236
237    @Override
238    public void visit(Relation r) {
239        // To be overridden in subclasses
240    }
241
242    /**
243     * Allow the tester to manage its own preferences
244     * @param testPanel The panel to add any preferences component
245     */
246    public void addGui(JPanel testPanel) {
247        checkEnabled = new JCheckBox(name, enabled);
248        checkEnabled.setToolTipText(description);
249        testPanel.add(checkEnabled, GBC.std());
250
251        GBC a = GBC.eol();
252        a.anchor = GridBagConstraints.EAST;
253        checkBeforeUpload = new JCheckBox();
254        checkBeforeUpload.setSelected(testBeforeUpload);
255        testPanel.add(checkBeforeUpload, a);
256    }
257
258    /**
259     * Called when the used submits the preferences
260     * @return {@code true} if restart is required, {@code false} otherwise
261     */
262    public boolean ok() {
263        enabled = checkEnabled.isSelected();
264        testBeforeUpload = checkBeforeUpload.isSelected();
265        return false;
266    }
267
268    /**
269     * Fixes the error with the appropriate command
270     *
271     * @param testError error to fix
272     * @return The command to fix the error
273     */
274    public Command fixError(TestError testError) {
275        return null;
276    }
277
278    /**
279     * Returns true if the given error can be fixed automatically
280     *
281     * @param testError The error to check if can be fixed
282     * @return true if the error can be fixed
283     */
284    public boolean isFixable(TestError testError) {
285        return false;
286    }
287
288    /**
289     * Returns true if this plugin must check the uploaded data before uploading
290     * @return true if this plugin must check the uploaded data before uploading
291     */
292    public boolean testBeforeUpload() {
293        return testBeforeUpload;
294    }
295
296    /**
297     * Sets the flag that marks an upload check
298     * @param isUpload if true, the test is before upload
299     */
300    public void setBeforeUpload(boolean isUpload) {
301        this.isBeforeUpload = isUpload;
302    }
303
304    /**
305     * Returns the test name.
306     * @return The test name
307     */
308    public String getName() {
309        return name;
310    }
311
312    /**
313     * Determines if the test has been canceled.
314     * @return {@code true} if the test has been canceled, {@code false} otherwise
315     */
316    public boolean isCanceled() {
317        return progressMonitor != null ? progressMonitor.isCanceled() : false;
318    }
319
320    /**
321     * Build a Delete command on all primitives that have not yet been deleted manually by user, or by another error fix.
322     * If all primitives have already been deleted, null is returned.
323     * @param primitives The primitives wanted for deletion
324     * @return a Delete command on all primitives that have not yet been deleted, or null otherwise
325     */
326    protected final Command deletePrimitivesIfNeeded(Collection<? extends OsmPrimitive> primitives) {
327        Collection<OsmPrimitive> primitivesToDelete = new ArrayList<>();
328        for (OsmPrimitive p : primitives) {
329            if (!p.isDeleted()) {
330                primitivesToDelete.add(p);
331            }
332        }
333        if (!primitivesToDelete.isEmpty()) {
334            return DeleteCommand.delete(primitivesToDelete);
335        } else {
336            return null;
337        }
338    }
339
340    /**
341     * Determines if the specified primitive denotes a building.
342     * @param p The primitive to be tested
343     * @return True if building key is set and different from no,entrance
344     */
345    protected static final boolean isBuilding(OsmPrimitive p) {
346        return p.hasTagDifferent("building", "no", "entrance");
347    }
348
349    /**
350     * Determines if the specified primitive denotes a residential area.
351     * @param p The primitive to be tested
352     * @return True if landuse key is equal to residential
353     */
354    protected static final boolean isResidentialArea(OsmPrimitive p) {
355        return p.hasTag("landuse", "residential");
356    }
357
358    @Override
359    public int hashCode() {
360        return Objects.hash(name, description);
361    }
362
363    @Override
364    public boolean equals(Object obj) {
365        if (this == obj) return true;
366        if (obj == null || getClass() != obj.getClass()) return false;
367        Test test = (Test) obj;
368        return Objects.equals(name, test.name) &&
369               Objects.equals(description, test.description);
370    }
371
372    @Override
373    public int compareTo(Test t) {
374        return name.compareTo(t.name);
375    }
376}
Note: See TracBrowser for help on using the repository browser.