source: josm/trunk/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java@ 3673

Last change on this file since 3673 was 3673, checked in by bastiK, 14 years ago

(hopefully) fix some coding errors

  • Property svn:eol-style set to native
File size: 42.3 KB
Line 
1// License: GPL. See LICENSE file for details.
2package org.openstreetmap.josm.data.validation.tests;
3
4import static org.openstreetmap.josm.tools.I18n.marktr;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.Dimension;
8import java.awt.GridBagConstraints;
9import java.awt.GridBagLayout;
10import java.awt.event.ActionEvent;
11import java.awt.event.ActionListener;
12import java.io.BufferedReader;
13import java.io.FileNotFoundException;
14import java.io.IOException;
15import java.io.InputStreamReader;
16import java.io.UnsupportedEncodingException;
17import java.text.MessageFormat;
18import java.util.ArrayList;
19import java.util.Arrays;
20import java.util.Collection;
21import java.util.Collections;
22import java.util.HashMap;
23import java.util.List;
24import java.util.Map;
25import java.util.Map.Entry;
26import java.util.regex.Matcher;
27import java.util.regex.Pattern;
28import java.util.regex.PatternSyntaxException;
29
30import javax.swing.DefaultListModel;
31import javax.swing.JButton;
32import javax.swing.JCheckBox;
33import javax.swing.JLabel;
34import javax.swing.JList;
35import javax.swing.JOptionPane;
36import javax.swing.JPanel;
37import javax.swing.JScrollPane;
38
39import org.openstreetmap.josm.Main;
40import org.openstreetmap.josm.command.ChangePropertyCommand;
41import org.openstreetmap.josm.command.Command;
42import org.openstreetmap.josm.command.SequenceCommand;
43import org.openstreetmap.josm.data.osm.Node;
44import org.openstreetmap.josm.data.osm.OsmPrimitive;
45import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
46import org.openstreetmap.josm.data.osm.OsmUtils;
47import org.openstreetmap.josm.data.osm.Relation;
48import org.openstreetmap.josm.data.osm.Way;
49import org.openstreetmap.josm.data.validation.Severity;
50import org.openstreetmap.josm.data.validation.Test;
51import org.openstreetmap.josm.data.validation.TestError;
52import org.openstreetmap.josm.data.validation.util.Bag;
53import org.openstreetmap.josm.data.validation.util.Entities;
54import org.openstreetmap.josm.data.validation.util.ValUtil;
55import org.openstreetmap.josm.gui.preferences.ValidatorPreference;
56import org.openstreetmap.josm.gui.preferences.TaggingPresetPreference;
57import org.openstreetmap.josm.gui.progress.ProgressMonitor;
58import org.openstreetmap.josm.gui.tagging.TaggingPreset;
59import org.openstreetmap.josm.io.MirroredInputStream;
60import org.openstreetmap.josm.tools.GBC;
61
62/**
63 * Check for misspelled or wrong properties
64 *
65 * @author frsantos
66 */
67public class TagChecker extends Test
68{
69 /** The default data files */
70 public static final String DATA_FILE = "resource://data/tagchecker.cfg";
71 public static final String IGNORE_FILE = "resource://data/ignoretags.cfg";
72 public static final String SPELL_FILE = "resource://data/words.cfg";
73
74 /** The spell check key substitutions: the key should be substituted by the value */
75 protected static Map<String, String> spellCheckKeyData;
76 /** The spell check preset values */
77 protected static Bag<String, String> presetsValueData;
78 /** The TagChecker data */
79 protected static List<CheckerData> checkerData = new ArrayList<CheckerData>();
80 protected static List<String> ignoreDataStartsWith = new ArrayList<String>();
81 protected static List<String> ignoreDataEquals = new ArrayList<String>();
82 protected static List<String> ignoreDataEndsWith = new ArrayList<String>();
83 protected static List<IgnoreKeyPair> ignoreDataKeyPair = new ArrayList<IgnoreKeyPair>();
84 protected static List<IgnoreTwoKeyPair> ignoreDataTwoKeyPair = new ArrayList<IgnoreTwoKeyPair>();
85
86 /** The preferences prefix */
87 protected static final String PREFIX = ValidatorPreference.PREFIX + "." + TagChecker.class.getSimpleName();
88
89 public static final String PREF_CHECK_VALUES = PREFIX + ".checkValues";
90 public static final String PREF_CHECK_KEYS = PREFIX + ".checkKeys";
91 public static final String PREF_CHECK_COMPLEX = PREFIX + ".checkComplex";
92 public static final String PREF_CHECK_FIXMES = PREFIX + ".checkFixmes";
93
94 public static final String PREF_SOURCES = PREFIX + ".sources";
95 public static final String PREF_USE_DATA_FILE = PREFIX + ".usedatafile";
96 public static final String PREF_USE_IGNORE_FILE = PREFIX + ".useignorefile";
97 public static final String PREF_USE_SPELL_FILE = PREFIX + ".usespellfile";
98
99 public static final String PREF_CHECK_KEYS_BEFORE_UPLOAD = PREF_CHECK_KEYS + "BeforeUpload";
100 public static final String PREF_CHECK_VALUES_BEFORE_UPLOAD = PREF_CHECK_VALUES + "BeforeUpload";
101 public static final String PREF_CHECK_COMPLEX_BEFORE_UPLOAD = PREF_CHECK_COMPLEX + "BeforeUpload";
102 public static final String PREF_CHECK_FIXMES_BEFORE_UPLOAD = PREF_CHECK_FIXMES + "BeforeUpload";
103
104 protected boolean checkKeys = false;
105 protected boolean checkValues = false;
106 protected boolean checkComplex = false;
107 protected boolean checkFixmes = false;
108
109 protected JCheckBox prefCheckKeys;
110 protected JCheckBox prefCheckValues;
111 protected JCheckBox prefCheckComplex;
112 protected JCheckBox prefCheckFixmes;
113 protected JCheckBox prefCheckPaint;
114
115 protected JCheckBox prefCheckKeysBeforeUpload;
116 protected JCheckBox prefCheckValuesBeforeUpload;
117 protected JCheckBox prefCheckComplexBeforeUpload;
118 protected JCheckBox prefCheckFixmesBeforeUpload;
119 protected JCheckBox prefCheckPaintBeforeUpload;
120
121 protected JCheckBox prefUseDataFile;
122 protected JCheckBox prefUseIgnoreFile;
123 protected JCheckBox prefUseSpellFile;
124
125 protected JButton addSrcButton;
126 protected JButton editSrcButton;
127 protected JButton deleteSrcButton;
128
129 protected static int EMPTY_VALUES = 1200;
130 protected static int INVALID_KEY = 1201;
131 protected static int INVALID_VALUE = 1202;
132 protected static int FIXME = 1203;
133 protected static int INVALID_SPACE = 1204;
134 protected static int INVALID_KEY_SPACE = 1205;
135 protected static int INVALID_HTML = 1206; /* 1207 was PAINT */
136 protected static int LONG_VALUE = 1208;
137 protected static int LONG_KEY = 1209;
138 protected static int LOW_CHAR_VALUE = 1210;
139 protected static int LOW_CHAR_KEY = 1211;
140 /** 1250 and up is used by tagcheck */
141
142 /** List of sources for spellcheck data */
143 protected JList sourcesList;
144
145 protected static Entities entities = new Entities();
146
147 /**
148 * Constructor
149 */
150 public TagChecker() {
151 super(tr("Properties checker :"),
152 tr("This plugin checks for errors in property keys and values."));
153 }
154
155 @Override
156 public void initialize() throws Exception {
157 initializeData();
158 initializePresets();
159 }
160
161 /**
162 * Reads the spellcheck file into a HashMap.
163 * The data file is a list of words, beginning with +/-. If it starts with +,
164 * the word is valid, but if it starts with -, the word should be replaced
165 * by the nearest + word before this.
166 *
167 * @throws FileNotFoundException
168 * @throws IOException
169 */
170 private static void initializeData() throws IOException {
171 spellCheckKeyData = new HashMap<String, String>();
172 String sources = Main.pref.get( PREF_SOURCES, "");
173 if (Main.pref.getBoolean(PREF_USE_DATA_FILE, true)) {
174 if (sources == null || sources.length() == 0) {
175 sources = DATA_FILE;
176 } else {
177 sources = DATA_FILE + ";" + sources;
178 }
179 }
180 if (Main.pref.getBoolean(PREF_USE_IGNORE_FILE, true)) {
181 if (sources == null || sources.length() == 0) {
182 sources = IGNORE_FILE;
183 } else {
184 sources = IGNORE_FILE + ";" + sources;
185 }
186 }
187 if (Main.pref.getBoolean(PREF_USE_SPELL_FILE, true)) {
188 if( sources == null || sources.length() == 0) {
189 sources = SPELL_FILE;
190 } else {
191 sources = SPELL_FILE + ";" + sources;
192 }
193 }
194
195 String errorSources = "";
196 if (sources.length() == 0)
197 return;
198 for (String source : sources.split(";")) {
199 try {
200 MirroredInputStream s = new MirroredInputStream(source, ValUtil.getPluginDir(), -1);
201 InputStreamReader r;
202 try {
203 r = new InputStreamReader(s, "UTF-8");
204 } catch (UnsupportedEncodingException e) {
205 r = new InputStreamReader(s);
206 }
207 BufferedReader reader = new BufferedReader(r);
208
209 String okValue = null;
210 boolean tagcheckerfile = false;
211 boolean ignorefile = false;
212 String line;
213 while ((line = reader.readLine()) != null && (tagcheckerfile || line.length() != 0)) {
214 if (line.startsWith("#")) {
215 if (line.startsWith("# JOSM TagChecker")) {
216 tagcheckerfile = true;
217 }
218 if (line.startsWith("# JOSM IgnoreTags")) {
219 ignorefile = true;
220 }
221 continue;
222 } else if (ignorefile) {
223 line = line.trim();
224 if (line.length() < 4) {
225 continue;
226 }
227
228 String key = line.substring(0, 2);
229 line = line.substring(2);
230
231 if (key.equals("S:")) {
232 ignoreDataStartsWith.add(line);
233 } else if (key.equals("E:")) {
234 ignoreDataEquals.add(line);
235 } else if (key.equals("F:")) {
236 ignoreDataEndsWith.add(line);
237 } else if (key.equals("K:")) {
238 IgnoreKeyPair tmp = new IgnoreKeyPair();
239 int mid = line.indexOf("=");
240 tmp.key = line.substring(0, mid);
241 tmp.value = line.substring(mid+1);
242 ignoreDataKeyPair.add(tmp);
243 } else if (key.equals("T:")) {
244 IgnoreTwoKeyPair tmp = new IgnoreTwoKeyPair();
245 int mid = line.indexOf("=");
246 int split = line.indexOf("|");
247 tmp.key1 = line.substring(0, mid);
248 tmp.value1 = line.substring(mid+1, split);
249 line = line.substring(split+1);
250 mid = line.indexOf("=");
251 tmp.key2 = line.substring(0, mid);
252 tmp.value2 = line.substring(mid+1);
253 ignoreDataTwoKeyPair.add(tmp);
254 }
255 continue;
256 } else if (tagcheckerfile) {
257 if (line.length() > 0) {
258 CheckerData d = new CheckerData();
259 String err = d.getData(line);
260
261 if (err == null) {
262 checkerData.add(d);
263 } else {
264 System.err.println(tr("Invalid tagchecker line - {0}: {1}", err, line));
265 }
266 }
267 } else if (line.charAt(0) == '+') {
268 okValue = line.substring(1);
269 } else if (line.charAt(0) == '-' && okValue != null) {
270 spellCheckKeyData.put(line.substring(1), okValue);
271 } else {
272 System.err.println(tr("Invalid spellcheck line: {0}", line));
273 }
274 }
275 } catch (IOException e) {
276 errorSources += source + "\n";
277 }
278 }
279
280 if (errorSources.length() > 0)
281 throw new IOException( tr("Could not access data file(s):\n{0}", errorSources) );
282 }
283
284 /**
285 * Reads the presets data.
286 *
287 * @throws Exception
288 */
289 public static void initializePresets() throws Exception {
290
291 if (!Main.pref.getBoolean(PREF_CHECK_VALUES, true))
292 return;
293
294 Collection<TaggingPreset> presets = TaggingPresetPreference.taggingPresets;
295 if (presets != null) {
296 presetsValueData = new Bag<String, String>();
297 for (String a : OsmPrimitive.getUninterestingKeys()) {
298 presetsValueData.add(a);
299 }
300 // TODO directionKeys are no longer in OsmPrimitive (search pattern is used instead)
301 /* for(String a : OsmPrimitive.getDirectionKeys())
302 presetsValueData.add(a);
303 */
304 for (String a : Main.pref.getCollection(ValidatorPreference.PREFIX + ".knownkeys",
305 Arrays.asList(new String[]{"is_in", "int_ref", "fixme", "population"}))) {
306 presetsValueData.add(a);
307 }
308 for (TaggingPreset p : presets) {
309 for(TaggingPreset.Item i : p.data) {
310 if (i instanceof TaggingPreset.Combo) {
311 TaggingPreset.Combo combo = (TaggingPreset.Combo) i;
312 if (combo.values != null) {
313 for(String value : combo.values.split(",")) {
314 presetsValueData.add(combo.key, value);
315 }
316 }
317 } else if (i instanceof TaggingPreset.Key) {
318 TaggingPreset.Key k = (TaggingPreset.Key) i;
319 presetsValueData.add(k.key, k.value);
320 } else if (i instanceof TaggingPreset.Text) {
321 TaggingPreset.Text k = (TaggingPreset.Text) i;
322 presetsValueData.add(k.key);
323 } else if (i instanceof TaggingPreset.Check) {
324 TaggingPreset.Check k = (TaggingPreset.Check) i;
325 presetsValueData.add(k.key, "yes");
326 presetsValueData.add(k.key, "no");
327 }
328 }
329 }
330 }
331 }
332
333 @Override
334 public void visit(Node n) {
335 checkPrimitive(n);
336 }
337
338 @Override
339 public void visit(Relation n) {
340 checkPrimitive(n);
341 }
342
343 @Override
344 public void visit(Way w) {
345 checkPrimitive(w);
346 }
347
348 /**
349 * Checks given string (key or value) if it contains characters with code below 0x20 (either newline or some other special characters)
350 * @param s string to check
351 */
352 private boolean containsLow(String s) {
353 if (s == null)
354 return false;
355 for (int i = 0; i < s.length(); i++) {
356 if (s.charAt(i) < 0x20)
357 return true;
358 }
359 return false;
360 }
361
362 /**
363 * Checks the primitive properties
364 * @param p The primitive to check
365 */
366 private void checkPrimitive(OsmPrimitive p) {
367 // Just a collection to know if a primitive has been already marked with error
368 Bag<OsmPrimitive, String> withErrors = new Bag<OsmPrimitive, String>();
369
370 if (checkComplex) {
371 Map<String, String> props = (p.getKeys() == null) ? Collections.<String, String>emptyMap() : p.getKeys();
372 for (Entry<String, String> prop : props.entrySet()) {
373 boolean ignore = true;
374 String key1 = prop.getKey();
375 String value1 = prop.getValue();
376
377 for (IgnoreTwoKeyPair a : ignoreDataTwoKeyPair) {
378 if (key1.equals(a.key1) && value1.equals(a.value1)) {
379 ignore = false;
380 for (Entry<String, String> prop2 : props.entrySet()) {
381 String key2 = prop2.getKey();
382 String value2 = prop2.getValue();
383 for (IgnoreTwoKeyPair b : ignoreDataTwoKeyPair) {
384 if (key2.equals(b.key2) && value2.equals(b.value2)) {
385 ignore = true;
386 break;
387 }
388 }
389 if (ignore) {
390 break;
391 }
392 }
393 }
394 if (ignore) {
395 break;
396 }
397 }
398
399 if (!ignore) {
400 errors.add( new TestError(this, Severity.ERROR, tr("Illegal tag/value combinations"),
401 tr("Illegal tag/value combinations"), tr("Illegal tag/value combinations"), 1272, p) );
402 withErrors.add(p, "TC");
403 }
404 }
405
406 Map<String, String> keys = p.getKeys();
407 for (CheckerData d : checkerData) {
408 if (d.match(p, keys)) {
409 errors.add( new TestError(this, d.getSeverity(), tr("Illegal tag/value combinations"),
410 d.getDescription(), d.getDescriptionOrig(), d.getCode(), p) );
411 withErrors.add(p, "TC");
412 }
413 }
414 }
415
416 Map<String, String> props = (p.getKeys() == null) ? Collections.<String, String>emptyMap() : p.getKeys();
417 for (Entry<String, String> prop : props.entrySet()) {
418 String s = marktr("Key ''{0}'' invalid.");
419 String key = prop.getKey();
420 String value = prop.getValue();
421 if (checkValues && (containsLow(value)) && !withErrors.contains(p, "ICV")) {
422 errors.add( new TestError(this, Severity.WARNING, tr("Tag value contains character with code less than 0x20"),
423 tr(s, key), MessageFormat.format(s, key), LOW_CHAR_VALUE, p) );
424 withErrors.add(p, "ICV");
425 }
426 if (checkKeys && (containsLow(key)) && !withErrors.contains(p, "ICK")) {
427 errors.add( new TestError(this, Severity.WARNING, tr("Tag key contains character with code less than 0x20"),
428 tr(s, key), MessageFormat.format(s, key), LOW_CHAR_KEY, p) );
429 withErrors.add(p, "ICK");
430 }
431 if (checkValues && (value!=null && value.length() > 255) && !withErrors.contains(p, "LV")) {
432 errors.add( new TestError(this, Severity.ERROR, tr("Tag value longer than allowed"),
433 tr(s, key), MessageFormat.format(s, key), LONG_VALUE, p) );
434 withErrors.add(p, "LV");
435 }
436 if (checkKeys && (key!=null && key.length() > 255) && !withErrors.contains(p, "LK")) {
437 errors.add( new TestError(this, Severity.ERROR, tr("Tag key longer than allowed"),
438 tr(s, key), MessageFormat.format(s, key), LONG_KEY, p) );
439 withErrors.add(p, "LK");
440 }
441 if (checkValues && (value==null || value.trim().length() == 0) && !withErrors.contains(p, "EV")) {
442 errors.add( new TestError(this, Severity.WARNING, tr("Tags with empty values"),
443 tr(s, key), MessageFormat.format(s, key), EMPTY_VALUES, p) );
444 withErrors.add(p, "EV");
445 }
446 if (checkKeys && spellCheckKeyData.containsKey(key) && !withErrors.contains(p, "IPK")) {
447 errors.add( new TestError(this, Severity.WARNING, tr("Invalid property key"),
448 tr(s, key), MessageFormat.format(s, key), INVALID_KEY, p) );
449 withErrors.add(p, "IPK");
450 }
451 if (checkKeys && key.indexOf(" ") >= 0 && !withErrors.contains(p, "IPK")) {
452 errors.add( new TestError(this, Severity.WARNING, tr("Invalid white space in property key"),
453 tr(s, key), MessageFormat.format(s, key), INVALID_KEY_SPACE, p) );
454 withErrors.add(p, "IPK");
455 }
456 if (checkValues && value != null && (value.startsWith(" ") || value.endsWith(" ")) && !withErrors.contains(p, "SPACE")) {
457 errors.add( new TestError(this, Severity.OTHER, tr("Property values start or end with white space"),
458 tr(s, key), MessageFormat.format(s, key), INVALID_SPACE, p) );
459 withErrors.add(p, "SPACE");
460 }
461 if (checkValues && value != null && !value.equals(entities.unescape(value)) && !withErrors.contains(p, "HTML")) {
462 errors.add( new TestError(this, Severity.OTHER, tr("Property values contain HTML entity"),
463 tr(s, key), MessageFormat.format(s, key), INVALID_HTML, p) );
464 withErrors.add(p, "HTML");
465 }
466 if (checkValues && value != null && value.length() > 0 && presetsValueData != null) {
467 List<String> values = presetsValueData.get(key);
468 if (values == null) {
469 boolean ignore = false;
470 for (String a : ignoreDataStartsWith) {
471 if (key.startsWith(a)) {
472 ignore = true;
473 }
474 }
475 for (String a : ignoreDataEquals) {
476 if(key.equals(a)) {
477 ignore = true;
478 }
479 }
480 for (String a : ignoreDataEndsWith) {
481 if(key.endsWith(a)) {
482 ignore = true;
483 }
484 }
485 if (!ignore) {
486 String i = marktr("Key ''{0}'' not in presets.");
487 errors.add( new TestError(this, Severity.OTHER, tr("Presets do not contain property key"),
488 tr(i, key), MessageFormat.format(i, key), INVALID_VALUE, p) );
489 withErrors.add(p, "UPK");
490 }
491 } else if (values.size() > 0 && !values.contains(prop.getValue())) {
492 boolean ignore = false;
493 for (IgnoreKeyPair a : ignoreDataKeyPair) {
494 if (key.equals(a.key) && value.equals(a.value)) {
495 ignore = true;
496 }
497 }
498
499 for (IgnoreTwoKeyPair a : ignoreDataTwoKeyPair) {
500 if (key.equals(a.key2) && value.equals(a.value2)) {
501 ignore = true;
502 }
503 }
504
505 if (!ignore) {
506 String i = marktr("Value ''{0}'' for key ''{1}'' not in presets.");
507 errors.add( new TestError(this, Severity.OTHER, tr("Presets do not contain property value"),
508 tr(i, prop.getValue(), key), MessageFormat.format(i, prop.getValue(), key), INVALID_VALUE, p) );
509 withErrors.add(p, "UPV");
510 }
511 }
512 }
513 if (checkFixmes && value != null && value.length() > 0) {
514 if ((value.toLowerCase().contains("fixme")
515 || value.contains("check and delete")
516 || key.contains("todo") || key.toLowerCase().contains("fixme"))
517 && !withErrors.contains(p, "FIXME")) {
518 errors.add(new TestError(this, Severity.OTHER,
519 tr("FIXMES"), FIXME, p));
520 withErrors.add(p, "FIXME");
521 }
522 }
523 }
524 }
525
526 @Override
527 public void startTest(ProgressMonitor monitor) {
528 super.startTest(monitor);
529 checkKeys = Main.pref.getBoolean(PREF_CHECK_KEYS, true);
530 if (isBeforeUpload) {
531 checkKeys = checkKeys && Main.pref.getBoolean(PREF_CHECK_KEYS_BEFORE_UPLOAD, true);
532 }
533
534 checkValues = Main.pref.getBoolean(PREF_CHECK_VALUES, true);
535 if (isBeforeUpload) {
536 checkValues = checkValues && Main.pref.getBoolean(PREF_CHECK_VALUES_BEFORE_UPLOAD, true);
537 }
538
539 checkComplex = Main.pref.getBoolean(PREF_CHECK_COMPLEX, true);
540 if (isBeforeUpload) {
541 checkComplex = checkValues && Main.pref.getBoolean(PREF_CHECK_COMPLEX_BEFORE_UPLOAD, true);
542 }
543
544 checkFixmes = Main.pref.getBoolean(PREF_CHECK_FIXMES, true);
545 if (isBeforeUpload) {
546 checkFixmes = checkFixmes && Main.pref.getBoolean(PREF_CHECK_FIXMES_BEFORE_UPLOAD, true);
547 }
548 }
549
550 @Override
551 public void visit(Collection<OsmPrimitive> selection) {
552 if (checkKeys || checkValues || checkComplex || checkFixmes) {
553 super.visit(selection);
554 }
555 }
556
557 @Override
558 public void addGui(JPanel testPanel) {
559 GBC a = GBC.eol();
560 a.anchor = GridBagConstraints.EAST;
561
562 testPanel.add(new JLabel(name), GBC.eol().insets(3,0,0,0));
563
564 prefCheckKeys = new JCheckBox(tr("Check property keys."), Main.pref.getBoolean(PREF_CHECK_KEYS, true));
565 prefCheckKeys.setToolTipText(tr("Validate that property keys are valid checking against list of words."));
566 testPanel.add(prefCheckKeys, GBC.std().insets(20,0,0,0));
567
568 prefCheckKeysBeforeUpload = new JCheckBox();
569 prefCheckKeysBeforeUpload.setSelected(Main.pref.getBoolean(PREF_CHECK_KEYS_BEFORE_UPLOAD, true));
570 testPanel.add(prefCheckKeysBeforeUpload, a);
571
572 prefCheckComplex = new JCheckBox(tr("Use complex property checker."), Main.pref.getBoolean(PREF_CHECK_COMPLEX, true));
573 prefCheckComplex.setToolTipText(tr("Validate property values and tags using complex rules."));
574 testPanel.add(prefCheckComplex, GBC.std().insets(20,0,0,0));
575
576 prefCheckComplexBeforeUpload = new JCheckBox();
577 prefCheckComplexBeforeUpload.setSelected(Main.pref.getBoolean(PREF_CHECK_COMPLEX_BEFORE_UPLOAD, true));
578 testPanel.add(prefCheckComplexBeforeUpload, a);
579
580 sourcesList = new JList(new DefaultListModel());
581
582 String sources = Main.pref.get( PREF_SOURCES );
583 if (sources != null && sources.length() > 0) {
584 for (String source : sources.split(";")) {
585 ((DefaultListModel)sourcesList.getModel()).addElement(source);
586 }
587 }
588
589 addSrcButton = new JButton(tr("Add"));
590 addSrcButton.addActionListener(new ActionListener() {
591 @Override
592 public void actionPerformed(ActionEvent e) {
593 String source = JOptionPane.showInputDialog(
594 Main.parent,
595 tr("TagChecker source"),
596 tr("TagChecker source"),
597 JOptionPane.QUESTION_MESSAGE);
598 if (source != null) {
599 ((DefaultListModel)sourcesList.getModel()).addElement(source);
600 }
601 sourcesList.clearSelection();
602 }
603 });
604
605 editSrcButton = new JButton(tr("Edit"));
606 editSrcButton.addActionListener(new ActionListener() {
607 @Override
608 public void actionPerformed(ActionEvent e) {
609 int row = sourcesList.getSelectedIndex();
610 if (row == -1 && sourcesList.getModel().getSize() == 1) {
611 sourcesList.setSelectedIndex(0);
612 row = 0;
613 }
614 if (row == -1) {
615 if (sourcesList.getModel().getSize() == 0) {
616 String source = JOptionPane.showInputDialog(Main.parent, tr("TagChecker source"), tr("TagChecker source"), JOptionPane.QUESTION_MESSAGE);
617 if (source != null) {
618 ((DefaultListModel)sourcesList.getModel()).addElement(source);
619 }
620 } else {
621 JOptionPane.showMessageDialog(
622 Main.parent,
623 tr("Please select the row to edit."),
624 tr("Information"),
625 JOptionPane.INFORMATION_MESSAGE
626 );
627 }
628 } else {
629 String source = (String)JOptionPane.showInputDialog(Main.parent,
630 tr("TagChecker source"),
631 tr("TagChecker source"),
632 JOptionPane.QUESTION_MESSAGE, null, null,
633 sourcesList.getSelectedValue());
634 if (source != null) {
635 ((DefaultListModel)sourcesList.getModel()).setElementAt(source, row);
636 }
637 }
638 sourcesList.clearSelection();
639 }
640 });
641
642 deleteSrcButton = new JButton(tr("Delete"));
643 deleteSrcButton.addActionListener(new ActionListener() {
644 @Override
645 public void actionPerformed(ActionEvent e) {
646 if (sourcesList.getSelectedIndex() == -1) {
647 JOptionPane.showMessageDialog(Main.parent, tr("Please select the row to delete."), tr("Information"), JOptionPane.QUESTION_MESSAGE);
648 } else {
649 ((DefaultListModel)sourcesList.getModel()).remove(sourcesList.getSelectedIndex());
650 }
651 }
652 });
653 sourcesList.setMinimumSize(new Dimension(300,50));
654 sourcesList.setVisibleRowCount(3);
655
656 sourcesList.setToolTipText(tr("The sources (URL or filename) of spell check (see http://wiki.openstreetmap.org/index.php/User:JLS/speller) or tag checking data files."));
657 addSrcButton.setToolTipText(tr("Add a new source to the list."));
658 editSrcButton.setToolTipText(tr("Edit the selected source."));
659 deleteSrcButton.setToolTipText(tr("Delete the selected source from the list."));
660
661 testPanel.add(new JLabel(tr("Data sources")), GBC.eol().insets(23,0,0,0));
662 testPanel.add(new JScrollPane(sourcesList), GBC.eol().insets(23,0,0,0).fill(GridBagConstraints.HORIZONTAL));
663 final JPanel buttonPanel = new JPanel(new GridBagLayout());
664 testPanel.add(buttonPanel, GBC.eol().fill(GridBagConstraints.HORIZONTAL));
665 buttonPanel.add(addSrcButton, GBC.std().insets(0,5,0,0));
666 buttonPanel.add(editSrcButton, GBC.std().insets(5,5,5,0));
667 buttonPanel.add(deleteSrcButton, GBC.std().insets(0,5,0,0));
668
669 ActionListener disableCheckActionListener = new ActionListener() {
670 @Override
671 public void actionPerformed(ActionEvent e) {
672 handlePrefEnable();
673 }
674 };
675 prefCheckKeys.addActionListener(disableCheckActionListener);
676 prefCheckKeysBeforeUpload.addActionListener(disableCheckActionListener);
677 prefCheckComplex.addActionListener(disableCheckActionListener);
678 prefCheckComplexBeforeUpload.addActionListener(disableCheckActionListener);
679
680 handlePrefEnable();
681
682 prefCheckValues = new JCheckBox(tr("Check property values."), Main.pref.getBoolean(PREF_CHECK_VALUES, true));
683 prefCheckValues.setToolTipText(tr("Validate that property values are valid checking against presets."));
684 testPanel.add(prefCheckValues, GBC.std().insets(20,0,0,0));
685
686 prefCheckValuesBeforeUpload = new JCheckBox();
687 prefCheckValuesBeforeUpload.setSelected(Main.pref.getBoolean(PREF_CHECK_VALUES_BEFORE_UPLOAD, true));
688 testPanel.add(prefCheckValuesBeforeUpload, a);
689
690 prefCheckFixmes = new JCheckBox(tr("Check for FIXMES."), Main.pref.getBoolean(PREF_CHECK_FIXMES, true));
691 prefCheckFixmes.setToolTipText(tr("Looks for nodes or ways with FIXME in any property value."));
692 testPanel.add(prefCheckFixmes, GBC.std().insets(20,0,0,0));
693
694 prefCheckFixmesBeforeUpload = new JCheckBox();
695 prefCheckFixmesBeforeUpload.setSelected(Main.pref.getBoolean(PREF_CHECK_FIXMES_BEFORE_UPLOAD, true));
696 testPanel.add(prefCheckFixmesBeforeUpload, a);
697
698 prefUseDataFile = new JCheckBox(tr("Use default data file."), Main.pref.getBoolean(PREF_USE_DATA_FILE, true));
699 prefUseDataFile.setToolTipText(tr("Use the default data file (recommended)."));
700 testPanel.add(prefUseDataFile, GBC.eol().insets(20,0,0,0));
701
702 prefUseIgnoreFile = new JCheckBox(tr("Use default tag ignore file."), Main.pref.getBoolean(PREF_USE_IGNORE_FILE, true));
703 prefUseIgnoreFile.setToolTipText(tr("Use the default tag ignore file (recommended)."));
704 testPanel.add(prefUseIgnoreFile, GBC.eol().insets(20,0,0,0));
705
706 prefUseSpellFile = new JCheckBox(tr("Use default spellcheck file."), Main.pref.getBoolean(PREF_USE_SPELL_FILE, true));
707 prefUseSpellFile.setToolTipText(tr("Use the default spellcheck file (recommended)."));
708 testPanel.add(prefUseSpellFile, GBC.eol().insets(20,0,0,0));
709 }
710
711 public void handlePrefEnable() {
712 boolean selected = prefCheckKeys.isSelected() || prefCheckKeysBeforeUpload.isSelected()
713 || prefCheckComplex.isSelected() || prefCheckComplexBeforeUpload.isSelected();
714 sourcesList.setEnabled( selected );
715 addSrcButton.setEnabled(selected);
716 editSrcButton.setEnabled(selected);
717 deleteSrcButton.setEnabled(selected);
718 }
719
720 @Override
721 public boolean ok()
722 {
723 enabled = prefCheckKeys.isSelected() || prefCheckValues.isSelected() || prefCheckComplex.isSelected() || prefCheckFixmes.isSelected();
724 testBeforeUpload = prefCheckKeysBeforeUpload.isSelected() || prefCheckValuesBeforeUpload.isSelected()
725 || prefCheckFixmesBeforeUpload.isSelected() || prefCheckComplexBeforeUpload.isSelected();
726
727 Main.pref.put(PREF_CHECK_VALUES, prefCheckValues.isSelected());
728 Main.pref.put(PREF_CHECK_COMPLEX, prefCheckComplex.isSelected());
729 Main.pref.put(PREF_CHECK_KEYS, prefCheckKeys.isSelected());
730 Main.pref.put(PREF_CHECK_FIXMES, prefCheckFixmes.isSelected());
731 Main.pref.put(PREF_CHECK_VALUES_BEFORE_UPLOAD, prefCheckValuesBeforeUpload.isSelected());
732 Main.pref.put(PREF_CHECK_COMPLEX_BEFORE_UPLOAD, prefCheckComplexBeforeUpload.isSelected());
733 Main.pref.put(PREF_CHECK_KEYS_BEFORE_UPLOAD, prefCheckKeysBeforeUpload.isSelected());
734 Main.pref.put(PREF_CHECK_FIXMES_BEFORE_UPLOAD, prefCheckFixmesBeforeUpload.isSelected());
735 Main.pref.put(PREF_USE_DATA_FILE, prefUseDataFile.isSelected());
736 Main.pref.put(PREF_USE_IGNORE_FILE, prefUseIgnoreFile.isSelected());
737 Main.pref.put(PREF_USE_SPELL_FILE, prefUseSpellFile.isSelected());
738 String sources = "";
739 if (sourcesList.getModel().getSize() > 0) {
740 String sb = "";
741 for (int i = 0; i < sourcesList.getModel().getSize(); ++i) {
742 sb += ";"+sourcesList.getModel().getElementAt(i);
743 }
744 sources = sb.substring(1);
745 }
746 if (sources.length() == 0) {
747 sources = null;
748 }
749 return Main.pref.put(PREF_SOURCES, sources);
750 }
751
752 @Override
753 public Command fixError(TestError testError) {
754
755 List<Command> commands = new ArrayList<Command>(50);
756
757 int i = -1;
758 List<? extends OsmPrimitive> primitives = testError.getPrimitives();
759 for (OsmPrimitive p : primitives) {
760 i++;
761 Map<String, String> tags = p.getKeys();
762 if (tags == null || tags.isEmpty()) {
763 continue;
764 }
765
766 for (Entry<String, String> prop: tags.entrySet()) {
767 String key = prop.getKey();
768 String value = prop.getValue();
769 if (value == null || value.trim().length() == 0) {
770 commands.add(new ChangePropertyCommand(Collections.singleton(primitives.get(i)), key, null));
771 } else if (value.startsWith(" ") || value.endsWith(" ")) {
772 commands.add(new ChangePropertyCommand(Collections.singleton(primitives.get(i)), key, value.trim()));
773 } else if (key.startsWith(" ") || key.endsWith(" ")) {
774 commands.add(new ChangePropertyKeyCommand(Collections.singleton(primitives.get(i)), key, key.trim()));
775 } else {
776 String evalue = entities.unescape(value);
777 if (!evalue.equals(value)) {
778 commands.add(new ChangePropertyCommand(Collections.singleton(primitives.get(i)), key, evalue));
779 } else {
780 String replacementKey = spellCheckKeyData.get(key);
781 if (replacementKey != null) {
782 commands.add(new ChangePropertyKeyCommand(Collections.singleton(primitives.get(i)),
783 key, replacementKey));
784 }
785 }
786 }
787 }
788 }
789
790 if (commands.isEmpty())
791 return null;
792 if (commands.size() == 1)
793 return commands.get(0);
794
795 return new SequenceCommand(tr("Fix properties"), commands);
796 }
797
798 @Override
799 public boolean isFixable(TestError testError) {
800
801 if (testError.getTester() instanceof TagChecker) {
802 int code = testError.getCode();
803 return code == INVALID_KEY || code == EMPTY_VALUES || code == INVALID_SPACE || code == INVALID_KEY_SPACE || code == INVALID_HTML;
804 }
805
806 return false;
807 }
808
809 protected static class IgnoreTwoKeyPair {
810 public String key1;
811 public String value1;
812 public String key2;
813 public String value2;
814 }
815
816 protected static class IgnoreKeyPair {
817 public String key;
818 public String value;
819 }
820
821 protected static class CheckerData {
822 private String description;
823 private List<CheckerElement> data = new ArrayList<CheckerElement>();
824 private OsmPrimitiveType type;
825 private int code;
826 protected Severity severity;
827 protected static int TAG_CHECK_ERROR = 1250;
828 protected static int TAG_CHECK_WARN = 1260;
829 protected static int TAG_CHECK_INFO = 1270;
830
831 private static class CheckerElement {
832 public Object tag;
833 public Object value;
834 public boolean noMatch;
835 public boolean tagAll = false;
836 public boolean valueAll = false;
837 public boolean valueBool = false;
838
839 private Pattern getPattern(String str) throws IllegalStateException, PatternSyntaxException {
840 if (str.endsWith("/i"))
841 return Pattern.compile(str.substring(1,str.length()-2), Pattern.CASE_INSENSITIVE);
842 if (str.endsWith("/"))
843 return Pattern.compile(str.substring(1,str.length()-1));
844
845 throw new IllegalStateException();
846 }
847 public CheckerElement(String exp) throws IllegalStateException, PatternSyntaxException {
848 Matcher m = Pattern.compile("(.+)([!=]=)(.+)").matcher(exp);
849 m.matches();
850
851 String n = m.group(1).trim();
852
853 if(n.equals("*")) {
854 tagAll = true;
855 } else {
856 tag = n.startsWith("/") ? getPattern(n) : n;
857 noMatch = m.group(2).equals("!=");
858 n = m.group(3).trim();
859 if (n.equals("*")) {
860 valueAll = true;
861 } else if (n.equals("BOOLEAN_TRUE")) {
862 valueBool = true;
863 value = OsmUtils.trueval;
864 } else if (n.equals("BOOLEAN_FALSE")) {
865 valueBool = true;
866 value = OsmUtils.falseval;
867 } else {
868 value = n.startsWith("/") ? getPattern(n) : n;
869 }
870 }
871 }
872
873 public boolean match(OsmPrimitive osm, Map<String, String> keys) {
874 for (Entry<String, String> prop: keys.entrySet()) {
875 String key = prop.getKey();
876 String val = valueBool ? OsmUtils.getNamedOsmBoolean(prop.getValue()) : prop.getValue();
877 if ((tagAll || (tag instanceof Pattern ? ((Pattern) tag).matcher(key).matches() : key.equals(tag)))
878 && (valueAll || (value instanceof Pattern ? ((Pattern) value).matcher(val).matches() : val.equals(value))))
879 return !noMatch;
880 }
881 return noMatch;
882 }
883 };
884
885 public String getData(String str) {
886 Matcher m = Pattern.compile(" *# *([^#]+) *$").matcher(str);
887 str = m.replaceFirst("").trim();
888 try {
889 description = m.group(1);
890 if (description != null && description.length() == 0) {
891 description = null;
892 }
893 } catch (IllegalStateException e) {
894 description = null;
895 }
896 String[] n = str.split(" *: *", 3);
897 if (n[0].equals("way")) {
898 type = OsmPrimitiveType.WAY;
899 } else if (n[0].equals("node")) {
900 type = OsmPrimitiveType.NODE;
901 } else if (n[0].equals("relation")) {
902 type = OsmPrimitiveType.RELATION;
903 } else if (n[0].equals("*")) {
904 type = null;
905 } else
906 return tr("Could not find element type");
907 if (n.length != 3)
908 return tr("Incorrect number of parameters");
909
910 if (n[1].equals("W")) {
911 severity = Severity.WARNING;
912 code = TAG_CHECK_WARN;
913 } else if (n[1].equals("E")) {
914 severity = Severity.ERROR;
915 code = TAG_CHECK_ERROR;
916 } else if(n[1].equals("I")) {
917 severity = Severity.OTHER;
918 code = TAG_CHECK_INFO;
919 } else
920 return tr("Could not find warning level");
921 for (String exp: n[2].split(" *&& *")) {
922 try {
923 data.add(new CheckerElement(exp));
924 } catch (IllegalStateException e) {
925 return tr("Illegal expression ''{0}''", exp);
926 }
927 catch (PatternSyntaxException e) {
928 return tr("Illegal regular expression ''{0}''", exp);
929 }
930 }
931 return null;
932 }
933
934 public boolean match(OsmPrimitive osm, Map<String, String> keys) {
935 if (type != null && OsmPrimitiveType.from(osm) != type)
936 return false;
937
938 for (CheckerElement ce : data) {
939 if (!ce.match(osm, keys))
940 return false;
941 }
942 return true;
943 }
944
945 public String getDescription() {
946 return tr(description);
947 }
948
949 public String getDescriptionOrig() {
950 return description;
951 }
952
953 public Severity getSeverity() {
954 return severity;
955 }
956
957 public int getCode() {
958 if (type == null)
959 return code;
960
961 return code + type.ordinal() + 1;
962 }
963 }
964}
Note: See TracBrowser for help on using the repository browser.