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

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

see #12472 - fix more warnings, increase maximum number of reported warnings from 100 to 1000

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