001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.validation; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.GridBagConstraints; 007import java.util.ArrayList; 008import java.util.Collection; 009import java.util.List; 010 011import javax.swing.JCheckBox; 012import javax.swing.JPanel; 013 014import org.openstreetmap.josm.Main; 015import org.openstreetmap.josm.command.Command; 016import org.openstreetmap.josm.command.DeleteCommand; 017import org.openstreetmap.josm.data.osm.Node; 018import org.openstreetmap.josm.data.osm.OsmPrimitive; 019import org.openstreetmap.josm.data.osm.Relation; 020import org.openstreetmap.josm.data.osm.Way; 021import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor; 022import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 023import org.openstreetmap.josm.gui.progress.ProgressMonitor; 024import org.openstreetmap.josm.tools.GBC; 025import org.openstreetmap.josm.tools.Utils; 026 027/** 028 * Parent class for all validation tests. 029 * <p> 030 * A test is a primitive visitor, so that it can access to all data to be 031 * validated. These primitives are always visited in the same order: nodes 032 * first, then ways. 033 * 034 * @author frsantos 035 */ 036public class Test extends AbstractVisitor { 037 038 /** Name of the test */ 039 protected final String name; 040 041 /** Description of the test */ 042 protected final String description; 043 044 /** Whether this test is enabled. Enabled by default */ 045 public boolean enabled = true; 046 047 /** The preferences check for validation */ 048 protected JCheckBox checkEnabled; 049 050 /** The preferences check for validation on upload */ 051 protected JCheckBox checkBeforeUpload; 052 053 /** Whether this test must check before upload. Enabled by default */ 054 public boolean testBeforeUpload = true; 055 056 /** Whether this test is performing just before an upload */ 057 protected boolean isBeforeUpload; 058 059 /** The list of errors */ 060 protected List<TestError> errors = new ArrayList<>(30); 061 062 /** Whether the test is run on a partial selection data */ 063 protected boolean partialSelection; 064 065 /** the progress monitor to use */ 066 protected ProgressMonitor progressMonitor; 067 068 /** the start time to compute elapsed time when test finishes */ 069 protected long startTime; 070 071 /** 072 * Constructor 073 * @param name Name of the test 074 * @param description Description of the test 075 */ 076 public Test(String name, String description) { 077 this.name = name; 078 this.description = description; 079 } 080 081 /** 082 * Constructor 083 * @param name Name of the test 084 */ 085 public Test(String name) { 086 this(name, null); 087 } 088 089 /** 090 * A test that forwards all primitives to {@link #check(OsmPrimitive)}. 091 */ 092 public abstract static class TagTest extends Test { 093 /** 094 * Constructs a new {@code TagTest} with given name and description. 095 * @param name The test name 096 * @param description The test description 097 */ 098 public TagTest(String name, String description) { 099 super(name, description); 100 } 101 102 /** 103 * Constructs a new {@code TagTest} with given name. 104 * @param name The test name 105 */ 106 public TagTest(String name) { 107 super(name); 108 } 109 110 /** 111 * Checks the tags of the given primitive. 112 * @param p The primitive to test 113 */ 114 public abstract void check(final OsmPrimitive p); 115 116 @Override 117 public void visit(Node n) { 118 check(n); 119 } 120 121 @Override 122 public void visit(Way w) { 123 check(w); 124 } 125 126 @Override 127 public void visit(Relation r) { 128 check(r); 129 } 130 } 131 132 /** 133 * Initializes any global data used this tester. 134 * @throws Exception When cannot initialize the test 135 */ 136 public void initialize() throws Exception { 137 this.startTime = -1; 138 } 139 140 /** 141 * Start the test using a given progress monitor 142 * 143 * @param progressMonitor the progress monitor 144 */ 145 public void startTest(ProgressMonitor progressMonitor) { 146 if (progressMonitor == null) { 147 this.progressMonitor = NullProgressMonitor.INSTANCE; 148 } else { 149 this.progressMonitor = progressMonitor; 150 } 151 String startMessage = tr("Running test {0}", name); 152 this.progressMonitor.beginTask(startMessage); 153 Main.debug(startMessage); 154 this.errors = new ArrayList<>(30); 155 this.startTime = System.currentTimeMillis(); 156 } 157 158 /** 159 * Flag notifying that this test is run over a partial data selection 160 * @param partialSelection Whether the test is on a partial selection data 161 */ 162 public void setPartialSelection(boolean partialSelection) { 163 this.partialSelection = partialSelection; 164 } 165 166 /** 167 * Gets the validation errors accumulated until this moment. 168 * @return The list of errors 169 */ 170 public List<TestError> getErrors() { 171 return errors; 172 } 173 174 /** 175 * Notification of the end of the test. The tester may perform additional 176 * actions and destroy the used structures. 177 * <p> 178 * If you override this method, don't forget to cleanup {@code progressMonitor} 179 * (most overrides call {@code super.endTest()} to do this). 180 */ 181 public void endTest() { 182 progressMonitor.finishTask(); 183 progressMonitor = null; 184 if (startTime > 0) { 185 // fix #11567 where elapsedTime is < 0 186 long elapsedTime = Math.max(0, System.currentTimeMillis() - startTime); 187 Main.debug(tr("Test ''{0}'' completed in {1}", getName(), Utils.getDurationString(elapsedTime))); 188 } 189 } 190 191 /** 192 * Visits all primitives to be tested. These primitives are always visited 193 * in the same order: nodes first, then ways. 194 * 195 * @param selection The primitives to be tested 196 */ 197 public void visit(Collection<OsmPrimitive> selection) { 198 if (progressMonitor != null) { 199 progressMonitor.setTicksCount(selection.size()); 200 } 201 for (OsmPrimitive p : selection) { 202 if (isCanceled()) { 203 break; 204 } 205 if (isPrimitiveUsable(p)) { 206 p.accept(this); 207 } 208 if (progressMonitor != null) { 209 progressMonitor.worked(1); 210 } 211 } 212 } 213 214 /** 215 * Determines if the primitive is usable for tests. 216 * @param p The primitive 217 * @return {@code true} if the primitive can be tested, {@code false} otherwise 218 */ 219 public boolean isPrimitiveUsable(OsmPrimitive p) { 220 return p.isUsable() && (!(p instanceof Way) || (((Way) p).getNodesCount() > 1)); // test only Ways with at least 2 nodes 221 } 222 223 @Override 224 public void visit(Node n) {} 225 226 @Override 227 public void visit(Way w) {} 228 229 @Override 230 public void visit(Relation r) {} 231 232 /** 233 * Allow the tester to manage its own preferences 234 * @param testPanel The panel to add any preferences component 235 */ 236 public void addGui(JPanel testPanel) { 237 checkEnabled = new JCheckBox(name, enabled); 238 checkEnabled.setToolTipText(description); 239 testPanel.add(checkEnabled, GBC.std()); 240 241 GBC a = GBC.eol(); 242 a.anchor = GridBagConstraints.EAST; 243 checkBeforeUpload = new JCheckBox(); 244 checkBeforeUpload.setSelected(testBeforeUpload); 245 testPanel.add(checkBeforeUpload, a); 246 } 247 248 /** 249 * Called when the used submits the preferences 250 * @return {@code true} if restart is required, {@code false} otherwise 251 */ 252 public boolean ok() { 253 enabled = checkEnabled.isSelected(); 254 testBeforeUpload = checkBeforeUpload.isSelected(); 255 return false; 256 } 257 258 /** 259 * Fixes the error with the appropriate command 260 * 261 * @param testError error to fix 262 * @return The command to fix the error 263 */ 264 public Command fixError(TestError testError) { 265 return null; 266 } 267 268 /** 269 * Returns true if the given error can be fixed automatically 270 * 271 * @param testError The error to check if can be fixed 272 * @return true if the error can be fixed 273 */ 274 public boolean isFixable(TestError testError) { 275 return false; 276 } 277 278 /** 279 * Returns true if this plugin must check the uploaded data before uploading 280 * @return true if this plugin must check the uploaded data before uploading 281 */ 282 public boolean testBeforeUpload() { 283 return testBeforeUpload; 284 } 285 286 /** 287 * Sets the flag that marks an upload check 288 * @param isUpload if true, the test is before upload 289 */ 290 public void setBeforeUpload(boolean isUpload) { 291 this.isBeforeUpload = isUpload; 292 } 293 294 /** 295 * Returns the test name. 296 * @return The test name 297 */ 298 public String getName() { 299 return name; 300 } 301 302 /** 303 * Determines if the test has been canceled. 304 * @return {@code true} if the test has been canceled, {@code false} otherwise 305 */ 306 public boolean isCanceled() { 307 return progressMonitor.isCanceled(); 308 } 309 310 /** 311 * Build a Delete command on all primitives that have not yet been deleted manually by user, or by another error fix. 312 * If all primitives have already been deleted, null is returned. 313 * @param primitives The primitives wanted for deletion 314 * @return a Delete command on all primitives that have not yet been deleted, or null otherwise 315 */ 316 protected final Command deletePrimitivesIfNeeded(Collection<? extends OsmPrimitive> primitives) { 317 Collection<OsmPrimitive> primitivesToDelete = new ArrayList<>(); 318 for (OsmPrimitive p : primitives) { 319 if (!p.isDeleted()) { 320 primitivesToDelete.add(p); 321 } 322 } 323 if (!primitivesToDelete.isEmpty()) { 324 return DeleteCommand.delete(Main.main.getEditLayer(), primitivesToDelete); 325 } else { 326 return null; 327 } 328 } 329 330 /** 331 * Determines if the specified primitive denotes a building. 332 * @param p The primitive to be tested 333 * @return True if building key is set and different from no,entrance 334 */ 335 protected static final boolean isBuilding(OsmPrimitive p) { 336 String v = p.get("building"); 337 return v != null && !"no".equals(v) && !"entrance".equals(v); 338 } 339 340 @Override 341 public int hashCode() { 342 final int prime = 31; 343 int result = 1; 344 result = prime * result + ((description == null) ? 0 : description.hashCode()); 345 result = prime * result + ((name == null) ? 0 : name.hashCode()); 346 return result; 347 } 348 349 @Override 350 public boolean equals(Object obj) { 351 if (this == obj) 352 return true; 353 if (obj == null) 354 return false; 355 if (!(obj instanceof Test)) 356 return false; 357 Test other = (Test) obj; 358 if (description == null) { 359 if (other.description != null) 360 return false; 361 } else if (!description.equals(other.description)) 362 return false; 363 if (name == null) { 364 if (other.name != null) 365 return false; 366 } else if (!name.equals(other.name)) 367 return false; 368 return true; 369 } 370}