001 // License: GPL. See LICENSE file for details. 002 package org.openstreetmap.josm.data.validation; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.awt.GridBagConstraints; 007 import java.util.ArrayList; 008 import java.util.Collection; 009 import java.util.List; 010 011 import javax.swing.JCheckBox; 012 import javax.swing.JPanel; 013 014 import org.openstreetmap.josm.Main; 015 import org.openstreetmap.josm.command.Command; 016 import org.openstreetmap.josm.command.DeleteCommand; 017 import org.openstreetmap.josm.data.osm.Node; 018 import org.openstreetmap.josm.data.osm.OsmPrimitive; 019 import org.openstreetmap.josm.data.osm.Relation; 020 import org.openstreetmap.josm.data.osm.Way; 021 import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor; 022 import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 023 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 024 import org.openstreetmap.josm.tools.GBC; 025 026 /** 027 * Parent class for all validation tests. 028 * <p> 029 * A test is a primitive visitor, so that it can access to all data to be 030 * validated. These primitives are always visited in the same order: nodes 031 * first, then ways. 032 * 033 * @author frsantos 034 */ 035 public class Test extends AbstractVisitor 036 { 037 /** Name of the test */ 038 protected final String name; 039 040 /** Description of the test */ 041 protected final String description; 042 043 /** Whether this test is enabled. Enabled by default */ 044 public boolean enabled = true; 045 046 /** The preferences check for validation */ 047 protected JCheckBox checkEnabled; 048 049 /** The preferences check for validation on upload */ 050 protected JCheckBox checkBeforeUpload; 051 052 /** Whether this test must check before upload. Enabled by default */ 053 public boolean testBeforeUpload = true; 054 055 /** Whether this test is performing just before an upload */ 056 protected boolean isBeforeUpload; 057 058 /** The list of errors */ 059 protected List<TestError> errors = new ArrayList<TestError>(30); 060 061 /** Whether the test is run on a partial selection data */ 062 protected boolean partialSelection; 063 064 /** the progress monitor to use */ 065 protected ProgressMonitor progressMonitor; 066 /** 067 * Constructor 068 * @param name Name of the test 069 * @param description Description of the test 070 */ 071 public Test(String name, String description) { 072 this.name = name; 073 this.description = description; 074 } 075 076 /** 077 * Constructor 078 * @param name Name of the test 079 */ 080 public Test(String name) { 081 this(name, null); 082 } 083 084 /** 085 * Initializes any global data used this tester. 086 * @throws Exception When cannot initialize the test 087 */ 088 public void initialize() throws Exception { 089 } 090 091 /** 092 * Start the test using a given progress monitor 093 * 094 * @param progressMonitor the progress monitor 095 */ 096 public void startTest(ProgressMonitor progressMonitor) { 097 if (progressMonitor == null) { 098 this.progressMonitor = NullProgressMonitor.INSTANCE; 099 } else { 100 this.progressMonitor = progressMonitor; 101 } 102 this.progressMonitor.beginTask(tr("Running test {0}", name)); 103 errors = new ArrayList<TestError>(30); 104 } 105 106 /** 107 * Flag notifying that this test is run over a partial data selection 108 * @param partialSelection Whether the test is on a partial selection data 109 */ 110 public void setPartialSelection(boolean partialSelection) { 111 this.partialSelection = partialSelection; 112 } 113 114 /** 115 * Gets the validation errors accumulated until this moment. 116 * @return The list of errors 117 */ 118 public List<TestError> getErrors() { 119 return errors; 120 } 121 122 /** 123 * Notification of the end of the test. The tester may perform additional 124 * actions and destroy the used structures. 125 * <p> 126 * If you override this method, don't forget to cleanup {@link #progressMonitor} 127 * (most overrides call {@code super.endTest()} to do this). 128 */ 129 public void endTest() { 130 progressMonitor.finishTask(); 131 progressMonitor = null; 132 } 133 134 /** 135 * Visits all primitives to be tested. These primitives are always visited 136 * in the same order: nodes first, then ways. 137 * 138 * @param selection The primitives to be tested 139 */ 140 public void visit(Collection<OsmPrimitive> selection) { 141 progressMonitor.setTicksCount(selection.size()); 142 for (OsmPrimitive p : selection) { 143 if (p.isUsable()) { 144 p.visit(this); 145 } 146 progressMonitor.worked(1); 147 } 148 } 149 150 @Override 151 public void visit(Node n) {} 152 153 @Override 154 public void visit(Way w) {} 155 156 @Override 157 public void visit(Relation r) {} 158 159 /** 160 * Allow the tester to manage its own preferences 161 * @param testPanel The panel to add any preferences component 162 */ 163 public void addGui(JPanel testPanel) { 164 checkEnabled = new JCheckBox(name, enabled); 165 checkEnabled.setToolTipText(description); 166 testPanel.add(checkEnabled, GBC.std()); 167 168 GBC a = GBC.eol(); 169 a.anchor = GridBagConstraints.EAST; 170 checkBeforeUpload = new JCheckBox(); 171 checkBeforeUpload.setSelected(testBeforeUpload); 172 testPanel.add(checkBeforeUpload, a); 173 } 174 175 /** 176 * Called when the used submits the preferences 177 */ 178 public boolean ok() { 179 enabled = checkEnabled.isSelected(); 180 testBeforeUpload = checkBeforeUpload.isSelected(); 181 return false; 182 } 183 184 /** 185 * Fixes the error with the appropriate command 186 * 187 * @param testError 188 * @return The command to fix the error 189 */ 190 public Command fixError(TestError testError) { 191 return null; 192 } 193 194 /** 195 * Returns true if the given error can be fixed automatically 196 * 197 * @param testError The error to check if can be fixed 198 * @return true if the error can be fixed 199 */ 200 public boolean isFixable(TestError testError) { 201 return false; 202 } 203 204 /** 205 * Returns true if this plugin must check the uploaded data before uploading 206 * @return true if this plugin must check the uploaded data before uploading 207 */ 208 public boolean testBeforeUpload() { 209 return testBeforeUpload; 210 } 211 212 /** 213 * Sets the flag that marks an upload check 214 * @param isUpload if true, the test is before upload 215 */ 216 public void setBeforeUpload(boolean isUpload) { 217 this.isBeforeUpload = isUpload; 218 } 219 220 public String getName() { 221 return name; 222 } 223 224 public boolean isCanceled() { 225 return progressMonitor.isCanceled(); 226 } 227 228 /** 229 * Build a Delete command on all primitives that have not yet been deleted manually by user, or by another error fix. 230 * If all primitives have already been deleted, null is returned. 231 * @param primitives The primitives wanted for deletion 232 * @return a Delete command on all primitives that have not yet been deleted, or null otherwise 233 */ 234 protected final Command deletePrimitivesIfNeeded(Collection<? extends OsmPrimitive> primitives) { 235 Collection<OsmPrimitive> primitivesToDelete = new ArrayList<OsmPrimitive>(); 236 for (OsmPrimitive p : primitives) { 237 if (!p.isDeleted()) { 238 primitivesToDelete.add(p); 239 } 240 } 241 if (!primitivesToDelete.isEmpty()) { 242 return DeleteCommand.delete(Main.map.mapView.getEditLayer(), primitivesToDelete); 243 } else { 244 return null; 245 } 246 } 247 248 /** 249 * Determines if the specified primitive denotes a building. 250 * @param p The primitive to be tested 251 * @return True if building key is set and different from no,entrance 252 */ 253 protected static final boolean isBuilding(OsmPrimitive p) { 254 String v = p.get("building"); 255 return v != null && !v.equals("no") && !v.equals("entrance"); 256 } 257 }