001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.validation; 003 004import java.util.ArrayList; 005import java.util.Collection; 006import java.util.Collections; 007import java.util.List; 008import java.util.TreeSet; 009 010import org.openstreetmap.josm.Main; 011import org.openstreetmap.josm.command.Command; 012import org.openstreetmap.josm.data.osm.Node; 013import org.openstreetmap.josm.data.osm.OsmPrimitive; 014import org.openstreetmap.josm.data.osm.Relation; 015import org.openstreetmap.josm.data.osm.Way; 016import org.openstreetmap.josm.data.osm.WaySegment; 017import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 018import org.openstreetmap.josm.data.osm.event.DataChangedEvent; 019import org.openstreetmap.josm.data.osm.event.DataSetListener; 020import org.openstreetmap.josm.data.osm.event.NodeMovedEvent; 021import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; 022import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; 023import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; 024import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; 025import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; 026import org.openstreetmap.josm.data.validation.util.MultipleNameVisitor; 027import org.openstreetmap.josm.tools.AlphanumComparator; 028 029/** 030 * Validation error 031 * @since 3669 032 */ 033public class TestError implements Comparable<TestError>, DataSetListener { 034 /** is this error on the ignore list */ 035 private boolean ignored; 036 /** Severity */ 037 private Severity severity; 038 /** The error message */ 039 private String message; 040 /** Deeper error description */ 041 private String description; 042 private String descriptionEn; 043 /** The affected primitives */ 044 private Collection<? extends OsmPrimitive> primitives; 045 /** The primitives or way segments to be highlighted */ 046 private Collection<?> highlighted; 047 /** The tester that raised this error */ 048 private Test tester; 049 /** Internal code used by testers to classify errors */ 050 private int code; 051 /** If this error is selected */ 052 private boolean selected; 053 054 /** 055 * Constructs a new {@code TestError}. 056 * @param tester The tester 057 * @param severity The severity of this error 058 * @param message The error message 059 * @param description The translated description 060 * @param descriptionEn The English description 061 * @param primitives The affected primitives 062 * @param code The test error reference code 063 * @param highlighted OSM primitives to highlight 064 */ 065 public TestError(Test tester, Severity severity, String message, String description, String descriptionEn, 066 int code, Collection<? extends OsmPrimitive> primitives, Collection<?> highlighted) { 067 this.tester = tester; 068 this.severity = severity; 069 this.message = message; 070 this.description = description; 071 this.descriptionEn = descriptionEn; 072 this.primitives = primitives; 073 this.highlighted = highlighted; 074 this.code = code; 075 } 076 077 /** 078 * Constructs a new {@code TestError} without description. 079 * @param tester The tester 080 * @param severity The severity of this error 081 * @param message The error message 082 * @param primitives The affected primitives 083 * @param code The test error reference code 084 * @param highlighted OSM primitives to highlight 085 */ 086 public TestError(Test tester, Severity severity, String message, int code, Collection<? extends OsmPrimitive> primitives, 087 Collection<?> highlighted) { 088 this(tester, severity, message, null, null, code, primitives, highlighted); 089 } 090 091 /** 092 * Constructs a new {@code TestError}. 093 * @param tester The tester 094 * @param severity The severity of this error 095 * @param message The error message 096 * @param description The translated description 097 * @param descriptionEn The English description 098 * @param primitives The affected primitives 099 * @param code The test error reference code 100 */ 101 public TestError(Test tester, Severity severity, String message, String description, String descriptionEn, 102 int code, Collection<? extends OsmPrimitive> primitives) { 103 this(tester, severity, message, description, descriptionEn, code, primitives, primitives); 104 } 105 106 /** 107 * Constructs a new {@code TestError} without description. 108 * @param tester The tester 109 * @param severity The severity of this error 110 * @param message The error message 111 * @param primitives The affected primitives 112 * @param code The test error reference code 113 */ 114 public TestError(Test tester, Severity severity, String message, int code, Collection<? extends OsmPrimitive> primitives) { 115 this(tester, severity, message, null, null, code, primitives, primitives); 116 } 117 118 /** 119 * Constructs a new {@code TestError} without description, for a single primitive. 120 * @param tester The tester 121 * @param severity The severity of this error 122 * @param message The error message 123 * @param primitive The affected primitive 124 * @param code The test error reference code 125 */ 126 public TestError(Test tester, Severity severity, String message, int code, OsmPrimitive primitive) { 127 this(tester, severity, message, null, null, code, Collections.singletonList(primitive), Collections 128 .singletonList(primitive)); 129 } 130 131 /** 132 * Constructs a new {@code TestError} for a single primitive. 133 * @param tester The tester 134 * @param severity The severity of this error 135 * @param message The error message 136 * @param description The translated description 137 * @param descriptionEn The English description 138 * @param primitive The affected primitive 139 * @param code The test error reference code 140 */ 141 public TestError(Test tester, Severity severity, String message, String description, String descriptionEn, 142 int code, OsmPrimitive primitive) { 143 this(tester, severity, message, description, descriptionEn, code, Collections.singletonList(primitive)); 144 } 145 146 /** 147 * Gets the error message 148 * @return the error message 149 */ 150 public String getMessage() { 151 return message; 152 } 153 154 /** 155 * Gets the error message 156 * @return the error description 157 */ 158 public String getDescription() { 159 return description; 160 } 161 162 /** 163 * Sets the error message 164 * @param message The error message 165 */ 166 public void setMessage(String message) { 167 this.message = message; 168 } 169 170 /** 171 * Gets the list of primitives affected by this error 172 * @return the list of primitives affected by this error 173 */ 174 public Collection<? extends OsmPrimitive> getPrimitives() { 175 return primitives; 176 } 177 178 /** 179 * Gets the list of primitives affected by this error and are selectable 180 * @return the list of selectable primitives affected by this error 181 */ 182 public Collection<? extends OsmPrimitive> getSelectablePrimitives() { 183 List<OsmPrimitive> selectablePrimitives = new ArrayList<>(primitives.size()); 184 for (OsmPrimitive o : primitives) { 185 if (o.isSelectable()) { 186 selectablePrimitives.add(o); 187 } 188 } 189 return selectablePrimitives; 190 } 191 192 /** 193 * Sets the list of primitives affected by this error 194 * @param primitives the list of primitives affected by this error 195 */ 196 public void setPrimitives(List<? extends OsmPrimitive> primitives) { 197 this.primitives = primitives; 198 } 199 200 /** 201 * Gets the severity of this error 202 * @return the severity of this error 203 */ 204 public Severity getSeverity() { 205 return severity; 206 } 207 208 /** 209 * Sets the severity of this error 210 * @param severity the severity of this error 211 */ 212 public void setSeverity(Severity severity) { 213 this.severity = severity; 214 } 215 216 /** 217 * Returns the ignore state for this error. 218 * @return the ignore state for this error 219 */ 220 public String getIgnoreState() { 221 Collection<String> strings = new TreeSet<>(); 222 StringBuilder ignorestring = new StringBuilder(getIgnoreSubGroup()); 223 for (OsmPrimitive o : primitives) { 224 // ignore data not yet uploaded 225 if (o.isNew()) 226 return null; 227 String type = "u"; 228 if (o instanceof Way) { 229 type = "w"; 230 } else if (o instanceof Relation) { 231 type = "r"; 232 } else if (o instanceof Node) { 233 type = "n"; 234 } 235 strings.add(type + '_' + o.getId()); 236 } 237 for (String o : strings) { 238 ignorestring.append(':').append(o); 239 } 240 return ignorestring.toString(); 241 } 242 243 public String getIgnoreSubGroup() { 244 String ignorestring = getIgnoreGroup(); 245 if (descriptionEn != null) { 246 ignorestring += '_' + descriptionEn; 247 } 248 return ignorestring; 249 } 250 251 public String getIgnoreGroup() { 252 return Integer.toString(code); 253 } 254 255 public void setIgnored(boolean state) { 256 ignored = state; 257 } 258 259 public boolean isIgnored() { 260 return ignored; 261 } 262 263 /** 264 * Gets the tester that raised this error 265 * @return the tester that raised this error 266 */ 267 public Test getTester() { 268 return tester; 269 } 270 271 public void setTester(Test tester) { 272 this.tester = tester; 273 } 274 275 /** 276 * Gets the code 277 * @return the code 278 */ 279 public int getCode() { 280 return code; 281 } 282 283 /** 284 * Returns true if the error can be fixed automatically 285 * 286 * @return true if the error can be fixed 287 */ 288 public boolean isFixable() { 289 return tester != null && tester.isFixable(this); 290 } 291 292 /** 293 * Fixes the error with the appropriate command 294 * 295 * @return The command to fix the error 296 */ 297 public Command getFix() { 298 if (tester == null || !tester.isFixable(this) || primitives.isEmpty()) 299 return null; 300 301 return tester.fixError(this); 302 } 303 304 /** 305 * Sets the selection flag of this error 306 * @param selected if this error is selected 307 */ 308 public void setSelected(boolean selected) { 309 this.selected = selected; 310 } 311 312 @SuppressWarnings("unchecked") 313 public void visitHighlighted(ValidatorVisitor v) { 314 for (Object o : highlighted) { 315 if (o instanceof OsmPrimitive) { 316 v.visit((OsmPrimitive) o); 317 } else if (o instanceof WaySegment) { 318 v.visit((WaySegment) o); 319 } else if (o instanceof List<?>) { 320 v.visit((List<Node>) o); 321 } 322 } 323 } 324 325 /** 326 * Returns the selection flag of this error 327 * @return true if this error is selected 328 * @since 5671 329 */ 330 public boolean isSelected() { 331 return selected; 332 } 333 334 /** 335 * Returns The primitives or way segments to be highlighted 336 * @return The primitives or way segments to be highlighted 337 * @since 5671 338 */ 339 public Collection<?> getHighlighted() { 340 return highlighted; 341 } 342 343 @Override 344 public int compareTo(TestError o) { 345 if (equals(o)) return 0; 346 347 MultipleNameVisitor v1 = new MultipleNameVisitor(); 348 MultipleNameVisitor v2 = new MultipleNameVisitor(); 349 350 v1.visit(getPrimitives()); 351 v2.visit(o.getPrimitives()); 352 return AlphanumComparator.getInstance().compare(v1.toString(), v2.toString()); 353 } 354 355 @Override public void primitivesRemoved(PrimitivesRemovedEvent event) { 356 // Remove purged primitives (fix #8639) 357 try { 358 primitives.removeAll(event.getPrimitives()); 359 } catch (UnsupportedOperationException e) { 360 if (event.getPrimitives().containsAll(primitives)) { 361 primitives = Collections.emptyList(); 362 } else { 363 Main.warn(e, "Unable to remove primitives from "+this+'.'); 364 } 365 } 366 } 367 368 @Override public void primitivesAdded(PrimitivesAddedEvent event) { 369 // Do nothing 370 } 371 372 @Override public void tagsChanged(TagsChangedEvent event) { 373 // Do nothing 374 } 375 376 @Override public void nodeMoved(NodeMovedEvent event) { 377 // Do nothing 378 } 379 380 @Override public void wayNodesChanged(WayNodesChangedEvent event) { 381 // Do nothing 382 } 383 384 @Override public void relationMembersChanged(RelationMembersChangedEvent event) { 385 // Do nothing 386 } 387 388 @Override public void otherDatasetChange(AbstractDatasetChangedEvent event) { 389 // Do nothing 390 } 391 392 @Override public void dataChanged(DataChangedEvent event) { 393 // Do nothing 394 } 395 396 @Override 397 public String toString() { 398 return "TestError [tester=" + tester + ", code=" + code + ", message=" + message + ']'; 399 } 400}