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

Last change on this file was 19307, checked in by taylor.smock, 5 months ago

Fix most new PMD issues

It would be better to use the newer switch syntax introduced in Java 14 (JEP 361),
but we currently target Java 11+. When we move to Java 17, this should be
reverted and the newer switch syntax should be used.

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