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<TestError>(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 * Initializes any global data used this tester. 091 * @throws Exception When cannot initialize the test 092 */ 093 public void initialize() throws Exception { 094 this.startTime = -1; 095 } 096 097 /** 098 * Start the test using a given progress monitor 099 * 100 * @param progressMonitor the progress monitor 101 */ 102 public void startTest(ProgressMonitor progressMonitor) { 103 if (progressMonitor == null) { 104 this.progressMonitor = NullProgressMonitor.INSTANCE; 105 } else { 106 this.progressMonitor = progressMonitor; 107 } 108 String startMessage = tr("Running test {0}", name); 109 this.progressMonitor.beginTask(startMessage); 110 Main.debug(startMessage); 111 this.errors = new ArrayList<TestError>(30); 112 this.startTime = System.currentTimeMillis(); 113 } 114 115 /** 116 * Flag notifying that this test is run over a partial data selection 117 * @param partialSelection Whether the test is on a partial selection data 118 */ 119 public void setPartialSelection(boolean partialSelection) { 120 this.partialSelection = partialSelection; 121 } 122 123 /** 124 * Gets the validation errors accumulated until this moment. 125 * @return The list of errors 126 */ 127 public List<TestError> getErrors() { 128 return errors; 129 } 130 131 /** 132 * Notification of the end of the test. The tester may perform additional 133 * actions and destroy the used structures. 134 * <p> 135 * If you override this method, don't forget to cleanup {@link #progressMonitor} 136 * (most overrides call {@code super.endTest()} to do this). 137 */ 138 public void endTest() { 139 progressMonitor.finishTask(); 140 progressMonitor = null; 141 if (startTime > 0) { 142 long elapsedTime = System.currentTimeMillis() - startTime; 143 Main.info(tr("Test ''{0}'' completed in {1}", getName(), Utils.getDurationString(elapsedTime))); 144 } 145 } 146 147 /** 148 * Visits all primitives to be tested. These primitives are always visited 149 * in the same order: nodes first, then ways. 150 * 151 * @param selection The primitives to be tested 152 */ 153 public void visit(Collection<OsmPrimitive> selection) { 154 progressMonitor.setTicksCount(selection.size()); 155 for (OsmPrimitive p : selection) { 156 if (isPrimitiveUsable(p)) { 157 p.accept(this); 158 } 159 progressMonitor.worked(1); 160 } 161 } 162 163 public boolean isPrimitiveUsable(OsmPrimitive p) { 164 return p.isUsable() && (!(p instanceof Way) || (((Way) p).getNodesCount() > 1)); // test only Ways with at least 2 nodes 165 } 166 167 @Override 168 public void visit(Node n) {} 169 170 @Override 171 public void visit(Way w) {} 172 173 @Override 174 public void visit(Relation r) {} 175 176 /** 177 * Allow the tester to manage its own preferences 178 * @param testPanel The panel to add any preferences component 179 */ 180 public void addGui(JPanel testPanel) { 181 checkEnabled = new JCheckBox(name, enabled); 182 checkEnabled.setToolTipText(description); 183 testPanel.add(checkEnabled, GBC.std()); 184 185 GBC a = GBC.eol(); 186 a.anchor = GridBagConstraints.EAST; 187 checkBeforeUpload = new JCheckBox(); 188 checkBeforeUpload.setSelected(testBeforeUpload); 189 testPanel.add(checkBeforeUpload, a); 190 } 191 192 /** 193 * Called when the used submits the preferences 194 */ 195 public boolean ok() { 196 enabled = checkEnabled.isSelected(); 197 testBeforeUpload = checkBeforeUpload.isSelected(); 198 return false; 199 } 200 201 /** 202 * Fixes the error with the appropriate command 203 * 204 * @param testError 205 * @return The command to fix the error 206 */ 207 public Command fixError(TestError testError) { 208 return null; 209 } 210 211 /** 212 * Returns true if the given error can be fixed automatically 213 * 214 * @param testError The error to check if can be fixed 215 * @return true if the error can be fixed 216 */ 217 public boolean isFixable(TestError testError) { 218 return false; 219 } 220 221 /** 222 * Returns true if this plugin must check the uploaded data before uploading 223 * @return true if this plugin must check the uploaded data before uploading 224 */ 225 public boolean testBeforeUpload() { 226 return testBeforeUpload; 227 } 228 229 /** 230 * Sets the flag that marks an upload check 231 * @param isUpload if true, the test is before upload 232 */ 233 public void setBeforeUpload(boolean isUpload) { 234 this.isBeforeUpload = isUpload; 235 } 236 237 public String getName() { 238 return name; 239 } 240 241 public boolean isCanceled() { 242 return progressMonitor.isCanceled(); 243 } 244 245 /** 246 * Build a Delete command on all primitives that have not yet been deleted manually by user, or by another error fix. 247 * If all primitives have already been deleted, null is returned. 248 * @param primitives The primitives wanted for deletion 249 * @return a Delete command on all primitives that have not yet been deleted, or null otherwise 250 */ 251 protected final Command deletePrimitivesIfNeeded(Collection<? extends OsmPrimitive> primitives) { 252 Collection<OsmPrimitive> primitivesToDelete = new ArrayList<OsmPrimitive>(); 253 for (OsmPrimitive p : primitives) { 254 if (!p.isDeleted()) { 255 primitivesToDelete.add(p); 256 } 257 } 258 if (!primitivesToDelete.isEmpty()) { 259 return DeleteCommand.delete(Main.main.getEditLayer(), primitivesToDelete); 260 } else { 261 return null; 262 } 263 } 264 265 /** 266 * Determines if the specified primitive denotes a building. 267 * @param p The primitive to be tested 268 * @return True if building key is set and different from no,entrance 269 */ 270 protected static final boolean isBuilding(OsmPrimitive p) { 271 String v = p.get("building"); 272 return v != null && !v.equals("no") && !v.equals("entrance"); 273 } 274}