1 | // License: GPL. For details, see LICENSE file.
|
---|
2 | package org.openstreetmap.josm.io;
|
---|
3 |
|
---|
4 | import java.io.BufferedWriter;
|
---|
5 | import java.io.OutputStream;
|
---|
6 | import java.io.OutputStreamWriter;
|
---|
7 | import java.io.PrintWriter;
|
---|
8 | import java.nio.charset.StandardCharsets;
|
---|
9 | import java.util.ArrayList;
|
---|
10 | import java.util.Collection;
|
---|
11 | import java.util.Date;
|
---|
12 | import java.util.HashMap;
|
---|
13 | import java.util.List;
|
---|
14 | import java.util.Map;
|
---|
15 | import java.util.Map.Entry;
|
---|
16 | import java.util.Set;
|
---|
17 | import java.util.TreeSet;
|
---|
18 | import java.util.stream.Collectors;
|
---|
19 |
|
---|
20 | import org.openstreetmap.josm.command.AddPrimitivesCommand;
|
---|
21 | import org.openstreetmap.josm.command.ChangePropertyCommand;
|
---|
22 | import org.openstreetmap.josm.command.ChangePropertyKeyCommand;
|
---|
23 | import org.openstreetmap.josm.command.Command;
|
---|
24 | import org.openstreetmap.josm.command.DeleteCommand;
|
---|
25 | import org.openstreetmap.josm.data.coor.LatLon;
|
---|
26 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
27 | import org.openstreetmap.josm.data.validation.OsmValidator;
|
---|
28 | import org.openstreetmap.josm.data.validation.Severity;
|
---|
29 | import org.openstreetmap.josm.data.validation.Test;
|
---|
30 | import org.openstreetmap.josm.data.validation.TestError;
|
---|
31 | import org.openstreetmap.josm.tools.LanguageInfo;
|
---|
32 | import org.openstreetmap.josm.tools.Logging;
|
---|
33 | import org.openstreetmap.josm.tools.date.DateUtils;
|
---|
34 |
|
---|
35 | /**
|
---|
36 | * Class to write a collection of validator errors out to XML.
|
---|
37 | * The format is inspired by the
|
---|
38 | * <a href="https://wiki.openstreetmap.org/wiki/Osmose#Issues_file_format">Osmose API issues file format</a>
|
---|
39 | * @since 12667
|
---|
40 | */
|
---|
41 | public class ValidatorErrorWriter extends XmlWriter {
|
---|
42 |
|
---|
43 | /**
|
---|
44 | * Constructs a new {@code ValidatorErrorWriter} that will write to the given {@link PrintWriter}.
|
---|
45 | * @param out PrintWriter to write XML to
|
---|
46 | */
|
---|
47 | public ValidatorErrorWriter(PrintWriter out) {
|
---|
48 | super(out);
|
---|
49 | }
|
---|
50 |
|
---|
51 | /**
|
---|
52 | * Constructs a new {@code ValidatorErrorWriter} that will write to a given {@link OutputStream}.
|
---|
53 | * @param out OutputStream to write XML to
|
---|
54 | */
|
---|
55 | public ValidatorErrorWriter(OutputStream out) {
|
---|
56 | super(new PrintWriter(new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))));
|
---|
57 | }
|
---|
58 |
|
---|
59 | /**
|
---|
60 | * Write validator errors to designated output target
|
---|
61 | * @param validationErrors Test error collection to write
|
---|
62 | */
|
---|
63 | public void write(Collection<TestError> validationErrors) {
|
---|
64 | Set<Test> analysers = validationErrors.stream().map(TestError::getTester).collect(Collectors.toCollection(TreeSet::new));
|
---|
65 | String timestamp = DateUtils.fromDate(new Date());
|
---|
66 |
|
---|
67 | out.println("<?xml version='1.0' encoding='UTF-8'?>");
|
---|
68 | out.println("<analysers generator='JOSM' timestamp='"+timestamp+"'>");
|
---|
69 |
|
---|
70 | OsmWriter osmWriter = OsmWriterFactory.createOsmWriter(out, true, OsmChangeBuilder.DEFAULT_API_VERSION);
|
---|
71 | String lang = LanguageInfo.getJOSMLocaleCode();
|
---|
72 |
|
---|
73 | for (Test test : analysers) {
|
---|
74 | out.println(" <analyser timestamp='"+timestamp+"' name='"+XmlWriter.encode(test.getName())+"'>");
|
---|
75 | // Build map of test error classes for the current test
|
---|
76 | Map<ErrorClass, List<TestError>> map = new HashMap<>();
|
---|
77 | for (Entry<Severity, Map<String, Map<String, List<TestError>>>> e1 :
|
---|
78 | OsmValidator.getErrorsBySeverityMessageDescription(validationErrors, e -> e.getTester() == test).entrySet()) {
|
---|
79 | for (Entry<String, Map<String, List<TestError>>> e2 : e1.getValue().entrySet()) {
|
---|
80 | ErrorClass errorClass = new ErrorClass(e1.getKey(), e2.getKey());
|
---|
81 | List<TestError> list = map.get(errorClass);
|
---|
82 | if (list == null) {
|
---|
83 | list = new ArrayList<>();
|
---|
84 | map.put(errorClass, list);
|
---|
85 | }
|
---|
86 | e2.getValue().values().stream().forEach(list::addAll);
|
---|
87 | }
|
---|
88 | }
|
---|
89 | // Write classes
|
---|
90 | for (ErrorClass ec : map.keySet()) {
|
---|
91 | out.println(" <class id='"+ec.id+"' level='"+ec.severity.getLevel()+"'>");
|
---|
92 | out.println(" <classtext lang='"+XmlWriter.encode(lang)+"' title='"+XmlWriter.encode(ec.message)+"'/>");
|
---|
93 | out.println(" </class>");
|
---|
94 | }
|
---|
95 |
|
---|
96 | // Write errors
|
---|
97 | for (Entry<ErrorClass, List<TestError>> entry : map.entrySet()) {
|
---|
98 | for (TestError error : entry.getValue()) {
|
---|
99 | LatLon ll = error.getPrimitives().iterator().next().getBBox().getCenter();
|
---|
100 | out.println(" <error class='"+entry.getKey().id+"'>");
|
---|
101 | out.print(" <location");
|
---|
102 | osmWriter.writeLatLon(ll);
|
---|
103 | out.println("/>");
|
---|
104 | for (OsmPrimitive p : error.getPrimitives()) {
|
---|
105 | p.accept(osmWriter);
|
---|
106 | }
|
---|
107 | out.println(" <text lang='"+XmlWriter.encode(lang)+"' value='"+XmlWriter.encode(error.getDescription())+"'/>");
|
---|
108 | if (error.isFixable()) {
|
---|
109 | out.println(" <fixes>");
|
---|
110 | Command fix = error.getFix();
|
---|
111 | if (fix instanceof AddPrimitivesCommand) {
|
---|
112 | Logging.info("TODO: {0}", fix);
|
---|
113 | } else if (fix instanceof DeleteCommand) {
|
---|
114 | Logging.info("TODO: {0}", fix);
|
---|
115 | } else if (fix instanceof ChangePropertyCommand) {
|
---|
116 | Logging.info("TODO: {0}", fix);
|
---|
117 | } else if (fix instanceof ChangePropertyKeyCommand) {
|
---|
118 | Logging.info("TODO: {0}", fix);
|
---|
119 | } else {
|
---|
120 | Logging.warn("Unsupported command type: {0}", fix);
|
---|
121 | }
|
---|
122 | out.println(" </fixes>");
|
---|
123 | }
|
---|
124 | out.println(" </error>");
|
---|
125 | }
|
---|
126 | }
|
---|
127 |
|
---|
128 | out.println(" </analyser>");
|
---|
129 | }
|
---|
130 |
|
---|
131 | out.println("</analysers>");
|
---|
132 | out.flush();
|
---|
133 | }
|
---|
134 |
|
---|
135 | private static class ErrorClass {
|
---|
136 | static int idCounter;
|
---|
137 | final Severity severity;
|
---|
138 | final String message;
|
---|
139 | final int id;
|
---|
140 |
|
---|
141 | ErrorClass(Severity severity, String message) {
|
---|
142 | this.severity = severity;
|
---|
143 | this.message = message;
|
---|
144 | this.id = ++idCounter;
|
---|
145 | }
|
---|
146 | }
|
---|
147 | }
|
---|