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