source: josm/trunk/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java@ 6532

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

fix #8687, see #9414, see #9470 - tagchecker: update numeric tests to new MapCSS format, with embedded unit tests. MapCSS syntax updated a bit for regex.

File size: 10.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.tr;
5
6import java.io.InputStreamReader;
7import java.io.Reader;
8import java.io.UnsupportedEncodingException;
9import java.util.ArrayList;
10import java.util.Collection;
11import java.util.HashMap;
12import java.util.LinkedHashMap;
13import java.util.LinkedList;
14import java.util.List;
15import java.util.Map;
16
17import org.openstreetmap.josm.command.ChangePropertyCommand;
18import org.openstreetmap.josm.command.ChangePropertyKeyCommand;
19import org.openstreetmap.josm.command.Command;
20import org.openstreetmap.josm.command.SequenceCommand;
21import org.openstreetmap.josm.data.osm.Node;
22import org.openstreetmap.josm.data.osm.OsmPrimitive;
23import org.openstreetmap.josm.data.osm.Relation;
24import org.openstreetmap.josm.data.osm.Tag;
25import org.openstreetmap.josm.data.osm.Way;
26import org.openstreetmap.josm.data.validation.FixableTestError;
27import org.openstreetmap.josm.data.validation.Severity;
28import org.openstreetmap.josm.data.validation.Test;
29import org.openstreetmap.josm.data.validation.TestError;
30import org.openstreetmap.josm.gui.mappaint.Environment;
31import org.openstreetmap.josm.gui.mappaint.mapcss.ExpressionFactory;
32import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction;
33import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule;
34import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
35import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
36import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser;
37import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException;
38import org.openstreetmap.josm.tools.CheckParameterUtil;
39import org.openstreetmap.josm.tools.Predicate;
40import org.openstreetmap.josm.tools.Utils;
41
42/**
43 * MapCSS-based tag checker/fixer.
44 * @since 6506
45 */
46public class MapCSSTagChecker extends Test {
47
48 /**
49 * Constructs a new {@code MapCSSTagChecker}.
50 */
51 public MapCSSTagChecker() {
52 super(tr("Tag checker (new)"), tr("This test checks for errors in tag keys and values."));
53 }
54
55 final List<TagCheck> checks = new ArrayList<TagCheck>();
56
57 static class TagCheck implements Predicate<OsmPrimitive> {
58 protected final List<Selector> selector;
59 protected final List<Tag> change = new ArrayList<Tag>();
60 protected final Map<String, String> keyChange = new LinkedHashMap<String, String>();
61 protected final List<Tag> alternatives = new ArrayList<Tag>();
62 protected final Map<String, Severity> errors = new HashMap<String, Severity>();
63 protected final Map<String, Boolean> assertions = new HashMap<String, Boolean>();
64
65 TagCheck(List<Selector> selector) {
66 this.selector = selector;
67 }
68
69 static TagCheck ofMapCSSRule(final MapCSSRule rule) {
70 final TagCheck check = new TagCheck(rule.selectors);
71 for (Instruction i : rule.declaration) {
72 if (i instanceof Instruction.AssignmentInstruction) {
73 final Instruction.AssignmentInstruction ai = (Instruction.AssignmentInstruction) i;
74 final String val = ai.val instanceof ExpressionFactory.ArrayFunction
75 ? (String) ((ExpressionFactory.ArrayFunction) ai.val).evaluate(new Environment())
76 : ai.val instanceof String
77 ? (String) ai.val
78 : null;
79 if (ai.key.startsWith("throw")) {
80 final Severity severity = Severity.valueOf(ai.key.substring("throw".length()).toUpperCase());
81 check.errors.put(val, severity);
82 } else if ("fixAdd".equals(ai.key) && val != null) {
83 check.change.add(Tag.ofString(val));
84 } else if ("fixRemove".equals(ai.key) && val != null) {
85 CheckParameterUtil.ensureThat(!val.contains("="), "Unexpected '='. Please only specify the key to remove!");
86 check.change.add(new Tag(val));
87 } else if ("fixChangeKey".equals(ai.key) && val != null) {
88 CheckParameterUtil.ensureThat(val.contains("=>"), "Separate old from new key by '=>'!");
89 final String[] x = val.split("=>", 2);
90 check.keyChange.put(x[0].trim(), x[1].trim());
91 } else if ("suggestAlternative".equals(ai.key) && val != null) {
92 check.alternatives.add(val.contains("=") ? Tag.ofString(val) : new Tag(val));
93 } else if ("assertMatch".equals(ai.key) && val != null) {
94 check.assertions.put(val, true);
95 } else if ("assertNoMatch".equals(ai.key) && val != null) {
96 check.assertions.put(val, false);
97 } else {
98 throw new RuntimeException("Cannot add instruction " + ai.key + ": " + ai.val + "!");
99 }
100 }
101 }
102 if (check.errors.isEmpty()) {
103 throw new RuntimeException("No throwError/throwWarning/throwOther given! You should specify a validation error message for " + rule.selectors);
104 } else if (check.errors.size() > 1) {
105 throw new RuntimeException("More than one throwError/throwWarning/throwOther given! You should specify a single validation error message for " + rule.selectors);
106 }
107 return check;
108 }
109
110 static List<TagCheck> readMapCSS(Reader css) throws ParseException {
111 CheckParameterUtil.ensureParameterNotNull(css, "css");
112 return readMapCSS(new MapCSSParser(css));
113 }
114
115 static List<TagCheck> readMapCSS(MapCSSParser css) throws ParseException {
116 CheckParameterUtil.ensureParameterNotNull(css, "css");
117 final MapCSSStyleSource source = new MapCSSStyleSource("");
118 css.sheet(source);
119 assert source.getErrors().isEmpty();
120 return new ArrayList<TagCheck>(Utils.transform(source.rules, new Utils.Function<MapCSSRule, TagCheck>() {
121 @Override
122 public TagCheck apply(MapCSSRule x) {
123 return TagCheck.ofMapCSSRule(x);
124 }
125 }));
126 }
127
128 @Override
129 public boolean evaluate(OsmPrimitive primitive) {
130 return matchesPrimitive(primitive);
131 }
132
133 /**
134 * Tests whether the {@link OsmPrimitive} contains a deprecated tag which is represented by this {@code MapCSSTagChecker}.
135 *
136 * @param primitive the primitive to test
137 * @return true when the primitive contains a deprecated tag
138 */
139 boolean matchesPrimitive(OsmPrimitive primitive) {
140 final Environment env = new Environment().withPrimitive(primitive);
141 for (Selector i : selector) {
142 if (i.matches(env)) {
143 return true;
144 }
145 }
146 return false;
147 }
148
149 /**
150 * Constructs a fix in terms of a {@link org.openstreetmap.josm.command.Command} for the {@link OsmPrimitive}
151 * if the error is fixable, or {@code null} otherwise.
152 *
153 * @param p the primitive to construct the fix for
154 * @return the fix or {@code null}
155 */
156 Command fixPrimitive(OsmPrimitive p) {
157 if (change.isEmpty() && keyChange.isEmpty()) {
158 return null;
159 }
160 Collection<Command> cmds = new LinkedList<Command>();
161 for (Tag tag : change) {
162 cmds.add(new ChangePropertyCommand(p, tag.getKey(), tag.getValue()));
163 }
164 for (Map.Entry<String, String> i : keyChange.entrySet()) {
165 cmds.add(new ChangePropertyKeyCommand(p, i.getKey(), i.getValue()));
166 }
167 return new SequenceCommand(tr("Fix of {0}", getDescription()), cmds);
168 }
169
170 /**
171 * Constructs a (localized) message for this deprecation check.
172 *
173 * @return a message
174 */
175 String getMessage() {
176 return errors.keySet().iterator().next();
177 }
178
179 /**
180 * Constructs a (localized) description for this deprecation check.
181 *
182 * @return a description (possibly with alternative suggestions)
183 */
184 String getDescription() {
185 if (alternatives.isEmpty()) {
186 return getMessage();
187 } else {
188 /* I18N: {0} is the test error message and {1} is an alternative */
189 return tr("{0}, use {1} instead", getMessage(), Utils.join(tr(" or "), alternatives));
190 }
191 }
192
193 Severity getSeverity() {
194 return errors.values().iterator().next();
195 }
196
197 }
198
199 /**
200 * Visiting call for primitives.
201 *
202 * @param p The primitive to inspect.
203 */
204 public void visit(OsmPrimitive p) {
205 for (TagCheck check : checks) {
206 if (check.matchesPrimitive(p)) {
207 final Command fix = check.fixPrimitive(p);
208 if (fix != null) {
209 errors.add(new FixableTestError(this, check.getSeverity(), check.getDescription(), 3000, p, fix));
210 } else {
211 errors.add(new TestError(this, check.getSeverity(), check.getDescription(), 3000, p));
212 }
213 }
214 }
215 }
216
217 @Override
218 public void visit(Node n) {
219 visit((OsmPrimitive) n);
220 }
221
222 @Override
223 public void visit(Way w) {
224 visit((OsmPrimitive) w);
225 }
226
227 @Override
228 public void visit(Relation r) {
229 visit((OsmPrimitive) r);
230 }
231
232 /**
233 * Adds a new MapCSS config file from the given {@code Reader}.
234 * @param css The reader
235 * @throws ParseException if the config file does not match MapCSS syntax
236 */
237 public void addMapCSS(Reader css) throws ParseException {
238 checks.addAll(TagCheck.readMapCSS(css));
239 }
240
241 /**
242 * Adds a new MapCSS config file from the given internal filename.
243 * @param internalConfigFile the filename in data/validator
244 * @throws ParseException if the config file does not match MapCSS syntax
245 * @throws UnsupportedEncodingException if UTF-8 charset is not supported on the platform
246 */
247 private void addMapCSS(String internalConfigFile) throws ParseException, UnsupportedEncodingException {
248 addMapCSS(new InputStreamReader(getClass().getResourceAsStream("/data/validator/"+internalConfigFile), "UTF-8"));
249 }
250
251 @Override
252 public void initialize() throws Exception {
253 addMapCSS("deprecated.mapcss");
254 addMapCSS("highway.mapcss");
255 addMapCSS("numeric.mapcss");
256 }
257}
Note: See TracBrowser for help on using the repository browser.