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.Utils; 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 long startTime = System.currentTimeMillis(); 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(startTime); 070 } 071 072 /** 073 * Checks for womplete ways with incomplete nodes. 074 */ 075 public void checkCompleteWaysWithIncompleteNodes() { 076 long startTime = System.currentTimeMillis(); 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(startTime); 087 } 088 089 /** 090 * Checks for complete nodes without coordinates. 091 */ 092 public void checkCompleteNodesWithoutCoordinates() { 093 long startTime = System.currentTimeMillis(); 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(startTime); 100 } 101 102 /** 103 * Checks that nodes can be retrieved through their coordinates. 104 */ 105 public void searchNodes() { 106 long startTime = System.currentTimeMillis(); 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(startTime); 119 } 120 121 /** 122 * Checks that ways can be retrieved through their bounding box. 123 */ 124 public void searchWays() { 125 long startTime = System.currentTimeMillis(); 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(startTime); 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 long startTime = System.currentTimeMillis(); 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(startTime); 170 } 171 172 /** 173 * Checks for zero and one-node ways. 174 */ 175 public void checkZeroNodesWays() { 176 long startTime = System.currentTimeMillis(); 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(startTime); 185 } 186 187 private void printElapsedTime(long startTime) { 188 if (Logging.isDebugEnabled()) { 189 StackTraceElement item = Thread.currentThread().getStackTrace()[2]; 190 String operation = getClass().getSimpleName() + '.' + item.getMethodName(); 191 long elapsedTime = System.currentTimeMillis() - startTime; 192 Logging.debug(tr("Test ''{0}'' completed in {1}", 193 operation, Utils.getDurationString(elapsedTime))); 194 } 195 } 196 197 /** 198 * Runs test. 199 */ 200 public void runTest() { 201 try { 202 long startTime = System.currentTimeMillis(); 203 referredPrimitiveNotInDataset(); 204 checkReferrers(); 205 checkCompleteWaysWithIncompleteNodes(); 206 checkCompleteNodesWithoutCoordinates(); 207 searchNodes(); 208 searchWays(); 209 checkZeroNodesWays(); 210 printElapsedTime(startTime); 211 if (errorCount > MAX_ERRORS) { 212 writer.println((errorCount - MAX_ERRORS) + " more..."); 213 } 214 215 } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) { 216 writer.println("Exception during dataset integrity test:"); 217 e.printStackTrace(writer); 218 Logging.warn(e); 219 } 220 } 221 222 /** 223 * Runs test on the given dataset. 224 * @param dataSet the dataset to test 225 * @return the errors as string 226 */ 227 public static String runTests(DataSet dataSet) { 228 StringWriter writer = new StringWriter(); 229 new DatasetConsistencyTest(dataSet, writer).runTest(); 230 return writer.toString(); 231 } 232}