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