001// License: GPL. See LICENSE file for details. 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 long elapsedTime = System.currentTimeMillis() - startTime; 186 Main.debug(tr("Test ''{0}'' completed in {1}", getName(), Utils.getDurationString(elapsedTime))); 187 } 188 } 189 190 /** 191 * Visits all primitives to be tested. These primitives are always visited 192 * in the same order: nodes first, then ways. 193 * 194 * @param selection The primitives to be tested 195 */ 196 public void visit(Collection<OsmPrimitive> selection) { 197 progressMonitor.setTicksCount(selection.size()); 198 for (OsmPrimitive p : selection) { 199 if (isPrimitiveUsable(p)) { 200 p.accept(this); 201 } 202 progressMonitor.worked(1); 203 } 204 } 205 206 /** 207 * Determines if the primitive is usable for tests. 208 * @param p The primitive 209 * @return {@code true} if the primitive can be tested, {@code false} otherwise 210 */ 211 public boolean isPrimitiveUsable(OsmPrimitive p) { 212 return p.isUsable() && (!(p instanceof Way) || (((Way) p).getNodesCount() > 1)); // test only Ways with at least 2 nodes 213 } 214 215 @Override 216 public void visit(Node n) {} 217 218 @Override 219 public void visit(Way w) {} 220 221 @Override 222 public void visit(Relation r) {} 223 224 /** 225 * Allow the tester to manage its own preferences 226 * @param testPanel The panel to add any preferences component 227 */ 228 public void addGui(JPanel testPanel) { 229 checkEnabled = new JCheckBox(name, enabled); 230 checkEnabled.setToolTipText(description); 231 testPanel.add(checkEnabled, GBC.std()); 232 233 GBC a = GBC.eol(); 234 a.anchor = GridBagConstraints.EAST; 235 checkBeforeUpload = new JCheckBox(); 236 checkBeforeUpload.setSelected(testBeforeUpload); 237 testPanel.add(checkBeforeUpload, a); 238 } 239 240 /** 241 * Called when the used submits the preferences 242 * @return {@code true} if restart is required, {@code false} otherwise 243 */ 244 public boolean ok() { 245 enabled = checkEnabled.isSelected(); 246 testBeforeUpload = checkBeforeUpload.isSelected(); 247 return false; 248 } 249 250 /** 251 * Fixes the error with the appropriate command 252 * 253 * @param testError 254 * @return The command to fix the error 255 */ 256 public Command fixError(TestError testError) { 257 return null; 258 } 259 260 /** 261 * Returns true if the given error can be fixed automatically 262 * 263 * @param testError The error to check if can be fixed 264 * @return true if the error can be fixed 265 */ 266 public boolean isFixable(TestError testError) { 267 return false; 268 } 269 270 /** 271 * Returns true if this plugin must check the uploaded data before uploading 272 * @return true if this plugin must check the uploaded data before uploading 273 */ 274 public boolean testBeforeUpload() { 275 return testBeforeUpload; 276 } 277 278 /** 279 * Sets the flag that marks an upload check 280 * @param isUpload if true, the test is before upload 281 */ 282 public void setBeforeUpload(boolean isUpload) { 283 this.isBeforeUpload = isUpload; 284 } 285 286 /** 287 * Returns the test name. 288 * @return The test name 289 */ 290 public String getName() { 291 return name; 292 } 293 294 /** 295 * Determines if the test has been canceled. 296 * @return {@code true} if the test has been canceled, {@code false} otherwise 297 */ 298 public boolean isCanceled() { 299 return progressMonitor.isCanceled(); 300 } 301 302 /** 303 * Build a Delete command on all primitives that have not yet been deleted manually by user, or by another error fix. 304 * If all primitives have already been deleted, null is returned. 305 * @param primitives The primitives wanted for deletion 306 * @return a Delete command on all primitives that have not yet been deleted, or null otherwise 307 */ 308 protected final Command deletePrimitivesIfNeeded(Collection<? extends OsmPrimitive> primitives) { 309 Collection<OsmPrimitive> primitivesToDelete = new ArrayList<>(); 310 for (OsmPrimitive p : primitives) { 311 if (!p.isDeleted()) { 312 primitivesToDelete.add(p); 313 } 314 } 315 if (!primitivesToDelete.isEmpty()) { 316 return DeleteCommand.delete(Main.main.getEditLayer(), primitivesToDelete); 317 } else { 318 return null; 319 } 320 } 321 322 /** 323 * Determines if the specified primitive denotes a building. 324 * @param p The primitive to be tested 325 * @return True if building key is set and different from no,entrance 326 */ 327 protected static final boolean isBuilding(OsmPrimitive p) { 328 String v = p.get("building"); 329 return v != null && !"no".equals(v) && !"entrance".equals(v); 330 } 331 332 @Override 333 public int hashCode() { 334 final int prime = 31; 335 int result = 1; 336 result = prime * result + ((description == null) ? 0 : description.hashCode()); 337 result = prime * result + ((name == null) ? 0 : name.hashCode()); 338 return result; 339 } 340 341 @Override 342 public boolean equals(Object obj) { 343 if (this == obj) 344 return true; 345 if (obj == null) 346 return false; 347 if (!(obj instanceof Test)) 348 return false; 349 Test other = (Test) obj; 350 if (description == null) { 351 if (other.description != null) 352 return false; 353 } else if (!description.equals(other.description)) 354 return false; 355 if (name == null) { 356 if (other.name != null) 357 return false; 358 } else if (!name.equals(other.name)) 359 return false; 360 return true; 361 } 362}