source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java@ 18871

Last change on this file since 18871 was 18871, checked in by taylor.smock, 7 months ago

See #23218: Use newer error_prone versions when compiling on Java 11+

error_prone 2.11 dropped support for compiling with Java 8, although it still
supports compiling for Java 8. The "major" new check for us is NotJavadoc since
we used /** in quite a few places which were not javadoc.

Other "new" checks that are of interest:

  • AlreadyChecked: if (foo) { doFoo(); } else if (!foo) { doBar(); }
  • UnnecessaryStringBuilder: Avoid StringBuilder (Java converts + to StringBuilder behind-the-scenes, but may also do something else if it performs better)
  • NonApiType: Avoid specific interface types in function definitions
  • NamedLikeContextualKeyword: Avoid using restricted names for classes and methods
  • UnusedMethod: Unused private methods should be removed

This fixes most of the new error_prone issues and some SonarLint issues.

  • Property svn:eol-style set to native
File size: 33.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint.mapcss;
3
4import static org.openstreetmap.josm.data.projection.Ellipsoid.WGS84;
5
6import java.awt.geom.Area;
7import java.awt.geom.Point2D;
8import java.text.MessageFormat;
9import java.util.ArrayList;
10import java.util.Arrays;
11import java.util.Collection;
12import java.util.Collections;
13import java.util.HashMap;
14import java.util.LinkedHashSet;
15import java.util.List;
16import java.util.Map;
17import java.util.Objects;
18import java.util.Set;
19import java.util.stream.Collectors;
20
21import org.openstreetmap.josm.data.osm.INode;
22import org.openstreetmap.josm.data.osm.IPrimitive;
23import org.openstreetmap.josm.data.osm.IRelation;
24import org.openstreetmap.josm.data.osm.IRelationMember;
25import org.openstreetmap.josm.data.osm.IWay;
26import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
27import org.openstreetmap.josm.data.osm.OsmUtils;
28import org.openstreetmap.josm.data.osm.Relation;
29import org.openstreetmap.josm.data.osm.Way;
30import org.openstreetmap.josm.data.osm.WaySegment;
31import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
32import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
33import org.openstreetmap.josm.data.validation.tests.CrossingWays;
34import org.openstreetmap.josm.gui.mappaint.Environment;
35import org.openstreetmap.josm.gui.mappaint.Range;
36import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.IndexCondition;
37import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.OpenEndPseudoClassCondition;
38import org.openstreetmap.josm.tools.CheckParameterUtil;
39import org.openstreetmap.josm.tools.CompositeList;
40import org.openstreetmap.josm.tools.Geometry;
41import org.openstreetmap.josm.tools.Geometry.PolygonIntersection;
42import org.openstreetmap.josm.tools.Logging;
43import org.openstreetmap.josm.tools.Pair;
44import org.openstreetmap.josm.tools.Utils;
45
46/**
47 * MapCSS selector.
48 * <p>
49 * A rule has two parts, a selector and a declaration block
50 * e.g.
51 * <pre>
52 * way[highway=residential]
53 * { width: 10; color: blue; }
54 * </pre>
55 *
56 * The selector decides, if the declaration block gets applied or not.
57 * <p>
58 * All implementing classes of Selector are immutable.
59 */
60public interface Selector {
61
62 /** selector base that matches anything. */
63 String BASE_ANY = "*";
64
65 /** selector base that matches on OSM object node. */
66 String BASE_NODE = "node";
67
68 /** selector base that matches on OSM object way. */
69 String BASE_WAY = "way";
70
71 /** selector base that matches on OSM object relation. */
72 String BASE_RELATION = "relation";
73
74 /** selector base that matches with any area regardless of whether the area border is only modelled with a single way or with
75 * a set of ways glued together with a relation.*/
76 String BASE_AREA = "area";
77
78 /** selector base for special rules containing meta information. */
79 String BASE_META = "meta";
80
81 /** selector base for style information not specific to nodes, ways or relations. */
82 String BASE_CANVAS = "canvas";
83
84 /** selector base for artificial bases created to use preferences. */
85 String BASE_SETTING = "setting";
86
87 /** selector base for grouping settings. */
88 String BASE_SETTINGS = "settings";
89
90 /**
91 * Apply the selector to the primitive and check if it matches.
92 *
93 * @param env the Environment. env.mc and env.layer are read-only when matching a selector.
94 * env.source is not needed. This method will set the matchingReferrers field of env as
95 * a side effect! Make sure to clear it before invoking this method.
96 * @return true, if the selector applies
97 */
98 boolean matches(Environment env);
99
100 /**
101 * Returns the subpart, if supported. A subpart identifies different rendering layers (<code>::subpart</code> syntax).
102 * @return the subpart, if supported
103 * @throws UnsupportedOperationException if not supported
104 */
105 Subpart getSubpart();
106
107 /**
108 * Returns the scale range, an interval of the form "lower &lt; x &lt;= upper" where 0 &lt;= lower &lt; upper.
109 * @return the scale range, if supported
110 * @throws UnsupportedOperationException if not supported
111 */
112 Range getRange();
113
114 String getBase();
115
116 /**
117 * Returns the list of conditions.
118 * @return the list of conditions
119 */
120 List<Condition> getConditions();
121
122 /**
123 * The type of child of parent selector.
124 * @see ChildOrParentSelector
125 */
126 enum ChildOrParentSelectorType {
127 CHILD, PARENT, SUBSET_OR_EQUAL, NOT_SUBSET_OR_EQUAL, SUPERSET_OR_EQUAL, NOT_SUPERSET_OR_EQUAL, CROSSING, SIBLING,
128 }
129
130 /**
131 * <p>Represents a child selector or a parent selector.</p>
132 *
133 * <p>In addition to the standard CSS notation for child selectors, JOSM also supports
134 * an "inverse" notation:</p>
135 * <pre>
136 * selector_a &gt; selector_b { ... } // the standard notation (child selector)
137 * relation[type=route] &gt; way { ... } // example (all ways of a route)
138 *
139 * selector_a &lt; selector_b { ... } // the inverse notation (parent selector)
140 * node[traffic_calming] &lt; way { ... } // example (way that has a traffic calming node)
141 * </pre>
142 * <p>Child: see <a href="https://josm.openstreetmap.de/wiki/Help/Styles/MapCSSImplementation#Childselector">wiki</a>
143 * <br>Parent: see <a href="https://josm.openstreetmap.de/wiki/Help/Styles/MapCSSImplementation#Parentselector">wiki</a></p>
144 */
145 class ChildOrParentSelector implements Selector {
146 public final Selector left;
147 public final LinkSelector link;
148 public final Selector right;
149 public final ChildOrParentSelectorType type;
150
151 /**
152 * Constructs a new {@code ChildOrParentSelector}.
153 * @param a the first selector
154 * @param link link
155 * @param b the second selector
156 * @param type the selector type
157 */
158 public ChildOrParentSelector(Selector a, LinkSelector link, Selector b, ChildOrParentSelectorType type) {
159 CheckParameterUtil.ensureParameterNotNull(a, "a");
160 CheckParameterUtil.ensureParameterNotNull(b, "b");
161 CheckParameterUtil.ensureParameterNotNull(link, "link");
162 CheckParameterUtil.ensureParameterNotNull(type, "type");
163 this.left = a;
164 this.link = link;
165 this.right = b;
166 this.type = type;
167 }
168
169 @Override
170 public String getBase() {
171 // take the base from the rightmost selector
172 return right.getBase();
173 }
174
175 @Override
176 public List<Condition> getConditions() {
177 return new CompositeList<>(left.getConditions(), right.getConditions());
178 }
179
180 /**
181 * <p>Finds the first referrer matching {@link #left}</p>
182 *
183 * <p>The visitor works on an environment and it saves the matching
184 * referrer in {@code e.parent} and its relative position in the
185 * list referrers "child list" in {@code e.index}.</p>
186 *
187 * <p>If after execution {@code e.parent} is null, no matching
188 * referrer was found.</p>
189 *
190 */
191 private class MatchingReferrerFinder implements PrimitiveVisitor {
192 private final Environment e;
193
194 /**
195 * Constructor
196 * @param e the environment against which we match
197 */
198 MatchingReferrerFinder(Environment e) {
199 this.e = e;
200 }
201
202 @Override
203 public void visit(INode n) {
204 // node should never be a referrer
205 throw new AssertionError();
206 }
207
208 private void doVisit(IPrimitive parent) {
209 // If e.parent is already set to the first matching referrer.
210 // We skip any following referrer injected into the visitor.
211 if (e.parent != null) return;
212
213 IPrimitive osm = e.osm;
214 try {
215 e.osm = parent;
216 if (!left.matches(e))
217 return;
218 } finally {
219 e.osm = osm;
220 }
221 int count = parent instanceof IWay<?>
222 ? ((IWay<?>) parent).getNodesCount()
223 : ((IRelation<?>) parent).getMembersCount();
224 if (link.getConditions().isEmpty()) {
225 // index is not needed, we can avoid the sequential search below
226 e.parent = parent;
227 e.count = count;
228 return;
229 }
230 // see #18964
231 int step = firstAndLastOnly() ? count - 1 : 1;
232 for (int i = 0; i < count; i += step) {
233 IPrimitive o = parent instanceof IWay<?>
234 ? ((IWay<?>) parent).getNode(i)
235 : ((IRelation<?>) parent).getMember(i).getMember();
236 if (Objects.equals(o, e.osm)
237 && link.matches(e.withParentAndIndexAndLinkContext(parent, i, count))) {
238 e.parent = parent;
239 e.index = i;
240 e.count = count;
241 return;
242 }
243 }
244 }
245
246 private boolean firstAndLastOnly() {
247 return link.getConditions().stream().allMatch(c -> c instanceof IndexCondition && ((IndexCondition) c).isFirstOrLast);
248 }
249
250 @Override
251 public void visit(IWay<?> w) {
252 doVisit(w);
253 }
254
255 @Override
256 public void visit(IRelation<?> r) {
257 doVisit(r);
258 }
259 }
260
261 private abstract static class AbstractFinder implements PrimitiveVisitor {
262 protected final Environment e;
263
264 protected AbstractFinder(Environment e) {
265 this.e = e;
266 }
267
268 @Override
269 public void visit(INode n) {
270 }
271
272 @Override
273 public void visit(IWay<?> w) {
274 }
275
276 @Override
277 public void visit(IRelation<?> r) {
278 }
279
280 public void visit(Collection<? extends IPrimitive> primitives) {
281 for (IPrimitive p : primitives) {
282 if (e.child != null) {
283 // abort if first match has been found
284 break;
285 } else if (isPrimitiveUsable(p)) {
286 p.accept(this);
287 }
288 }
289 }
290
291 public boolean isPrimitiveUsable(IPrimitive p) {
292 return !e.osm.equals(p) && p.isUsable();
293 }
294
295 protected void addToChildren(Environment e, IPrimitive p) {
296 if (e.children == null) {
297 e.children = new LinkedHashSet<>();
298 }
299 e.children.add(p);
300 }
301 }
302
303 private class MultipolygonOpenEndFinder extends AbstractFinder {
304
305 @Override
306 public void visit(IWay<?> w) {
307 w.visitReferrers(innerVisitor);
308 }
309
310 MultipolygonOpenEndFinder(Environment e) {
311 super(e);
312 }
313
314 private final PrimitiveVisitor innerVisitor = new AbstractFinder(e) {
315 @Override
316 public void visit(IRelation<?> r) {
317 if (r instanceof Relation && left.matches(e.withPrimitive(r))) {
318 final List<?> openEnds = MultipolygonCache.getInstance().get((Relation) r).getOpenEnds();
319 final int openEndIndex = openEnds.indexOf(e.osm);
320 if (openEndIndex >= 0) {
321 e.parent = r;
322 e.index = openEndIndex;
323 e.count = openEnds.size();
324 }
325 }
326 }
327 };
328 }
329
330 private final class CrossingFinder extends AbstractFinder {
331
332 private final String layer;
333 private Area area;
334 /** Will contain all way segments, grouped by cells */
335 Map<Point2D, List<WaySegment>> cellSegments;
336
337 private CrossingFinder(Environment e) {
338 super(e);
339 CheckParameterUtil.ensureThat(isArea(e.osm), "Only areas are supported");
340 layer = OsmUtils.getLayer(e.osm);
341 }
342
343 private Area getAreaEastNorth(IPrimitive p, Environment e) {
344 if (e.mpAreaCache != null && p.isMultipolygon()) {
345 Area a = e.mpAreaCache.get(p);
346 if (a == null) {
347 a = Geometry.getAreaEastNorth(p);
348 e.mpAreaCache.put(p, a);
349 }
350 return a;
351 }
352 return Geometry.getAreaEastNorth(p);
353 }
354
355 private Map<List<Way>, List<WaySegment>> findCrossings(IPrimitive area,
356 Map<Point2D, List<WaySegment>> cellSegments) {
357 /* The detected crossing ways */
358 Map<List<Way>, List<WaySegment>> crossingWays = new HashMap<>(50);
359 if (area instanceof Way) {
360 CrossingWays.findIntersectingWay((Way) area, cellSegments, crossingWays, false);
361 } else if (area instanceof Relation && area.isMultipolygon()) {
362 Relation r = (Relation) area;
363 for (Way w : r.getMemberPrimitives(Way.class)) {
364 if (!w.hasIncompleteNodes()) {
365 CrossingWays.findIntersectingWay(w, cellSegments, crossingWays, false);
366 }
367 }
368 }
369 return crossingWays;
370 }
371
372 @Override
373 public void visit(Collection<? extends IPrimitive> primitives) {
374 Set<? extends IPrimitive> toIgnore;
375 if (e.osm instanceof Relation) {
376 toIgnore = ((Relation) e.osm).getMemberPrimitives();
377 } else {
378 toIgnore = null;
379 }
380 boolean filterWithTested = e.toMatchForSurrounding != null && !e.toMatchForSurrounding.isEmpty();
381 for (IPrimitive p : primitives) {
382 if (filterWithTested && !e.toMatchForSurrounding.contains(p))
383 continue;
384 if (isPrimitiveUsable(p) && Objects.equals(layer, OsmUtils.getLayer(p))
385 && left.matches(new Environment(p).withParent(e.osm)) && isArea(p)
386 && (toIgnore == null || !toIgnore.contains(p))) {
387 if (e.osm instanceof Way && ((Way) e.osm).referrers(Relation.class).anyMatch(ref -> ref == p))
388 continue;
389 visitArea(p);
390 }
391 }
392 }
393
394 private void visitArea(IPrimitive p) {
395 if (area == null) {
396 area = getAreaEastNorth(e.osm, e);
397 }
398 Area otherArea = getAreaEastNorth(p, e);
399 if (area.isEmpty() || otherArea.isEmpty()) {
400 useFindCrossings(p);
401 } else {
402 // we have complete data. This allows to find intersections with shared nodes
403 // See #16707
404 Pair<PolygonIntersection, Area> is = Geometry.polygonIntersectionResult(
405 otherArea, area, Geometry.INTERSECTION_EPS_EAST_NORTH);
406 if (Geometry.PolygonIntersection.CROSSING == is.a) {
407 addToChildren(e, p);
408 // store intersection area to improve highlight and zoom to problem
409 if (e.intersections == null) {
410 e.intersections = new HashMap<>();
411 }
412 e.intersections.put(p, is.b);
413 }
414 }
415
416 }
417
418 private void useFindCrossings(IPrimitive p) {
419 if (cellSegments == null) {
420 // lazy initialisation
421 cellSegments = new HashMap<>();
422 findCrossings(e.osm, cellSegments); // ignore self intersections etc. here
423 }
424 // need a copy
425 final Map<Point2D, List<WaySegment>> tmpCellSegments = new HashMap<>(cellSegments);
426 // calculate all crossings between e.osm and p
427 Map<List<Way>, List<WaySegment>> crossingWays = findCrossings(p, tmpCellSegments);
428 if (!crossingWays.isEmpty()) {
429 addToChildren(e, p);
430 if (e.crossingWaysMap == null) {
431 e.crossingWaysMap = new HashMap<>();
432 }
433 e.crossingWaysMap.put(p, crossingWays);
434 }
435 }
436 }
437
438 /**
439 * Finds elements which are inside the right element, collects those in {@code children}
440 */
441 private class ContainsFinder extends AbstractFinder {
442 protected List<IPrimitive> toCheck;
443
444 protected ContainsFinder(Environment e) {
445 super(e);
446 CheckParameterUtil.ensureThat(!(e.osm instanceof INode), "Nodes not supported");
447 }
448
449 @Override
450 public void visit(Collection<? extends IPrimitive> primitives) {
451 for (IPrimitive p : primitives) {
452 if (p != e.osm && isPrimitiveUsable(p) && left.matches(new Environment(p).withParent(e.osm))) {
453 if (toCheck == null) {
454 toCheck = new ArrayList<>();
455 }
456 toCheck.add(p);
457 }
458 }
459 }
460
461 void execGeometryTests() {
462 if (Utils.isEmpty(toCheck))
463 return;
464 for (IPrimitive p : Geometry.filterInsideAnyPolygon(toCheck, e.osm)) {
465 addToChildren(e, p);
466 }
467 }
468 }
469
470 /**
471 * Finds elements which are inside the left element, or in other words, it finds elements enclosing e.osm.
472 * The found enclosing elements are collected in {@code e.children}.
473 */
474 private class InsideOrEqualFinder extends AbstractFinder {
475
476 protected InsideOrEqualFinder(Environment e) {
477 super(e);
478 }
479
480 @Override
481 public void visit(IWay<?> w) {
482 if (left.matches(new Environment(w).withParent(e.osm))
483 && w.getBBox().bounds(e.osm.getBBox())
484 && !Geometry.filterInsidePolygon(Collections.singletonList(e.osm), w).isEmpty()) {
485 addToChildren(e, w);
486 }
487 }
488
489 @Override
490 public void visit(IRelation<?> r) {
491 if (r instanceof Relation && r.isMultipolygon() && r.getBBox().bounds(e.osm.getBBox())
492 && left.matches(new Environment(r).withParent(e.osm))
493 && !Geometry.filterInsideMultipolygon(Collections.singletonList(e.osm), (Relation) r).isEmpty()) {
494 addToChildren(e, r);
495 }
496 }
497 }
498
499 private void visitBBox(Environment e, AbstractFinder finder) {
500 boolean withNodes = finder instanceof ContainsFinder;
501 if (e.osm.getDataSet() == null) {
502 // do nothing
503 } else if (left instanceof GeneralSelector) {
504 if (withNodes && ((GeneralSelector) left).matchesBase(OsmPrimitiveType.NODE)) {
505 finder.visit(e.osm.getDataSet().searchNodes(e.osm.getBBox()));
506 }
507 if (((GeneralSelector) left).matchesBase(OsmPrimitiveType.WAY)) {
508 finder.visit(e.osm.getDataSet().searchWays(e.osm.getBBox()));
509 }
510 if (((GeneralSelector) left).matchesBase(OsmPrimitiveType.RELATION)) {
511 finder.visit(e.osm.getDataSet().searchRelations(e.osm.getBBox()));
512 }
513 } else {
514 if (withNodes) {
515 finder.visit(e.osm.getDataSet().searchNodes(e.osm.getBBox()));
516 }
517 finder.visit(e.osm.getDataSet().searchWays(e.osm.getBBox()));
518 finder.visit(e.osm.getDataSet().searchRelations(e.osm.getBBox()));
519 }
520 }
521
522 private static boolean isArea(IPrimitive p) {
523 return (p instanceof IWay && ((IWay<?>) p).isClosed() && ((IWay<?>) p).getNodesCount() >= 4)
524 || (p instanceof IRelation && p.isMultipolygon() && !p.isIncomplete());
525 }
526
527 @Override
528 public boolean matches(Environment e) {
529
530 if (!right.matches(e))
531 return false;
532
533 if (ChildOrParentSelectorType.SUBSET_OR_EQUAL == type || ChildOrParentSelectorType.NOT_SUBSET_OR_EQUAL == type) {
534
535 if (e.osm.getDataSet() == null || !isArea(e.osm)) {
536 // only areas can contain elements
537 return ChildOrParentSelectorType.NOT_SUBSET_OR_EQUAL == type;
538 }
539 ContainsFinder containsFinder = new ContainsFinder(e);
540 e.parent = e.osm;
541
542 visitBBox(e, containsFinder);
543 containsFinder.execGeometryTests();
544 return ChildOrParentSelectorType.SUBSET_OR_EQUAL == type ? e.children != null : e.children == null;
545
546 } else if (ChildOrParentSelectorType.SUPERSET_OR_EQUAL == type || ChildOrParentSelectorType.NOT_SUPERSET_OR_EQUAL == type) {
547
548 if (e.osm.getDataSet() == null || (e.osm instanceof INode && !((INode) e.osm).isLatLonKnown())
549 || (!(e.osm instanceof INode) && !isArea(e.osm))) {
550 return ChildOrParentSelectorType.NOT_SUPERSET_OR_EQUAL == type;
551 }
552
553 InsideOrEqualFinder insideOrEqualFinder = new InsideOrEqualFinder(e);
554 e.parent = e.osm;
555
556 visitBBox(e, insideOrEqualFinder);
557 return ChildOrParentSelectorType.SUPERSET_OR_EQUAL == type ? e.children != null : e.children == null;
558
559 } else if (ChildOrParentSelectorType.CROSSING == type) {
560 e.parent = e.osm;
561 if (e.osm.getDataSet() != null && isArea(e.osm)) {
562 final CrossingFinder crossingFinder = new CrossingFinder(e);
563 visitBBox(e, crossingFinder);
564 return e.children != null;
565 }
566 return e.children != null;
567 } else if (ChildOrParentSelectorType.SIBLING == type) {
568 if (e.osm instanceof INode) {
569 for (IPrimitive ref : e.osm.getReferrers(true)) {
570 if (ref instanceof IWay) {
571 IWay<?> w = (IWay<?>) ref;
572 final int i = w.getNodes().indexOf(e.osm);
573 if (i - 1 >= 0) {
574 final INode n = w.getNode(i - 1);
575 final Environment e2 = e.withPrimitive(n).withParent(w).withChild(e.osm);
576 if (left.matches(e2) && link.matches(e2.withLinkContext())) {
577 e.child = n;
578 e.index = i;
579 e.count = w.getNodesCount();
580 e.parent = w;
581 return true;
582 }
583 }
584 }
585 }
586 }
587 } else if (ChildOrParentSelectorType.CHILD == type
588 && !link.getConditions().isEmpty()
589 && link.getConditions().get(0) instanceof OpenEndPseudoClassCondition) {
590 if (e.osm instanceof INode) {
591 e.osm.visitReferrers(new MultipolygonOpenEndFinder(e));
592 return e.parent != null;
593 }
594 } else if (ChildOrParentSelectorType.CHILD == type) {
595 MatchingReferrerFinder collector = new MatchingReferrerFinder(e);
596 e.osm.visitReferrers(collector);
597 if (e.parent != null)
598 return true;
599 } else if (ChildOrParentSelectorType.PARENT == type) {
600 if (e.osm instanceof IWay) {
601 List<? extends INode> wayNodes = ((IWay<?>) e.osm).getNodes();
602 for (int i = 0; i < wayNodes.size(); i++) {
603 INode n = wayNodes.get(i);
604 if (left.matches(e.withPrimitive(n))
605 && link.matches(e.withChildAndIndexAndLinkContext(n, i, wayNodes.size()))) {
606 e.child = n;
607 e.index = i;
608 e.count = wayNodes.size();
609 return true;
610 }
611 }
612 } else if (e.osm instanceof IRelation) {
613 List<? extends IRelationMember<?>> members = ((IRelation<?>) e.osm).getMembers();
614 for (int i = 0; i < members.size(); i++) {
615 IPrimitive member = members.get(i).getMember();
616 if (left.matches(e.withPrimitive(member))
617 && link.matches(e.withChildAndIndexAndLinkContext(member, i, members.size()))) {
618 e.child = member;
619 e.index = i;
620 e.count = members.size();
621 return true;
622 }
623 }
624 }
625 }
626 return false;
627 }
628
629 @Override
630 public Subpart getSubpart() {
631 return right.getSubpart();
632 }
633
634 @Override
635 public Range getRange() {
636 return right.getRange();
637 }
638
639 @Override
640 public String toString() {
641 return left.toString() + ' ' + (ChildOrParentSelectorType.PARENT == type ? '<' : '>') + link + ' ' + right;
642 }
643 }
644
645 /**
646 * Super class of {@link org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector} and
647 * {@link org.openstreetmap.josm.gui.mappaint.mapcss.Selector.LinkSelector}.
648 * @since 5841
649 */
650 abstract class AbstractSelector implements Selector {
651
652 private final Condition[] conds;
653
654 protected AbstractSelector(List<Condition> conditions) {
655 this.conds = conditions.toArray(new Condition[0]);
656 }
657
658 /**
659 * Determines if all conditions match the given environment.
660 * @param env The environment to check
661 * @return {@code true} if all conditions apply, false otherwise.
662 */
663 @Override
664 public boolean matches(Environment env) {
665 CheckParameterUtil.ensureParameterNotNull(env, "env");
666 // Avoid `conds.stream().allMatch(...)` for its high heap allocations
667 for (Condition c : conds) {
668 try {
669 if (!c.applies(env)) return false;
670 } catch (RuntimeException e) {
671 Logging.log(Logging.LEVEL_ERROR, "Exception while applying condition" + c + ':', e);
672 return false;
673 }
674 }
675 return true;
676 }
677
678 @Override
679 public List<Condition> getConditions() {
680 return Arrays.asList(conds);
681 }
682 }
683
684 /**
685 * In a child selector, conditions on the link between a parent and a child object.
686 * See <a href="https://josm.openstreetmap.de/wiki/Help/Styles/MapCSSImplementation#Linkselector">wiki</a>
687 */
688 class LinkSelector extends AbstractSelector {
689
690 public LinkSelector(List<Condition> conditions) {
691 super(conditions);
692 }
693
694 @Override
695 public boolean matches(Environment env) {
696 Utils.ensure(env.isLinkContext(), "Requires LINK context in environment, got ''{0}''", env.getContext());
697 return super.matches(env);
698 }
699
700 @Override
701 public String getBase() {
702 throw new UnsupportedOperationException();
703 }
704
705 @Override
706 public Subpart getSubpart() {
707 throw new UnsupportedOperationException();
708 }
709
710 @Override
711 public Range getRange() {
712 throw new UnsupportedOperationException();
713 }
714
715 @Override
716 public String toString() {
717 return "LinkSelector{conditions=" + getConditions() + '}';
718 }
719 }
720
721 /**
722 * General selector. See <a href="https://josm.openstreetmap.de/wiki/Help/Styles/MapCSSImplementation#Selectors">wiki</a>
723 */
724 class GeneralSelector extends AbstractSelector {
725
726 public final String base;
727 public final Range range;
728 public final Subpart subpart;
729
730 public GeneralSelector(String base, Range range, List<Condition> conds, Subpart subpart) {
731 super(conds);
732 this.base = checkBase(base);
733 this.range = Objects.requireNonNull(range, "range");
734 this.subpart = subpart != null ? subpart : Subpart.DEFAULT_SUBPART;
735 }
736
737 @Override
738 public Subpart getSubpart() {
739 return subpart;
740 }
741
742 @Override
743 public Range getRange() {
744 return range;
745 }
746
747 public boolean matchesConditions(Environment e) {
748 return super.matches(e);
749 }
750
751 @Override
752 public boolean matches(Environment e) {
753 return matchesBase(e) && super.matches(e);
754 }
755
756 /**
757 * Set base and check if this is a known value.
758 * @param base value for base
759 * @return the matching String constant for a known value
760 * @throws IllegalArgumentException if value is not knwon
761 */
762 private static String checkBase(String base) {
763 switch(base) {
764 case "*": return BASE_ANY;
765 case "node": return BASE_NODE;
766 case "way": return BASE_WAY;
767 case "relation": return BASE_RELATION;
768 case "area": return BASE_AREA;
769 case "meta": return BASE_META;
770 case "canvas": return BASE_CANVAS;
771 case "setting": return BASE_SETTING;
772 case "settings": return BASE_SETTINGS;
773 default:
774 throw new IllegalArgumentException(MessageFormat.format("Unknown MapCSS base selector {0}", base));
775 }
776 }
777
778 @Override
779 public String getBase() {
780 return base;
781 }
782
783 public boolean matchesBase(OsmPrimitiveType type) {
784 if (BASE_ANY.equals(base)) {
785 return true;
786 } else if (OsmPrimitiveType.NODE == type) {
787 return BASE_NODE.equals(base);
788 } else if (OsmPrimitiveType.WAY == type) {
789 return BASE_WAY.equals(base) || BASE_AREA.equals(base);
790 } else if (OsmPrimitiveType.RELATION == type) {
791 return BASE_AREA.equals(base) || BASE_RELATION.equals(base) || BASE_CANVAS.equals(base);
792 }
793 return false;
794 }
795
796 public boolean matchesBase(IPrimitive p) {
797 if (!matchesBase(p.getType())) {
798 return false;
799 } else {
800 if (p instanceof IRelation) {
801 if (BASE_AREA.equals(base)) {
802 return ((IRelation<?>) p).isMultipolygon();
803 } else if (BASE_CANVAS.equals(base)) {
804 return p.get("#canvas") != null;
805 }
806 }
807 return true;
808 }
809 }
810
811 public boolean matchesBase(Environment e) {
812 return matchesBase(e.osm);
813 }
814
815 public static Range fromLevel(int a, int b) {
816 // for input validation in Range constructor below
817 double lower = 0;
818 double upper = Double.POSITIVE_INFINITY;
819 if (b != Integer.MAX_VALUE) {
820 lower = level2scale(b + 1);
821 }
822 if (a != 0) {
823 upper = level2scale(a);
824 }
825 return new Range(lower, upper);
826 }
827
828 public static double level2scale(int lvl) {
829 if (lvl < 0)
830 throw new IllegalArgumentException("lvl must be >= 0 but is "+lvl);
831 // preliminary formula - map such that mapnik imagery tiles of the same
832 // or similar level are displayed at the given scale
833 return 2.0 * Math.PI * WGS84.a / Math.pow(2.0, lvl) / 2.56;
834 }
835
836 public static int scale2level(double scale) {
837 if (scale < 0)
838 throw new IllegalArgumentException("scale must be >= 0 but is "+scale);
839 return (int) Math.floor(Math.log(2 * Math.PI * WGS84.a / 2.56 / scale) / Math.log(2));
840 }
841
842 @Override
843 public String toString() {
844 return base
845 + (Range.ZERO_TO_INFINITY.equals(range) ? "" : range)
846 + getConditions().stream().map(String::valueOf).collect(Collectors.joining(""))
847 + (subpart != null && subpart != Subpart.DEFAULT_SUBPART ? ("::" + subpart) : "");
848 }
849 }
850}
Note: See TracBrowser for help on using the repository browser.