[2500] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
| 2 | package org.openstreetmap.josm.data.osm;
|
---|
| 3 |
|
---|
[7500] | 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 5 |
|
---|
[2500] | 6 | import java.io.PrintWriter;
|
---|
| 7 | import java.io.StringWriter;
|
---|
| 8 | import java.io.Writer;
|
---|
| 9 |
|
---|
[7500] | 10 | import org.openstreetmap.josm.Main;
|
---|
[11746] | 11 | import org.openstreetmap.josm.tools.JosmRuntimeException;
|
---|
[7500] | 12 | import org.openstreetmap.josm.tools.Utils;
|
---|
[2500] | 13 |
|
---|
| 14 | /**
|
---|
[7500] | 15 | * This class can be used to run consistency tests on dataset. Any errors found will be written to provided PrintWriter.
|
---|
[2500] | 16 | * <br>
|
---|
[7500] | 17 | * Texts here should not be translated because they're not intended for users but for josm developers.
|
---|
| 18 | * @since 2500
|
---|
[2500] | 19 | */
|
---|
| 20 | public class DatasetConsistencyTest {
|
---|
| 21 |
|
---|
[2958] | 22 | private static final int MAX_ERRORS = 100;
|
---|
[2500] | 23 | private final DataSet dataSet;
|
---|
| 24 | private final PrintWriter writer;
|
---|
[2958] | 25 | private int errorCount;
|
---|
[2500] | 26 |
|
---|
[7500] | 27 | /**
|
---|
| 28 | * Constructs a new {@code DatasetConsistencyTest}.
|
---|
| 29 | * @param dataSet The dataset to test
|
---|
| 30 | * @param writer The writer used to write results
|
---|
| 31 | */
|
---|
[2500] | 32 | public DatasetConsistencyTest(DataSet dataSet, Writer writer) {
|
---|
| 33 | this.dataSet = dataSet;
|
---|
| 34 | this.writer = new PrintWriter(writer);
|
---|
| 35 | }
|
---|
| 36 |
|
---|
[2925] | 37 | private void printError(String type, String message, Object... args) {
|
---|
[2958] | 38 | errorCount++;
|
---|
| 39 | if (errorCount <= MAX_ERRORS) {
|
---|
[8846] | 40 | writer.println('[' + type + "] " + String.format(message, args));
|
---|
[2958] | 41 | }
|
---|
[2925] | 42 | }
|
---|
| 43 |
|
---|
[7500] | 44 | /**
|
---|
| 45 | * Checks that parent primitive is referred from its child members
|
---|
| 46 | */
|
---|
[2958] | 47 | public void checkReferrers() {
|
---|
[7500] | 48 | long startTime = System.currentTimeMillis();
|
---|
[3274] | 49 | // It's also error when referred primitive's dataset is null but it's already covered by referredPrimitiveNotInDataset check
|
---|
[7500] | 50 | for (Way way : dataSet.getWays()) {
|
---|
[2521] | 51 | if (!way.isDeleted()) {
|
---|
[7500] | 52 | for (Node n : way.getNodes()) {
|
---|
[3274] | 53 | if (n.getDataSet() != null && !n.getReferrers().contains(way)) {
|
---|
[2925] | 54 | printError("WAY NOT IN REFERRERS", "%s is part of %s but is not in referrers", n, way);
|
---|
[2521] | 55 | }
|
---|
[2500] | 56 | }
|
---|
| 57 | }
|
---|
| 58 | }
|
---|
| 59 |
|
---|
[7500] | 60 | for (Relation relation : dataSet.getRelations()) {
|
---|
[2521] | 61 | if (!relation.isDeleted()) {
|
---|
[7500] | 62 | for (RelationMember m : relation.getMembers()) {
|
---|
[3274] | 63 | if (m.getMember().getDataSet() != null && !m.getMember().getReferrers().contains(relation)) {
|
---|
[2925] | 64 | printError("RELATION NOT IN REFERRERS", "%s is part of %s but is not in referrers", m.getMember(), relation);
|
---|
[2521] | 65 | }
|
---|
[2500] | 66 | }
|
---|
| 67 | }
|
---|
| 68 | }
|
---|
[7500] | 69 | printElapsedTime(startTime);
|
---|
[2500] | 70 | }
|
---|
| 71 |
|
---|
[7500] | 72 | /**
|
---|
| 73 | * Checks for womplete ways with incomplete nodes.
|
---|
| 74 | */
|
---|
[2958] | 75 | public void checkCompleteWaysWithIncompleteNodes() {
|
---|
[7500] | 76 | long startTime = System.currentTimeMillis();
|
---|
| 77 | for (Way way : dataSet.getWays()) {
|
---|
[2594] | 78 | if (way.isUsable()) {
|
---|
[7500] | 79 | for (Node node : way.getNodes()) {
|
---|
[2578] | 80 | if (node.isIncomplete()) {
|
---|
[2925] | 81 | printError("USABLE HAS INCOMPLETE", "%s is usable but contains incomplete node '%s'", way, node);
|
---|
[2500] | 82 | }
|
---|
| 83 | }
|
---|
| 84 | }
|
---|
| 85 | }
|
---|
[7500] | 86 | printElapsedTime(startTime);
|
---|
[2500] | 87 | }
|
---|
| 88 |
|
---|
[7500] | 89 | /**
|
---|
| 90 | * Checks for complete nodes without coordinates.
|
---|
| 91 | */
|
---|
[2958] | 92 | public void checkCompleteNodesWithoutCoordinates() {
|
---|
[7500] | 93 | long startTime = System.currentTimeMillis();
|
---|
| 94 | for (Node node : dataSet.getNodes()) {
|
---|
[7828] | 95 | if (!node.isIncomplete() && node.isVisible() && !node.isLatLonKnown()) {
|
---|
[2925] | 96 | printError("COMPLETE WITHOUT COORDINATES", "%s is not incomplete but has null coordinates", node);
|
---|
[2500] | 97 | }
|
---|
| 98 | }
|
---|
[7500] | 99 | printElapsedTime(startTime);
|
---|
[2500] | 100 | }
|
---|
| 101 |
|
---|
[7500] | 102 | /**
|
---|
| 103 | * Checks that nodes can be retrieved through their coordinates.
|
---|
| 104 | */
|
---|
[2958] | 105 | public void searchNodes() {
|
---|
[7500] | 106 | long startTime = System.currentTimeMillis();
|
---|
[7501] | 107 | dataSet.getReadLock().lock();
|
---|
| 108 | try {
|
---|
| 109 | for (Node n : dataSet.getNodes()) {
|
---|
| 110 | // Call isDrawable() as an efficient replacement to previous checks (!deleted, !incomplete, getCoor() != null)
|
---|
| 111 | if (n.isDrawable() && !dataSet.containsNode(n)) {
|
---|
[7502] | 112 | printError("SEARCH NODES", "%s not found using Dataset.containsNode()", n);
|
---|
[2500] | 113 | }
|
---|
| 114 | }
|
---|
[7501] | 115 | } finally {
|
---|
| 116 | dataSet.getReadLock().unlock();
|
---|
[2500] | 117 | }
|
---|
[7500] | 118 | printElapsedTime(startTime);
|
---|
[2500] | 119 | }
|
---|
| 120 |
|
---|
[7500] | 121 | /**
|
---|
| 122 | * Checks that ways can be retrieved through their bounding box.
|
---|
| 123 | */
|
---|
[2958] | 124 | public void searchWays() {
|
---|
[7500] | 125 | long startTime = System.currentTimeMillis();
|
---|
[7501] | 126 | dataSet.getReadLock().lock();
|
---|
| 127 | try {
|
---|
| 128 | for (Way w : dataSet.getWays()) {
|
---|
| 129 | if (!w.isIncomplete() && !w.isDeleted() && w.getNodesCount() >= 2 && !dataSet.containsWay(w)) {
|
---|
[7502] | 130 | printError("SEARCH WAYS", "%s not found using Dataset.containsWay()", w);
|
---|
[7501] | 131 | }
|
---|
[2500] | 132 | }
|
---|
[7501] | 133 | } finally {
|
---|
| 134 | dataSet.getReadLock().unlock();
|
---|
[2500] | 135 | }
|
---|
[7500] | 136 | printElapsedTime(startTime);
|
---|
[2500] | 137 | }
|
---|
| 138 |
|
---|
[2501] | 139 | private void checkReferredPrimitive(OsmPrimitive primitive, OsmPrimitive parent) {
|
---|
[3274] | 140 | if (primitive.getDataSet() == null) {
|
---|
| 141 | printError("NO DATASET", "%s is referenced by %s but not found in dataset", primitive, parent);
|
---|
| 142 | } else if (dataSet.getPrimitiveById(primitive) == null) {
|
---|
[2925] | 143 | printError("REFERENCED BUT NOT IN DATA", "%s is referenced by %s but not found in dataset", primitive, parent);
|
---|
[10378] | 144 | } else if (dataSet.getPrimitiveById(primitive) != primitive) {
|
---|
[2925] | 145 | printError("DIFFERENT INSTANCE", "%s is different instance that referred by %s", primitive, parent);
|
---|
[2501] | 146 | }
|
---|
[3274] | 147 |
|
---|
[2501] | 148 | if (primitive.isDeleted()) {
|
---|
[2925] | 149 | printError("DELETED REFERENCED", "%s refers to deleted primitive %s", parent, primitive);
|
---|
[2501] | 150 | }
|
---|
| 151 | }
|
---|
| 152 |
|
---|
[7500] | 153 | /**
|
---|
| 154 | * Checks that referred primitives are present in dataset.
|
---|
| 155 | */
|
---|
[2958] | 156 | public void referredPrimitiveNotInDataset() {
|
---|
[7500] | 157 | long startTime = System.currentTimeMillis();
|
---|
| 158 | for (Way way : dataSet.getWays()) {
|
---|
| 159 | for (Node node : way.getNodes()) {
|
---|
[2501] | 160 | checkReferredPrimitive(node, way);
|
---|
| 161 | }
|
---|
| 162 | }
|
---|
| 163 |
|
---|
[7500] | 164 | for (Relation relation : dataSet.getRelations()) {
|
---|
| 165 | for (RelationMember member : relation.getMembers()) {
|
---|
[2501] | 166 | checkReferredPrimitive(member.getMember(), relation);
|
---|
| 167 | }
|
---|
| 168 | }
|
---|
[7500] | 169 | printElapsedTime(startTime);
|
---|
[2501] | 170 | }
|
---|
| 171 |
|
---|
[7500] | 172 | /**
|
---|
| 173 | * Checks for zero and one-node ways.
|
---|
| 174 | */
|
---|
[2958] | 175 | public void checkZeroNodesWays() {
|
---|
[7500] | 176 | long startTime = System.currentTimeMillis();
|
---|
| 177 | for (Way way : dataSet.getWays()) {
|
---|
[3336] | 178 | if (way.isUsable() && way.getNodesCount() == 0) {
|
---|
[2925] | 179 | printError("WARN - ZERO NODES", "Way %s has zero nodes", way);
|
---|
[2527] | 180 | } else if (way.isUsable() && way.getNodesCount() == 1) {
|
---|
[2925] | 181 | printError("WARN - NO NODES", "Way %s has only one node", way);
|
---|
[2527] | 182 | }
|
---|
| 183 | }
|
---|
[7500] | 184 | printElapsedTime(startTime);
|
---|
[2527] | 185 | }
|
---|
| 186 |
|
---|
[7500] | 187 | private void printElapsedTime(long startTime) {
|
---|
| 188 | if (Main.isDebugEnabled()) {
|
---|
| 189 | StackTraceElement item = Thread.currentThread().getStackTrace()[2];
|
---|
[8846] | 190 | String operation = getClass().getSimpleName() + '.' + item.getMethodName();
|
---|
[7500] | 191 | long elapsedTime = System.currentTimeMillis() - startTime;
|
---|
| 192 | Main.debug(tr("Test ''{0}'' completed in {1}",
|
---|
| 193 | operation, Utils.getDurationString(elapsedTime)));
|
---|
| 194 | }
|
---|
| 195 | }
|
---|
| 196 |
|
---|
| 197 | /**
|
---|
| 198 | * Runs test.
|
---|
| 199 | */
|
---|
[2500] | 200 | public void runTest() {
|
---|
[2501] | 201 | try {
|
---|
[7500] | 202 | long startTime = System.currentTimeMillis();
|
---|
[3274] | 203 | referredPrimitiveNotInDataset();
|
---|
[2501] | 204 | checkReferrers();
|
---|
| 205 | checkCompleteWaysWithIncompleteNodes();
|
---|
| 206 | checkCompleteNodesWithoutCoordinates();
|
---|
| 207 | searchNodes();
|
---|
| 208 | searchWays();
|
---|
[2527] | 209 | checkZeroNodesWays();
|
---|
[7500] | 210 | printElapsedTime(startTime);
|
---|
[2958] | 211 | if (errorCount > MAX_ERRORS) {
|
---|
| 212 | writer.println((errorCount - MAX_ERRORS) + " more...");
|
---|
| 213 | }
|
---|
[7500] | 214 |
|
---|
[11746] | 215 | } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) {
|
---|
[2501] | 216 | writer.println("Exception during dataset integrity test:");
|
---|
| 217 | e.printStackTrace(writer);
|
---|
[10627] | 218 | Main.warn(e);
|
---|
[2501] | 219 | }
|
---|
[2500] | 220 | }
|
---|
| 221 |
|
---|
[7500] | 222 | /**
|
---|
| 223 | * Runs test on the given dataset.
|
---|
| 224 | * @param dataSet the dataset to test
|
---|
| 225 | * @return the errors as string
|
---|
| 226 | */
|
---|
[2500] | 227 | public static String runTests(DataSet dataSet) {
|
---|
| 228 | StringWriter writer = new StringWriter();
|
---|
| 229 | new DatasetConsistencyTest(dataSet, writer).runTest();
|
---|
| 230 | return writer.toString();
|
---|
| 231 | }
|
---|
| 232 | }
|
---|