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

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

Refactoring: OsmUtils.splitMultipleValues

  • Property svn:eol-style set to native
File size: 32.1 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.EnumSet;
7import java.util.Map;
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;
262 this.v = v;
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;
314 this.v = v;
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);
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 for (Map.Entry<String, String> kv: env.osm.getKeys().entrySet()) {
410 if (keyPattern.matcher(kv.getKey()).find() && pattern.matcher(kv.getValue()).find()) {
411 return true;
412 }
413 }
414 return false;
415 }
416 }
417
418 /**
419 * Role condition.
420 */
421 public static class RoleCondition implements Condition {
422 final String role;
423 final Op op;
424
425 /**
426 * Constructs a new {@code RoleCondition}.
427 * @param role role
428 * @param op operation
429 */
430 public RoleCondition(String role, Op op) {
431 this.role = role;
432 this.op = op;
433 }
434
435 @Override
436 public boolean applies(Environment env) {
437 String testRole = env.getRole();
438 if (testRole == null) return false;
439 return op.eval(testRole, role);
440 }
441 }
442
443 /**
444 * Index condition.
445 */
446 public static class IndexCondition implements Condition {
447 final String index;
448 final Op op;
449
450 /**
451 * Constructs a new {@code IndexCondition}.
452 * @param index index
453 * @param op operation
454 */
455 public IndexCondition(String index, Op op) {
456 this.index = index;
457 this.op = op;
458 }
459
460 @Override
461 public boolean applies(Environment env) {
462 if (env.index == null) return false;
463 if (index.startsWith("-")) {
464 return env.count != null && op.eval(Integer.toString(env.index - env.count), index);
465 } else {
466 return op.eval(Integer.toString(env.index + 1), index);
467 }
468 }
469 }
470
471 /**
472 * This defines how {@link KeyCondition} matches a given key.
473 */
474 public enum KeyMatchType {
475 /**
476 * The key needs to be equal to the given label.
477 */
478 EQ,
479 /**
480 * The key needs to have a true value (yes, ...)
481 * @see OsmUtils#isTrue(String)
482 */
483 TRUE,
484 /**
485 * The key needs to have a false value (no, ...)
486 * @see OsmUtils#isFalse(String)
487 */
488 FALSE,
489 /**
490 * The key needs to match the given regular expression.
491 */
492 REGEX
493 }
494
495 /**
496 * <p>KeyCondition represent one of the following conditions in either the link or the
497 * primitive context:</p>
498 * <pre>
499 * ["a label"] PRIMITIVE: the primitive has a tag "a label"
500 * LINK: the parent is a relation and it has at least one member with the role
501 * "a label" referring to the child
502 *
503 * [!"a label"] PRIMITIVE: the primitive doesn't have a tag "a label"
504 * LINK: the parent is a relation but doesn't have a member with the role
505 * "a label" referring to the child
506 *
507 * ["a label"?] PRIMITIVE: the primitive has a tag "a label" whose value evaluates to a true-value
508 * LINK: not supported
509 *
510 * ["a label"?!] PRIMITIVE: the primitive has a tag "a label" whose value evaluates to a false-value
511 * LINK: not supported
512 * </pre>
513 */
514 public static class KeyCondition implements Condition, ToTagConvertable {
515
516 /**
517 * The key name.
518 */
519 public final String label;
520 /**
521 * If we should negate the result of the match.
522 */
523 public final boolean negateResult;
524 /**
525 * Describes how to match the label against the key.
526 * @see KeyMatchType
527 */
528 public final KeyMatchType matchType;
529 /**
530 * A predicate used to match a the regexp against the key. Only used if the match type is regexp.
531 */
532 public final Predicate<String> containsPattern;
533
534 /**
535 * Creates a new KeyCondition
536 * @param label The key name (or regexp) to use.
537 * @param negateResult If we should negate the result.,
538 * @param matchType The match type.
539 */
540 public KeyCondition(String label, boolean negateResult, KeyMatchType matchType) {
541 this.label = label;
542 this.negateResult = negateResult;
543 this.matchType = matchType == null ? KeyMatchType.EQ : matchType;
544 this.containsPattern = KeyMatchType.REGEX == matchType
545 ? Pattern.compile(label).asPredicate()
546 : null;
547 }
548
549 @Override
550 public boolean applies(Environment e) {
551 switch(e.getContext()) {
552 case PRIMITIVE:
553 switch (matchType) {
554 case TRUE:
555 return e.osm.isKeyTrue(label) ^ negateResult;
556 case FALSE:
557 return e.osm.isKeyFalse(label) ^ negateResult;
558 case REGEX:
559 return e.osm.keySet().stream().anyMatch(containsPattern) ^ negateResult;
560 default:
561 return e.osm.hasKey(label) ^ negateResult;
562 }
563 case LINK:
564 Utils.ensure(false, "Illegal state: KeyCondition not supported in LINK context");
565 return false;
566 default: throw new AssertionError();
567 }
568 }
569
570 /**
571 * Get the matched key and the corresponding value.
572 * <p>
573 * WARNING: This ignores {@link #negateResult}.
574 * <p>
575 * WARNING: For regexp, the regular expression is returned instead of a key if the match failed.
576 * @param p The primitive to get the value from.
577 * @return The tag.
578 */
579 @Override
580 public Tag asTag(OsmPrimitive p) {
581 String key = label;
582 if (KeyMatchType.REGEX == matchType) {
583 key = p.keySet().stream().filter(containsPattern).findAny().orElse(key);
584 }
585 return new Tag(key, p.get(key));
586 }
587
588 @Override
589 public String toString() {
590 return '[' + (negateResult ? "!" : "") + label + ']';
591 }
592 }
593
594 /**
595 * Class condition.
596 */
597 public static class ClassCondition implements Condition {
598
599 /** Class identifier */
600 public final String id;
601 final boolean not;
602
603 /**
604 * Constructs a new {@code ClassCondition}.
605 * @param id id
606 * @param not negation or not
607 */
608 public ClassCondition(String id, boolean not) {
609 this.id = id;
610 this.not = not;
611 }
612
613 @Override
614 public boolean applies(Environment env) {
615 Cascade cascade = env.getCascade(env.layer);
616 return cascade != null && (not ^ cascade.containsKey(id));
617 }
618
619 @Override
620 public String toString() {
621 return (not ? "!" : "") + '.' + id;
622 }
623 }
624
625 /**
626 * Like <a href="http://www.w3.org/TR/css3-selectors/#pseudo-classes">CSS pseudo classes</a>, MapCSS pseudo classes
627 * are written in lower case with dashes between words.
628 */
629 public static final class PseudoClasses {
630
631 private PseudoClasses() {
632 // Hide default constructor for utilities classes
633 }
634
635 /**
636 * {@code closed} tests whether the way is closed or the relation is a closed multipolygon
637 * @param e MapCSS environment
638 * @return {@code true} if the way is closed or the relation is a closed multipolygon
639 */
640 static boolean closed(Environment e) { // NO_UCD (unused code)
641 if (e.osm instanceof IWay<?> && ((IWay<?>) e.osm).isClosed())
642 return true;
643 return e.osm instanceof IRelation<?> && ((IRelation<?>) e.osm).isMultipolygon();
644 }
645
646 /**
647 * {@code :modified} tests whether the object has been modified.
648 * @param e MapCSS environment
649 * @return {@code true} if the object has been modified
650 * @see IPrimitive#isModified()
651 */
652 static boolean modified(Environment e) { // NO_UCD (unused code)
653 return e.osm.isModified() || e.osm.isNewOrUndeleted();
654 }
655
656 /**
657 * {@code ;new} tests whether the object is new.
658 * @param e MapCSS environment
659 * @return {@code true} if the object is new
660 * @see IPrimitive#isNew()
661 */
662 static boolean _new(Environment e) { // NO_UCD (unused code)
663 return e.osm.isNew();
664 }
665
666 /**
667 * {@code :connection} tests whether the object is a connection node.
668 * @param e MapCSS environment
669 * @return {@code true} if the object is a connection node
670 * @see Node#isConnectionNode()
671 */
672 static boolean connection(Environment e) { // NO_UCD (unused code)
673 return e.osm instanceof INode && e.osm.getDataSet() != null && ((INode) e.osm).isConnectionNode();
674 }
675
676 /**
677 * {@code :tagged} tests whether the object is tagged.
678 * @param e MapCSS environment
679 * @return {@code true} if the object is tagged
680 * @see IPrimitive#isTagged()
681 */
682 static boolean tagged(Environment e) { // NO_UCD (unused code)
683 return e.osm.isTagged();
684 }
685
686 /**
687 * {@code :same-tags} tests whether the object has the same tags as its child/parent.
688 * @param e MapCSS environment
689 * @return {@code true} if the object has the same tags as its child/parent
690 * @see IPrimitive#hasSameInterestingTags(IPrimitive)
691 */
692 static boolean sameTags(Environment e) { // NO_UCD (unused code)
693 return e.osm.hasSameInterestingTags(Utils.firstNonNull(e.child, e.parent));
694 }
695
696 /**
697 * {@code :area-style} tests whether the object has an area style. This is useful for validators.
698 * @param e MapCSS environment
699 * @return {@code true} if the object has an area style
700 * @see ElemStyles#hasAreaElemStyle(IPrimitive, boolean)
701 */
702 static boolean areaStyle(Environment e) { // NO_UCD (unused code)
703 // only for validator
704 return ElemStyles.hasAreaElemStyle(e.osm, false);
705 }
706
707 /**
708 * {@code unconnected}: tests whether the object is a unconnected node.
709 * @param e MapCSS environment
710 * @return {@code true} if the object is a unconnected node
711 */
712 static boolean unconnected(Environment e) { // NO_UCD (unused code)
713 return e.osm instanceof Node && ((Node) e.osm).getParentWays().isEmpty();
714 }
715
716 /**
717 * {@code righthandtraffic} checks if there is right-hand traffic at the current location.
718 * @param e MapCSS environment
719 * @return {@code true} if there is right-hand traffic at the current location
720 * @see Functions#is_right_hand_traffic(Environment)
721 */
722 static boolean righthandtraffic(Environment e) { // NO_UCD (unused code)
723 return Functions.is_right_hand_traffic(e);
724 }
725
726 /**
727 * {@code clockwise} whether the way is closed and oriented clockwise,
728 * or non-closed and the 1st, 2nd and last node are in clockwise order.
729 * @param e MapCSS environment
730 * @return {@code true} if the way clockwise
731 * @see Functions#is_clockwise(Environment)
732 */
733 static boolean clockwise(Environment e) { // NO_UCD (unused code)
734 return Functions.is_clockwise(e);
735 }
736
737 /**
738 * {@code anticlockwise} whether the way is closed and oriented anticlockwise,
739 * or non-closed and the 1st, 2nd and last node are in anticlockwise order.
740 * @param e MapCSS environment
741 * @return {@code true} if the way clockwise
742 * @see Functions#is_anticlockwise(Environment)
743 */
744 static boolean anticlockwise(Environment e) { // NO_UCD (unused code)
745 return Functions.is_anticlockwise(e);
746 }
747
748 /**
749 * {@code unclosed-multipolygon} tests whether the object is an unclosed multipolygon.
750 * @param e MapCSS environment
751 * @return {@code true} if the object is an unclosed multipolygon
752 */
753 static boolean unclosed_multipolygon(Environment e) { // NO_UCD (unused code)
754 return e.osm instanceof Relation && ((Relation) e.osm).isMultipolygon() &&
755 !e.osm.isIncomplete() && !((Relation) e.osm).hasIncompleteMembers() &&
756 !MultipolygonCache.getInstance().get((Relation) e.osm).getOpenEnds().isEmpty();
757 }
758
759 private static final Predicate<OsmPrimitive> IN_DOWNLOADED_AREA = new InDataSourceArea(false);
760
761 /**
762 * {@code in-downloaded-area} tests whether the object is within source area ("downloaded area").
763 * @param e MapCSS environment
764 * @return {@code true} if the object is within source area ("downloaded area")
765 * @see InDataSourceArea
766 */
767 static boolean inDownloadedArea(Environment e) { // NO_UCD (unused code)
768 return e.osm instanceof OsmPrimitive && IN_DOWNLOADED_AREA.test((OsmPrimitive) e.osm);
769 }
770
771 static boolean completely_downloaded(Environment e) { // NO_UCD (unused code)
772 if (e.osm instanceof IRelation<?>) {
773 return !((IRelation<?>) e.osm).hasIncompleteMembers();
774 } else {
775 return true;
776 }
777 }
778
779 static boolean closed2(Environment e) { // NO_UCD (unused code)
780 if (e.osm instanceof IWay<?> && ((IWay<?>) e.osm).isClosed())
781 return true;
782 if (e.osm instanceof Relation && ((Relation) e.osm).isMultipolygon()) {
783 Multipolygon multipolygon = MultipolygonCache.getInstance().get((Relation) e.osm);
784 return multipolygon != null && multipolygon.getOpenEnds().isEmpty();
785 }
786 return false;
787 }
788
789 static boolean selected(Environment e) { // NO_UCD (unused code)
790 if (e.mc != null) {
791 e.mc.getCascade(e.layer).setDefaultSelectedHandling(false);
792 }
793 return e.osm.isSelected();
794 }
795 }
796
797 /**
798 * Pseudo class condition.
799 */
800 public static class PseudoClassCondition implements Condition {
801
802 final Method method;
803 final boolean not;
804
805 protected PseudoClassCondition(Method method, boolean not) {
806 this.method = method;
807 this.not = not;
808 }
809
810 /**
811 * Create a new pseudo class condition
812 * @param id The id of the pseudo class
813 * @param not <code>true</code> to invert the condition
814 * @param context The context the class is found in.
815 * @return The new condition
816 */
817 public static PseudoClassCondition createPseudoClassCondition(String id, boolean not, Context context) {
818 CheckParameterUtil.ensureThat(!"sameTags".equals(id) || Context.LINK == context, "sameTags only supported in LINK context");
819 if ("open_end".equals(id)) {
820 return new OpenEndPseudoClassCondition(not);
821 }
822 final Method method = getMethod(id);
823 if (method != null) {
824 return new PseudoClassCondition(method, not);
825 }
826 throw new MapCSSException("Invalid pseudo class specified: " + id);
827 }
828
829 protected static Method getMethod(String id) {
830 String cleanId = id.replaceAll("-|_", "");
831 for (Method method : PseudoClasses.class.getDeclaredMethods()) {
832 // for backwards compatibility, consider :sameTags == :same-tags == :same_tags (#11150)
833 final String methodName = method.getName().replaceAll("-|_", "");
834 if (methodName.equalsIgnoreCase(cleanId)) {
835 return method;
836 }
837 }
838 return null;
839 }
840
841 @Override
842 public boolean applies(Environment e) {
843 try {
844 return not ^ (Boolean) method.invoke(null, e);
845 } catch (ReflectiveOperationException ex) {
846 throw new JosmRuntimeException(ex);
847 }
848 }
849
850 @Override
851 public String toString() {
852 return (not ? "!" : "") + ':' + method.getName();
853 }
854 }
855
856 /**
857 * Open end pseudo class condition.
858 */
859 public static class OpenEndPseudoClassCondition extends PseudoClassCondition {
860 /**
861 * Constructs a new {@code OpenEndPseudoClassCondition}.
862 * @param not negation or not
863 */
864 public OpenEndPseudoClassCondition(boolean not) {
865 super(null, not);
866 }
867
868 @Override
869 public boolean applies(Environment e) {
870 return true;
871 }
872 }
873
874 /**
875 * A condition that is fulfilled whenever the expression is evaluated to be true.
876 */
877 public static class ExpressionCondition implements Condition {
878
879 final Expression e;
880
881 /**
882 * Constructs a new {@code ExpressionFactory}
883 * @param e expression
884 */
885 public ExpressionCondition(Expression e) {
886 this.e = e;
887 }
888
889 /**
890 * Returns the expression.
891 * @return the expression
892 * @since 14484
893 */
894 public final Expression getExpression() {
895 return e;
896 }
897
898 @Override
899 public boolean applies(Environment env) {
900 Boolean b = Cascade.convertTo(e.evaluate(env), Boolean.class);
901 return b != null && b;
902 }
903
904 @Override
905 public String toString() {
906 return '[' + e.toString() + ']';
907 }
908 }
909}
Note: See TracBrowser for help on using the repository browser.