source: josm/trunk/src/org/openstreetmap/josm/data/validation/TestError.java@ 16445

Last change on this file since 16445 was 16445, checked in by simon04, 4 years ago

see #19251 - Java 8: use Stream

  • Property svn:eol-style set to native
File size: 17.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.validation;
3
4import java.awt.geom.Area;
5import java.awt.geom.PathIterator;
6import java.text.MessageFormat;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.List;
12import java.util.Locale;
13import java.util.TreeSet;
14import java.util.function.Supplier;
15import java.util.stream.Collectors;
16import java.util.stream.Stream;
17
18import org.openstreetmap.josm.command.Command;
19import org.openstreetmap.josm.data.coor.EastNorth;
20import org.openstreetmap.josm.data.osm.Node;
21import org.openstreetmap.josm.data.osm.OsmPrimitive;
22import org.openstreetmap.josm.data.osm.OsmUtils;
23import org.openstreetmap.josm.data.osm.Relation;
24import org.openstreetmap.josm.data.osm.Way;
25import org.openstreetmap.josm.data.osm.WaySegment;
26import org.openstreetmap.josm.data.validation.util.MultipleNameVisitor;
27import org.openstreetmap.josm.tools.AlphanumComparator;
28import org.openstreetmap.josm.tools.CheckParameterUtil;
29import org.openstreetmap.josm.tools.I18n;
30
31/**
32 * Validation error
33 * @since 3669
34 */
35public class TestError implements Comparable<TestError> {
36 /** is this error on the ignore list */
37 private boolean ignored;
38 /** Severity */
39 private final Severity severity;
40 /** The error message */
41 private final String message;
42 /** Deeper error description */
43 private final String description;
44 private final String descriptionEn;
45 /** The affected primitives */
46 private final Collection<? extends OsmPrimitive> primitives;
47 /** The primitives or way segments to be highlighted */
48 private final Collection<?> highlighted;
49 /** The tester that raised this error */
50 private final Test tester;
51 /** Internal code used by testers to classify errors */
52 private final int code;
53 /** If this error is selected */
54 private boolean selected;
55 /** Supplying a command to fix the error */
56 private final Supplier<Command> fixingCommand;
57
58 /**
59 * A builder for a {@code TestError}.
60 * @since 11129
61 */
62 public static final class Builder {
63 private final Test tester;
64 private final Severity severity;
65 private final int code;
66 private String message;
67 private String description;
68 private String descriptionEn;
69 private Collection<? extends OsmPrimitive> primitives;
70 private Collection<?> highlighted;
71 private Supplier<Command> fixingCommand;
72
73 Builder(Test tester, Severity severity, int code) {
74 this.tester = tester;
75 this.severity = severity;
76 this.code = code;
77 }
78
79 /**
80 * Sets the error message.
81 *
82 * @param message The error message
83 * @return {@code this}
84 */
85 public Builder message(String message) {
86 this.message = message;
87 return this;
88 }
89
90 /**
91 * Sets the error message.
92 *
93 * @param message The message of this error group
94 * @param description The translated description of this error
95 * @param descriptionEn The English description (for ignoring errors)
96 * @return {@code this}
97 */
98 public Builder messageWithManuallyTranslatedDescription(String message, String description, String descriptionEn) {
99 this.message = message;
100 this.description = description;
101 this.descriptionEn = descriptionEn;
102 return this;
103 }
104
105 /**
106 * Sets the error message.
107 *
108 * @param message The the message of this error group
109 * @param marktrDescription The {@linkplain I18n#marktr prepared for i18n} description of this error
110 * @param args The description arguments to be applied in {@link I18n#tr(String, Object...)}
111 * @return {@code this}
112 */
113 public Builder message(String message, String marktrDescription, Object... args) {
114 this.message = message;
115 this.description = I18n.tr(marktrDescription, args);
116 this.descriptionEn = new MessageFormat(marktrDescription, Locale.ENGLISH).format(args);
117 return this;
118 }
119
120 /**
121 * Sets the primitives affected by this error.
122 *
123 * @param primitives the primitives affected by this error
124 * @return {@code this}
125 */
126 public Builder primitives(OsmPrimitive... primitives) {
127 return primitives(Arrays.asList(primitives));
128 }
129
130 /**
131 * Sets the primitives affected by this error.
132 *
133 * @param primitives the primitives affected by this error
134 * @return {@code this}
135 */
136 public Builder primitives(Collection<? extends OsmPrimitive> primitives) {
137 CheckParameterUtil.ensureThat(this.primitives == null, "primitives already set");
138 CheckParameterUtil.ensureParameterNotNull(primitives, "primitives");
139 this.primitives = primitives;
140 if (this.highlighted == null) {
141 this.highlighted = primitives;
142 }
143 return this;
144 }
145
146 /**
147 * Sets the primitives to highlight when selecting this error.
148 *
149 * @param highlighted the primitives to highlight
150 * @return {@code this}
151 * @see ValidatorVisitor#visit(OsmPrimitive)
152 */
153 public Builder highlight(OsmPrimitive... highlighted) {
154 return highlight(Arrays.asList(highlighted));
155 }
156
157 /**
158 * Sets the primitives to highlight when selecting this error.
159 *
160 * @param highlighted the primitives to highlight
161 * @return {@code this}
162 * @see ValidatorVisitor#visit(OsmPrimitive)
163 */
164 public Builder highlight(Collection<? extends OsmPrimitive> highlighted) {
165 CheckParameterUtil.ensureParameterNotNull(highlighted, "highlighted");
166 this.highlighted = highlighted;
167 return this;
168 }
169
170 /**
171 * Sets the way segments to highlight when selecting this error.
172 *
173 * @param highlighted the way segments to highlight
174 * @return {@code this}
175 * @see ValidatorVisitor#visit(WaySegment)
176 */
177 public Builder highlightWaySegments(Collection<WaySegment> highlighted) {
178 CheckParameterUtil.ensureParameterNotNull(highlighted, "highlighted");
179 this.highlighted = highlighted;
180 return this;
181 }
182
183 /**
184 * Sets the node pairs to highlight when selecting this error.
185 *
186 * @param highlighted the node pairs to highlight
187 * @return {@code this}
188 * @see ValidatorVisitor#visit(List)
189 */
190 public Builder highlightNodePairs(Collection<List<Node>> highlighted) {
191 CheckParameterUtil.ensureParameterNotNull(highlighted, "highlighted");
192 this.highlighted = highlighted;
193 return this;
194 }
195
196 /**
197 * Sets an area to highlight when selecting this error.
198 *
199 * @param highlighted the area to highlight
200 * @return {@code this}
201 */
202 public Builder highlight(Area highlighted) {
203 CheckParameterUtil.ensureParameterNotNull(highlighted, "highlighted");
204 this.highlighted = Collections.singleton(highlighted);
205 return this;
206 }
207
208 /**
209 * Sets a supplier to obtain a command to fix the error.
210 *
211 * @param fixingCommand the fix supplier. Can be null
212 * @return {@code this}
213 */
214 public Builder fix(Supplier<Command> fixingCommand) {
215 CheckParameterUtil.ensureThat(this.fixingCommand == null, "fixingCommand already set");
216 this.fixingCommand = fixingCommand;
217 return this;
218 }
219
220 /**
221 * Returns a new test error with the specified values
222 *
223 * @return a new test error with the specified values
224 * @throws IllegalArgumentException when {@link #message} or {@link #primitives} is null.
225 */
226 public TestError build() {
227 CheckParameterUtil.ensureParameterNotNull(message, "message not set");
228 CheckParameterUtil.ensureParameterNotNull(primitives, "primitives not set");
229 if (this.highlighted == null) {
230 this.highlighted = Collections.emptySet();
231 }
232 return new TestError(this);
233 }
234 }
235
236 /**
237 * Starts building a new {@code TestError}
238 * @param tester The tester
239 * @param severity The severity of this error
240 * @param code The test error reference code
241 * @return a new test builder
242 * @since 11129
243 */
244 public static Builder builder(Test tester, Severity severity, int code) {
245 return new Builder(tester, severity, code);
246 }
247
248 TestError(Builder builder) {
249 this.tester = builder.tester;
250 this.severity = builder.severity;
251 this.message = builder.message;
252 this.description = builder.description;
253 this.descriptionEn = builder.descriptionEn;
254 this.primitives = builder.primitives;
255 this.highlighted = builder.highlighted;
256 this.code = builder.code;
257 this.fixingCommand = builder.fixingCommand;
258 }
259
260 /**
261 * Gets the error message
262 * @return the error message
263 */
264 public String getMessage() {
265 return message;
266 }
267
268 /**
269 * Gets the error message
270 * @return the error description
271 */
272 public String getDescription() {
273 return description;
274 }
275
276 /**
277 * Gets the list of primitives affected by this error
278 * @return the list of primitives affected by this error
279 */
280 public Collection<? extends OsmPrimitive> getPrimitives() {
281 return Collections.unmodifiableCollection(primitives);
282 }
283
284 /**
285 * Gets all primitives of the given type affected by this error
286 * @param type restrict primitives to subclasses
287 * @param <T> type of primitives
288 * @return the primitives as Stream
289 */
290 public final <T extends OsmPrimitive> Stream<T> primitives(Class<T> type) {
291 return primitives.stream()
292 .filter(type::isInstance)
293 .map(type::cast);
294 }
295
296 /**
297 * Gets the severity of this error
298 * @return the severity of this error
299 */
300 public Severity getSeverity() {
301 return severity;
302 }
303
304 /**
305 * Returns the ignore state for this error.
306 * @return the ignore state for this error or null if any primitive is new
307 */
308 public String getIgnoreState() {
309 Collection<String> strings = new TreeSet<>();
310 for (OsmPrimitive o : primitives) {
311 // ignore data not yet uploaded
312 if (o.isNew())
313 return null;
314 String type = "u";
315 if (o instanceof Way) {
316 type = "w";
317 } else if (o instanceof Relation) {
318 type = "r";
319 } else if (o instanceof Node) {
320 type = "n";
321 }
322 strings.add(type + '_' + o.getId());
323 }
324 return strings.stream().map(o -> ':' + o).collect(Collectors.joining("", getIgnoreSubGroup(), ""));
325 }
326
327 /**
328 * Check if this error matches an entry in the ignore list and
329 * set the ignored flag if it is.
330 * @return the new ignored state
331 */
332 public boolean updateIgnored() {
333 setIgnored(calcIgnored());
334 return isIgnored();
335 }
336
337 private boolean calcIgnored() {
338 if (OsmValidator.hasIgnoredError(getIgnoreGroup()))
339 return true;
340 if (OsmValidator.hasIgnoredError(getIgnoreSubGroup()))
341 return true;
342 String state = getIgnoreState();
343 return state != null && OsmValidator.hasIgnoredError(state);
344 }
345
346 /**
347 * Gets the ignores subgroup that is more specialized than {@link #getIgnoreGroup()}
348 * @return The ignore sub group
349 */
350 public String getIgnoreSubGroup() {
351 if (code == 3000) {
352 // see #19053
353 return "3000_" + (description == null ? message : description);
354 }
355 String ignorestring = getIgnoreGroup();
356 if (descriptionEn != null) {
357 ignorestring += '_' + descriptionEn;
358 }
359 return ignorestring;
360 }
361
362 /**
363 * Gets the ignore group ID that is used to allow the user to ignore all same errors
364 * @return The group id
365 * @see TestError#getIgnoreSubGroup()
366 */
367 public String getIgnoreGroup() {
368 if (code == 3000) {
369 // see #19053
370 return "3000_" + getMessage();
371 }
372 return Integer.toString(code);
373 }
374
375 /**
376 * Flags this error as ignored
377 * @param state The ignore flag
378 */
379 public void setIgnored(boolean state) {
380 ignored = state;
381 }
382
383 /**
384 * Checks if this error is ignored
385 * @return <code>true</code> if it is ignored
386 */
387 public boolean isIgnored() {
388 return ignored;
389 }
390
391 /**
392 * Gets the tester that raised this error
393 * @return the tester that raised this error
394 */
395 public Test getTester() {
396 return tester;
397 }
398
399 /**
400 * Gets the code
401 * @return the code
402 */
403 public int getCode() {
404 return code;
405 }
406
407 /**
408 * Returns true if the error can be fixed automatically
409 *
410 * @return true if the error can be fixed
411 */
412 public boolean isFixable() {
413 return (fixingCommand != null || ((tester != null) && tester.isFixable(this)))
414 && OsmUtils.isOsmCollectionEditable(primitives);
415 }
416
417 /**
418 * Fixes the error with the appropriate command
419 *
420 * @return The command to fix the error
421 */
422 public Command getFix() {
423 // obtain fix from the error
424 final Command fix = fixingCommand != null ? fixingCommand.get() : null;
425 if (fix != null) {
426 return fix;
427 }
428
429 // obtain fix from the tester
430 if (tester == null || !tester.isFixable(this) || primitives.isEmpty())
431 return null;
432
433 return tester.fixError(this);
434 }
435
436 /**
437 * Sets the selection flag of this error
438 * @param selected if this error is selected
439 */
440 public void setSelected(boolean selected) {
441 this.selected = selected;
442 }
443
444 /**
445 * Visits all highlighted validation elements
446 * @param v The visitor that should receive a visit-notification on all highlighted elements
447 */
448 @SuppressWarnings("unchecked")
449 public void visitHighlighted(ValidatorVisitor v) {
450 for (Object o : highlighted) {
451 if (o instanceof OsmPrimitive) {
452 v.visit((OsmPrimitive) o);
453 } else if (o instanceof WaySegment) {
454 v.visit((WaySegment) o);
455 } else if (o instanceof List<?>) {
456 v.visit((List<Node>) o);
457 } else if (o instanceof Area) {
458 for (List<Node> l : getHiliteNodesForArea((Area) o)) {
459 v.visit(l);
460 }
461 }
462 }
463 }
464
465 /**
466 * Calculate list of node pairs describing the area.
467 * @param area the area
468 * @return list of node pairs describing the area
469 */
470 private static List<List<Node>> getHiliteNodesForArea(Area area) {
471 List<List<Node>> hilite = new ArrayList<>();
472 PathIterator pit = area.getPathIterator(null);
473 double[] res = new double[6];
474 List<Node> nodes = new ArrayList<>();
475 while (!pit.isDone()) {
476 int type = pit.currentSegment(res);
477 Node n = new Node(new EastNorth(res[0], res[1]));
478 switch (type) {
479 case PathIterator.SEG_MOVETO:
480 if (!nodes.isEmpty()) {
481 hilite.add(nodes);
482 }
483 nodes = new ArrayList<>();
484 nodes.add(n);
485 break;
486 case PathIterator.SEG_LINETO:
487 nodes.add(n);
488 break;
489 case PathIterator.SEG_CLOSE:
490 if (!nodes.isEmpty()) {
491 nodes.add(nodes.get(0));
492 hilite.add(nodes);
493 nodes = new ArrayList<>();
494 }
495 break;
496 default:
497 break;
498 }
499 pit.next();
500 }
501 if (nodes.size() > 1) {
502 hilite.add(nodes);
503 }
504 return hilite;
505 }
506
507 /**
508 * Returns the selection flag of this error
509 * @return true if this error is selected
510 * @since 5671
511 */
512 public boolean isSelected() {
513 return selected;
514 }
515
516 /**
517 * Returns The primitives or way segments to be highlighted
518 * @return The primitives or way segments to be highlighted
519 * @since 5671
520 */
521 public Collection<?> getHighlighted() {
522 return Collections.unmodifiableCollection(highlighted);
523 }
524
525 @Override
526 public int compareTo(TestError o) {
527 if (equals(o)) return 0;
528
529 return AlphanumComparator.getInstance().compare(getNameVisitor().toString(), o.getNameVisitor().toString());
530 }
531
532 /**
533 * @return Name visitor (used in cell renderer and for sorting)
534 */
535 public MultipleNameVisitor getNameVisitor() {
536 MultipleNameVisitor v = new MultipleNameVisitor();
537 v.visit(getPrimitives());
538 return v;
539 }
540
541 @Override
542 public String toString() {
543 return "TestError [tester=" + tester + ", code=" + code + ", message=" + message + ']';
544 }
545
546}
Note: See TracBrowser for help on using the repository browser.