| 1 | // License: GPL. For details, see LICENSE file.
|
|---|
| 2 | package org.openstreetmap.josm.data.validation.tests;
|
|---|
| 3 |
|
|---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
|---|
| 5 |
|
|---|
| 6 | import java.io.BufferedReader;
|
|---|
| 7 | import java.io.IOException;
|
|---|
| 8 | import java.io.InputStream;
|
|---|
| 9 | import java.io.Reader;
|
|---|
| 10 | import java.io.StringReader;
|
|---|
| 11 | import java.text.MessageFormat;
|
|---|
| 12 | import java.util.ArrayList;
|
|---|
| 13 | import java.util.Arrays;
|
|---|
| 14 | import java.util.Collection;
|
|---|
| 15 | import java.util.Collections;
|
|---|
| 16 | import java.util.HashMap;
|
|---|
| 17 | import java.util.HashSet;
|
|---|
| 18 | import java.util.Iterator;
|
|---|
| 19 | import java.util.LinkedHashMap;
|
|---|
| 20 | import java.util.LinkedHashSet;
|
|---|
| 21 | import java.util.LinkedList;
|
|---|
| 22 | import java.util.List;
|
|---|
| 23 | import java.util.Locale;
|
|---|
| 24 | import java.util.Map;
|
|---|
| 25 | import java.util.Objects;
|
|---|
| 26 | import java.util.Set;
|
|---|
| 27 | import java.util.function.Predicate;
|
|---|
| 28 | import java.util.regex.Matcher;
|
|---|
| 29 | import java.util.regex.Pattern;
|
|---|
| 30 |
|
|---|
| 31 | import org.openstreetmap.josm.Main;
|
|---|
| 32 | import org.openstreetmap.josm.command.ChangePropertyCommand;
|
|---|
| 33 | import org.openstreetmap.josm.command.ChangePropertyKeyCommand;
|
|---|
| 34 | import org.openstreetmap.josm.command.Command;
|
|---|
| 35 | import org.openstreetmap.josm.command.DeleteCommand;
|
|---|
| 36 | import org.openstreetmap.josm.command.SequenceCommand;
|
|---|
| 37 | import org.openstreetmap.josm.data.osm.DataSet;
|
|---|
| 38 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
|---|
| 39 | import org.openstreetmap.josm.data.osm.OsmUtils;
|
|---|
| 40 | import org.openstreetmap.josm.data.osm.Tag;
|
|---|
| 41 | import org.openstreetmap.josm.data.preferences.sources.SourceEntry;
|
|---|
| 42 | import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
|
|---|
| 43 | import org.openstreetmap.josm.data.validation.OsmValidator;
|
|---|
| 44 | import org.openstreetmap.josm.data.validation.Severity;
|
|---|
| 45 | import org.openstreetmap.josm.data.validation.Test;
|
|---|
| 46 | import org.openstreetmap.josm.data.validation.TestError;
|
|---|
| 47 | import org.openstreetmap.josm.gui.mappaint.Environment;
|
|---|
| 48 | import org.openstreetmap.josm.gui.mappaint.Keyword;
|
|---|
| 49 | import org.openstreetmap.josm.gui.mappaint.MultiCascade;
|
|---|
| 50 | import org.openstreetmap.josm.gui.mappaint.mapcss.Condition;
|
|---|
| 51 | import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.ClassCondition;
|
|---|
| 52 | import org.openstreetmap.josm.gui.mappaint.mapcss.Expression;
|
|---|
| 53 | import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction;
|
|---|
| 54 | import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule;
|
|---|
| 55 | import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule.Declaration;
|
|---|
| 56 | import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
|
|---|
| 57 | import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
|
|---|
| 58 | import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.AbstractSelector;
|
|---|
| 59 | import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector;
|
|---|
| 60 | import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser;
|
|---|
| 61 | import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException;
|
|---|
| 62 | import org.openstreetmap.josm.io.CachedFile;
|
|---|
| 63 | import org.openstreetmap.josm.io.IllegalDataException;
|
|---|
| 64 | import org.openstreetmap.josm.io.UTFInputStreamReader;
|
|---|
| 65 | import org.openstreetmap.josm.tools.CheckParameterUtil;
|
|---|
| 66 | import org.openstreetmap.josm.tools.Logging;
|
|---|
| 67 | import org.openstreetmap.josm.tools.MultiMap;
|
|---|
| 68 | import org.openstreetmap.josm.tools.Utils;
|
|---|
| 69 |
|
|---|
| 70 | /**
|
|---|
| 71 | * MapCSS-based tag checker/fixer.
|
|---|
| 72 | * @since 6506
|
|---|
| 73 | */
|
|---|
| 74 | public class MapCSSTagChecker extends Test.TagTest {
|
|---|
| 75 |
|
|---|
| 76 | /**
|
|---|
| 77 | * A grouped MapCSSRule with multiple selectors for a single declaration.
|
|---|
| 78 | * @see MapCSSRule
|
|---|
| 79 | */
|
|---|
| 80 | public static class GroupedMapCSSRule {
|
|---|
| 81 | /** MapCSS selectors **/
|
|---|
| 82 | public final List<Selector> selectors;
|
|---|
| 83 | /** MapCSS declaration **/
|
|---|
| 84 | public final Declaration declaration;
|
|---|
| 85 |
|
|---|
| 86 | /**
|
|---|
| 87 | * Constructs a new {@code GroupedMapCSSRule}.
|
|---|
| 88 | * @param selectors MapCSS selectors
|
|---|
| 89 | * @param declaration MapCSS declaration
|
|---|
| 90 | */
|
|---|
| 91 | public GroupedMapCSSRule(List<Selector> selectors, Declaration declaration) {
|
|---|
| 92 | this.selectors = selectors;
|
|---|
| 93 | this.declaration = declaration;
|
|---|
| 94 | }
|
|---|
| 95 |
|
|---|
| 96 | @Override
|
|---|
| 97 | public int hashCode() {
|
|---|
| 98 | return Objects.hash(selectors, declaration);
|
|---|
| 99 | }
|
|---|
| 100 |
|
|---|
| 101 | @Override
|
|---|
| 102 | public boolean equals(Object obj) {
|
|---|
| 103 | if (this == obj) return true;
|
|---|
| 104 | if (obj == null || getClass() != obj.getClass()) return false;
|
|---|
| 105 | GroupedMapCSSRule that = (GroupedMapCSSRule) obj;
|
|---|
| 106 | return Objects.equals(selectors, that.selectors) &&
|
|---|
| 107 | Objects.equals(declaration, that.declaration);
|
|---|
| 108 | }
|
|---|
| 109 |
|
|---|
| 110 | @Override
|
|---|
| 111 | public String toString() {
|
|---|
| 112 | return "GroupedMapCSSRule [selectors=" + selectors + ", declaration=" + declaration + ']';
|
|---|
| 113 | }
|
|---|
| 114 | }
|
|---|
| 115 |
|
|---|
| 116 | /**
|
|---|
| 117 | * The preference key for tag checker source entries.
|
|---|
| 118 | * @since 6670
|
|---|
| 119 | */
|
|---|
| 120 | public static final String ENTRIES_PREF_KEY = "validator." + MapCSSTagChecker.class.getName() + ".entries";
|
|---|
| 121 |
|
|---|
| 122 | /**
|
|---|
| 123 | * Constructs a new {@code MapCSSTagChecker}.
|
|---|
| 124 | */
|
|---|
| 125 | public MapCSSTagChecker() {
|
|---|
| 126 | super(tr("Tag checker (MapCSS based)"), tr("This test checks for errors in tag keys and values."));
|
|---|
| 127 | }
|
|---|
| 128 |
|
|---|
| 129 | /**
|
|---|
| 130 | * Represents a fix to a validation test. The fixing {@link Command} can be obtained by {@link #createCommand(OsmPrimitive, Selector)}.
|
|---|
| 131 | */
|
|---|
| 132 | @FunctionalInterface
|
|---|
| 133 | interface FixCommand {
|
|---|
| 134 | /**
|
|---|
| 135 | * Creates the fixing {@link Command} for the given primitive. The {@code matchingSelector} is used to evaluate placeholders
|
|---|
| 136 | * (cf. {@link MapCSSTagChecker.TagCheck#insertArguments(Selector, String, OsmPrimitive)}).
|
|---|
| 137 | * @param p OSM primitive
|
|---|
| 138 | * @param matchingSelector matching selector
|
|---|
| 139 | * @return fix command
|
|---|
| 140 | */
|
|---|
| 141 | Command createCommand(OsmPrimitive p, Selector matchingSelector);
|
|---|
| 142 |
|
|---|
| 143 | /**
|
|---|
| 144 | * Checks that object is either an {@link Expression} or a {@link String}.
|
|---|
| 145 | * @param obj object to check
|
|---|
| 146 | * @throws IllegalArgumentException if object is not an {@code Expression} or a {@code String}
|
|---|
| 147 | */
|
|---|
| 148 | static void checkObject(final Object obj) {
|
|---|
| 149 | CheckParameterUtil.ensureThat(obj instanceof Expression || obj instanceof String,
|
|---|
| 150 | () -> "instance of Exception or String expected, but got " + obj);
|
|---|
| 151 | }
|
|---|
| 152 |
|
|---|
| 153 | /**
|
|---|
| 154 | * Evaluates given object as {@link Expression} or {@link String} on the matched {@link OsmPrimitive} and {@code matchingSelector}.
|
|---|
| 155 | * @param obj object to evaluate ({@link Expression} or {@link String})
|
|---|
| 156 | * @param p OSM primitive
|
|---|
| 157 | * @param matchingSelector matching selector
|
|---|
| 158 | * @return result string
|
|---|
| 159 | */
|
|---|
| 160 | static String evaluateObject(final Object obj, final OsmPrimitive p, final Selector matchingSelector) {
|
|---|
| 161 | final String s;
|
|---|
| 162 | if (obj instanceof Expression) {
|
|---|
| 163 | s = (String) ((Expression) obj).evaluate(new Environment(p));
|
|---|
| 164 | } else if (obj instanceof String) {
|
|---|
| 165 | s = (String) obj;
|
|---|
| 166 | } else {
|
|---|
| 167 | return null;
|
|---|
| 168 | }
|
|---|
| 169 | return TagCheck.insertArguments(matchingSelector, s, p);
|
|---|
| 170 | }
|
|---|
| 171 |
|
|---|
| 172 | /**
|
|---|
| 173 | * Creates a fixing command which executes a {@link ChangePropertyCommand} on the specified tag.
|
|---|
| 174 | * @param obj object to evaluate ({@link Expression} or {@link String})
|
|---|
| 175 | * @return created fix command
|
|---|
| 176 | */
|
|---|
| 177 | static FixCommand fixAdd(final Object obj) {
|
|---|
| 178 | checkObject(obj);
|
|---|
| 179 | return new FixCommand() {
|
|---|
| 180 | @Override
|
|---|
| 181 | public Command createCommand(OsmPrimitive p, Selector matchingSelector) {
|
|---|
| 182 | final Tag tag = Tag.ofString(evaluateObject(obj, p, matchingSelector));
|
|---|
| 183 | return new ChangePropertyCommand(p, tag.getKey(), tag.getValue());
|
|---|
| 184 | }
|
|---|
| 185 |
|
|---|
| 186 | @Override
|
|---|
| 187 | public String toString() {
|
|---|
| 188 | return "fixAdd: " + obj;
|
|---|
| 189 | }
|
|---|
| 190 | };
|
|---|
| 191 | }
|
|---|
| 192 |
|
|---|
| 193 | /**
|
|---|
| 194 | * Creates a fixing command which executes a {@link ChangePropertyCommand} to delete the specified key.
|
|---|
| 195 | * @param obj object to evaluate ({@link Expression} or {@link String})
|
|---|
| 196 | * @return created fix command
|
|---|
| 197 | */
|
|---|
| 198 | static FixCommand fixRemove(final Object obj) {
|
|---|
| 199 | checkObject(obj);
|
|---|
| 200 | return new FixCommand() {
|
|---|
| 201 | @Override
|
|---|
| 202 | public Command createCommand(OsmPrimitive p, Selector matchingSelector) {
|
|---|
| 203 | final String key = evaluateObject(obj, p, matchingSelector);
|
|---|
| 204 | return new ChangePropertyCommand(p, key, "");
|
|---|
| 205 | }
|
|---|
| 206 |
|
|---|
| 207 | @Override
|
|---|
| 208 | public String toString() {
|
|---|
| 209 | return "fixRemove: " + obj;
|
|---|
| 210 | }
|
|---|
| 211 | };
|
|---|
| 212 | }
|
|---|
| 213 |
|
|---|
| 214 | /**
|
|---|
| 215 | * Creates a fixing command which executes a {@link ChangePropertyKeyCommand} on the specified keys.
|
|---|
| 216 | * @param oldKey old key
|
|---|
| 217 | * @param newKey new key
|
|---|
| 218 | * @return created fix command
|
|---|
| 219 | */
|
|---|
| 220 | static FixCommand fixChangeKey(final String oldKey, final String newKey) {
|
|---|
| 221 | return new FixCommand() {
|
|---|
| 222 | @Override
|
|---|
| 223 | public Command createCommand(OsmPrimitive p, Selector matchingSelector) {
|
|---|
| 224 | return new ChangePropertyKeyCommand(p,
|
|---|
| 225 | TagCheck.insertArguments(matchingSelector, oldKey, p),
|
|---|
| 226 | TagCheck.insertArguments(matchingSelector, newKey, p));
|
|---|
| 227 | }
|
|---|
| 228 |
|
|---|
| 229 | @Override
|
|---|
| 230 | public String toString() {
|
|---|
| 231 | return "fixChangeKey: " + oldKey + " => " + newKey;
|
|---|
| 232 | }
|
|---|
| 233 | };
|
|---|
| 234 | }
|
|---|
| 235 | }
|
|---|
| 236 |
|
|---|
| 237 | final MultiMap<String, TagCheck> checks = new MultiMap<>();
|
|---|
| 238 |
|
|---|
| 239 | /**
|
|---|
| 240 | * Result of {@link TagCheck#readMapCSS}
|
|---|
| 241 | * @since 8936
|
|---|
| 242 | */
|
|---|
| 243 | public static class ParseResult {
|
|---|
| 244 | /** Checks successfully parsed */
|
|---|
| 245 | public final List<TagCheck> parseChecks;
|
|---|
| 246 | /** Errors that occured during parsing */
|
|---|
| 247 | public final Collection<Throwable> parseErrors;
|
|---|
| 248 |
|
|---|
| 249 | /**
|
|---|
| 250 | * Constructs a new {@code ParseResult}.
|
|---|
| 251 | * @param parseChecks Checks successfully parsed
|
|---|
| 252 | * @param parseErrors Errors that occured during parsing
|
|---|
| 253 | */
|
|---|
| 254 | public ParseResult(List<TagCheck> parseChecks, Collection<Throwable> parseErrors) {
|
|---|
| 255 | this.parseChecks = parseChecks;
|
|---|
| 256 | this.parseErrors = parseErrors;
|
|---|
| 257 | }
|
|---|
| 258 | }
|
|---|
| 259 |
|
|---|
| 260 | /**
|
|---|
| 261 | * Tag check.
|
|---|
| 262 | */
|
|---|
| 263 | public static class TagCheck implements Predicate<OsmPrimitive> {
|
|---|
| 264 | /** The selector of this {@code TagCheck} */
|
|---|
| 265 | protected final GroupedMapCSSRule rule;
|
|---|
| 266 | /** Commands to apply in order to fix a matching primitive */
|
|---|
| 267 | protected final List<FixCommand> fixCommands = new ArrayList<>();
|
|---|
| 268 | /** Tags (or arbitraty strings) of alternatives to be presented to the user */
|
|---|
| 269 | protected final List<String> alternatives = new ArrayList<>();
|
|---|
| 270 | /** An {@link org.openstreetmap.josm.gui.mappaint.mapcss.Instruction.AssignmentInstruction}-{@link Severity} pair.
|
|---|
| 271 | * Is evaluated on the matching primitive to give the error message. Map is checked to contain exactly one element. */
|
|---|
| 272 | protected final Map<Instruction.AssignmentInstruction, Severity> errors = new HashMap<>();
|
|---|
| 273 | /** Unit tests */
|
|---|
| 274 | protected final Map<String, Boolean> assertions = new HashMap<>();
|
|---|
| 275 | /** MapCSS Classes to set on matching primitives */
|
|---|
| 276 | protected final Set<String> setClassExpressions = new HashSet<>();
|
|---|
| 277 | /** Denotes whether the object should be deleted for fixing it */
|
|---|
| 278 | protected boolean deletion;
|
|---|
| 279 | /** A string used to group similar tests */
|
|---|
| 280 | protected String group;
|
|---|
| 281 |
|
|---|
| 282 | TagCheck(GroupedMapCSSRule rule) {
|
|---|
| 283 | this.rule = rule;
|
|---|
| 284 | }
|
|---|
| 285 |
|
|---|
| 286 | private static final String POSSIBLE_THROWS = possibleThrows();
|
|---|
| 287 |
|
|---|
| 288 | static final String possibleThrows() {
|
|---|
| 289 | StringBuilder sb = new StringBuilder();
|
|---|
| 290 | for (Severity s : Severity.values()) {
|
|---|
| 291 | if (sb.length() > 0) {
|
|---|
| 292 | sb.append('/');
|
|---|
| 293 | }
|
|---|
| 294 | sb.append("throw")
|
|---|
| 295 | .append(s.name().charAt(0))
|
|---|
| 296 | .append(s.name().substring(1).toLowerCase(Locale.ENGLISH));
|
|---|
| 297 | }
|
|---|
| 298 | return sb.toString();
|
|---|
| 299 | }
|
|---|
| 300 |
|
|---|
| 301 | static TagCheck ofMapCSSRule(final GroupedMapCSSRule rule) throws IllegalDataException {
|
|---|
| 302 | final TagCheck check = new TagCheck(rule);
|
|---|
| 303 | for (Instruction i : rule.declaration.instructions) {
|
|---|
| 304 | if (i instanceof Instruction.AssignmentInstruction) {
|
|---|
| 305 | final Instruction.AssignmentInstruction ai = (Instruction.AssignmentInstruction) i;
|
|---|
| 306 | if (ai.isSetInstruction) {
|
|---|
| 307 | check.setClassExpressions.add(ai.key);
|
|---|
| 308 | continue;
|
|---|
| 309 | }
|
|---|
| 310 | try {
|
|---|
| 311 | final String val = ai.val instanceof Expression
|
|---|
| 312 | ? (String) ((Expression) ai.val).evaluate(new Environment())
|
|---|
| 313 | : ai.val instanceof String
|
|---|
| 314 | ? (String) ai.val
|
|---|
| 315 | : ai.val instanceof Keyword
|
|---|
| 316 | ? ((Keyword) ai.val).val
|
|---|
| 317 | : null;
|
|---|
| 318 | if (ai.key.startsWith("throw")) {
|
|---|
| 319 | try {
|
|---|
| 320 | check.errors.put(ai, Severity.valueOf(ai.key.substring("throw".length()).toUpperCase(Locale.ENGLISH)));
|
|---|
| 321 | } catch (IllegalArgumentException e) {
|
|---|
| 322 | Logging.log(Logging.LEVEL_WARN,
|
|---|
| 323 | "Unsupported "+ai.key+" instruction. Allowed instructions are "+POSSIBLE_THROWS+'.', e);
|
|---|
| 324 | }
|
|---|
| 325 | } else if ("fixAdd".equals(ai.key)) {
|
|---|
| 326 | check.fixCommands.add(FixCommand.fixAdd(ai.val));
|
|---|
| 327 | } else if ("fixRemove".equals(ai.key)) {
|
|---|
| 328 | CheckParameterUtil.ensureThat(!(ai.val instanceof String) || !(val != null && val.contains("=")),
|
|---|
| 329 | "Unexpected '='. Please only specify the key to remove!");
|
|---|
| 330 | check.fixCommands.add(FixCommand.fixRemove(ai.val));
|
|---|
| 331 | } else if (val != null && "fixChangeKey".equals(ai.key)) {
|
|---|
| 332 | CheckParameterUtil.ensureThat(val.contains("=>"), "Separate old from new key by '=>'!");
|
|---|
| 333 | final String[] x = val.split("=>", 2);
|
|---|
| 334 | check.fixCommands.add(FixCommand.fixChangeKey(Tag.removeWhiteSpaces(x[0]), Tag.removeWhiteSpaces(x[1])));
|
|---|
| 335 | } else if (val != null && "fixDeleteObject".equals(ai.key)) {
|
|---|
| 336 | CheckParameterUtil.ensureThat("this".equals(val), "fixDeleteObject must be followed by 'this'");
|
|---|
| 337 | check.deletion = true;
|
|---|
| 338 | } else if (val != null && "suggestAlternative".equals(ai.key)) {
|
|---|
| 339 | check.alternatives.add(val);
|
|---|
| 340 | } else if (val != null && "assertMatch".equals(ai.key)) {
|
|---|
| 341 | check.assertions.put(val, Boolean.TRUE);
|
|---|
| 342 | } else if (val != null && "assertNoMatch".equals(ai.key)) {
|
|---|
| 343 | check.assertions.put(val, Boolean.FALSE);
|
|---|
| 344 | } else if (val != null && "group".equals(ai.key)) {
|
|---|
| 345 | check.group = val;
|
|---|
| 346 | } else {
|
|---|
| 347 | throw new IllegalDataException("Cannot add instruction " + ai.key + ": " + ai.val + '!');
|
|---|
| 348 | }
|
|---|
| 349 | } catch (IllegalArgumentException e) {
|
|---|
| 350 | throw new IllegalDataException(e);
|
|---|
| 351 | }
|
|---|
| 352 | }
|
|---|
| 353 | }
|
|---|
| 354 | if (check.errors.isEmpty() && check.setClassExpressions.isEmpty()) {
|
|---|
| 355 | throw new IllegalDataException(
|
|---|
| 356 | "No "+POSSIBLE_THROWS+" given! You should specify a validation error message for " + rule.selectors);
|
|---|
| 357 | } else if (check.errors.size() > 1) {
|
|---|
| 358 | throw new IllegalDataException(
|
|---|
| 359 | "More than one "+POSSIBLE_THROWS+" given! You should specify a single validation error message for "
|
|---|
| 360 | + rule.selectors);
|
|---|
| 361 | }
|
|---|
| 362 | return check;
|
|---|
| 363 | }
|
|---|
| 364 |
|
|---|
| 365 | static ParseResult readMapCSS(Reader css) throws ParseException {
|
|---|
| 366 | CheckParameterUtil.ensureParameterNotNull(css, "css");
|
|---|
| 367 |
|
|---|
| 368 | final MapCSSStyleSource source = new MapCSSStyleSource("");
|
|---|
| 369 | final MapCSSParser preprocessor = new MapCSSParser(css, MapCSSParser.LexicalState.PREPROCESSOR);
|
|---|
| 370 | final StringReader mapcss = new StringReader(preprocessor.pp_root(source));
|
|---|
| 371 | final MapCSSParser parser = new MapCSSParser(mapcss, MapCSSParser.LexicalState.DEFAULT);
|
|---|
| 372 | parser.sheet(source);
|
|---|
| 373 | // Ignore "meta" rule(s) from external rules of JOSM wiki
|
|---|
| 374 | removeMetaRules(source);
|
|---|
| 375 | // group rules with common declaration block
|
|---|
| 376 | Map<Declaration, List<Selector>> g = new LinkedHashMap<>();
|
|---|
| 377 | for (MapCSSRule rule : source.rules) {
|
|---|
| 378 | if (!g.containsKey(rule.declaration)) {
|
|---|
| 379 | List<Selector> sels = new ArrayList<>();
|
|---|
| 380 | sels.add(rule.selector);
|
|---|
| 381 | g.put(rule.declaration, sels);
|
|---|
| 382 | } else {
|
|---|
| 383 | g.get(rule.declaration).add(rule.selector);
|
|---|
| 384 | }
|
|---|
| 385 | }
|
|---|
| 386 | List<TagCheck> parseChecks = new ArrayList<>();
|
|---|
| 387 | for (Map.Entry<Declaration, List<Selector>> map : g.entrySet()) {
|
|---|
| 388 | try {
|
|---|
| 389 | parseChecks.add(TagCheck.ofMapCSSRule(
|
|---|
| 390 | new GroupedMapCSSRule(map.getValue(), map.getKey())));
|
|---|
| 391 | } catch (IllegalDataException e) {
|
|---|
| 392 | Logging.error("Cannot add MapCss rule: "+e.getMessage());
|
|---|
| 393 | source.logError(e);
|
|---|
| 394 | }
|
|---|
| 395 | }
|
|---|
| 396 | return new ParseResult(parseChecks, source.getErrors());
|
|---|
| 397 | }
|
|---|
| 398 |
|
|---|
| 399 | private static void removeMetaRules(MapCSSStyleSource source) {
|
|---|
| 400 | for (Iterator<MapCSSRule> it = source.rules.iterator(); it.hasNext();) {
|
|---|
| 401 | MapCSSRule x = it.next();
|
|---|
| 402 | if (x.selector instanceof GeneralSelector) {
|
|---|
| 403 | GeneralSelector gs = (GeneralSelector) x.selector;
|
|---|
| 404 | if ("meta".equals(gs.base)) {
|
|---|
| 405 | it.remove();
|
|---|
| 406 | }
|
|---|
| 407 | }
|
|---|
| 408 | }
|
|---|
| 409 | }
|
|---|
| 410 |
|
|---|
| 411 | @Override
|
|---|
| 412 | public boolean test(OsmPrimitive primitive) {
|
|---|
| 413 | // Tests whether the primitive contains a deprecated tag which is represented by this MapCSSTagChecker.
|
|---|
| 414 | return whichSelectorMatchesPrimitive(primitive) != null;
|
|---|
| 415 | }
|
|---|
| 416 |
|
|---|
| 417 | Selector whichSelectorMatchesPrimitive(OsmPrimitive primitive) {
|
|---|
| 418 | return whichSelectorMatchesEnvironment(new Environment(primitive));
|
|---|
| 419 | }
|
|---|
| 420 |
|
|---|
| 421 | Selector whichSelectorMatchesEnvironment(Environment env) {
|
|---|
| 422 | for (Selector i : rule.selectors) {
|
|---|
| 423 | env.clearSelectorMatchingInformation();
|
|---|
| 424 | if (i.matches(env)) {
|
|---|
| 425 | return i;
|
|---|
| 426 | }
|
|---|
| 427 | }
|
|---|
| 428 | return null;
|
|---|
| 429 | }
|
|---|
| 430 |
|
|---|
| 431 | /**
|
|---|
| 432 | * Determines the {@code index}-th key/value/tag (depending on {@code type}) of the
|
|---|
| 433 | * {@link org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector}.
|
|---|
| 434 | * @param matchingSelector matching selector
|
|---|
| 435 | * @param index index
|
|---|
| 436 | * @param type selector type ("key", "value" or "tag")
|
|---|
| 437 | * @param p OSM primitive
|
|---|
| 438 | * @return argument value, can be {@code null}
|
|---|
| 439 | */
|
|---|
| 440 | static String determineArgument(Selector.GeneralSelector matchingSelector, int index, String type, OsmPrimitive p) {
|
|---|
| 441 | try {
|
|---|
| 442 | final Condition c = matchingSelector.getConditions().get(index);
|
|---|
| 443 | final Tag tag = c instanceof Condition.ToTagConvertable
|
|---|
| 444 | ? ((Condition.ToTagConvertable) c).asTag(p)
|
|---|
| 445 | : null;
|
|---|
| 446 | if (tag == null) {
|
|---|
| 447 | return null;
|
|---|
| 448 | } else if ("key".equals(type)) {
|
|---|
| 449 | return tag.getKey();
|
|---|
| 450 | } else if ("value".equals(type)) {
|
|---|
| 451 | return tag.getValue();
|
|---|
| 452 | } else if ("tag".equals(type)) {
|
|---|
| 453 | return tag.toString();
|
|---|
| 454 | }
|
|---|
| 455 | } catch (IndexOutOfBoundsException ignore) {
|
|---|
| 456 | Logging.debug(ignore);
|
|---|
| 457 | }
|
|---|
| 458 | return null;
|
|---|
| 459 | }
|
|---|
| 460 |
|
|---|
| 461 | /**
|
|---|
| 462 | * Replaces occurrences of <code>{i.key}</code>, <code>{i.value}</code>, <code>{i.tag}</code> in {@code s} by the corresponding
|
|---|
| 463 | * key/value/tag of the {@code index}-th {@link Condition} of {@code matchingSelector}.
|
|---|
| 464 | * @param matchingSelector matching selector
|
|---|
| 465 | * @param s any string
|
|---|
| 466 | * @param p OSM primitive
|
|---|
| 467 | * @return string with arguments inserted
|
|---|
| 468 | */
|
|---|
| 469 | static String insertArguments(Selector matchingSelector, String s, OsmPrimitive p) {
|
|---|
| 470 | if (s != null && matchingSelector instanceof Selector.ChildOrParentSelector) {
|
|---|
| 471 | return insertArguments(((Selector.ChildOrParentSelector) matchingSelector).right, s, p);
|
|---|
| 472 | } else if (s == null || !(matchingSelector instanceof GeneralSelector)) {
|
|---|
| 473 | return s;
|
|---|
| 474 | }
|
|---|
| 475 | final Matcher m = Pattern.compile("\\{(\\d+)\\.(key|value|tag)\\}").matcher(s);
|
|---|
| 476 | final StringBuffer sb = new StringBuffer();
|
|---|
| 477 | while (m.find()) {
|
|---|
| 478 | final String argument = determineArgument((Selector.GeneralSelector) matchingSelector,
|
|---|
| 479 | Integer.parseInt(m.group(1)), m.group(2), p);
|
|---|
| 480 | try {
|
|---|
| 481 | // Perform replacement with null-safe + regex-safe handling
|
|---|
| 482 | m.appendReplacement(sb, String.valueOf(argument).replace("^(", "").replace(")$", ""));
|
|---|
| 483 | } catch (IndexOutOfBoundsException | IllegalArgumentException e) {
|
|---|
| 484 | Logging.log(Logging.LEVEL_ERROR, tr("Unable to replace argument {0} in {1}: {2}", argument, sb, e.getMessage()), e);
|
|---|
| 485 | }
|
|---|
| 486 | }
|
|---|
| 487 | m.appendTail(sb);
|
|---|
| 488 | return sb.toString();
|
|---|
| 489 | }
|
|---|
| 490 |
|
|---|
| 491 | /**
|
|---|
| 492 | * Constructs a fix in terms of a {@link org.openstreetmap.josm.command.Command} for the {@link OsmPrimitive}
|
|---|
| 493 | * if the error is fixable, or {@code null} otherwise.
|
|---|
| 494 | *
|
|---|
| 495 | * @param p the primitive to construct the fix for
|
|---|
| 496 | * @return the fix or {@code null}
|
|---|
| 497 | */
|
|---|
| 498 | Command fixPrimitive(OsmPrimitive p) {
|
|---|
| 499 | if (fixCommands.isEmpty() && !deletion) {
|
|---|
| 500 | return null;
|
|---|
| 501 | }
|
|---|
| 502 | final Selector matchingSelector = whichSelectorMatchesPrimitive(p);
|
|---|
| 503 | Collection<Command> cmds = new LinkedList<>();
|
|---|
| 504 | for (FixCommand fixCommand : fixCommands) {
|
|---|
| 505 | cmds.add(fixCommand.createCommand(p, matchingSelector));
|
|---|
| 506 | }
|
|---|
| 507 | if (deletion && !p.isDeleted()) {
|
|---|
| 508 | cmds.add(new DeleteCommand(p));
|
|---|
| 509 | }
|
|---|
| 510 | return new SequenceCommand(tr("Fix of {0}", getDescriptionForMatchingSelector(p, matchingSelector)), cmds);
|
|---|
| 511 | }
|
|---|
| 512 |
|
|---|
| 513 | /**
|
|---|
| 514 | * Constructs a (localized) message for this deprecation check.
|
|---|
| 515 | * @param p OSM primitive
|
|---|
| 516 | *
|
|---|
| 517 | * @return a message
|
|---|
| 518 | */
|
|---|
| 519 | String getMessage(OsmPrimitive p) {
|
|---|
| 520 | if (errors.isEmpty()) {
|
|---|
| 521 | // Return something to avoid NPEs
|
|---|
| 522 | return rule.declaration.toString();
|
|---|
| 523 | } else {
|
|---|
| 524 | final Object val = errors.keySet().iterator().next().val;
|
|---|
| 525 | return String.valueOf(
|
|---|
| 526 | val instanceof Expression
|
|---|
| 527 | ? ((Expression) val).evaluate(new Environment(p))
|
|---|
| 528 | : val
|
|---|
| 529 | );
|
|---|
| 530 | }
|
|---|
| 531 | }
|
|---|
| 532 |
|
|---|
| 533 | /**
|
|---|
| 534 | * Constructs a (localized) description for this deprecation check.
|
|---|
| 535 | * @param p OSM primitive
|
|---|
| 536 | *
|
|---|
| 537 | * @return a description (possibly with alternative suggestions)
|
|---|
| 538 | * @see #getDescriptionForMatchingSelector
|
|---|
| 539 | */
|
|---|
| 540 | String getDescription(OsmPrimitive p) {
|
|---|
| 541 | if (alternatives.isEmpty()) {
|
|---|
| 542 | return getMessage(p);
|
|---|
| 543 | } else {
|
|---|
| 544 | /* I18N: {0} is the test error message and {1} is an alternative */
|
|---|
| 545 | return tr("{0}, use {1} instead", getMessage(p), Utils.join(tr(" or "), alternatives));
|
|---|
| 546 | }
|
|---|
| 547 | }
|
|---|
| 548 |
|
|---|
| 549 | /**
|
|---|
| 550 | * Constructs a (localized) description for this deprecation check
|
|---|
| 551 | * where any placeholders are replaced by values of the matched selector.
|
|---|
| 552 | *
|
|---|
| 553 | * @param matchingSelector matching selector
|
|---|
| 554 | * @param p OSM primitive
|
|---|
| 555 | * @return a description (possibly with alternative suggestions)
|
|---|
| 556 | */
|
|---|
| 557 | String getDescriptionForMatchingSelector(OsmPrimitive p, Selector matchingSelector) {
|
|---|
| 558 | return insertArguments(matchingSelector, getDescription(p), p);
|
|---|
| 559 | }
|
|---|
| 560 |
|
|---|
| 561 | Severity getSeverity() {
|
|---|
| 562 | return errors.isEmpty() ? null : errors.values().iterator().next();
|
|---|
| 563 | }
|
|---|
| 564 |
|
|---|
| 565 | @Override
|
|---|
| 566 | public String toString() {
|
|---|
| 567 | return getDescription(null);
|
|---|
| 568 | }
|
|---|
| 569 |
|
|---|
| 570 | /**
|
|---|
| 571 | * Constructs a {@link TestError} for the given primitive, or returns null if the primitive does not give rise to an error.
|
|---|
| 572 | *
|
|---|
| 573 | * @param p the primitive to construct the error for
|
|---|
| 574 | * @return an instance of {@link TestError}, or returns null if the primitive does not give rise to an error.
|
|---|
| 575 | */
|
|---|
| 576 | TestError getErrorForPrimitive(OsmPrimitive p) {
|
|---|
| 577 | final Environment env = new Environment(p);
|
|---|
| 578 | return getErrorForPrimitive(p, whichSelectorMatchesEnvironment(env), env, null);
|
|---|
| 579 | }
|
|---|
| 580 |
|
|---|
| 581 | TestError getErrorForPrimitive(OsmPrimitive p, Selector matchingSelector, Environment env, Test tester) {
|
|---|
| 582 | if (matchingSelector != null && !errors.isEmpty()) {
|
|---|
| 583 | final Command fix = fixPrimitive(p);
|
|---|
| 584 | final String description = getDescriptionForMatchingSelector(p, matchingSelector);
|
|---|
| 585 | final String description1 = group == null ? description : group;
|
|---|
| 586 | final String description2 = group == null ? null : description;
|
|---|
| 587 | final List<OsmPrimitive> primitives;
|
|---|
| 588 | if (env.child != null) {
|
|---|
| 589 | primitives = Arrays.asList(p, env.child);
|
|---|
| 590 | } else {
|
|---|
| 591 | primitives = Collections.singletonList(p);
|
|---|
| 592 | }
|
|---|
| 593 | final TestError.Builder error = TestError.builder(tester, getSeverity(), 3000)
|
|---|
| 594 | .messageWithManuallyTranslatedDescription(description1, description2, matchingSelector.toString())
|
|---|
| 595 | .primitives(primitives);
|
|---|
| 596 | if (fix != null) {
|
|---|
| 597 | return error.fix(() -> fix).build();
|
|---|
| 598 | } else {
|
|---|
| 599 | return error.build();
|
|---|
| 600 | }
|
|---|
| 601 | } else {
|
|---|
| 602 | return null;
|
|---|
| 603 | }
|
|---|
| 604 | }
|
|---|
| 605 |
|
|---|
| 606 | /**
|
|---|
| 607 | * Returns the set of tagchecks on which this check depends on.
|
|---|
| 608 | * @param schecks the collection of tagcheks to search in
|
|---|
| 609 | * @return the set of tagchecks on which this check depends on
|
|---|
| 610 | * @since 7881
|
|---|
| 611 | */
|
|---|
| 612 | public Set<TagCheck> getTagCheckDependencies(Collection<TagCheck> schecks) {
|
|---|
| 613 | Set<TagCheck> result = new HashSet<>();
|
|---|
| 614 | Set<String> classes = getClassesIds();
|
|---|
| 615 | if (schecks != null && !classes.isEmpty()) {
|
|---|
| 616 | for (TagCheck tc : schecks) {
|
|---|
| 617 | if (this.equals(tc)) {
|
|---|
| 618 | continue;
|
|---|
| 619 | }
|
|---|
| 620 | for (String id : tc.setClassExpressions) {
|
|---|
| 621 | if (classes.contains(id)) {
|
|---|
| 622 | result.add(tc);
|
|---|
| 623 | break;
|
|---|
| 624 | }
|
|---|
| 625 | }
|
|---|
| 626 | }
|
|---|
| 627 | }
|
|---|
| 628 | return result;
|
|---|
| 629 | }
|
|---|
| 630 |
|
|---|
| 631 | /**
|
|---|
| 632 | * Returns the list of ids of all MapCSS classes referenced in the rule selectors.
|
|---|
| 633 | * @return the list of ids of all MapCSS classes referenced in the rule selectors
|
|---|
| 634 | * @since 7881
|
|---|
| 635 | */
|
|---|
| 636 | public Set<String> getClassesIds() {
|
|---|
| 637 | Set<String> result = new HashSet<>();
|
|---|
| 638 | for (Selector s : rule.selectors) {
|
|---|
| 639 | if (s instanceof AbstractSelector) {
|
|---|
| 640 | for (Condition c : ((AbstractSelector) s).getConditions()) {
|
|---|
| 641 | if (c instanceof ClassCondition) {
|
|---|
| 642 | result.add(((ClassCondition) c).id);
|
|---|
| 643 | }
|
|---|
| 644 | }
|
|---|
| 645 | }
|
|---|
| 646 | }
|
|---|
| 647 | return result;
|
|---|
| 648 | }
|
|---|
| 649 | }
|
|---|
| 650 |
|
|---|
| 651 | static class MapCSSTagCheckerAndRule extends MapCSSTagChecker {
|
|---|
| 652 | public final GroupedMapCSSRule rule;
|
|---|
| 653 |
|
|---|
| 654 | MapCSSTagCheckerAndRule(GroupedMapCSSRule rule) {
|
|---|
| 655 | this.rule = rule;
|
|---|
| 656 | }
|
|---|
| 657 |
|
|---|
| 658 | @Override
|
|---|
| 659 | public synchronized boolean equals(Object obj) {
|
|---|
| 660 | return super.equals(obj)
|
|---|
| 661 | || (obj instanceof TagCheck && rule.equals(((TagCheck) obj).rule))
|
|---|
| 662 | || (obj instanceof GroupedMapCSSRule && rule.equals(obj));
|
|---|
| 663 | }
|
|---|
| 664 |
|
|---|
| 665 | @Override
|
|---|
| 666 | public synchronized int hashCode() {
|
|---|
| 667 | return Objects.hash(super.hashCode(), rule);
|
|---|
| 668 | }
|
|---|
| 669 |
|
|---|
| 670 | @Override
|
|---|
| 671 | public String toString() {
|
|---|
| 672 | return "MapCSSTagCheckerAndRule [rule=" + rule + ']';
|
|---|
| 673 | }
|
|---|
| 674 | }
|
|---|
| 675 |
|
|---|
| 676 | /**
|
|---|
| 677 | * Obtains all {@link TestError}s for the {@link OsmPrimitive} {@code p}.
|
|---|
| 678 | * @param p The OSM primitive
|
|---|
| 679 | * @param includeOtherSeverity if {@code true}, errors of severity {@link Severity#OTHER} (info) will also be returned
|
|---|
| 680 | * @return all errors for the given primitive, with or without those of "info" severity
|
|---|
| 681 | */
|
|---|
| 682 | public synchronized Collection<TestError> getErrorsForPrimitive(OsmPrimitive p, boolean includeOtherSeverity) {
|
|---|
| 683 | return getErrorsForPrimitive(p, includeOtherSeverity, checks.values());
|
|---|
| 684 | }
|
|---|
| 685 |
|
|---|
| 686 | private static Collection<TestError> getErrorsForPrimitive(OsmPrimitive p, boolean includeOtherSeverity,
|
|---|
| 687 | Collection<Set<TagCheck>> checksCol) {
|
|---|
| 688 | final List<TestError> r = new ArrayList<>();
|
|---|
| 689 | final Environment env = new Environment(p, new MultiCascade(), Environment.DEFAULT_LAYER, null);
|
|---|
| 690 | for (Set<TagCheck> schecks : checksCol) {
|
|---|
| 691 | for (TagCheck check : schecks) {
|
|---|
| 692 | if (Severity.OTHER.equals(check.getSeverity()) && !includeOtherSeverity) {
|
|---|
| 693 | continue;
|
|---|
| 694 | }
|
|---|
| 695 | final Selector selector = check.whichSelectorMatchesEnvironment(env);
|
|---|
| 696 | if (selector != null) {
|
|---|
| 697 | check.rule.declaration.execute(env);
|
|---|
| 698 | final TestError error = check.getErrorForPrimitive(p, selector, env, new MapCSSTagCheckerAndRule(check.rule));
|
|---|
| 699 | if (error != null) {
|
|---|
| 700 | r.add(error);
|
|---|
| 701 | }
|
|---|
| 702 | }
|
|---|
| 703 | }
|
|---|
| 704 | }
|
|---|
| 705 | return r;
|
|---|
| 706 | }
|
|---|
| 707 |
|
|---|
| 708 | /**
|
|---|
| 709 | * Visiting call for primitives.
|
|---|
| 710 | *
|
|---|
| 711 | * @param p The primitive to inspect.
|
|---|
| 712 | */
|
|---|
| 713 | @Override
|
|---|
| 714 | public void check(OsmPrimitive p) {
|
|---|
| 715 | errors.addAll(getErrorsForPrimitive(p, ValidatorPrefHelper.PREF_OTHER.get()));
|
|---|
| 716 | }
|
|---|
| 717 |
|
|---|
| 718 | /**
|
|---|
| 719 | * Adds a new MapCSS config file from the given URL.
|
|---|
| 720 | * @param url The unique URL of the MapCSS config file
|
|---|
| 721 | * @return List of tag checks and parsing errors, or null
|
|---|
| 722 | * @throws ParseException if the config file does not match MapCSS syntax
|
|---|
| 723 | * @throws IOException if any I/O error occurs
|
|---|
| 724 | * @since 7275
|
|---|
| 725 | */
|
|---|
| 726 | public synchronized ParseResult addMapCSS(String url) throws ParseException, IOException {
|
|---|
| 727 | CheckParameterUtil.ensureParameterNotNull(url, "url");
|
|---|
| 728 | ParseResult result;
|
|---|
| 729 | try (CachedFile cache = new CachedFile(url);
|
|---|
| 730 | InputStream zip = cache.findZipEntryInputStream("validator.mapcss", "");
|
|---|
| 731 | InputStream s = zip != null ? zip : cache.getInputStream();
|
|---|
| 732 | Reader reader = new BufferedReader(UTFInputStreamReader.create(s))) {
|
|---|
| 733 | result = TagCheck.readMapCSS(reader);
|
|---|
| 734 | checks.remove(url);
|
|---|
| 735 | checks.putAll(url, result.parseChecks);
|
|---|
| 736 | // Check assertions, useful for development of local files
|
|---|
| 737 | if (Main.pref.getBoolean("validator.check_assert_local_rules", false) && Utils.isLocalUrl(url)) {
|
|---|
| 738 | for (String msg : checkAsserts(result.parseChecks)) {
|
|---|
| 739 | Logging.warn(msg);
|
|---|
| 740 | }
|
|---|
| 741 | }
|
|---|
| 742 | }
|
|---|
| 743 | return result;
|
|---|
| 744 | }
|
|---|
| 745 |
|
|---|
| 746 | @Override
|
|---|
| 747 | public synchronized void initialize() throws Exception {
|
|---|
| 748 | checks.clear();
|
|---|
| 749 | for (SourceEntry source : new ValidatorPrefHelper().get()) {
|
|---|
| 750 | if (!source.active) {
|
|---|
| 751 | continue;
|
|---|
| 752 | }
|
|---|
| 753 | String i = source.url;
|
|---|
| 754 | try {
|
|---|
| 755 | if (!i.startsWith("resource:")) {
|
|---|
| 756 | Logging.info(tr("Adding {0} to tag checker", i));
|
|---|
| 757 | } else if (Logging.isDebugEnabled()) {
|
|---|
| 758 | Logging.debug(tr("Adding {0} to tag checker", i));
|
|---|
| 759 | }
|
|---|
| 760 | addMapCSS(i);
|
|---|
| 761 | if (Main.pref.getBoolean("validator.auto_reload_local_rules", true) && source.isLocal()) {
|
|---|
| 762 | Main.fileWatcher.registerSource(source);
|
|---|
| 763 | }
|
|---|
| 764 | } catch (IOException | IllegalStateException | IllegalArgumentException ex) {
|
|---|
| 765 | Logging.warn(tr("Failed to add {0} to tag checker", i));
|
|---|
| 766 | Logging.log(Logging.LEVEL_WARN, ex);
|
|---|
| 767 | } catch (ParseException ex) {
|
|---|
| 768 | Logging.warn(tr("Failed to add {0} to tag checker", i));
|
|---|
| 769 | Logging.warn(ex);
|
|---|
| 770 | }
|
|---|
| 771 | }
|
|---|
| 772 | }
|
|---|
| 773 |
|
|---|
| 774 | /**
|
|---|
| 775 | * Checks that rule assertions are met for the given set of TagChecks.
|
|---|
| 776 | * @param schecks The TagChecks for which assertions have to be checked
|
|---|
| 777 | * @return A set of error messages, empty if all assertions are met
|
|---|
| 778 | * @since 7356
|
|---|
| 779 | */
|
|---|
| 780 | public Set<String> checkAsserts(final Collection<TagCheck> schecks) {
|
|---|
| 781 | Set<String> assertionErrors = new LinkedHashSet<>();
|
|---|
| 782 | final DataSet ds = new DataSet();
|
|---|
| 783 | for (final TagCheck check : schecks) {
|
|---|
| 784 | Logging.debug("Check: {0}", check);
|
|---|
| 785 | for (final Map.Entry<String, Boolean> i : check.assertions.entrySet()) {
|
|---|
| 786 | Logging.debug("- Assertion: {0}", i);
|
|---|
| 787 | final OsmPrimitive p = OsmUtils.createPrimitive(i.getKey());
|
|---|
| 788 | // Build minimal ordered list of checks to run to test the assertion
|
|---|
| 789 | List<Set<TagCheck>> checksToRun = new ArrayList<>();
|
|---|
| 790 | Set<TagCheck> checkDependencies = check.getTagCheckDependencies(schecks);
|
|---|
| 791 | if (!checkDependencies.isEmpty()) {
|
|---|
| 792 | checksToRun.add(checkDependencies);
|
|---|
| 793 | }
|
|---|
| 794 | checksToRun.add(Collections.singleton(check));
|
|---|
| 795 | // Add primitive to dataset to avoid DataIntegrityProblemException when evaluating selectors
|
|---|
| 796 | ds.addPrimitive(p);
|
|---|
| 797 | final Collection<TestError> pErrors = getErrorsForPrimitive(p, true, checksToRun);
|
|---|
| 798 | Logging.debug("- Errors: {0}", pErrors);
|
|---|
| 799 | @SuppressWarnings({"EqualsBetweenInconvertibleTypes", "EqualsIncompatibleType"})
|
|---|
| 800 | final boolean isError = pErrors.stream().anyMatch(e -> e.getTester().equals(check.rule));
|
|---|
| 801 | if (isError != i.getValue()) {
|
|---|
| 802 | final String error = MessageFormat.format("Expecting test ''{0}'' (i.e., {1}) to {2} {3} (i.e., {4})",
|
|---|
| 803 | check.getMessage(p), check.rule.selectors, i.getValue() ? "match" : "not match", i.getKey(), p.getKeys());
|
|---|
| 804 | assertionErrors.add(error);
|
|---|
| 805 | }
|
|---|
| 806 | ds.removePrimitive(p);
|
|---|
| 807 | }
|
|---|
| 808 | }
|
|---|
| 809 | return assertionErrors;
|
|---|
| 810 | }
|
|---|
| 811 |
|
|---|
| 812 | @Override
|
|---|
| 813 | public synchronized int hashCode() {
|
|---|
| 814 | return Objects.hash(super.hashCode(), checks);
|
|---|
| 815 | }
|
|---|
| 816 |
|
|---|
| 817 | @Override
|
|---|
| 818 | public synchronized boolean equals(Object obj) {
|
|---|
| 819 | if (this == obj) return true;
|
|---|
| 820 | if (obj == null || getClass() != obj.getClass()) return false;
|
|---|
| 821 | if (!super.equals(obj)) return false;
|
|---|
| 822 | MapCSSTagChecker that = (MapCSSTagChecker) obj;
|
|---|
| 823 | return Objects.equals(checks, that.checks);
|
|---|
| 824 | }
|
|---|
| 825 |
|
|---|
| 826 | /**
|
|---|
| 827 | * Reload tagchecker rule.
|
|---|
| 828 | * @param rule tagchecker rule to reload
|
|---|
| 829 | * @since 12825
|
|---|
| 830 | */
|
|---|
| 831 | public static void reloadRule(SourceEntry rule) {
|
|---|
| 832 | MapCSSTagChecker tagChecker = OsmValidator.getTest(MapCSSTagChecker.class);
|
|---|
| 833 | if (tagChecker != null) {
|
|---|
| 834 | try {
|
|---|
| 835 | tagChecker.addMapCSS(rule.url);
|
|---|
| 836 | } catch (IOException | ParseException e) {
|
|---|
| 837 | Logging.warn(e);
|
|---|
| 838 | }
|
|---|
| 839 | }
|
|---|
| 840 | }
|
|---|
| 841 | }
|
|---|