source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/ConditionFactory.java@ 16438

Last change on this file since 16438 was 16438, checked in by simon04, 4 years ago

see #19251 - Java 8: use Stream

  • Property svn:eol-style set to native
File size: 32.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint.mapcss;
3
4import java.lang.reflect.Method;
5import java.text.MessageFormat;
6import java.util.Arrays;
7import java.util.EnumSet;
8import java.util.Objects;
9import java.util.Set;
10import java.util.function.BiPredicate;
11import java.util.function.IntFunction;
12import java.util.function.Predicate;
13import java.util.regex.Pattern;
14import java.util.regex.PatternSyntaxException;
15
16import org.openstreetmap.josm.data.osm.INode;
17import org.openstreetmap.josm.data.osm.IPrimitive;
18import org.openstreetmap.josm.data.osm.IRelation;
19import org.openstreetmap.josm.data.osm.IWay;
20import org.openstreetmap.josm.data.osm.Node;
21import org.openstreetmap.josm.data.osm.OsmPrimitive;
22import org.openstreetmap.josm.data.osm.OsmUtils;
23import org.openstreetmap.josm.data.osm.Relation;
24import org.openstreetmap.josm.data.osm.Tag;
25import org.openstreetmap.josm.data.osm.search.SearchCompiler.InDataSourceArea;
26import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
27import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
28import org.openstreetmap.josm.gui.mappaint.Cascade;
29import org.openstreetmap.josm.gui.mappaint.ElemStyles;
30import org.openstreetmap.josm.gui.mappaint.Environment;
31import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.Context;
32import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.ToTagConvertable;
33import org.openstreetmap.josm.tools.CheckParameterUtil;
34import org.openstreetmap.josm.tools.JosmRuntimeException;
35import org.openstreetmap.josm.tools.Utils;
36
37/**
38 * Factory to generate {@link Condition}s.
39 * @since 10837 (Extracted from Condition)
40 */
41public final class ConditionFactory {
42
43 private ConditionFactory() {
44 // Hide default constructor for utils classes
45 }
46
47 /**
48 * Create a new condition that checks the key and the value of the object.
49 * @param k The key.
50 * @param v The reference value
51 * @param op The operation to use when comparing the value
52 * @param context The type of context to use.
53 * @param considerValAsKey whether to consider {@code v} as another key and compare the values of key {@code k} and key {@code v}.
54 * @return The new condition.
55 * @throws MapCSSException if the arguments are incorrect
56 */
57 public static Condition createKeyValueCondition(String k, String v, Op op, Context context, boolean considerValAsKey) {
58 switch (context) {
59 case PRIMITIVE:
60 if (KeyValueRegexpCondition.SUPPORTED_OPS.contains(op) && !considerValAsKey) {
61 try {
62 return new KeyValueRegexpCondition(k, v, op, false);
63 } catch (PatternSyntaxException e) {
64 throw new MapCSSException(e);
65 }
66 }
67 if (!considerValAsKey && op == Op.EQ)
68 return new SimpleKeyValueCondition(k, v);
69 return new KeyValueCondition(k, v, op, considerValAsKey);
70 case LINK:
71 if (considerValAsKey)
72 throw new MapCSSException("''considerValAsKey'' not supported in LINK context");
73 if ("role".equalsIgnoreCase(k))
74 return new RoleCondition(v, op);
75 else if ("index".equalsIgnoreCase(k))
76 return new IndexCondition(v, op);
77 else
78 throw new MapCSSException(
79 MessageFormat.format("Expected key ''role'' or ''index'' in link context. Got ''{0}''.", k));
80
81 default: throw new AssertionError();
82 }
83 }
84
85 /**
86 * Create a condition in which the key and the value need to match a given regexp
87 * @param k The key regexp
88 * @param v The value regexp
89 * @param op The operation to use when comparing the key and the value.
90 * @return The new condition.
91 */
92 public static Condition createRegexpKeyRegexpValueCondition(String k, String v, Op op) {
93 return new RegexpKeyValueRegexpCondition(k, v, op);
94 }
95
96 /**
97 * Creates a condition that checks the given key.
98 * @param k The key to test for
99 * @param not <code>true</code> to invert the match
100 * @param matchType The match type to check for.
101 * @param context The context this rule is found in.
102 * @return the new condition.
103 */
104 public static Condition createKeyCondition(String k, boolean not, KeyMatchType matchType, Context context) {
105 switch (context) {
106 case PRIMITIVE:
107 return new KeyCondition(k, not, matchType);
108 case LINK:
109 if (matchType != null)
110 throw new MapCSSException("Question mark operator ''?'' and regexp match not supported in LINK context");
111 if (not)
112 return new RoleCondition(k, Op.NEQ);
113 else
114 return new RoleCondition(k, Op.EQ);
115
116 default: throw new AssertionError();
117 }
118 }
119
120 /**
121 * Create a new pseudo class condition
122 * @param id The id of the pseudo class
123 * @param not <code>true</code> to invert the condition
124 * @param context The context the class is found in.
125 * @return The new condition
126 */
127 public static PseudoClassCondition createPseudoClassCondition(String id, boolean not, Context context) {
128 return PseudoClassCondition.createPseudoClassCondition(id, not, context);
129 }
130
131 /**
132 * Create a new class condition
133 * @param id The id of the class to match
134 * @param not <code>true</code> to invert the condition
135 * @param context Ignored
136 * @return The new condition
137 */
138 public static ClassCondition createClassCondition(String id, boolean not, Context context) {
139 return new ClassCondition(id, not);
140 }
141
142 /**
143 * Create a new condition that a expression needs to be fulfilled
144 * @param e the expression to check
145 * @param context Ignored
146 * @return The new condition
147 */
148 public static ExpressionCondition createExpressionCondition(Expression e, Context context) {
149 return new ExpressionCondition(e);
150 }
151
152 /**
153 * This is the operation that {@link KeyValueCondition} uses to match.
154 */
155 public enum Op {
156 /** The value equals the given reference. */
157 EQ(Objects::equals),
158 /** The value does not equal the reference. */
159 NEQ(EQ),
160 /** The value is greater than or equal to the given reference value (as float). */
161 GREATER_OR_EQUAL(comparisonResult -> comparisonResult >= 0),
162 /** The value is greater than the given reference value (as float). */
163 GREATER(comparisonResult -> comparisonResult > 0),
164 /** The value is less than or equal to the given reference value (as float). */
165 LESS_OR_EQUAL(comparisonResult -> comparisonResult <= 0),
166 /** The value is less than the given reference value (as float). */
167 LESS(comparisonResult -> comparisonResult < 0),
168 /** The reference is treated as regular expression and the value needs to match it. */
169 REGEX((test, prototype) -> Pattern.compile(prototype).matcher(test).find()),
170 /** The reference is treated as regular expression and the value needs to not match it. */
171 NREGEX(REGEX),
172 /** The reference is treated as a list separated by ';'. Spaces around the ; are ignored.
173 * The value needs to be equal one of the list elements. */
174 ONE_OF((test, prototype) -> OsmUtils.splitMultipleValues(test).anyMatch(prototype::equals)),
175 /** The value needs to begin with the reference string. */
176 BEGINS_WITH(String::startsWith),
177 /** The value needs to end with the reference string. */
178 ENDS_WITH(String::endsWith),
179 /** The value needs to contain the reference string. */
180 CONTAINS(String::contains);
181
182 static final Set<Op> NEGATED_OPS = EnumSet.of(NEQ, NREGEX);
183
184 private final BiPredicate<String, String> function;
185
186 private final boolean negated;
187
188 /**
189 * Create a new string operation.
190 * @param func The function to apply during {@link #eval(String, String)}.
191 */
192 Op(BiPredicate<String, String> func) {
193 this.function = func;
194 negated = false;
195 }
196
197 /**
198 * Create a new float operation that compares two float values
199 * @param comparatorResult A function to mapt the result of the comparison
200 */
201 Op(IntFunction<Boolean> comparatorResult) {
202 this.function = (test, prototype) -> {
203 float testFloat;
204 try {
205 testFloat = Float.parseFloat(test);
206 } catch (NumberFormatException e) {
207 return Boolean.FALSE;
208 }
209 float prototypeFloat = Float.parseFloat(prototype);
210
211 int res = Float.compare(testFloat, prototypeFloat);
212 return comparatorResult.apply(res);
213 };
214 negated = false;
215 }
216
217 /**
218 * Create a new Op by negating an other op.
219 * @param negate inverse operation
220 */
221 Op(Op negate) {
222 this.function = (a, b) -> !negate.function.test(a, b);
223 negated = true;
224 }
225
226 /**
227 * Evaluates a value against a reference string.
228 * @param testString The value. May be <code>null</code>
229 * @param prototypeString The reference string-
230 * @return <code>true</code> if and only if this operation matches for the given value/reference pair.
231 */
232 public boolean eval(String testString, String prototypeString) {
233 if (testString == null)
234 return negated;
235 else
236 return function.test(testString, prototypeString);
237 }
238 }
239
240 /**
241 * Most common case of a KeyValueCondition, this is the basic key=value case.
242 *
243 * Extra class for performance reasons.
244 */
245 public static class SimpleKeyValueCondition implements Condition, ToTagConvertable {
246 /**
247 * The key to search for.
248 */
249 public final String k;
250 /**
251 * The value to search for.
252 */
253 public final String v;
254
255 /**
256 * Create a new SimpleKeyValueCondition.
257 * @param k The key
258 * @param v The value.
259 */
260 public SimpleKeyValueCondition(String k, String v) {
261 this.k = k.intern();
262 this.v = v.intern();
263 }
264
265 @Override
266 public boolean applies(Environment e) {
267 return v.equals(e.osm.get(k));
268 }
269
270 @Override
271 public Tag asTag(OsmPrimitive primitive) {
272 return new Tag(k, v);
273 }
274
275 @Override
276 public String toString() {
277 return '[' + k + '=' + v + ']';
278 }
279
280 }
281
282 /**
283 * <p>Represents a key/value condition which is either applied to a primitive.</p>
284 *
285 */
286 public static class KeyValueCondition implements Condition, ToTagConvertable {
287 /**
288 * The key to search for.
289 */
290 public final String k;
291 /**
292 * The value to search for.
293 */
294 public final String v;
295 /**
296 * The key/value match operation.
297 */
298 public final Op op;
299 /**
300 * If this flag is set, {@link #v} is treated as a key and the value is the value set for that key.
301 */
302 public final boolean considerValAsKey;
303
304 /**
305 * <p>Creates a key/value-condition.</p>
306 *
307 * @param k the key
308 * @param v the value
309 * @param op the operation
310 * @param considerValAsKey whether to consider {@code v} as another key and compare the values of key {@code k} and key {@code v}.
311 */
312 public KeyValueCondition(String k, String v, Op op, boolean considerValAsKey) {
313 this.k = k.intern();
314 this.v = v.intern();
315 this.op = op;
316 this.considerValAsKey = considerValAsKey;
317 }
318
319 /**
320 * Determines if this condition requires an exact key match.
321 * @return {@code true} if this condition requires an exact key match.
322 * @since 14801
323 */
324 public boolean requiresExactKeyMatch() {
325 return !Op.NEGATED_OPS.contains(op);
326 }
327
328 @Override
329 public boolean applies(Environment env) {
330 return op.eval(env.osm.get(k), considerValAsKey ? env.osm.get(v) : v);
331 }
332
333 @Override
334 public Tag asTag(OsmPrimitive primitive) {
335 return new Tag(k, v);
336 }
337
338 @Override
339 public String toString() {
340 return '[' + k + '\'' + op + '\'' + v + ']';
341 }
342 }
343
344 /**
345 * This condition requires a fixed key to match a given regexp
346 */
347 public static class KeyValueRegexpCondition extends KeyValueCondition {
348 protected static final Set<Op> SUPPORTED_OPS = EnumSet.of(Op.REGEX, Op.NREGEX);
349
350 final Pattern pattern;
351
352 /**
353 * Constructs a new {@code KeyValueRegexpCondition}.
354 * @param k key
355 * @param v value
356 * @param op operation
357 * @param considerValAsKey must be false
358 * @throws PatternSyntaxException if the value syntax is invalid
359 */
360 public KeyValueRegexpCondition(String k, String v, Op op, boolean considerValAsKey) {
361 super(k, v, op, considerValAsKey); /* value is needed in validator messages */
362 CheckParameterUtil.ensureThat(!considerValAsKey, "considerValAsKey is not supported");
363 CheckParameterUtil.ensureThat(SUPPORTED_OPS.contains(op), "Op must be REGEX or NREGEX");
364 this.pattern = Pattern.compile(v);
365 }
366
367 protected boolean matches(Environment env) {
368 final String value = env.osm.get(k);
369 return value != null && pattern.matcher(value).find();
370 }
371
372 @Override
373 public boolean applies(Environment env) {
374 if (Op.REGEX == op) {
375 return matches(env);
376 } else if (Op.NREGEX == op) {
377 return !matches(env);
378 } else {
379 throw new IllegalStateException();
380 }
381 }
382 }
383
384 /**
385 * A condition that checks that a key with the matching pattern has a value with the matching pattern.
386 */
387 public static class RegexpKeyValueRegexpCondition extends KeyValueRegexpCondition {
388
389 final Pattern keyPattern;
390
391 /**
392 * Create a condition in which the key and the value need to match a given regexp
393 * @param k The key regexp
394 * @param v The value regexp
395 * @param op The operation to use when comparing the key and the value.
396 */
397 public RegexpKeyValueRegexpCondition(String k, String v, Op op) {
398 super(k, v, op, false);
399 this.keyPattern = Pattern.compile(k);
400 }
401
402 @Override
403 public boolean requiresExactKeyMatch() {
404 return false;
405 }
406
407 @Override
408 protected boolean matches(Environment env) {
409 return env.osm.getKeys().entrySet().stream()
410 .anyMatch(kv -> keyPattern.matcher(kv.getKey()).find() && pattern.matcher(kv.getValue()).find());
411 }
412 }
413
414 /**
415 * Role condition.
416 */
417 public static class RoleCondition implements Condition {
418 final String role;
419 final Op op;
420
421 /**
422 * Constructs a new {@code RoleCondition}.
423 * @param role role
424 * @param op operation
425 */
426 public RoleCondition(String role, Op op) {
427 this.role = role;
428 this.op = op;
429 }
430
431 @Override
432 public boolean applies(Environment env) {
433 String testRole = env.getRole();
434 if (testRole == null) return false;
435 return op.eval(testRole, role);
436 }
437 }
438
439 /**
440 * Index condition.
441 */
442 public static class IndexCondition implements Condition {
443 final String index;
444 final Op op;
445 final boolean isFirstOrLast;
446
447 /**
448 * Constructs a new {@code IndexCondition}.
449 * @param index index
450 * @param op operation
451 */
452 public IndexCondition(String index, Op op) {
453 this.index = index;
454 this.op = op;
455 isFirstOrLast = op == Op.EQ && ("1".equals(index) || "-1".equals(index));
456 }
457
458 @Override
459 public boolean applies(Environment env) {
460 if (env.index == null) return false;
461 if (index.startsWith("-")) {
462 return env.count != null && op.eval(Integer.toString(env.index - env.count), index);
463 } else {
464 return op.eval(Integer.toString(env.index + 1), index);
465 }
466 }
467 }
468
469 /**
470 * This defines how {@link KeyCondition} matches a given key.
471 */
472 public enum KeyMatchType {
473 /**
474 * The key needs to be equal to the given label.
475 */
476 EQ,
477 /**
478 * The key needs to have a true value (yes, ...)
479 * @see OsmUtils#isTrue(String)
480 */
481 TRUE,
482 /**
483 * The key needs to have a false value (no, ...)
484 * @see OsmUtils#isFalse(String)
485 */
486 FALSE,
487 /**
488 * The key needs to match the given regular expression.
489 */
490 REGEX
491 }
492
493 /**
494 * <p>KeyCondition represent one of the following conditions in either the link or the
495 * primitive context:</p>
496 * <pre>
497 * ["a label"] PRIMITIVE: the primitive has a tag "a label"
498 * LINK: the parent is a relation and it has at least one member with the role
499 * "a label" referring to the child
500 *
501 * [!"a label"] PRIMITIVE: the primitive doesn't have a tag "a label"
502 * LINK: the parent is a relation but doesn't have a member with the role
503 * "a label" referring to the child
504 *
505 * ["a label"?] PRIMITIVE: the primitive has a tag "a label" whose value evaluates to a true-value
506 * LINK: not supported
507 *
508 * ["a label"?!] PRIMITIVE: the primitive has a tag "a label" whose value evaluates to a false-value
509 * LINK: not supported
510 * </pre>
511 */
512 public static class KeyCondition implements Condition, ToTagConvertable {
513
514 /**
515 * The key name.
516 */
517 public final String label;
518 /**
519 * If we should negate the result of the match.
520 */
521 public final boolean negateResult;
522 /**
523 * Describes how to match the label against the key.
524 * @see KeyMatchType
525 */
526 public final KeyMatchType matchType;
527 /**
528 * A predicate used to match a the regexp against the key. Only used if the match type is regexp.
529 */
530 public final Predicate<String> containsPattern;
531
532 /**
533 * Creates a new KeyCondition
534 * @param label The key name (or regexp) to use.
535 * @param negateResult If we should negate the result.,
536 * @param matchType The match type.
537 */
538 public KeyCondition(String label, boolean negateResult, KeyMatchType matchType) {
539 this.label = label;
540 this.negateResult = negateResult;
541 this.matchType = matchType == null ? KeyMatchType.EQ : matchType;
542 this.containsPattern = KeyMatchType.REGEX == matchType
543 ? Pattern.compile(label).asPredicate()
544 : null;
545 }
546
547 @Override
548 public boolean applies(Environment e) {
549 switch(e.getContext()) {
550 case PRIMITIVE:
551 switch (matchType) {
552 case TRUE:
553 return e.osm.isKeyTrue(label) ^ negateResult;
554 case FALSE:
555 return e.osm.isKeyFalse(label) ^ negateResult;
556 case REGEX:
557 return e.osm.keySet().stream().anyMatch(containsPattern) ^ negateResult;
558 default:
559 return e.osm.hasKey(label) ^ negateResult;
560 }
561 case LINK:
562 Utils.ensure(false, "Illegal state: KeyCondition not supported in LINK context");
563 return false;
564 default: throw new AssertionError();
565 }
566 }
567
568 /**
569 * Get the matched key and the corresponding value.
570 * <p>
571 * WARNING: This ignores {@link #negateResult}.
572 * <p>
573 * WARNING: For regexp, the regular expression is returned instead of a key if the match failed.
574 * @param p The primitive to get the value from.
575 * @return The tag.
576 */
577 @Override
578 public Tag asTag(OsmPrimitive p) {
579 String key = label;
580 if (KeyMatchType.REGEX == matchType) {
581 key = p.keySet().stream().filter(containsPattern).findAny().orElse(key);
582 }
583 return new Tag(key, p.get(key));
584 }
585
586 @Override
587 public String toString() {
588 return '[' + (negateResult ? "!" : "") + label + ']';
589 }
590 }
591
592 /**
593 * Class condition.
594 */
595 public static class ClassCondition implements Condition {
596
597 /** Class identifier */
598 public final String id;
599 final boolean not;
600
601 /**
602 * Constructs a new {@code ClassCondition}.
603 * @param id id
604 * @param not negation or not
605 */
606 public ClassCondition(String id, boolean not) {
607 this.id = id;
608 this.not = not;
609 }
610
611 @Override
612 public boolean applies(Environment env) {
613 Cascade cascade = env.getCascade(env.layer);
614 return cascade != null && (not ^ cascade.containsKey(id));
615 }
616
617 @Override
618 public String toString() {
619 return (not ? "!" : "") + '.' + id;
620 }
621 }
622
623 /**
624 * Like <a href="http://www.w3.org/TR/css3-selectors/#pseudo-classes">CSS pseudo classes</a>, MapCSS pseudo classes
625 * are written in lower case with dashes between words.
626 */
627 public static final class PseudoClasses {
628
629 private PseudoClasses() {
630 // Hide default constructor for utilities classes
631 }
632
633 /**
634 * {@code closed} tests whether the way is closed or the relation is a closed multipolygon
635 * @param e MapCSS environment
636 * @return {@code true} if the way is closed or the relation is a closed multipolygon
637 */
638 static boolean closed(Environment e) { // NO_UCD (unused code)
639 if (e.osm instanceof IWay<?> && ((IWay<?>) e.osm).isClosed())
640 return true;
641 return e.osm instanceof IRelation<?> && ((IRelation<?>) e.osm).isMultipolygon();
642 }
643
644 /**
645 * {@code :modified} tests whether the object has been modified.
646 * @param e MapCSS environment
647 * @return {@code true} if the object has been modified
648 * @see IPrimitive#isModified()
649 */
650 static boolean modified(Environment e) { // NO_UCD (unused code)
651 return e.osm.isModified() || e.osm.isNewOrUndeleted();
652 }
653
654 /**
655 * {@code ;new} tests whether the object is new.
656 * @param e MapCSS environment
657 * @return {@code true} if the object is new
658 * @see IPrimitive#isNew()
659 */
660 static boolean _new(Environment e) { // NO_UCD (unused code)
661 return e.osm.isNew();
662 }
663
664 /**
665 * {@code :connection} tests whether the object is a connection node.
666 * @param e MapCSS environment
667 * @return {@code true} if the object is a connection node
668 * @see Node#isConnectionNode()
669 */
670 static boolean connection(Environment e) { // NO_UCD (unused code)
671 return e.osm instanceof INode && e.osm.getDataSet() != null && ((INode) e.osm).isConnectionNode();
672 }
673
674 /**
675 * {@code :tagged} tests whether the object is tagged.
676 * @param e MapCSS environment
677 * @return {@code true} if the object is tagged
678 * @see IPrimitive#isTagged()
679 */
680 static boolean tagged(Environment e) { // NO_UCD (unused code)
681 return e.osm.isTagged();
682 }
683
684 /**
685 * {@code :same-tags} tests whether the object has the same tags as its child/parent.
686 * @param e MapCSS environment
687 * @return {@code true} if the object has the same tags as its child/parent
688 * @see IPrimitive#hasSameInterestingTags(IPrimitive)
689 */
690 static boolean sameTags(Environment e) { // NO_UCD (unused code)
691 return e.osm.hasSameInterestingTags(Utils.firstNonNull(e.child, e.parent));
692 }
693
694 /**
695 * {@code :area-style} tests whether the object has an area style. This is useful for validators.
696 * @param e MapCSS environment
697 * @return {@code true} if the object has an area style
698 * @see ElemStyles#hasAreaElemStyle(IPrimitive, boolean)
699 */
700 static boolean areaStyle(Environment e) { // NO_UCD (unused code)
701 // only for validator
702 return ElemStyles.hasAreaElemStyle(e.osm, false);
703 }
704
705 /**
706 * {@code unconnected}: tests whether the object is a unconnected node.
707 * @param e MapCSS environment
708 * @return {@code true} if the object is a unconnected node
709 */
710 static boolean unconnected(Environment e) { // NO_UCD (unused code)
711 return e.osm instanceof Node && ((Node) e.osm).getParentWays().isEmpty();
712 }
713
714 /**
715 * {@code righthandtraffic} checks if there is right-hand traffic at the current location.
716 * @param e MapCSS environment
717 * @return {@code true} if there is right-hand traffic at the current location
718 * @see Functions#is_right_hand_traffic(Environment)
719 */
720 static boolean righthandtraffic(Environment e) { // NO_UCD (unused code)
721 return Functions.is_right_hand_traffic(e);
722 }
723
724 /**
725 * {@code clockwise} whether the way is closed and oriented clockwise,
726 * or non-closed and the 1st, 2nd and last node are in clockwise order.
727 * @param e MapCSS environment
728 * @return {@code true} if the way clockwise
729 * @see Functions#is_clockwise(Environment)
730 */
731 static boolean clockwise(Environment e) { // NO_UCD (unused code)
732 return Functions.is_clockwise(e);
733 }
734
735 /**
736 * {@code anticlockwise} whether the way is closed and oriented anticlockwise,
737 * or non-closed and the 1st, 2nd and last node are in anticlockwise order.
738 * @param e MapCSS environment
739 * @return {@code true} if the way clockwise
740 * @see Functions#is_anticlockwise(Environment)
741 */
742 static boolean anticlockwise(Environment e) { // NO_UCD (unused code)
743 return Functions.is_anticlockwise(e);
744 }
745
746 /**
747 * {@code unclosed-multipolygon} tests whether the object is an unclosed multipolygon.
748 * @param e MapCSS environment
749 * @return {@code true} if the object is an unclosed multipolygon
750 */
751 static boolean unclosed_multipolygon(Environment e) { // NO_UCD (unused code)
752 return e.osm instanceof Relation && ((Relation) e.osm).isMultipolygon() &&
753 !e.osm.isIncomplete() && !((Relation) e.osm).hasIncompleteMembers() &&
754 !MultipolygonCache.getInstance().get((Relation) e.osm).getOpenEnds().isEmpty();
755 }
756
757 private static final Predicate<OsmPrimitive> IN_DOWNLOADED_AREA = new InDataSourceArea(false);
758
759 /**
760 * {@code in-downloaded-area} tests whether the object is within source area ("downloaded area").
761 * @param e MapCSS environment
762 * @return {@code true} if the object is within source area ("downloaded area")
763 * @see InDataSourceArea
764 */
765 static boolean inDownloadedArea(Environment e) { // NO_UCD (unused code)
766 return e.osm instanceof OsmPrimitive && IN_DOWNLOADED_AREA.test((OsmPrimitive) e.osm);
767 }
768
769 static boolean completely_downloaded(Environment e) { // NO_UCD (unused code)
770 if (e.osm instanceof IRelation<?>) {
771 return !((IRelation<?>) e.osm).hasIncompleteMembers();
772 } else {
773 return true;
774 }
775 }
776
777 static boolean closed2(Environment e) { // NO_UCD (unused code)
778 if (e.osm instanceof IWay<?> && ((IWay<?>) e.osm).isClosed())
779 return true;
780 if (e.osm instanceof Relation && ((Relation) e.osm).isMultipolygon()) {
781 Multipolygon multipolygon = MultipolygonCache.getInstance().get((Relation) e.osm);
782 return multipolygon != null && multipolygon.getOpenEnds().isEmpty();
783 }
784 return false;
785 }
786
787 static boolean selected(Environment e) { // NO_UCD (unused code)
788 if (e.mc != null) {
789 e.mc.getCascade(e.layer).setDefaultSelectedHandling(false);
790 }
791 return e.osm.isSelected();
792 }
793 }
794
795 /**
796 * Pseudo class condition.
797 */
798 public static class PseudoClassCondition implements Condition {
799
800 final Method method;
801 final boolean not;
802
803 protected PseudoClassCondition(Method method, boolean not) {
804 this.method = method;
805 this.not = not;
806 }
807
808 /**
809 * Create a new pseudo class condition
810 * @param id The id of the pseudo class
811 * @param not <code>true</code> to invert the condition
812 * @param context The context the class is found in.
813 * @return The new condition
814 */
815 public static PseudoClassCondition createPseudoClassCondition(String id, boolean not, Context context) {
816 CheckParameterUtil.ensureThat(!"sameTags".equals(id) || Context.LINK == context, "sameTags only supported in LINK context");
817 if ("open_end".equals(id)) {
818 return new OpenEndPseudoClassCondition(not);
819 }
820 final Method method = getMethod(id);
821 if (method != null) {
822 return new PseudoClassCondition(method, not);
823 }
824 throw new MapCSSException("Invalid pseudo class specified: " + id);
825 }
826
827 protected static Method getMethod(String id) {
828 String cleanId = clean(id);
829 return Arrays.stream(PseudoClasses.class.getDeclaredMethods())
830 .filter(method -> clean(method.getName()).equalsIgnoreCase(cleanId))
831 .findFirst().orElse(null);
832 }
833
834 private static String clean(String id) {
835 // for backwards compatibility, consider :sameTags == :same-tags == :same_tags (#11150)
836 return id.replaceAll("[-_]", "");
837 }
838
839 @Override
840 public boolean applies(Environment e) {
841 try {
842 return not ^ (Boolean) method.invoke(null, e);
843 } catch (ReflectiveOperationException ex) {
844 throw new JosmRuntimeException(ex);
845 }
846 }
847
848 @Override
849 public String toString() {
850 return (not ? "!" : "") + ':' + method.getName();
851 }
852 }
853
854 /**
855 * Open end pseudo class condition.
856 */
857 public static class OpenEndPseudoClassCondition extends PseudoClassCondition {
858 /**
859 * Constructs a new {@code OpenEndPseudoClassCondition}.
860 * @param not negation or not
861 */
862 public OpenEndPseudoClassCondition(boolean not) {
863 super(null, not);
864 }
865
866 @Override
867 public boolean applies(Environment e) {
868 return true;
869 }
870 }
871
872 /**
873 * A condition that is fulfilled whenever the expression is evaluated to be true.
874 */
875 public static class ExpressionCondition implements Condition {
876
877 final Expression e;
878
879 /**
880 * Constructs a new {@code ExpressionFactory}
881 * @param e expression
882 */
883 public ExpressionCondition(Expression e) {
884 this.e = e;
885 }
886
887 /**
888 * Returns the expression.
889 * @return the expression
890 * @since 14484
891 */
892 public final Expression getExpression() {
893 return e;
894 }
895
896 @Override
897 public boolean applies(Environment env) {
898 Boolean b = Cascade.convertTo(e.evaluate(env), Boolean.class);
899 return b != null && b;
900 }
901
902 @Override
903 public String toString() {
904 return '[' + e.toString() + ']';
905 }
906 }
907}
Note: See TracBrowser for help on using the repository browser.