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

Last change on this file was 18960, checked in by GerdP, 3 months ago

fix #23397: Improve the results of partial validations

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