001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.data.osm;
003    
004    import java.io.PrintWriter;
005    import java.io.StringWriter;
006    import java.io.Writer;
007    
008    import org.openstreetmap.josm.data.coor.LatLon;
009    
010    /**
011     * This class can be used to run consistency tests on dataset. Any errors found will be written to provided PrintWriter
012     * <br>
013     * Texts here should not be translated because they're not intended for users but for josm developers
014     *
015     */
016    public class DatasetConsistencyTest {
017    
018        private static final int MAX_ERRORS = 100;
019        private final DataSet dataSet;
020        private final PrintWriter writer;
021        private int errorCount;
022    
023        public DatasetConsistencyTest(DataSet dataSet, Writer writer) {
024            this.dataSet = dataSet;
025            this.writer = new PrintWriter(writer);
026        }
027    
028        private void printError(String type, String message, Object... args) {
029            errorCount++;
030            if (errorCount <= MAX_ERRORS) {
031                writer.println("[" + type + "] " + String.format(message, args));
032            }
033        }
034    
035        public void checkReferrers() {
036            // It's also error when referred primitive's dataset is null but it's already covered by referredPrimitiveNotInDataset check
037            for (Way way:dataSet.getWays()) {
038                if (!way.isDeleted()) {
039                    for (Node n:way.getNodes()) {
040                        if (n.getDataSet() != null && !n.getReferrers().contains(way)) {
041                            printError("WAY NOT IN REFERRERS", "%s is part of %s but is not in referrers", n, way);
042                        }
043                    }
044                }
045            }
046    
047            for (Relation relation:dataSet.getRelations()) {
048                if (!relation.isDeleted()) {
049                    for (RelationMember m:relation.getMembers()) {
050                        if (m.getMember().getDataSet() != null && !m.getMember().getReferrers().contains(relation)) {
051                            printError("RELATION NOT IN REFERRERS", "%s is part of %s but is not in referrers", m.getMember(), relation);
052                        }
053                    }
054                }
055            }
056        }
057    
058        public void checkCompleteWaysWithIncompleteNodes() {
059            for (Way way:dataSet.getWays()) {
060                if (way.isUsable()) {
061                    for (Node node:way.getNodes()) {
062                        if (node.isIncomplete()) {
063                            printError("USABLE HAS INCOMPLETE", "%s is usable but contains incomplete node '%s'", way, node);
064                        }
065                    }
066                }
067            }
068        }
069    
070        public void checkCompleteNodesWithoutCoordinates() {
071            for (Node node:dataSet.getNodes()) {
072                if (!node.isIncomplete() && node.isVisible() && (node.getCoor() == null || node.getEastNorth() == null)) {
073                    printError("COMPLETE WITHOUT COORDINATES", "%s is not incomplete but has null coordinates", node);
074                }
075            }
076        }
077    
078        public void searchNodes() {
079            for (Node n:dataSet.getNodes()) {
080                if (!n.isIncomplete() && !n.isDeleted()) {
081                    LatLon c = n.getCoor();
082                    if (c != null) {
083                        BBox box = new BBox(new LatLon(c.lat() - 0.0001, c.lon() - 0.0001), new LatLon(c.lat() + 0.0001, c.lon() + 0.0001));
084                        if (!dataSet.searchNodes(box).contains(n)) {
085                            printError("SEARCH NODES", "%s not found using Dataset.searchNodes()", n);
086                        }
087                    }
088                }
089            }
090        }
091    
092        public void searchWays() {
093            for (Way w:dataSet.getWays()) {
094                if (!w.isIncomplete() && !w.isDeleted() && w.getNodesCount() >= 2 && !dataSet.searchWays(w.getBBox()).contains(w)) {
095                    printError("SEARCH WAYS", "%s not found using Dataset.searchWays()", w);
096                }
097            }
098        }
099    
100        private void checkReferredPrimitive(OsmPrimitive primitive, OsmPrimitive parent) {
101            if (primitive.getDataSet() == null) {
102                printError("NO DATASET", "%s is referenced by %s but not found in dataset", primitive, parent);
103            } else if (dataSet.getPrimitiveById(primitive) == null) {
104                printError("REFERENCED BUT NOT IN DATA", "%s is referenced by %s but not found in dataset", primitive, parent);
105            } else  if (dataSet.getPrimitiveById(primitive) != primitive) {
106                printError("DIFFERENT INSTANCE", "%s is different instance that referred by %s", primitive, parent);
107            }
108    
109            if (primitive.isDeleted()) {
110                printError("DELETED REFERENCED", "%s refers to deleted primitive %s", parent, primitive);
111            }
112        }
113    
114        public void referredPrimitiveNotInDataset() {
115            for (Way way:dataSet.getWays()) {
116                for (Node node:way.getNodes()) {
117                    checkReferredPrimitive(node, way);
118                }
119            }
120    
121            for (Relation relation:dataSet.getRelations()) {
122                for (RelationMember member:relation.getMembers()) {
123                    checkReferredPrimitive(member.getMember(), relation);
124                }
125            }
126        }
127    
128    
129        public void checkZeroNodesWays() {
130            for (Way way:dataSet.getWays()) {
131                if (way.isUsable() && way.getNodesCount() == 0) {
132                    printError("WARN - ZERO NODES", "Way %s has zero nodes", way);
133                } else if (way.isUsable() && way.getNodesCount() == 1) {
134                    printError("WARN - NO NODES", "Way %s has only one node", way);
135                }
136            }
137        }
138    
139        public void runTest() {
140            try {
141                referredPrimitiveNotInDataset();
142                checkReferrers();
143                checkCompleteWaysWithIncompleteNodes();
144                checkCompleteNodesWithoutCoordinates();
145                searchNodes();
146                searchWays();
147                checkZeroNodesWays();
148                if (errorCount > MAX_ERRORS) {
149                    writer.println((errorCount - MAX_ERRORS) + " more...");
150                }
151            } catch (Exception e) {
152                writer.println("Exception during dataset integrity test:");
153                e.printStackTrace(writer);
154            }
155        }
156    
157        public static String runTests(DataSet dataSet) {
158            StringWriter writer = new StringWriter();
159            new DatasetConsistencyTest(dataSet, writer).runTest();
160            return writer.toString();
161        }
162    
163    }