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