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