001    // License: GPL. See LICENSE file for details.
002    package org.openstreetmap.josm.data.validation;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.io.BufferedReader;
007    import java.io.File;
008    import java.io.FileNotFoundException;
009    import java.io.FileReader;
010    import java.io.FileWriter;
011    import java.io.IOException;
012    import java.io.PrintWriter;
013    import java.util.ArrayList;
014    import java.util.Collection;
015    import java.util.HashMap;
016    import java.util.Map;
017    import java.util.TreeSet;
018    import java.util.regex.Matcher;
019    import java.util.regex.Pattern;
020    
021    import javax.swing.JOptionPane;
022    
023    import org.openstreetmap.josm.Main;
024    import org.openstreetmap.josm.data.projection.Epsg4326;
025    import org.openstreetmap.josm.data.projection.Lambert;
026    import org.openstreetmap.josm.data.projection.Mercator;
027    import org.openstreetmap.josm.data.validation.tests.BuildingInBuilding;
028    import org.openstreetmap.josm.data.validation.tests.Coastlines;
029    import org.openstreetmap.josm.data.validation.tests.CrossingWays;
030    import org.openstreetmap.josm.data.validation.tests.DeprecatedTags;
031    import org.openstreetmap.josm.data.validation.tests.DuplicateNode;
032    import org.openstreetmap.josm.data.validation.tests.DuplicateRelation;
033    import org.openstreetmap.josm.data.validation.tests.DuplicateWay;
034    import org.openstreetmap.josm.data.validation.tests.DuplicatedWayNodes;
035    import org.openstreetmap.josm.data.validation.tests.MultipolygonTest;
036    import org.openstreetmap.josm.data.validation.tests.NameMismatch;
037    import org.openstreetmap.josm.data.validation.tests.NodesDuplicatingWayTags;
038    import org.openstreetmap.josm.data.validation.tests.NodesWithSameName;
039    import org.openstreetmap.josm.data.validation.tests.OverlappingAreas;
040    import org.openstreetmap.josm.data.validation.tests.OverlappingWays;
041    import org.openstreetmap.josm.data.validation.tests.PowerLines;
042    import org.openstreetmap.josm.data.validation.tests.RelationChecker;
043    import org.openstreetmap.josm.data.validation.tests.SelfIntersectingWay;
044    import org.openstreetmap.josm.data.validation.tests.SimilarNamedWays;
045    import org.openstreetmap.josm.data.validation.tests.TagChecker;
046    import org.openstreetmap.josm.data.validation.tests.TurnrestrictionTest;
047    import org.openstreetmap.josm.data.validation.tests.UnclosedWays;
048    import org.openstreetmap.josm.data.validation.tests.UnconnectedWays;
049    import org.openstreetmap.josm.data.validation.tests.UntaggedNode;
050    import org.openstreetmap.josm.data.validation.tests.UntaggedWay;
051    import org.openstreetmap.josm.data.validation.tests.WayConnectedToArea;
052    import org.openstreetmap.josm.data.validation.tests.WronglyOrderedWays;
053    import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
054    import org.openstreetmap.josm.gui.layer.Layer;
055    import org.openstreetmap.josm.gui.layer.OsmDataLayer;
056    import org.openstreetmap.josm.gui.layer.ValidatorLayer;
057    import org.openstreetmap.josm.gui.preferences.ValidatorPreference;
058    
059    /**
060     *
061     * A OSM data validator
062     *
063     * @author Francisco R. Santos <frsantos@gmail.com>
064     */
065    public class OsmValidator implements LayerChangeListener {
066    
067        public static ValidatorLayer errorLayer = null;
068    
069        /** Grid detail, multiplier of east,north values for valuable cell sizing */
070        public static double griddetail;
071    
072        public static final Collection<String> ignoredErrors = new TreeSet<String>();
073    
074        /**
075         * All available tests
076         * TODO: is there any way to find out automatically all available tests?
077         */
078        @SuppressWarnings("unchecked")
079        public static Class<Test>[] allAvailableTests = new Class[] {
080            DuplicateNode.class, // ID    1 ..   99
081            OverlappingWays.class, // ID  101 ..  199
082            UntaggedNode.class, // ID  201 ..  299
083            UntaggedWay.class, // ID  301 ..  399
084            SelfIntersectingWay.class, // ID  401 ..  499
085            DuplicatedWayNodes.class, // ID  501 ..  599
086            CrossingWays.class, // ID  601 ..  699
087            SimilarNamedWays.class, // ID  701 ..  799
088            NodesWithSameName.class, // ID  801 ..  899
089            Coastlines.class, // ID  901 ..  999
090            WronglyOrderedWays.class, // ID 1001 .. 1099
091            UnclosedWays.class, // ID 1101 .. 1199
092            TagChecker.class, // ID 1201 .. 1299
093            UnconnectedWays.class, // ID 1301 .. 1399
094            DuplicateWay.class, // ID 1401 .. 1499
095            NameMismatch.class, // ID  1501 ..  1599
096            MultipolygonTest.class, // ID  1601 ..  1699
097            RelationChecker.class, // ID  1701 ..  1799
098            TurnrestrictionTest.class, // ID  1801 ..  1899
099            DuplicateRelation.class, // ID 1901 .. 1999
100            BuildingInBuilding.class, // ID 2001 .. 2099
101            DeprecatedTags.class, // ID 2101 .. 2199
102            OverlappingAreas.class, // ID 2201 .. 2299
103            WayConnectedToArea.class, // ID 2301 .. 2399
104            NodesDuplicatingWayTags.class, // ID 2401 .. 2499
105            PowerLines.class, // ID 2501 .. 2599
106        };
107    
108        public OsmValidator() {
109            checkValidatorDir();
110            initializeGridDetail();
111            initializeTests(getTests());
112            loadIgnoredErrors(); //FIXME: load only when needed
113        }
114    
115        /**
116         * Returns the plugin's directory of the plugin
117         *
118         * @return The directory of the plugin
119         */
120        public static String getValidatorDir()
121        {
122            return Main.pref.getPreferencesDir() + "validator/";
123        }
124    
125        /**
126         * Check if plugin directory exists (store ignored errors file)
127         */
128        private void checkValidatorDir() {
129            try {
130                File pathDir = new File(getValidatorDir());
131                if (!pathDir.exists()) {
132                    pathDir.mkdirs();
133                }
134            } catch (Exception e){
135                e.printStackTrace();
136            }
137        }
138    
139        private void loadIgnoredErrors() {
140            ignoredErrors.clear();
141            if (Main.pref.getBoolean(ValidatorPreference.PREF_USE_IGNORE, true)) {
142                try {
143                    final BufferedReader in = new BufferedReader(new FileReader(getValidatorDir() + "ignorederrors"));
144                    for (String line = in.readLine(); line != null; line = in.readLine()) {
145                        ignoredErrors.add(line);
146                    }
147                } catch (final FileNotFoundException e) {
148                    // Ignore
149                } catch (final IOException e) {
150                    e.printStackTrace();
151                }
152            }
153        }
154    
155        public static void addIgnoredError(String s) {
156            ignoredErrors.add(s);
157        }
158    
159        public static boolean hasIgnoredError(String s) {
160            return ignoredErrors.contains(s);
161        }
162    
163        public static void saveIgnoredErrors() {
164            try {
165                final PrintWriter out = new PrintWriter(new FileWriter(getValidatorDir() + "ignorederrors"), false);
166                for (String e : ignoredErrors) {
167                    out.println(e);
168                }
169                out.close();
170            } catch (final IOException e) {
171                e.printStackTrace();
172            }
173        }
174    
175        public static void initializeErrorLayer() {
176            if (!Main.pref.getBoolean(ValidatorPreference.PREF_LAYER, true))
177                return;
178            if (errorLayer == null) {
179                errorLayer = new ValidatorLayer();
180                Main.main.addLayer(errorLayer);
181            }
182        }
183    
184        /** Gets a map from simple names to all tests. */
185        public static Map<String, Test> getAllTestsMap() {
186            Map<String, Test> tests = new HashMap<String, Test>();
187            for (Class<Test> testClass : getAllAvailableTests()) {
188                try {
189                    Test test = testClass.newInstance();
190                    tests.put(testClass.getSimpleName(), test);
191                } catch (Exception e) {
192                    e.printStackTrace();
193                    continue;
194                }
195            }
196            applyPrefs(tests, false);
197            applyPrefs(tests, true);
198            return tests;
199        }
200    
201        private static void applyPrefs(Map<String, Test> tests, boolean beforeUpload) {
202            Pattern regexp = Pattern.compile("(\\w+)=(true|false),?");
203            Matcher m = regexp.matcher(Main.pref.get(beforeUpload ? ValidatorPreference.PREF_TESTS_BEFORE_UPLOAD
204                    : ValidatorPreference.PREF_TESTS));
205            int pos = 0;
206            while (m.find(pos)) {
207                String testName = m.group(1);
208                Test test = tests.get(testName);
209                if (test != null) {
210                    boolean enabled = Boolean.valueOf(m.group(2));
211                    if (beforeUpload) {
212                        test.testBeforeUpload = enabled;
213                    } else {
214                        test.enabled = enabled;
215                    }
216                }
217                pos = m.end();
218            }
219        }
220    
221        public static Collection<Test> getTests() {
222            return getAllTestsMap().values();
223        }
224    
225        public static Collection<Test> getEnabledTests(boolean beforeUpload) {
226            Collection<Test> enabledTests = getTests();
227            for (Test t : new ArrayList<Test>(enabledTests)) {
228                if (beforeUpload ? t.testBeforeUpload : t.enabled) {
229                    continue;
230                }
231                enabledTests.remove(t);
232            }
233            return enabledTests;
234        }
235    
236        /**
237         * Gets the list of all available test classes
238         *
239         * @return An array of the test classes
240         */
241        public static Class<Test>[] getAllAvailableTests() {
242            return allAvailableTests;
243        }
244    
245        /**
246         * Initialize grid details based on current projection system. Values based on
247         * the original value fixed for EPSG:4326 (10000) using heuristics (that is, test&error
248         * until most bugs were discovered while keeping the processing time reasonable)
249         */
250        public void initializeGridDetail() {
251            if (Main.getProjection().toString().equals(new Epsg4326().toString())) {
252                OsmValidator.griddetail = 10000;
253            } else if (Main.getProjection().toString().equals(new Mercator().toString())) {
254                OsmValidator.griddetail = 0.01;
255            } else if (Main.getProjection().toString().equals(new Lambert().toString())) {
256                OsmValidator.griddetail = 0.1;
257            }
258        }
259    
260        /**
261         * Initializes all tests
262         * @param allTests The tests to initialize
263         */
264        public static void initializeTests(Collection<Test> allTests) {
265            for (Test test : allTests) {
266                try {
267                    if (test.enabled) {
268                        test.initialize();
269                    }
270                } catch (Exception e) {
271                    e.printStackTrace();
272                    JOptionPane.showMessageDialog(Main.parent,
273                            tr("Error initializing test {0}:\n {1}", test.getClass()
274                                    .getSimpleName(), e),
275                                    tr("Error"),
276                                    JOptionPane.ERROR_MESSAGE);
277                }
278            }
279        }
280    
281        /* -------------------------------------------------------------------------- */
282        /* interface LayerChangeListener                                              */
283        /* -------------------------------------------------------------------------- */
284        @Override
285        public void activeLayerChange(Layer oldLayer, Layer newLayer) {
286        }
287    
288        @Override
289        public void layerAdded(Layer newLayer) {
290        }
291    
292        @Override
293        public void layerRemoved(Layer oldLayer) {
294            if (oldLayer == errorLayer) {
295                errorLayer = null;
296                return;
297            }
298            if (Main.map.mapView.getLayersOfType(OsmDataLayer.class).isEmpty()) {
299                if (errorLayer != null) {
300                    Main.map.mapView.removeLayer(errorLayer);
301                }
302            }
303        }
304    }