source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Condition.java@ 7099

Last change on this file since 7099 was 7099, checked in by bastiK, 10 years ago

see #9691 - mapcss: fix concurrency problem with Expressions (cannot save stuff to field of static singlton FUNCTIONS_INSTANCE)

  • Property svn:eol-style set to native
File size: 14.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint.mapcss;
3
4import java.text.MessageFormat;
5import java.util.EnumSet;
6import java.util.Objects;
7import java.util.Set;
8import java.util.regex.Pattern;
9
10import org.openstreetmap.josm.data.osm.Node;
11import org.openstreetmap.josm.data.osm.OsmPrimitive;
12import org.openstreetmap.josm.data.osm.Relation;
13import org.openstreetmap.josm.data.osm.Tag;
14import org.openstreetmap.josm.data.osm.Way;
15import org.openstreetmap.josm.gui.mappaint.Cascade;
16import org.openstreetmap.josm.gui.mappaint.ElemStyles;
17import org.openstreetmap.josm.gui.mappaint.Environment;
18import org.openstreetmap.josm.tools.CheckParameterUtil;
19import org.openstreetmap.josm.tools.Predicate;
20import org.openstreetmap.josm.tools.Predicates;
21import org.openstreetmap.josm.tools.Utils;
22
23public abstract class Condition {
24
25 public abstract boolean applies(Environment e);
26
27 public static Condition createKeyValueCondition(String k, String v, Op op, Context context, boolean considerValAsKey) {
28 switch (context) {
29 case PRIMITIVE:
30 if (KeyValueRegexpCondition.SUPPORTED_OPS.contains(op) && !considerValAsKey)
31 return new KeyValueRegexpCondition(k, v, op, false);
32 if (!considerValAsKey && op.equals(Op.EQ))
33 return new SimpleKeyValueCondition(k, v);
34 return new KeyValueCondition(k, v, op, considerValAsKey);
35 case LINK:
36 if (considerValAsKey)
37 throw new MapCSSException("''considerValAsKey'' not supported in LINK context");
38 if ("role".equalsIgnoreCase(k))
39 return new RoleCondition(v, op);
40 else if ("index".equalsIgnoreCase(k))
41 return new IndexCondition(v, op);
42 else
43 throw new MapCSSException(
44 MessageFormat.format("Expected key ''role'' or ''index'' in link context. Got ''{0}''.", k));
45
46 default: throw new AssertionError();
47 }
48 }
49
50 public static Condition createKeyCondition(String k, boolean not, KeyMatchType matchType, Context context) {
51 switch (context) {
52 case PRIMITIVE:
53 return new KeyCondition(k, not, matchType);
54 case LINK:
55 if (matchType != null)
56 throw new MapCSSException("Question mark operator ''?'' and regexp match not supported in LINK context");
57 if (not)
58 return new RoleCondition(k, Op.NEQ);
59 else
60 return new RoleCondition(k, Op.EQ);
61
62 default: throw new AssertionError();
63 }
64 }
65
66 public static PseudoClassCondition createPseudoClassCondition(String id, boolean not, Context context) {
67 return new PseudoClassCondition(id, not, context);
68 }
69
70 public static ClassCondition createClassCondition(String id, boolean not, Context context) {
71 return new ClassCondition(id, not);
72 }
73
74 public static ExpressionCondition createExpressionCondition(Expression e, Context context) {
75 return new ExpressionCondition(e);
76 }
77
78 public static enum Op {
79 EQ, NEQ, GREATER_OR_EQUAL, GREATER, LESS_OR_EQUAL, LESS,
80 REGEX, NREGEX, ONE_OF, BEGINS_WITH, ENDS_WITH, CONTAINS;
81
82 private static final Set<Op> NEGATED_OPS = EnumSet.of(NEQ, NREGEX);
83
84 public boolean eval(String testString, String prototypeString) {
85 if (testString == null && !NEGATED_OPS.contains(this))
86 return false;
87 switch (this) {
88 case EQ:
89 return Objects.equals(testString, prototypeString);
90 case NEQ:
91 return !Objects.equals(testString, prototypeString);
92 case REGEX:
93 case NREGEX:
94 final boolean contains = Pattern.compile(prototypeString).matcher(testString).find();
95 return REGEX.equals(this) ? contains : !contains;
96 case ONE_OF:
97 String[] parts = testString.split(";");
98 for (String part : parts) {
99 if (Objects.equals(prototypeString, part.trim()))
100 return true;
101 }
102 return false;
103 case BEGINS_WITH:
104 return testString.startsWith(prototypeString);
105 case ENDS_WITH:
106 return testString.endsWith(prototypeString);
107 case CONTAINS:
108 return testString.contains(prototypeString);
109 }
110
111 float test_float;
112 try {
113 test_float = Float.parseFloat(testString);
114 } catch (NumberFormatException e) {
115 return false;
116 }
117 float prototype_float = Float.parseFloat(prototypeString);
118
119 switch (this) {
120 case GREATER_OR_EQUAL:
121 return test_float >= prototype_float;
122 case GREATER:
123 return test_float > prototype_float;
124 case LESS_OR_EQUAL:
125 return test_float <= prototype_float;
126 case LESS:
127 return test_float < prototype_float;
128 default:
129 throw new AssertionError();
130 }
131 }
132 }
133
134 /**
135 * Context, where the condition applies.
136 */
137 public static enum Context {
138 /**
139 * normal primitive selector, e.g. way[highway=residential]
140 */
141 PRIMITIVE,
142
143 /**
144 * link between primitives, e.g. relation &gt;[role=outer] way
145 */
146 LINK
147 }
148
149 public static final EnumSet<Op> COMPARISON_OPERATERS =
150 EnumSet.of(Op.GREATER_OR_EQUAL, Op.GREATER, Op.LESS_OR_EQUAL, Op.LESS);
151
152 /**
153 * Most common case of a KeyValueCondition.
154 *
155 * Extra class for performance reasons.
156 */
157 public static class SimpleKeyValueCondition extends Condition {
158 public final String k;
159 public final String v;
160
161 public SimpleKeyValueCondition(String k, String v) {
162 this.k = k;
163 this.v = v;
164 }
165
166 @Override
167 public boolean applies(Environment e) {
168 return v.equals(e.osm.get(k));
169 }
170
171 public Tag asTag() {
172 return new Tag(k, v);
173 }
174
175 @Override
176 public String toString() {
177 return '[' + k + '=' + v + ']';
178 }
179
180 }
181
182 /**
183 * <p>Represents a key/value condition which is either applied to a primitive.</p>
184 *
185 */
186 public static class KeyValueCondition extends Condition {
187
188 public final String k;
189 public final String v;
190 public final Op op;
191 public boolean considerValAsKey;
192
193 /**
194 * <p>Creates a key/value-condition.</p>
195 *
196 * @param k the key
197 * @param v the value
198 * @param op the operation
199 * @param considerValAsKey whether to consider {@code v} as another key and compare the values of key {@code k} and key {@code v}.
200 */
201 public KeyValueCondition(String k, String v, Op op, boolean considerValAsKey) {
202 this.k = k;
203 this.v = v;
204 this.op = op;
205 this.considerValAsKey = considerValAsKey;
206 }
207
208 @Override
209 public boolean applies(Environment env) {
210 return op.eval(env.osm.get(k), considerValAsKey ? env.osm.get(v) : v);
211 }
212
213 public Tag asTag() {
214 return new Tag(k, v);
215 }
216
217 @Override
218 public String toString() {
219 return "[" + k + "'" + op + "'" + v + "]";
220 }
221 }
222
223 public static class KeyValueRegexpCondition extends KeyValueCondition {
224
225 public final Pattern pattern;
226 public static final EnumSet<Op> SUPPORTED_OPS = EnumSet.of(Op.REGEX, Op.NREGEX);
227
228 public KeyValueRegexpCondition(String k, String v, Op op, boolean considerValAsKey) {
229 super(k, v, op, considerValAsKey);
230 CheckParameterUtil.ensureThat(!considerValAsKey, "considerValAsKey is not supported");
231 CheckParameterUtil.ensureThat(SUPPORTED_OPS.contains(op), "Op must be REGEX or NREGEX");
232 this.pattern = Pattern.compile(v);
233 }
234
235 @Override
236 public boolean applies(Environment env) {
237 final String value = env.osm.get(k);
238 if (Op.REGEX.equals(op)) {
239 return value != null && pattern.matcher(value).find();
240 } else if (Op.NREGEX.equals(op)) {
241 return value == null || !pattern.matcher(value).find();
242 } else {
243 throw new IllegalStateException();
244 }
245 }
246 }
247
248 public static class RoleCondition extends Condition {
249 public final String role;
250 public final Op op;
251
252 public RoleCondition(String role, Op op) {
253 this.role = role;
254 this.op = op;
255 }
256
257 @Override
258 public boolean applies(Environment env) {
259 String testRole = env.getRole();
260 if (testRole == null) return false;
261 return op.eval(testRole, role);
262 }
263 }
264
265 public static class IndexCondition extends Condition {
266 public final String index;
267 public final Op op;
268
269 public IndexCondition(String index, Op op) {
270 this.index = index;
271 this.op = op;
272 }
273
274 @Override
275 public boolean applies(Environment env) {
276 if (env.index == null) return false;
277 return op.eval(Integer.toString(env.index + 1), index);
278 }
279 }
280
281 public static enum KeyMatchType {
282 EQ, TRUE, FALSE, REGEX
283 }
284
285 /**
286 * <p>KeyCondition represent one of the following conditions in either the link or the
287 * primitive context:</p>
288 * <pre>
289 * ["a label"] PRIMITIVE: the primitive has a tag "a label"
290 * LINK: the parent is a relation and it has at least one member with the role
291 * "a label" referring to the child
292 *
293 * [!"a label"] PRIMITIVE: the primitive doesn't have a tag "a label"
294 * LINK: the parent is a relation but doesn't have a member with the role
295 * "a label" referring to the child
296 *
297 * ["a label"?] PRIMITIVE: the primitive has a tag "a label" whose value evaluates to a true-value
298 * LINK: not supported
299 *
300 * ["a label"?!] PRIMITIVE: the primitive has a tag "a label" whose value evaluates to a false-value
301 * LINK: not supported
302 * </pre>
303 */
304 public static class KeyCondition extends Condition {
305
306 public final String label;
307 public final boolean negateResult;
308 public final KeyMatchType matchType;
309 public Predicate<String> containsPattern;
310
311 public KeyCondition(String label, boolean negateResult, KeyMatchType matchType){
312 this.label = label;
313 this.negateResult = negateResult;
314 this.matchType = matchType;
315 this.containsPattern = KeyMatchType.REGEX.equals(matchType)
316 ? Predicates.stringContainsPattern(Pattern.compile(label))
317 : null;
318 }
319
320 @Override
321 public boolean applies(Environment e) {
322 switch(e.getContext()) {
323 case PRIMITIVE:
324 if (KeyMatchType.TRUE.equals(matchType))
325 return e.osm.isKeyTrue(label) ^ negateResult;
326 else if (KeyMatchType.FALSE.equals(matchType))
327 return e.osm.isKeyFalse(label) ^ negateResult;
328 else if (KeyMatchType.REGEX.equals(matchType)) {
329 return Utils.exists(e.osm.keySet(), containsPattern) ^ negateResult;
330 } else {
331 return e.osm.hasKey(label) ^ negateResult;
332 }
333 case LINK:
334 Utils.ensure(false, "Illegal state: KeyCondition not supported in LINK context");
335 return false;
336 default: throw new AssertionError();
337 }
338 }
339
340 public Tag asTag() {
341 return new Tag(label);
342 }
343
344 @Override
345 public String toString() {
346 return "[" + (negateResult ? "!" : "") + label + "]";
347 }
348 }
349
350 public static class ClassCondition extends Condition {
351
352 public final String id;
353 public final boolean not;
354
355 public ClassCondition(String id, boolean not) {
356 this.id = id;
357 this.not = not;
358 }
359
360 @Override
361 public boolean applies(Environment env) {
362 return env != null && env.getCascade(env.layer) != null && not ^ env.getCascade(env.layer).containsKey(id);
363 }
364
365 @Override
366 public String toString() {
367 return (not ? "!" : "") + "." + id;
368 }
369 }
370
371 public static class PseudoClassCondition extends Condition {
372
373 public final String id;
374 public final boolean not;
375
376 public PseudoClassCondition(String id, boolean not, Context context) {
377 this.id = id;
378 this.not = not;
379 CheckParameterUtil.ensureThat(!"sameTags".equals(id) || Context.LINK.equals(context), "sameTags only supported in LINK context");
380 }
381
382 @Override
383 public boolean applies(Environment e) {
384 return not ^ appliesImpl(e);
385 }
386
387 public boolean appliesImpl(Environment e) {
388 switch(id) {
389 case "closed":
390 if (e.osm instanceof Way && ((Way) e.osm).isClosed())
391 return true;
392 if (e.osm instanceof Relation && ((Relation) e.osm).isMultipolygon())
393 return true;
394 break;
395 case "modified":
396 return e.osm.isModified() || e.osm.isNewOrUndeleted();
397 case "new":
398 return e.osm.isNew();
399 case "connection":
400 return e.osm instanceof Node && ((Node) e.osm).isConnectionNode();
401 case "tagged":
402 return e.osm.isTagged();
403 case "sameTags":
404 return e.osm.hasSameInterestingTags(Utils.firstNonNull(e.child, e.parent));
405 case "areaStyle":
406 return ElemStyles.hasAreaElemStyle(e.osm, false);
407 case "unconnected":
408 return e.osm instanceof Node && OsmPrimitive.getFilteredList(e.osm.getReferrers(), Way.class).isEmpty();
409 }
410 return false;
411 }
412
413 @Override
414 public String toString() {
415 return ":" + (not ? "!" : "") + id;
416 }
417 }
418
419 public static class ExpressionCondition extends Condition {
420
421 private final Expression e;
422
423 public ExpressionCondition(Expression e) {
424 this.e = e;
425 }
426
427 @Override
428 public boolean applies(Environment env) {
429 Boolean b = Cascade.convertTo(e.evaluate(env), Boolean.class);
430 return b != null && b;
431 }
432
433 @Override
434 public String toString() {
435 return "[" + e + "]";
436 }
437 }
438}
Note: See TracBrowser for help on using the repository browser.