source: josm/trunk/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerAsserts.java@ 17758

Last change on this file since 17758 was 17758, checked in by simon04, 3 years ago

fix #20744 - Evaluate MapCSS expression without array creation

File size: 7.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.validation.tests;
3
4import java.lang.reflect.Method;
5import java.text.MessageFormat;
6import java.util.ArrayList;
7import java.util.Collection;
8import java.util.Collections;
9import java.util.HashSet;
10import java.util.List;
11import java.util.Map;
12import java.util.Optional;
13import java.util.Set;
14import java.util.stream.Collectors;
15
16import org.openstreetmap.josm.command.Command;
17import org.openstreetmap.josm.data.coor.LatLon;
18import org.openstreetmap.josm.data.osm.DataSet;
19import org.openstreetmap.josm.data.osm.OsmPrimitive;
20import org.openstreetmap.josm.data.osm.OsmUtils;
21import org.openstreetmap.josm.data.osm.Relation;
22import org.openstreetmap.josm.data.osm.Way;
23import org.openstreetmap.josm.data.validation.TestError;
24import org.openstreetmap.josm.gui.mappaint.Environment;
25import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory;
26import org.openstreetmap.josm.gui.mappaint.mapcss.ExpressionFactory;
27import org.openstreetmap.josm.gui.mappaint.mapcss.Functions;
28import org.openstreetmap.josm.gui.mappaint.mapcss.LiteralExpression;
29import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
30import org.openstreetmap.josm.tools.DefaultGeoProperty;
31import org.openstreetmap.josm.tools.GeoProperty;
32import org.openstreetmap.josm.tools.GeoPropertyIndex;
33import org.openstreetmap.josm.tools.Logging;
34import org.openstreetmap.josm.tools.Territories;
35
36/**
37 * Utility class for checking rule assertions of {@link MapCSSTagCheckerRule}.
38 */
39final class MapCSSTagCheckerAsserts {
40
41 private MapCSSTagCheckerAsserts() {
42 // private constructor
43 }
44
45 private static final ArrayList<MapCSSTagCheckerRule> previousChecks = new ArrayList<>();
46
47 /**
48 * Checks that rule assertions are met for the given set of TagChecks.
49 * @param check The TagCheck for which assertions have to be checked
50 * @param assertions The assertions to check (map values correspond to expected result)
51 * @param assertionConsumer The handler for assertion error messages
52 */
53 static void checkAsserts(final MapCSSTagCheckerRule check, final Map<String, Boolean> assertions,
54 final MapCSSTagChecker.AssertionConsumer assertionConsumer) {
55 final Method insideMethod = getFunctionMethod("inside");
56 final DataSet ds = new DataSet();
57 Logging.debug("Check: {0}", check);
58 for (final Map.Entry<String, Boolean> i : assertions.entrySet()) {
59 Logging.debug("- Assertion: {0}", i);
60 final OsmPrimitive p = OsmUtils.createPrimitive(i.getKey(), getLocation(check, insideMethod), true);
61 // Build minimal ordered list of checks to run to test the assertion
62 List<Set<MapCSSTagCheckerRule>> checksToRun = new ArrayList<>();
63 Set<MapCSSTagCheckerRule> checkDependencies = getTagCheckDependencies(check, previousChecks);
64 if (!checkDependencies.isEmpty()) {
65 checksToRun.add(checkDependencies);
66 }
67 checksToRun.add(Collections.singleton(check));
68 // Add primitive to dataset to avoid DataIntegrityProblemException when evaluating selectors
69 addPrimitive(ds, p);
70 final Collection<TestError> pErrors = MapCSSTagChecker.getErrorsForPrimitive(p, true, checksToRun);
71 Logging.debug("- Errors: {0}", pErrors);
72 final boolean isError = pErrors.stream().anyMatch(e -> e.getTester() instanceof MapCSSTagChecker.MapCSSTagCheckerAndRule
73 && ((MapCSSTagChecker.MapCSSTagCheckerAndRule) e.getTester()).rule.equals(check.rule));
74 if (isError != i.getValue()) {
75 assertionConsumer.accept(MessageFormat.format("Expecting test ''{0}'' (i.e., {1}) to {2} {3} (i.e., {4})",
76 check.getMessage(p), check.rule.selectors, i.getValue() ? "match" : "not match", i.getKey(), p.getKeys()));
77 }
78 if (isError) {
79 // Check that autofix works as expected
80 Command fix = check.fixPrimitive(p);
81 if (fix != null && fix.executeCommand() && !MapCSSTagChecker.getErrorsForPrimitive(p, true, checksToRun).isEmpty()) {
82 assertionConsumer.accept(MessageFormat.format("Autofix does not work for test ''{0}'' (i.e., {1})",
83 check.getMessage(p), check.rule.selectors));
84 }
85 }
86 ds.removePrimitive(p);
87 }
88 previousChecks.add(check);
89 }
90
91 public static void clear() {
92 previousChecks.clear();
93 previousChecks.trimToSize();
94 }
95
96 private static Method getFunctionMethod(String method) {
97 try {
98 return Functions.class.getDeclaredMethod(method, Environment.class, String.class);
99 } catch (NoSuchMethodException | SecurityException e) {
100 Logging.error(e);
101 return null;
102 }
103 }
104
105 private static void addPrimitive(DataSet ds, OsmPrimitive p) {
106 if (p instanceof Way) {
107 ((Way) p).getNodes().forEach(n -> addPrimitive(ds, n));
108 } else if (p instanceof Relation) {
109 ((Relation) p).getMembers().forEach(m -> addPrimitive(ds, m.getMember()));
110 }
111 ds.addPrimitive(p);
112 }
113
114 private static LatLon getLocation(MapCSSTagCheckerRule check, Method insideMethod) {
115 Optional<String> inside = getFirstInsideCountry(check, insideMethod);
116 if (inside.isPresent()) {
117 GeoPropertyIndex<Boolean> index = Territories.getGeoPropertyIndex(inside.get());
118 if (index != null) {
119 GeoProperty<Boolean> prop = index.getGeoProperty();
120 if (prop instanceof DefaultGeoProperty) {
121 return ((DefaultGeoProperty) prop).getRandomLatLon();
122 }
123 }
124 }
125 return LatLon.ZERO;
126 }
127
128 private static Optional<String> getFirstInsideCountry(MapCSSTagCheckerRule check, Method insideMethod) {
129 return check.rule.selectors.stream()
130 .filter(s -> s instanceof Selector.GeneralSelector)
131 .flatMap(s -> ((Selector.GeneralSelector) s).getConditions().stream())
132 .filter(c -> c instanceof ConditionFactory.ExpressionCondition)
133 .map(c -> ((ConditionFactory.ExpressionCondition) c).getExpression())
134 .filter(c -> c instanceof ExpressionFactory.IsInsideFunction)
135 .map(c -> (ExpressionFactory.IsInsideFunction) c)
136 .map(ExpressionFactory.IsInsideFunction::getArg)
137 .filter(e -> e instanceof LiteralExpression)
138 .map(e -> ((LiteralExpression) e).getLiteral())
139 .filter(l -> l instanceof String)
140 .map(l -> ((String) l).split(",", -1)[0])
141 .findFirst();
142 }
143
144 /**
145 * Returns the set of tagchecks on which this check depends on.
146 * @param check the tagcheck
147 * @param schecks the collection of tagchecks to search in
148 * @return the set of tagchecks on which this check depends on
149 * @since 7881
150 */
151 private static Set<MapCSSTagCheckerRule> getTagCheckDependencies(MapCSSTagCheckerRule check,
152 Collection<MapCSSTagCheckerRule> schecks) {
153 Set<MapCSSTagCheckerRule> result = new HashSet<>();
154 Set<String> classes = check.rule.selectors.stream()
155 .filter(s -> s instanceof Selector.AbstractSelector)
156 .flatMap(s -> ((Selector.AbstractSelector) s).getConditions().stream())
157 .filter(c -> c instanceof ConditionFactory.ClassCondition)
158 .map(c -> ((ConditionFactory.ClassCondition) c).id)
159 .collect(Collectors.toSet());
160 if (schecks != null && !classes.isEmpty()) {
161 return schecks.stream()
162 .filter(tc -> !check.equals(tc))
163 .filter(tc -> tc.setClassExpressions.stream().anyMatch(classes::contains))
164 .collect(Collectors.toSet());
165 }
166 return result;
167 }
168}
Note: See TracBrowser for help on using the repository browser.