001// License: GPL. See LICENSE file for details. 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 = false; 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 description_en; 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 primitives The affected primitives 060 * @param code The test error reference code 061 */ 062 public TestError(Test tester, Severity severity, String message, String description, String description_en, 063 int code, Collection<? extends OsmPrimitive> primitives, Collection<?> highlighted) { 064 this.tester = tester; 065 this.severity = severity; 066 this.message = message; 067 this.description = description; 068 this.description_en = description_en; 069 this.primitives = primitives; 070 this.highlighted = highlighted; 071 this.code = code; 072 } 073 074 public TestError(Test tester, Severity severity, String message, int code, Collection<? extends OsmPrimitive> primitives, 075 Collection<?> highlighted) { 076 this(tester, severity, message, null, null, code, primitives, highlighted); 077 } 078 079 public TestError(Test tester, Severity severity, String message, String description, String description_en, 080 int code, Collection<? extends OsmPrimitive> primitives) { 081 this(tester, severity, message, description, description_en, code, primitives, primitives); 082 } 083 084 public TestError(Test tester, Severity severity, String message, int code, Collection<? extends OsmPrimitive> primitives) { 085 this(tester, severity, message, null, null, code, primitives, primitives); 086 } 087 088 public TestError(Test tester, Severity severity, String message, int code, OsmPrimitive primitive) { 089 this(tester, severity, message, null, null, code, Collections.singletonList(primitive), Collections 090 .singletonList(primitive)); 091 } 092 093 public TestError(Test tester, Severity severity, String message, String description, String description_en, 094 int code, OsmPrimitive primitive) { 095 this(tester, severity, message, description, description_en, code, Collections.singletonList(primitive)); 096 } 097 098 /** 099 * Gets the error message 100 * @return the error message 101 */ 102 public String getMessage() { 103 return message; 104 } 105 106 /** 107 * Gets the error message 108 * @return the error description 109 */ 110 public String getDescription() { 111 return description; 112 } 113 114 /** 115 * Sets the error message 116 * @param message The error message 117 */ 118 public void setMessage(String message) { 119 this.message = message; 120 } 121 122 /** 123 * Gets the list of primitives affected by this error 124 * @return the list of primitives affected by this error 125 */ 126 public Collection<? extends OsmPrimitive> getPrimitives() { 127 return primitives; 128 } 129 130 /** 131 * Gets the list of primitives affected by this error and are selectable 132 * @return the list of selectable primitives affected by this error 133 */ 134 public Collection<? extends OsmPrimitive> getSelectablePrimitives() { 135 List<OsmPrimitive> selectablePrimitives = new ArrayList<>(primitives.size()); 136 for (OsmPrimitive o : primitives) { 137 if (o.isSelectable()) { 138 selectablePrimitives.add(o); 139 } 140 } 141 return selectablePrimitives; 142 } 143 144 /** 145 * Sets the list of primitives affected by this error 146 * @param primitives the list of primitives affected by this error 147 */ 148 public void setPrimitives(List<OsmPrimitive> primitives) { 149 this.primitives = primitives; 150 } 151 152 /** 153 * Gets the severity of this error 154 * @return the severity of this error 155 */ 156 public Severity getSeverity() { 157 return severity; 158 } 159 160 /** 161 * Sets the severity of this error 162 * @param severity the severity of this error 163 */ 164 public void setSeverity(Severity severity) { 165 this.severity = severity; 166 } 167 168 /** 169 * Sets the ignore state for this error 170 */ 171 public String getIgnoreState() { 172 Collection<String> strings = new TreeSet<>(); 173 StringBuilder ignorestring = new StringBuilder(getIgnoreSubGroup()); 174 for (OsmPrimitive o : primitives) { 175 // ignore data not yet uploaded 176 if (o.isNew()) 177 return null; 178 String type = "u"; 179 if (o instanceof Way) { 180 type = "w"; 181 } else if (o instanceof Relation) { 182 type = "r"; 183 } else if (o instanceof Node) { 184 type = "n"; 185 } 186 strings.add(type + "_" + o.getId()); 187 } 188 for (String o : strings) { 189 ignorestring.append(":").append(o); 190 } 191 return ignorestring.toString(); 192 } 193 194 public String getIgnoreSubGroup() { 195 String ignorestring = getIgnoreGroup(); 196 if (description_en != null) { 197 ignorestring += "_" + description_en; 198 } 199 return ignorestring; 200 } 201 202 public String getIgnoreGroup() { 203 return Integer.toString(code); 204 } 205 206 public void setIgnored(boolean state) { 207 ignored = state; 208 } 209 210 public Boolean getIgnored() { 211 return ignored; 212 } 213 214 /** 215 * Gets the tester that raised this error 216 * @return the tester that raised this error 217 */ 218 public Test getTester() { 219 return tester; 220 } 221 222 public void setTester(Test tester) { 223 this.tester = tester; 224 } 225 226 /** 227 * Gets the code 228 * @return the code 229 */ 230 public int getCode() { 231 return code; 232 } 233 234 /** 235 * Returns true if the error can be fixed automatically 236 * 237 * @return true if the error can be fixed 238 */ 239 public boolean isFixable() { 240 return tester != null && tester.isFixable(this); 241 } 242 243 /** 244 * Fixes the error with the appropriate command 245 * 246 * @return The command to fix the error 247 */ 248 public Command getFix() { 249 if (tester == null || !tester.isFixable(this) || primitives.isEmpty()) 250 return null; 251 252 return tester.fixError(this); 253 } 254 255 /** 256 * Sets the selection flag of this error 257 * @param selected if this error is selected 258 */ 259 public void setSelected(boolean selected) { 260 this.selected = selected; 261 } 262 263 @SuppressWarnings("unchecked") 264 public void visitHighlighted(ValidatorVisitor v) { 265 for (Object o : highlighted) { 266 if (o instanceof OsmPrimitive) { 267 v.visit((OsmPrimitive) o); 268 } else if (o instanceof WaySegment) { 269 v.visit((WaySegment) o); 270 } else if (o instanceof List<?>) { 271 v.visit((List<Node>)o); 272 } 273 } 274 } 275 276 /** 277 * Returns the selection flag of this error 278 * @return true if this error is selected 279 * @since 5671 280 */ 281 public boolean isSelected() { 282 return selected; 283 } 284 285 /** 286 * Returns The primitives or way segments to be highlighted 287 * @return The primitives or way segments to be highlighted 288 * @since 5671 289 */ 290 public Collection<?> getHighlighted() { 291 return highlighted; 292 } 293 294 @Override 295 public int compareTo(TestError o) { 296 if (equals(o)) return 0; 297 298 MultipleNameVisitor v1 = new MultipleNameVisitor(); 299 MultipleNameVisitor v2 = new MultipleNameVisitor(); 300 301 v1.visit(getPrimitives()); 302 v2.visit(o.getPrimitives()); 303 return AlphanumComparator.getInstance().compare(v1.toString(), v2.toString()); 304 } 305 306 @Override public void primitivesRemoved(PrimitivesRemovedEvent event) { 307 // Remove purged primitives (fix #8639) 308 try { 309 primitives.removeAll(event.getPrimitives()); 310 } catch (UnsupportedOperationException e) { 311 if (event.getPrimitives().containsAll(primitives)) { 312 primitives = Collections.emptyList(); 313 } else { 314 Main.warn("Unable to remove primitives from "+this); 315 } 316 } 317 } 318 319 @Override public void primitivesAdded(PrimitivesAddedEvent event) {} 320 @Override public void tagsChanged(TagsChangedEvent event) {} 321 @Override public void nodeMoved(NodeMovedEvent event) {} 322 @Override public void wayNodesChanged(WayNodesChangedEvent event) {} 323 @Override public void relationMembersChanged(RelationMembersChangedEvent event) {} 324 @Override public void otherDatasetChange(AbstractDatasetChangedEvent event) {} 325 @Override public void dataChanged(DataChangedEvent event) {} 326 327 @Override 328 public String toString() { 329 return "TestError [tester=" + tester + ", code=" + code + ", message=" + message + "]"; 330 } 331}