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