001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.osm; 003 004import java.io.PrintWriter; 005import java.io.StringWriter; 006import java.io.Writer; 007 008import org.openstreetmap.josm.tools.JosmRuntimeException; 009import org.openstreetmap.josm.tools.Logging; 010import org.openstreetmap.josm.tools.Stopwatch; 011 012/** 013 * This class can be used to run consistency tests on dataset. Any errors found will be written to provided PrintWriter. 014 * <br> 015 * Texts here should not be translated because they're not intended for users but for josm developers. 016 * @since 2500 017 */ 018public class DatasetConsistencyTest { 019 020 private static final int MAX_ERRORS = 100; 021 private final DataSet dataSet; 022 private final PrintWriter writer; 023 private int errorCount; 024 025 /** 026 * Constructs a new {@code DatasetConsistencyTest}. 027 * @param dataSet The dataset to test 028 * @param writer The writer used to write results 029 */ 030 public DatasetConsistencyTest(DataSet dataSet, Writer writer) { 031 this.dataSet = dataSet; 032 this.writer = new PrintWriter(writer); 033 } 034 035 private void printError(String type, String message, Object... args) { 036 errorCount++; 037 if (errorCount <= MAX_ERRORS) { 038 writer.println('[' + type + "] " + String.format(message, args)); 039 } 040 } 041 042 /** 043 * Checks that parent primitive is referred from its child members 044 */ 045 public void checkReferrers() { 046 final Stopwatch stopwatch = Stopwatch.createStarted(); 047 // It's also error when referred primitive's dataset is null but it's already covered by referredPrimitiveNotInDataset check 048 for (Way way : dataSet.getWays()) { 049 if (!way.isDeleted()) { 050 for (Node n : way.getNodes()) { 051 if (n.getDataSet() != null && !n.getReferrers().contains(way)) { 052 printError("WAY NOT IN REFERRERS", "%s is part of %s but is not in referrers", n, way); 053 } 054 } 055 } 056 } 057 058 for (Relation relation : dataSet.getRelations()) { 059 if (!relation.isDeleted()) { 060 for (RelationMember m : relation.getMembers()) { 061 if (m.getMember().getDataSet() != null && !m.getMember().getReferrers().contains(relation)) { 062 printError("RELATION NOT IN REFERRERS", "%s is part of %s but is not in referrers", m.getMember(), relation); 063 } 064 } 065 } 066 } 067 printElapsedTime(stopwatch); 068 } 069 070 /** 071 * Checks for complete ways with incomplete nodes. 072 */ 073 public void checkCompleteWaysWithIncompleteNodes() { 074 final Stopwatch stopwatch = Stopwatch.createStarted(); 075 for (Way way : dataSet.getWays()) { 076 if (way.isUsable()) { 077 for (Node node : way.getNodes()) { 078 if (node.isIncomplete()) { 079 printError("USABLE HAS INCOMPLETE", "%s is usable but contains incomplete node '%s'", way, node); 080 } 081 } 082 } 083 } 084 printElapsedTime(stopwatch); 085 } 086 087 /** 088 * Checks for complete nodes without coordinates. 089 */ 090 public void checkCompleteNodesWithoutCoordinates() { 091 final Stopwatch stopwatch = Stopwatch.createStarted(); 092 for (Node node : dataSet.getNodes()) { 093 if (!node.isIncomplete() && node.isVisible() && !node.isLatLonKnown()) { 094 printError("COMPLETE WITHOUT COORDINATES", "%s is not incomplete but has null coordinates", node); 095 } 096 } 097 printElapsedTime(stopwatch); 098 } 099 100 /** 101 * Checks that nodes can be retrieved through their coordinates. 102 */ 103 public void searchNodes() { 104 final Stopwatch stopwatch = Stopwatch.createStarted(); 105 dataSet.getReadLock().lock(); 106 try { 107 for (Node n : dataSet.getNodes()) { 108 // Call isDrawable() as an efficient replacement to previous checks (!deleted, !incomplete, getCoor() != null) 109 if (n.isDrawable() && !dataSet.containsNode(n)) { 110 printError("SEARCH NODES", "%s not found using Dataset.containsNode()", n); 111 } 112 } 113 } finally { 114 dataSet.getReadLock().unlock(); 115 } 116 printElapsedTime(stopwatch); 117 } 118 119 /** 120 * Checks that ways can be retrieved through their bounding box. 121 */ 122 public void searchWays() { 123 final Stopwatch stopwatch = Stopwatch.createStarted(); 124 dataSet.getReadLock().lock(); 125 try { 126 for (Way w : dataSet.getWays()) { 127 if (!w.isIncomplete() && !w.isDeleted() && w.getNodesCount() >= 2 && !dataSet.containsWay(w)) { 128 printError("SEARCH WAYS", "%s not found using Dataset.containsWay()", w); 129 } 130 } 131 } finally { 132 dataSet.getReadLock().unlock(); 133 } 134 printElapsedTime(stopwatch); 135 } 136 137 private void checkReferredPrimitive(OsmPrimitive primitive, OsmPrimitive parent) { 138 if (primitive.getDataSet() == null) { 139 printError("NO DATASET", "%s is referenced by %s but not found in dataset", primitive, parent); 140 } else if (dataSet.getPrimitiveById(primitive) == null) { 141 printError("REFERENCED BUT NOT IN DATA", "%s is referenced by %s but not found in dataset", primitive, parent); 142 } else if (dataSet.getPrimitiveById(primitive) != primitive) { 143 printError("DIFFERENT INSTANCE", "%s is different instance that referred by %s", primitive, parent); 144 } 145 146 if (primitive.isDeleted()) { 147 printError("DELETED REFERENCED", "%s refers to deleted primitive %s", parent, primitive); 148 } 149 } 150 151 /** 152 * Checks that referred primitives are present in dataset. 153 */ 154 public void referredPrimitiveNotInDataset() { 155 final Stopwatch stopwatch = Stopwatch.createStarted(); 156 for (Way way : dataSet.getWays()) { 157 for (Node node : way.getNodes()) { 158 checkReferredPrimitive(node, way); 159 } 160 } 161 162 for (Relation relation : dataSet.getRelations()) { 163 for (RelationMember member : relation.getMembers()) { 164 checkReferredPrimitive(member.getMember(), relation); 165 } 166 } 167 printElapsedTime(stopwatch); 168 } 169 170 /** 171 * Checks for zero and one-node ways. 172 */ 173 public void checkZeroNodesWays() { 174 final Stopwatch stopwatch = Stopwatch.createStarted(); 175 for (Way way : dataSet.getWays()) { 176 if (way.isUsable() && way.isEmpty()) { 177 printError("WARN - ZERO NODES", "Way %s has zero nodes", way); 178 } else if (way.isUsable() && way.getNodesCount() == 1) { 179 printError("WARN - NO NODES", "Way %s has only one node", way); 180 } 181 } 182 printElapsedTime(stopwatch); 183 } 184 185 private void printElapsedTime(Stopwatch stopwatch) { 186 if (Logging.isDebugEnabled()) { 187 StackTraceElement item = Thread.currentThread().getStackTrace()[2]; 188 String operation = getClass().getSimpleName() + '.' + item.getMethodName(); 189 Logging.debug(stopwatch.toString(operation)); 190 } 191 } 192 193 /** 194 * Runs test. 195 */ 196 public void runTest() { 197 try { 198 final Stopwatch stopwatch = Stopwatch.createStarted(); 199 referredPrimitiveNotInDataset(); 200 checkReferrers(); 201 checkCompleteWaysWithIncompleteNodes(); 202 checkCompleteNodesWithoutCoordinates(); 203 searchNodes(); 204 searchWays(); 205 checkZeroNodesWays(); 206 printElapsedTime(stopwatch); 207 if (errorCount > MAX_ERRORS) { 208 writer.println((errorCount - MAX_ERRORS) + " more..."); 209 } 210 211 } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) { 212 writer.println("Exception during dataset integrity test:"); 213 e.printStackTrace(writer); 214 Logging.warn(e); 215 } 216 } 217 218 /** 219 * Runs test on the given dataset. 220 * @param dataSet the dataset to test 221 * @return the errors as string 222 */ 223 public static String runTests(DataSet dataSet) { 224 StringWriter writer = new StringWriter(); 225 new DatasetConsistencyTest(dataSet, writer).runTest(); 226 return writer.toString(); 227 } 228}