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

Last change on this file since 8846 was 8846, checked in by Don-vip, 9 years ago

sonar - fb-contrib - minor performance improvements:

  • Method passes constant String of length 1 to character overridden method
  • Method needlessly boxes a boolean constant
  • Method uses iterator().next() on a List to get the first item
  • Method converts String to boxed primitive using excessive boxing
  • Method converts String to primitive using excessive boxing
  • Method creates array using constants
  • Class defines List based fields but uses them like Sets
  • Property svn:eol-style set to native
File size: 35.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
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.GridBagConstraints;
8import java.awt.event.ActionEvent;
9import java.awt.event.ActionListener;
10import java.io.BufferedReader;
11import java.io.IOException;
12import java.io.InputStream;
13import java.text.MessageFormat;
14import java.util.ArrayList;
15import java.util.Arrays;
16import java.util.Collection;
17import java.util.HashMap;
18import java.util.List;
19import java.util.Locale;
20import java.util.Map;
21import java.util.Map.Entry;
22import java.util.Set;
23import java.util.regex.Matcher;
24import java.util.regex.Pattern;
25import java.util.regex.PatternSyntaxException;
26
27import javax.swing.JCheckBox;
28import javax.swing.JLabel;
29import javax.swing.JPanel;
30
31import org.openstreetmap.josm.Main;
32import org.openstreetmap.josm.command.ChangePropertyCommand;
33import org.openstreetmap.josm.command.ChangePropertyKeyCommand;
34import org.openstreetmap.josm.command.Command;
35import org.openstreetmap.josm.command.SequenceCommand;
36import org.openstreetmap.josm.data.osm.OsmPrimitive;
37import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
38import org.openstreetmap.josm.data.osm.OsmUtils;
39import org.openstreetmap.josm.data.osm.Tag;
40import org.openstreetmap.josm.data.validation.FixableTestError;
41import org.openstreetmap.josm.data.validation.Severity;
42import org.openstreetmap.josm.data.validation.Test.TagTest;
43import org.openstreetmap.josm.data.validation.TestError;
44import org.openstreetmap.josm.data.validation.util.Entities;
45import org.openstreetmap.josm.gui.preferences.validator.ValidatorPreference;
46import org.openstreetmap.josm.gui.progress.ProgressMonitor;
47import org.openstreetmap.josm.gui.tagging.TaggingPreset;
48import org.openstreetmap.josm.gui.tagging.TaggingPresetItem;
49import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Check;
50import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.CheckGroup;
51import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.KeyedItem;
52import org.openstreetmap.josm.gui.tagging.TaggingPresets;
53import org.openstreetmap.josm.gui.widgets.EditableList;
54import org.openstreetmap.josm.io.CachedFile;
55import org.openstreetmap.josm.io.UTFInputStreamReader;
56import org.openstreetmap.josm.tools.GBC;
57import org.openstreetmap.josm.tools.MultiMap;
58import org.openstreetmap.josm.tools.Utils;
59
60/**
61 * Check for misspelled or wrong tags
62 *
63 * @author frsantos
64 * @since 3669
65 */
66public class TagChecker extends TagTest {
67
68 /** The config file of ignored tags */
69 public static final String IGNORE_FILE = "resource://data/validator/ignoretags.cfg";
70 /** The config file of dictionary words */
71 public static final String SPELL_FILE = "resource://data/validator/words.cfg";
72
73 /** Normalized keys: the key should be substituted by the value if the key was not found in presets */
74 private static final Map<String, String> harmonizedKeys = new HashMap<>();
75 /** The spell check preset values */
76 private static volatile MultiMap<String, String> presetsValueData;
77 /** The TagChecker data */
78 private static final List<CheckerData> checkerData = new ArrayList<>();
79 private static final List<String> ignoreDataStartsWith = new ArrayList<>();
80 private static final List<String> ignoreDataEquals = new ArrayList<>();
81 private static final List<String> ignoreDataEndsWith = new ArrayList<>();
82 private static final List<IgnoreKeyPair> ignoreDataKeyPair = new ArrayList<>();
83
84 /** The preferences prefix */
85 protected static final String PREFIX = ValidatorPreference.PREFIX + "." + TagChecker.class.getSimpleName();
86
87 public static final String PREF_CHECK_VALUES = PREFIX + ".checkValues";
88 public static final String PREF_CHECK_KEYS = PREFIX + ".checkKeys";
89 public static final String PREF_CHECK_COMPLEX = PREFIX + ".checkComplex";
90 public static final String PREF_CHECK_FIXMES = PREFIX + ".checkFixmes";
91
92 public static final String PREF_SOURCES = PREFIX + ".source";
93
94 public static final String PREF_CHECK_KEYS_BEFORE_UPLOAD = PREF_CHECK_KEYS + "BeforeUpload";
95 public static final String PREF_CHECK_VALUES_BEFORE_UPLOAD = PREF_CHECK_VALUES + "BeforeUpload";
96 public static final String PREF_CHECK_COMPLEX_BEFORE_UPLOAD = PREF_CHECK_COMPLEX + "BeforeUpload";
97 public static final String PREF_CHECK_FIXMES_BEFORE_UPLOAD = PREF_CHECK_FIXMES + "BeforeUpload";
98
99 protected boolean checkKeys;
100 protected boolean checkValues;
101 protected boolean checkComplex;
102 protected boolean checkFixmes;
103
104 protected JCheckBox prefCheckKeys;
105 protected JCheckBox prefCheckValues;
106 protected JCheckBox prefCheckComplex;
107 protected JCheckBox prefCheckFixmes;
108 protected JCheckBox prefCheckPaint;
109
110 protected JCheckBox prefCheckKeysBeforeUpload;
111 protected JCheckBox prefCheckValuesBeforeUpload;
112 protected JCheckBox prefCheckComplexBeforeUpload;
113 protected JCheckBox prefCheckFixmesBeforeUpload;
114 protected JCheckBox prefCheckPaintBeforeUpload;
115
116 protected static final int EMPTY_VALUES = 1200;
117 protected static final int INVALID_KEY = 1201;
118 protected static final int INVALID_VALUE = 1202;
119 protected static final int FIXME = 1203;
120 protected static final int INVALID_SPACE = 1204;
121 protected static final int INVALID_KEY_SPACE = 1205;
122 protected static final int INVALID_HTML = 1206; /* 1207 was PAINT */
123 protected static final int LONG_VALUE = 1208;
124 protected static final int LONG_KEY = 1209;
125 protected static final int LOW_CHAR_VALUE = 1210;
126 protected static final int LOW_CHAR_KEY = 1211;
127 protected static final int MISSPELLED_VALUE = 1212;
128 protected static final int MISSPELLED_KEY = 1213;
129 /** 1250 and up is used by tagcheck */
130
131 protected EditableList sourcesList;
132
133 protected static final Entities entities = new Entities();
134
135 private static final List<String> DEFAULT_SOURCES = Arrays.asList(/*DATA_FILE, */IGNORE_FILE, SPELL_FILE);
136
137 /**
138 * Constructor
139 */
140 public TagChecker() {
141 super(tr("Tag checker"), tr("This test checks for errors in tag keys and values."));
142 }
143
144 @Override
145 public void initialize() throws IOException {
146 initializeData();
147 initializePresets();
148 }
149
150 /**
151 * Reads the spellcheck file into a HashMap.
152 * The data file is a list of words, beginning with +/-. If it starts with +,
153 * the word is valid, but if it starts with -, the word should be replaced
154 * by the nearest + word before this.
155 *
156 * @throws IOException if any I/O error occurs
157 */
158 private static void initializeData() throws IOException {
159 checkerData.clear();
160 ignoreDataStartsWith.clear();
161 ignoreDataEquals.clear();
162 ignoreDataEndsWith.clear();
163 ignoreDataKeyPair.clear();
164 harmonizedKeys.clear();
165
166 String errorSources = "";
167 for (String source : Main.pref.getCollection(PREF_SOURCES, DEFAULT_SOURCES)) {
168 try (
169 InputStream s = new CachedFile(source).getInputStream();
170 BufferedReader reader = new BufferedReader(UTFInputStreamReader.create(s));
171 ) {
172 String okValue = null;
173 boolean tagcheckerfile = false;
174 boolean ignorefile = false;
175 boolean isFirstLine = true;
176 String line;
177 while ((line = reader.readLine()) != null && (tagcheckerfile || !line.isEmpty())) {
178 if (line.startsWith("#")) {
179 if (line.startsWith("# JOSM TagChecker")) {
180 tagcheckerfile = true;
181 if (!DEFAULT_SOURCES.contains(source)) {
182 Main.info(tr("Adding {0} to tag checker", source));
183 }
184 } else
185 if (line.startsWith("# JOSM IgnoreTags")) {
186 ignorefile = true;
187 if (!DEFAULT_SOURCES.contains(source)) {
188 Main.info(tr("Adding {0} to ignore tags", source));
189 }
190 }
191 } else if (ignorefile) {
192 line = line.trim();
193 if (line.length() < 4) {
194 continue;
195 }
196
197 String key = line.substring(0, 2);
198 line = line.substring(2);
199
200 switch (key) {
201 case "S:":
202 ignoreDataStartsWith.add(line);
203 break;
204 case "E:":
205 ignoreDataEquals.add(line);
206 break;
207 case "F:":
208 ignoreDataEndsWith.add(line);
209 break;
210 case "K:":
211 IgnoreKeyPair tmp = new IgnoreKeyPair();
212 int mid = line.indexOf('=');
213 tmp.key = line.substring(0, mid);
214 tmp.value = line.substring(mid+1);
215 ignoreDataKeyPair.add(tmp);
216 }
217 } else if (tagcheckerfile) {
218 if (!line.isEmpty()) {
219 CheckerData d = new CheckerData();
220 String err = d.getData(line);
221
222 if (err == null) {
223 checkerData.add(d);
224 } else {
225 Main.error(tr("Invalid tagchecker line - {0}: {1}", err, line));
226 }
227 }
228 } else if (line.charAt(0) == '+') {
229 okValue = line.substring(1);
230 } else if (line.charAt(0) == '-' && okValue != null) {
231 harmonizedKeys.put(harmonizeKey(line.substring(1)), okValue);
232 } else {
233 Main.error(tr("Invalid spellcheck line: {0}", line));
234 }
235 if (isFirstLine) {
236 isFirstLine = false;
237 if (!(tagcheckerfile || ignorefile) && !DEFAULT_SOURCES.contains(source)) {
238 Main.info(tr("Adding {0} to spellchecker", source));
239 }
240 }
241 }
242 } catch (IOException e) {
243 errorSources += source + '\n';
244 }
245 }
246
247 if (!errorSources.isEmpty())
248 throw new IOException(tr("Could not access data file(s):\n{0}", errorSources));
249 }
250
251 /**
252 * Reads the presets data.
253 *
254 */
255 public static void initializePresets() {
256
257 if (!Main.pref.getBoolean(PREF_CHECK_VALUES, true))
258 return;
259
260 Collection<TaggingPreset> presets = TaggingPresets.getTaggingPresets();
261 if (!presets.isEmpty()) {
262 presetsValueData = new MultiMap<>();
263 for (String a : OsmPrimitive.getUninterestingKeys()) {
264 presetsValueData.putVoid(a);
265 }
266 // TODO directionKeys are no longer in OsmPrimitive (search pattern is used instead)
267 /* for (String a : OsmPrimitive.getDirectionKeys())
268 presetsValueData.add(a);
269 */
270 for (String a : Main.pref.getCollection(ValidatorPreference.PREFIX + ".knownkeys",
271 Arrays.asList(new String[]{"is_in", "int_ref", "fixme", "population"}))) {
272 presetsValueData.putVoid(a);
273 }
274 for (TaggingPreset p : presets) {
275 for (TaggingPresetItem i : p.data) {
276 if (i instanceof KeyedItem) {
277 addPresetValue(p, (KeyedItem) i);
278 } else if (i instanceof CheckGroup) {
279 for (Check c : ((CheckGroup) i).checks) {
280 addPresetValue(p, c);
281 }
282 }
283 }
284 }
285 }
286 }
287
288 private static void addPresetValue(TaggingPreset p, KeyedItem ky) {
289 Collection<String> values = ky.getValues();
290 if (ky.key != null && values != null) {
291 try {
292 presetsValueData.putAll(ky.key, values);
293 harmonizedKeys.put(harmonizeKey(ky.key), ky.key);
294 } catch (NullPointerException e) {
295 Main.error(p+": Unable to initialize "+ky);
296 }
297 }
298 }
299
300 /**
301 * Checks given string (key or value) if it contains characters with code below 0x20 (either newline or some other special characters)
302 * @param s string to check
303 */
304 private boolean containsLow(String s) {
305 if (s == null)
306 return false;
307 for (int i = 0; i < s.length(); i++) {
308 if (s.charAt(i) < 0x20)
309 return true;
310 }
311 return false;
312 }
313
314 /**
315 * Checks the primitive tags
316 * @param p The primitive to check
317 */
318 @Override
319 public void check(OsmPrimitive p) {
320 // Just a collection to know if a primitive has been already marked with error
321 MultiMap<OsmPrimitive, String> withErrors = new MultiMap<>();
322
323 if (checkComplex) {
324 Map<String, String> keys = p.getKeys();
325 for (CheckerData d : checkerData) {
326 if (d.match(p, keys)) {
327 errors.add(new TestError(this, d.getSeverity(), tr("Suspicious tag/value combinations"),
328 d.getDescription(), d.getDescriptionOrig(), d.getCode(), p));
329 withErrors.put(p, "TC");
330 }
331 }
332 }
333
334 for (Entry<String, String> prop : p.getKeys().entrySet()) {
335 String s = marktr("Key ''{0}'' invalid.");
336 String key = prop.getKey();
337 String value = prop.getValue();
338 if (checkValues && (containsLow(value)) && !withErrors.contains(p, "ICV")) {
339 errors.add(new TestError(this, Severity.WARNING, tr("Tag value contains character with code less than 0x20"),
340 tr(s, key), MessageFormat.format(s, key), LOW_CHAR_VALUE, p));
341 withErrors.put(p, "ICV");
342 }
343 if (checkKeys && (containsLow(key)) && !withErrors.contains(p, "ICK")) {
344 errors.add(new TestError(this, Severity.WARNING, tr("Tag key contains character with code less than 0x20"),
345 tr(s, key), MessageFormat.format(s, key), LOW_CHAR_KEY, p));
346 withErrors.put(p, "ICK");
347 }
348 if (checkValues && (value != null && value.length() > 255) && !withErrors.contains(p, "LV")) {
349 errors.add(new TestError(this, Severity.ERROR, tr("Tag value longer than allowed"),
350 tr(s, key), MessageFormat.format(s, key), LONG_VALUE, p));
351 withErrors.put(p, "LV");
352 }
353 if (checkKeys && (key != null && key.length() > 255) && !withErrors.contains(p, "LK")) {
354 errors.add(new TestError(this, Severity.ERROR, tr("Tag key longer than allowed"),
355 tr(s, key), MessageFormat.format(s, key), LONG_KEY, p));
356 withErrors.put(p, "LK");
357 }
358 if (checkValues && (value == null || value.trim().isEmpty()) && !withErrors.contains(p, "EV")) {
359 errors.add(new TestError(this, Severity.WARNING, tr("Tags with empty values"),
360 tr(s, key), MessageFormat.format(s, key), EMPTY_VALUES, p));
361 withErrors.put(p, "EV");
362 }
363 if (checkKeys && key != null && key.indexOf(' ') >= 0 && !withErrors.contains(p, "IPK")) {
364 errors.add(new TestError(this, Severity.WARNING, tr("Invalid white space in property key"),
365 tr(s, key), MessageFormat.format(s, key), INVALID_KEY_SPACE, p));
366 withErrors.put(p, "IPK");
367 }
368 if (checkValues && value != null && (value.startsWith(" ") || value.endsWith(" ")) && !withErrors.contains(p, "SPACE")) {
369 errors.add(new TestError(this, Severity.WARNING, tr("Property values start or end with white space"),
370 tr(s, key), MessageFormat.format(s, key), INVALID_SPACE, p));
371 withErrors.put(p, "SPACE");
372 }
373 if (checkValues && value != null && !value.equals(entities.unescape(value)) && !withErrors.contains(p, "HTML")) {
374 errors.add(new TestError(this, Severity.OTHER, tr("Property values contain HTML entity"),
375 tr(s, key), MessageFormat.format(s, key), INVALID_HTML, p));
376 withErrors.put(p, "HTML");
377 }
378 if (checkValues && key != null && value != null && !value.isEmpty() && presetsValueData != null) {
379 final Set<String> values = presetsValueData.get(key);
380 final boolean keyInPresets = values != null;
381 final boolean tagInPresets = values != null && (values.isEmpty() || values.contains(prop.getValue()));
382
383 boolean ignore = false;
384 for (String a : ignoreDataStartsWith) {
385 if (key.startsWith(a)) {
386 ignore = true;
387 }
388 }
389 for (String a : ignoreDataEquals) {
390 if (key.equals(a)) {
391 ignore = true;
392 }
393 }
394 for (String a : ignoreDataEndsWith) {
395 if (key.endsWith(a)) {
396 ignore = true;
397 }
398 }
399
400 if (!tagInPresets) {
401 for (IgnoreKeyPair a : ignoreDataKeyPair) {
402 if (key.equals(a.key) && value.equals(a.value)) {
403 ignore = true;
404 }
405 }
406 }
407
408 if (!ignore) {
409 if (!keyInPresets) {
410 String prettifiedKey = harmonizeKey(key);
411 String fixedKey = harmonizedKeys.get(prettifiedKey);
412 if (fixedKey != null && !"".equals(fixedKey)) {
413 // misspelled preset key
414 String i = marktr("Key ''{0}'' looks like ''{1}''.");
415 errors.add(new FixableTestError(this, Severity.WARNING, tr("Misspelled property key"),
416 tr(i, key, fixedKey),
417 MessageFormat.format(i, key, fixedKey), MISSPELLED_KEY, p,
418 new ChangePropertyKeyCommand(p, key, fixedKey)));
419 withErrors.put(p, "WPK");
420 } else {
421 String i = marktr("Key ''{0}'' not in presets.");
422 errors.add(new TestError(this, Severity.OTHER, tr("Presets do not contain property key"),
423 tr(i, key), MessageFormat.format(i, key), INVALID_VALUE, p));
424 withErrors.put(p, "UPK");
425 }
426 } else if (!tagInPresets) {
427 // try to fix common typos and check again if value is still unknown
428 String fixedValue = harmonizeValue(prop.getValue());
429 Map<String, String> possibleValues = getPossibleValues(values);
430 if (possibleValues.containsKey(fixedValue)) {
431 fixedValue = possibleValues.get(fixedValue);
432 // misspelled preset value
433 String i = marktr("Value ''{0}'' for key ''{1}'' looks like ''{2}''.");
434 errors.add(new FixableTestError(this, Severity.WARNING, tr("Misspelled property value"),
435 tr(i, prop.getValue(), key, fixedValue), MessageFormat.format(i, prop.getValue(), fixedValue),
436 MISSPELLED_VALUE, p, new ChangePropertyCommand(p, key, fixedValue)));
437 withErrors.put(p, "WPV");
438 } else {
439 // unknown preset value
440 String i = marktr("Value ''{0}'' for key ''{1}'' not in presets.");
441 errors.add(new TestError(this, Severity.OTHER, tr("Presets do not contain property value"),
442 tr(i, prop.getValue(), key), MessageFormat.format(i, prop.getValue(), key), INVALID_VALUE, p));
443 withErrors.put(p, "UPV");
444 }
445 }
446 }
447 }
448 if (checkFixmes && key != null && value != null && !value.isEmpty()) {
449 if ((value.toLowerCase(Locale.ENGLISH).contains("fixme")
450 || value.contains("check and delete")
451 || key.contains("todo") || key.toLowerCase(Locale.ENGLISH).contains("fixme"))
452 && !withErrors.contains(p, "FIXME")) {
453 errors.add(new TestError(this, Severity.OTHER,
454 tr("FIXMES"), FIXME, p));
455 withErrors.put(p, "FIXME");
456 }
457 }
458 }
459 }
460
461 private Map<String, String> getPossibleValues(Set<String> values) {
462 // generate a map with common typos
463 Map<String, String> map = new HashMap<>();
464 if (values != null) {
465 for (String value : values) {
466 map.put(value, value);
467 if (value.contains("_")) {
468 map.put(value.replace("_", ""), value);
469 }
470 }
471 }
472 return map;
473 }
474
475 private static String harmonizeKey(String key) {
476 key = key.toLowerCase(Locale.ENGLISH).replace('-', '_').replace(':', '_').replace(' ', '_');
477 return Utils.strip(key, "-_;:,");
478 }
479
480 private static String harmonizeValue(String value) {
481 value = value.toLowerCase(Locale.ENGLISH).replace('-', '_').replace(' ', '_');
482 return Utils.strip(value, "-_;:,");
483 }
484
485 @Override
486 public void startTest(ProgressMonitor monitor) {
487 super.startTest(monitor);
488 checkKeys = Main.pref.getBoolean(PREF_CHECK_KEYS, true);
489 if (isBeforeUpload) {
490 checkKeys = checkKeys && Main.pref.getBoolean(PREF_CHECK_KEYS_BEFORE_UPLOAD, true);
491 }
492
493 checkValues = Main.pref.getBoolean(PREF_CHECK_VALUES, true);
494 if (isBeforeUpload) {
495 checkValues = checkValues && Main.pref.getBoolean(PREF_CHECK_VALUES_BEFORE_UPLOAD, true);
496 }
497
498 checkComplex = Main.pref.getBoolean(PREF_CHECK_COMPLEX, true);
499 if (isBeforeUpload) {
500 checkComplex = checkValues && Main.pref.getBoolean(PREF_CHECK_COMPLEX_BEFORE_UPLOAD, true);
501 }
502
503 checkFixmes = Main.pref.getBoolean(PREF_CHECK_FIXMES, true);
504 if (isBeforeUpload) {
505 checkFixmes = checkFixmes && Main.pref.getBoolean(PREF_CHECK_FIXMES_BEFORE_UPLOAD, true);
506 }
507 }
508
509 @Override
510 public void visit(Collection<OsmPrimitive> selection) {
511 if (checkKeys || checkValues || checkComplex || checkFixmes) {
512 super.visit(selection);
513 }
514 }
515
516 @Override
517 public void addGui(JPanel testPanel) {
518 GBC a = GBC.eol();
519 a.anchor = GridBagConstraints.EAST;
520
521 testPanel.add(new JLabel(name+" :"), GBC.eol().insets(3, 0, 0, 0));
522
523 prefCheckKeys = new JCheckBox(tr("Check property keys."), Main.pref.getBoolean(PREF_CHECK_KEYS, true));
524 prefCheckKeys.setToolTipText(tr("Validate that property keys are valid checking against list of words."));
525 testPanel.add(prefCheckKeys, GBC.std().insets(20, 0, 0, 0));
526
527 prefCheckKeysBeforeUpload = new JCheckBox();
528 prefCheckKeysBeforeUpload.setSelected(Main.pref.getBoolean(PREF_CHECK_KEYS_BEFORE_UPLOAD, true));
529 testPanel.add(prefCheckKeysBeforeUpload, a);
530
531 prefCheckComplex = new JCheckBox(tr("Use complex property checker."), Main.pref.getBoolean(PREF_CHECK_COMPLEX, true));
532 prefCheckComplex.setToolTipText(tr("Validate property values and tags using complex rules."));
533 testPanel.add(prefCheckComplex, GBC.std().insets(20, 0, 0, 0));
534
535 prefCheckComplexBeforeUpload = new JCheckBox();
536 prefCheckComplexBeforeUpload.setSelected(Main.pref.getBoolean(PREF_CHECK_COMPLEX_BEFORE_UPLOAD, true));
537 testPanel.add(prefCheckComplexBeforeUpload, a);
538
539 final Collection<String> sources = Main.pref.getCollection(PREF_SOURCES, DEFAULT_SOURCES);
540 sourcesList = new EditableList(tr("TagChecker source"));
541 sourcesList.setItems(sources);
542 testPanel.add(new JLabel(tr("Data sources ({0})", "*.cfg")), GBC.eol().insets(23, 0, 0, 0));
543 testPanel.add(sourcesList, GBC.eol().fill(GridBagConstraints.HORIZONTAL).insets(23, 0, 0, 0));
544
545 ActionListener disableCheckActionListener = new ActionListener() {
546 @Override
547 public void actionPerformed(ActionEvent e) {
548 handlePrefEnable();
549 }
550 };
551 prefCheckKeys.addActionListener(disableCheckActionListener);
552 prefCheckKeysBeforeUpload.addActionListener(disableCheckActionListener);
553 prefCheckComplex.addActionListener(disableCheckActionListener);
554 prefCheckComplexBeforeUpload.addActionListener(disableCheckActionListener);
555
556 handlePrefEnable();
557
558 prefCheckValues = new JCheckBox(tr("Check property values."), Main.pref.getBoolean(PREF_CHECK_VALUES, true));
559 prefCheckValues.setToolTipText(tr("Validate that property values are valid checking against presets."));
560 testPanel.add(prefCheckValues, GBC.std().insets(20, 0, 0, 0));
561
562 prefCheckValuesBeforeUpload = new JCheckBox();
563 prefCheckValuesBeforeUpload.setSelected(Main.pref.getBoolean(PREF_CHECK_VALUES_BEFORE_UPLOAD, true));
564 testPanel.add(prefCheckValuesBeforeUpload, a);
565
566 prefCheckFixmes = new JCheckBox(tr("Check for FIXMES."), Main.pref.getBoolean(PREF_CHECK_FIXMES, true));
567 prefCheckFixmes.setToolTipText(tr("Looks for nodes or ways with FIXME in any property value."));
568 testPanel.add(prefCheckFixmes, GBC.std().insets(20, 0, 0, 0));
569
570 prefCheckFixmesBeforeUpload = new JCheckBox();
571 prefCheckFixmesBeforeUpload.setSelected(Main.pref.getBoolean(PREF_CHECK_FIXMES_BEFORE_UPLOAD, true));
572 testPanel.add(prefCheckFixmesBeforeUpload, a);
573 }
574
575 public void handlePrefEnable() {
576 boolean selected = prefCheckKeys.isSelected() || prefCheckKeysBeforeUpload.isSelected()
577 || prefCheckComplex.isSelected() || prefCheckComplexBeforeUpload.isSelected();
578 sourcesList.setEnabled(selected);
579 }
580
581 @Override
582 public boolean ok() {
583 enabled = prefCheckKeys.isSelected() || prefCheckValues.isSelected() || prefCheckComplex.isSelected() || prefCheckFixmes.isSelected();
584 testBeforeUpload = prefCheckKeysBeforeUpload.isSelected() || prefCheckValuesBeforeUpload.isSelected()
585 || prefCheckFixmesBeforeUpload.isSelected() || prefCheckComplexBeforeUpload.isSelected();
586
587 Main.pref.put(PREF_CHECK_VALUES, prefCheckValues.isSelected());
588 Main.pref.put(PREF_CHECK_COMPLEX, prefCheckComplex.isSelected());
589 Main.pref.put(PREF_CHECK_KEYS, prefCheckKeys.isSelected());
590 Main.pref.put(PREF_CHECK_FIXMES, prefCheckFixmes.isSelected());
591 Main.pref.put(PREF_CHECK_VALUES_BEFORE_UPLOAD, prefCheckValuesBeforeUpload.isSelected());
592 Main.pref.put(PREF_CHECK_COMPLEX_BEFORE_UPLOAD, prefCheckComplexBeforeUpload.isSelected());
593 Main.pref.put(PREF_CHECK_KEYS_BEFORE_UPLOAD, prefCheckKeysBeforeUpload.isSelected());
594 Main.pref.put(PREF_CHECK_FIXMES_BEFORE_UPLOAD, prefCheckFixmesBeforeUpload.isSelected());
595 return Main.pref.putCollection(PREF_SOURCES, sourcesList.getItems());
596 }
597
598 @Override
599 public Command fixError(TestError testError) {
600 List<Command> commands = new ArrayList<>(50);
601
602 if (testError instanceof FixableTestError) {
603 commands.add(testError.getFix());
604 } else {
605 Collection<? extends OsmPrimitive> primitives = testError.getPrimitives();
606 for (OsmPrimitive p : primitives) {
607 Map<String, String> tags = p.getKeys();
608 if (tags == null || tags.isEmpty()) {
609 continue;
610 }
611
612 for (Entry<String, String> prop: tags.entrySet()) {
613 String key = prop.getKey();
614 String value = prop.getValue();
615 if (value == null || value.trim().isEmpty()) {
616 commands.add(new ChangePropertyCommand(p, key, null));
617 } else if (value.startsWith(" ") || value.endsWith(" ")) {
618 commands.add(new ChangePropertyCommand(p, key, Tag.removeWhiteSpaces(value)));
619 } else if (key.startsWith(" ") || key.endsWith(" ")) {
620 commands.add(new ChangePropertyKeyCommand(p, key, Tag.removeWhiteSpaces(key)));
621 } else {
622 String evalue = entities.unescape(value);
623 if (!evalue.equals(value)) {
624 commands.add(new ChangePropertyCommand(p, key, evalue));
625 }
626 }
627 }
628 }
629 }
630
631 if (commands.isEmpty())
632 return null;
633 if (commands.size() == 1)
634 return commands.get(0);
635
636 return new SequenceCommand(tr("Fix tags"), commands);
637 }
638
639 @Override
640 public boolean isFixable(TestError testError) {
641 if (testError.getTester() instanceof TagChecker) {
642 int code = testError.getCode();
643 return code == INVALID_KEY || code == EMPTY_VALUES || code == INVALID_SPACE ||
644 code == INVALID_KEY_SPACE || code == INVALID_HTML || code == MISSPELLED_VALUE;
645 }
646
647 return false;
648 }
649
650 protected static class IgnoreKeyPair {
651 public String key;
652 public String value;
653 }
654
655 protected static class CheckerData {
656 private String description;
657 protected List<CheckerElement> data = new ArrayList<>();
658 private OsmPrimitiveType type;
659 private int code;
660 protected Severity severity;
661 protected static final int TAG_CHECK_ERROR = 1250;
662 protected static final int TAG_CHECK_WARN = 1260;
663 protected static final int TAG_CHECK_INFO = 1270;
664
665 protected static class CheckerElement {
666 public Object tag;
667 public Object value;
668 public boolean noMatch;
669 public boolean tagAll;
670 public boolean valueAll;
671 public boolean valueBool;
672
673 private Pattern getPattern(String str) throws PatternSyntaxException {
674 if (str.endsWith("/i"))
675 return Pattern.compile(str.substring(1, str.length()-2), Pattern.CASE_INSENSITIVE);
676 if (str.endsWith("/"))
677 return Pattern.compile(str.substring(1, str.length()-1));
678
679 throw new IllegalStateException();
680 }
681
682 public CheckerElement(String exp) throws PatternSyntaxException {
683 Matcher m = Pattern.compile("(.+)([!=]=)(.+)").matcher(exp);
684 m.matches();
685
686 String n = m.group(1).trim();
687
688 if ("*".equals(n)) {
689 tagAll = true;
690 } else {
691 tag = n.startsWith("/") ? getPattern(n) : n;
692 noMatch = "!=".equals(m.group(2));
693 n = m.group(3).trim();
694 if ("*".equals(n)) {
695 valueAll = true;
696 } else if ("BOOLEAN_TRUE".equals(n)) {
697 valueBool = true;
698 value = OsmUtils.trueval;
699 } else if ("BOOLEAN_FALSE".equals(n)) {
700 valueBool = true;
701 value = OsmUtils.falseval;
702 } else {
703 value = n.startsWith("/") ? getPattern(n) : n;
704 }
705 }
706 }
707
708 public boolean match(OsmPrimitive osm, Map<String, String> keys) {
709 for (Entry<String, String> prop: keys.entrySet()) {
710 String key = prop.getKey();
711 String val = valueBool ? OsmUtils.getNamedOsmBoolean(prop.getValue()) : prop.getValue();
712 if ((tagAll || (tag instanceof Pattern ? ((Pattern) tag).matcher(key).matches() : key.equals(tag)))
713 && (valueAll || (value instanceof Pattern ? ((Pattern) value).matcher(val).matches() : val.equals(value))))
714 return !noMatch;
715 }
716 return noMatch;
717 }
718 }
719
720 private static final Pattern CLEAN_STR_PATTERN = Pattern.compile(" *# *([^#]+) *$");
721 private static final Pattern SPLIT_TRIMMED_PATTERN = Pattern.compile(" *: *");
722 private static final Pattern SPLIT_ELEMENTS_PATTERN = Pattern.compile(" *&& *");
723
724 public String getData(final String str) {
725 Matcher m = CLEAN_STR_PATTERN.matcher(str);
726 String trimmed = m.replaceFirst("").trim();
727 try {
728 description = m.group(1);
729 if (description != null && description.isEmpty()) {
730 description = null;
731 }
732 } catch (IllegalStateException e) {
733 description = null;
734 }
735 String[] n = SPLIT_TRIMMED_PATTERN.split(trimmed, 3);
736 switch (n[0]) {
737 case "way":
738 type = OsmPrimitiveType.WAY;
739 break;
740 case "node":
741 type = OsmPrimitiveType.NODE;
742 break;
743 case "relation":
744 type = OsmPrimitiveType.RELATION;
745 break;
746 case "*":
747 type = null;
748 break;
749 default:
750 return tr("Could not find element type");
751 }
752 if (n.length != 3)
753 return tr("Incorrect number of parameters");
754
755 switch (n[1]) {
756 case "W":
757 severity = Severity.WARNING;
758 code = TAG_CHECK_WARN;
759 break;
760 case "E":
761 severity = Severity.ERROR;
762 code = TAG_CHECK_ERROR;
763 break;
764 case "I":
765 severity = Severity.OTHER;
766 code = TAG_CHECK_INFO;
767 break;
768 default:
769 return tr("Could not find warning level");
770 }
771 for (String exp: SPLIT_ELEMENTS_PATTERN.split(n[2])) {
772 try {
773 data.add(new CheckerElement(exp));
774 } catch (IllegalStateException e) {
775 return tr("Illegal expression ''{0}''", exp);
776 } catch (PatternSyntaxException e) {
777 return tr("Illegal regular expression ''{0}''", exp);
778 }
779 }
780 return null;
781 }
782
783 public boolean match(OsmPrimitive osm, Map<String, String> keys) {
784 if (type != null && OsmPrimitiveType.from(osm) != type)
785 return false;
786
787 for (CheckerElement ce : data) {
788 if (!ce.match(osm, keys))
789 return false;
790 }
791 return true;
792 }
793
794 public String getDescription() {
795 return tr(description);
796 }
797
798 public String getDescriptionOrig() {
799 return description;
800 }
801
802 public Severity getSeverity() {
803 return severity;
804 }
805
806 public int getCode() {
807 if (type == null)
808 return code;
809
810 return code + type.ordinal() + 1;
811 }
812 }
813}
Note: See TracBrowser for help on using the repository browser.