[12667] | 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 |
|
---|
[12692] | 67 | out.println("<?xml version='1.0' encoding='UTF-8'?>");
|
---|
| 68 | out.println("<analysers generator='JOSM' timestamp='"+timestamp+"'>");
|
---|
[12667] | 69 |
|
---|
| 70 | OsmWriter osmWriter = OsmWriterFactory.createOsmWriter(out, true, OsmChangeBuilder.DEFAULT_API_VERSION);
|
---|
| 71 | String lang = LanguageInfo.getJOSMLocaleCode();
|
---|
| 72 |
|
---|
| 73 | for (Test test : analysers) {
|
---|
[12692] | 74 | out.println(" <analyser timestamp='"+timestamp+"' name='"+XmlWriter.encode(test.getName())+"'>");
|
---|
[12667] | 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()) {
|
---|
[12692] | 91 | out.println(" <class id='"+ec.id+"' level='"+ec.severity.getLevel()+"'>");
|
---|
| 92 | out.println(" <classtext lang='"+XmlWriter.encode(lang)+"' title='"+XmlWriter.encode(ec.message)+"'/>");
|
---|
[12667] | 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();
|
---|
[12692] | 100 | out.println(" <error class='"+entry.getKey().id+"'>");
|
---|
| 101 | out.print(" <location");
|
---|
| 102 | osmWriter.writeLatLon(ll);
|
---|
[12699] | 103 | out.println("/>");
|
---|
[12667] | 104 | for (OsmPrimitive p : error.getPrimitives()) {
|
---|
| 105 | p.accept(osmWriter);
|
---|
| 106 | }
|
---|
[12698] | 107 | out.println(" <text lang='"+XmlWriter.encode(lang)+"' value='"+XmlWriter.encode(error.getDescription())+"'/>");
|
---|
[12667] | 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 {
|
---|
[12668] | 136 | static int idCounter;
|
---|
[12667] | 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 | }
|
---|