001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.mappaint.mapcss;
003
004import java.util.List;
005import java.util.regex.PatternSyntaxException;
006
007import org.openstreetmap.josm.Main;
008import org.openstreetmap.josm.data.osm.Node;
009import org.openstreetmap.josm.data.osm.OsmPrimitive;
010import org.openstreetmap.josm.data.osm.Relation;
011import org.openstreetmap.josm.data.osm.RelationMember;
012import org.openstreetmap.josm.data.osm.Way;
013import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
014import org.openstreetmap.josm.gui.mappaint.Environment;
015import org.openstreetmap.josm.gui.mappaint.Range;
016import org.openstreetmap.josm.tools.Pair;
017import org.openstreetmap.josm.tools.Utils;
018
019public interface Selector {
020
021    /**
022     * Apply the selector to the primitive and check if it matches.
023     *
024     * @param env the Environment. env.mc and env.layer are read-only when matching a selector.
025     * env.source is not needed. This method will set the matchingReferrers field of env as
026     * a side effect! Make sure to clear it before invoking this method.
027     * @return true, if the selector applies
028     */
029    public boolean matches(Environment env);
030
031    public String getSubpart();
032
033    public Range getRange();
034
035    /**
036     * <p>Represents a child selector or a parent selector.</p>
037     *
038     * <p>In addition to the standard CSS notation for child selectors, JOSM also supports
039     * an "inverse" notation:</p>
040     * <pre>
041     *    selector_a > selector_b { ... }       // the standard notation (child selector)
042     *    relation[type=route] > way { ... }    // example (all ways of a route)
043     *
044     *    selector_a < selector_b { ... }       // the inverse notation (parent selector)
045     *    node[traffic_calming] < way { ... }   // example (way that has a traffic calming node)
046     * </pre>
047     *
048     */
049    public static class ChildOrParentSelector implements Selector {
050        private final Selector left;
051        private final LinkSelector link;
052        private final Selector right;
053        /** true, if this represents a parent selector (otherwise it is a child selector)
054         */
055        private final boolean parentSelector;
056
057        /**
058         *
059         * @param a the first selector
060         * @param b the second selector
061         * @param parentSelector if true, this is a parent selector; otherwise a child selector
062         */
063        public ChildOrParentSelector(Selector a, LinkSelector link, Selector b, boolean parentSelector) {
064            this.left = a;
065            this.link = link;
066            this.right = b;
067            this.parentSelector = parentSelector;
068        }
069
070        /**
071         * <p>Finds the first referrer matching {@link #left}</p>
072         *
073         * <p>The visitor works on an environment and it saves the matching
074         * referrer in {@code e.parent} and its relative position in the
075         * list referrers "child list" in {@code e.index}.</p>
076         *
077         * <p>If after execution {@code e.parent} is null, no matching
078         * referrer was found.</p>
079         *
080         */
081        private  class MatchingReferrerFinder extends AbstractVisitor{
082            private Environment e;
083
084            /**
085             * Constructor
086             * @param e the environment against which we match
087             */
088            public MatchingReferrerFinder(Environment e){
089                this.e = e;
090            }
091
092            @Override
093            public void visit(Node n) {
094                // node should never be a referrer
095                throw new AssertionError();
096            }
097
098            @Override
099            public void visit(Way w) {
100                /*
101                 * If e.parent is already set to the first matching referrer. We skip any following
102                 * referrer injected into the visitor.
103                 */
104                if (e.parent != null) return;
105
106                if (!left.matches(e.withPrimitive(w)))
107                    return;
108                for (int i=0; i<w.getNodesCount(); i++) {
109                    Node n = w.getNode(i);
110                    if (n.equals(e.osm)) {
111                        if (link.matches(e.withParentAndIndexAndLinkContext(w, i))) {
112                            e.parent = w;
113                            e.index = i;
114                            return;
115                        }
116                    }
117                }
118            }
119
120            @Override
121            public void visit(Relation r) {
122                /*
123                 * If e.parent is already set to the first matching referrer. We skip any following
124                 * referrer injected into the visitor.
125                 */
126                if (e.parent != null) return;
127
128                if (!left.matches(e.withPrimitive(r)))
129                    return;
130                for (int i=0; i < r.getMembersCount(); i++) {
131                    RelationMember m = r.getMember(i);
132                    if (m.getMember().equals(e.osm)) {
133                        if (link.matches(e.withParentAndIndexAndLinkContext(r, i))) {
134                            e.parent = r;
135                            e.index = i;
136                            return;
137                        }
138                    }
139                }
140            }
141        }
142
143        @Override
144        public boolean matches(Environment e) {
145            if (!right.matches(e))
146                return false;
147
148            if (!parentSelector) {
149                MatchingReferrerFinder collector = new MatchingReferrerFinder(e);
150                e.osm.visitReferrers(collector);
151                if (e.parent != null)
152                    return true;
153            } else {
154                if (e.osm instanceof Way) {
155                    List<Node> wayNodes = ((Way) e.osm).getNodes();
156                    for (int i=0; i<wayNodes.size(); i++) {
157                        Node n = wayNodes.get(i);
158                        if (left.matches(e.withPrimitive(n))) {
159                            if (link.matches(e.withChildAndIndexAndLinkContext(n, i))) {
160                                e.child = n;
161                                e.index = i;
162                                return true;
163                            }
164                        }
165                    }
166                }
167                else if (e.osm instanceof Relation) {
168                    List<RelationMember> members = ((Relation) e.osm).getMembers();
169                    for (int i=0; i<members.size(); i++) {
170                        OsmPrimitive member = members.get(i).getMember();
171                        if (left.matches(e.withPrimitive(member))) {
172                            if (link.matches(e.withChildAndIndexAndLinkContext(member, i))) {
173                                e.child = member;
174                                e.index = i;
175                                return true;
176                            }
177                        }
178                    }
179                }
180            }
181            return false;
182        }
183
184        @Override
185        public String getSubpart() {
186            return right.getSubpart();
187        }
188
189        @Override
190        public Range getRange() {
191            return right.getRange();
192        }
193
194        @Override
195        public String toString() {
196            return left +" "+ (parentSelector? "<" : ">")+link+" " +right;
197        }
198    }
199
200    /**
201     * Super class of {@link GeneralSelector} and {@link LinkSelector}
202     * @since 5841
203     */
204    public static abstract class AbstractSelector implements Selector {
205
206        protected final List<Condition> conds;
207
208        protected AbstractSelector(List<Condition> conditions) {
209            if (conditions == null || conditions.isEmpty()) {
210                this.conds = null;
211            } else {
212                this.conds = conditions;
213            }
214        }
215
216        /**
217         * Determines if all conditions match the given environment.
218         * @param env The environment to check
219         * @return {@code true} if all conditions apply, false otherwise.
220         */
221        public final boolean matchesConditions(Environment env) {
222            if (conds == null) return true;
223            for (Condition c : conds) {
224                try {
225                    if (!c.applies(env)) return false;
226                } catch (PatternSyntaxException e) {
227                    Main.error("PatternSyntaxException while applying condition" + c +": "+e.getMessage());
228                    return false;
229                }
230            }
231            return true;
232        }
233    }
234
235    public static class LinkSelector extends AbstractSelector {
236
237        public LinkSelector(List<Condition> conditions) {
238            super(conditions);
239        }
240
241        @Override
242        public boolean matches(Environment env) {
243            Utils.ensure(env.isLinkContext(), "Requires LINK context in environment, got ''{0}''", env.getContext());
244            return matchesConditions(env);
245        }
246
247        @Override
248        public String getSubpart() {
249            throw new UnsupportedOperationException("Not supported yet.");
250        }
251
252        @Override
253        public Range getRange() {
254            throw new UnsupportedOperationException("Not supported yet.");
255        }
256
257        @Override
258        public String toString() {
259            return "LinkSelector{" + "conditions=" + conds + '}';
260        }
261    }
262
263    public static class GeneralSelector extends AbstractSelector {
264        private String base;
265        public Range range;
266        private String subpart;
267
268        public GeneralSelector(String base, Pair<Integer, Integer> zoom, List<Condition> conds, String subpart) {
269            super(conds);
270            this.base = base;
271            if (zoom != null) {
272                int a = zoom.a == null ? 0 : zoom.a;
273                int b = zoom.b == null ? Integer.MAX_VALUE : zoom.b;
274                if (a <= b) {
275                    range = fromLevel(a, b);
276                }
277            }
278            if (range == null) {
279                range = new Range();
280            }
281            this.subpart = subpart;
282        }
283
284        @Override
285        public String getSubpart() {
286            return subpart;
287        }
288        @Override
289        public Range getRange() {
290            return range;
291        }
292
293        public boolean matchesBase(Environment e){
294            if (e.osm instanceof Node) {
295                return base.equals("node") || base.equals("*");
296            } else if (e.osm instanceof Way) {
297                return base.equals("way") || base.equals("area") || base.equals("*");
298            } else if (e.osm instanceof Relation) {
299                if (base.equals("area")) {
300                    return ((Relation) e.osm).isMultipolygon();
301                } else if (base.equals("relation")) {
302                    return true;
303                } else if (base.equals("canvas")) {
304                    return e.osm.get("#canvas") != null;
305                }
306            }
307            return false;
308        }
309
310        @Override
311        public boolean matches(Environment e) {
312            if (!matchesBase(e)) return false;
313            return matchesConditions(e);
314        }
315
316        public String getBase() {
317            return base;
318        }
319
320        public static Range fromLevel(int a, int b) {
321            if (a > b)
322                throw new AssertionError();
323            double lower = 0;
324            double upper = Double.POSITIVE_INFINITY;
325            if (b != Integer.MAX_VALUE) {
326                lower = level2scale(b + 1);
327            }
328            if (a != 0) {
329                upper = level2scale(a);
330            }
331            return new Range(lower, upper);
332        }
333
334        final static double R = 6378135;
335
336        public static double level2scale(int lvl) {
337            if (lvl < 0)
338                throw new IllegalArgumentException();
339            // preliminary formula - map such that mapnik imagery tiles of the same
340            // or similar level are displayed at the given scale
341            return 2.0 * Math.PI * R / Math.pow(2.0, lvl) / 2.56;
342        }
343
344        @Override
345        public String toString() {
346            return base + (range == null ? "" : range) + Utils.join("", conds) + (subpart != null ? ("::" + subpart) : "");
347        }
348    }
349}