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

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

fix Sonar issue squid:S2444 - Lazy initialization of "static" fields should be "synchronized"

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