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