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

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

fixes for unit tests

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