001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.tools.template_engine;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.util.ArrayList;
007import java.util.Collection;
008import java.util.Collections;
009import java.util.List;
010import java.util.Objects;
011import java.util.stream.Collectors;
012import java.util.stream.Stream;
013
014import org.openstreetmap.josm.data.osm.Node;
015import org.openstreetmap.josm.data.osm.OsmPrimitive;
016import org.openstreetmap.josm.data.osm.Relation;
017import org.openstreetmap.josm.data.osm.RelationMember;
018import org.openstreetmap.josm.data.osm.Way;
019import org.openstreetmap.josm.data.osm.search.SearchCompiler.And;
020import org.openstreetmap.josm.data.osm.search.SearchCompiler.Child;
021import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match;
022import org.openstreetmap.josm.data.osm.search.SearchCompiler.Not;
023import org.openstreetmap.josm.data.osm.search.SearchCompiler.Or;
024import org.openstreetmap.josm.data.osm.search.SearchCompiler.Parent;
025import org.openstreetmap.josm.tools.Utils;
026
027/**
028 * The context switch offers possibility to use tags of referenced primitive when constructing primitive name.
029 * @author jttt
030 * @since 4546
031 */
032public class ContextSwitchTemplate implements TemplateEntry {
033
034    private static final TemplateEngineDataProvider EMPTY_PROVIDER = new TemplateEngineDataProvider() {
035        @Override
036        public Object getTemplateValue(String name, boolean special) {
037            return null;
038        }
039
040        @Override
041        public Collection<String> getTemplateKeys() {
042            return Collections.emptyList();
043        }
044
045        @Override
046        public boolean evaluateCondition(Match condition) {
047            return false;
048        }
049    };
050
051    private abstract static class ContextProvider extends Match {
052        protected Match condition;
053
054        abstract List<OsmPrimitive> getPrimitives(OsmPrimitive root);
055
056        @Override
057        public int hashCode() {
058            return Objects.hash(condition);
059        }
060
061        @Override
062        public boolean equals(Object obj) {
063            if (this == obj)
064                return true;
065            if (obj == null || getClass() != obj.getClass())
066                return false;
067            ContextProvider other = (ContextProvider) obj;
068            if (condition == null) {
069                if (other.condition != null)
070                    return false;
071            } else if (!condition.equals(other.condition))
072                return false;
073            return true;
074        }
075    }
076
077    private static class ParentSet extends ContextProvider {
078        private final Match childCondition;
079
080        ParentSet(Match child) {
081            this.childCondition = child;
082        }
083
084        @Override
085        public boolean match(OsmPrimitive osm) {
086            throw new UnsupportedOperationException();
087        }
088
089        @Override
090        List<OsmPrimitive> getPrimitives(OsmPrimitive root) {
091            List<OsmPrimitive> children;
092            if (childCondition instanceof ContextProvider) {
093                children = ((ContextProvider) childCondition).getPrimitives(root);
094            } else if (childCondition.match(root)) {
095                children = Collections.singletonList(root);
096            } else {
097                children = Collections.emptyList();
098            }
099
100            return children.stream()
101                    .flatMap(child -> child.getReferrers(true).stream())
102                    .filter(parent -> condition == null || condition.match(parent))
103                    .collect(Collectors.toList());
104        }
105
106        @Override
107        public int hashCode() {
108            return Objects.hash(super.hashCode(), childCondition);
109        }
110
111        @Override
112        public boolean equals(Object obj) {
113            if (this == obj)
114                return true;
115            if (!super.equals(obj) || getClass() != obj.getClass())
116                return false;
117            ParentSet other = (ParentSet) obj;
118            if (childCondition == null) {
119                if (other.childCondition != null)
120                    return false;
121            } else if (!childCondition.equals(other.childCondition))
122                return false;
123            return true;
124        }
125    }
126
127    private static class ChildSet extends ContextProvider {
128        private final Match parentCondition;
129
130        ChildSet(Match parentCondition) {
131            this.parentCondition = parentCondition;
132        }
133
134        @Override
135        public boolean match(OsmPrimitive osm) {
136            throw new UnsupportedOperationException();
137        }
138
139        @Override
140        List<OsmPrimitive> getPrimitives(OsmPrimitive root) {
141            List<OsmPrimitive> parents;
142            if (parentCondition instanceof ContextProvider) {
143                parents = ((ContextProvider) parentCondition).getPrimitives(root);
144            } else if (parentCondition.match(root)) {
145                parents = Collections.singletonList(root);
146            } else {
147                parents = Collections.emptyList();
148            }
149            List<OsmPrimitive> result = new ArrayList<>();
150            for (OsmPrimitive p: parents) {
151                if (p instanceof Way) {
152                    for (Node n: ((Way) p).getNodes()) {
153                        if (condition != null && condition.match(n)) {
154                            result.add(n);
155                        }
156                        result.add(n);
157                    }
158                } else if (p instanceof Relation) {
159                    for (RelationMember rm: ((Relation) p).getMembers()) {
160                        if (condition != null && condition.match(rm.getMember())) {
161                            result.add(rm.getMember());
162                        }
163                    }
164                }
165            }
166            return result;
167        }
168
169        @Override
170        public int hashCode() {
171            return Objects.hash(super.hashCode(), parentCondition);
172        }
173
174        @Override
175        public boolean equals(Object obj) {
176            if (this == obj)
177                return true;
178            if (!super.equals(obj) || getClass() != obj.getClass())
179                return false;
180            ChildSet other = (ChildSet) obj;
181            if (parentCondition == null) {
182                if (other.parentCondition != null)
183                    return false;
184            } else if (!parentCondition.equals(other.parentCondition))
185                return false;
186            return true;
187        }
188    }
189
190    private static class OrSet extends ContextProvider {
191        private final ContextProvider lhs;
192        private final ContextProvider rhs;
193
194        OrSet(ContextProvider lhs, ContextProvider rhs) {
195            this.lhs = lhs;
196            this.rhs = rhs;
197        }
198
199        @Override
200        public boolean match(OsmPrimitive osm) {
201            throw new UnsupportedOperationException();
202        }
203
204        @Override
205        List<OsmPrimitive> getPrimitives(OsmPrimitive root) {
206            return Stream.concat(lhs.getPrimitives(root).stream(), rhs.getPrimitives(root).stream())
207                    .filter(o -> condition == null || condition.match(o))
208                    .distinct()
209                    .collect(Collectors.toList());
210        }
211
212        @Override
213        public int hashCode() {
214            return Objects.hash(super.hashCode(), lhs, rhs);
215        }
216
217        @Override
218        public boolean equals(Object obj) {
219            if (this == obj)
220                return true;
221            if (!super.equals(obj) || getClass() != obj.getClass())
222                return false;
223            OrSet other = (OrSet) obj;
224            if (lhs == null) {
225                if (other.lhs != null)
226                    return false;
227            } else if (!lhs.equals(other.lhs))
228                return false;
229            if (rhs == null) {
230                if (other.rhs != null)
231                    return false;
232            } else if (!rhs.equals(other.rhs))
233                return false;
234            return true;
235        }
236    }
237
238    private static class AndSet extends ContextProvider {
239        private final ContextProvider lhs;
240        private final ContextProvider rhs;
241
242        AndSet(ContextProvider lhs, ContextProvider rhs) {
243            this.lhs = lhs;
244            this.rhs = rhs;
245        }
246
247        @Override
248        public boolean match(OsmPrimitive osm) {
249            throw new UnsupportedOperationException();
250        }
251
252        @Override
253        List<OsmPrimitive> getPrimitives(OsmPrimitive root) {
254            List<OsmPrimitive> lhsList = lhs.getPrimitives(root);
255            return rhs.getPrimitives(root).stream()
256                    .filter(lhsList::contains)
257                    .filter(o -> condition == null || condition.match(o))
258                    .collect(Collectors.toList());
259        }
260
261        @Override
262        public int hashCode() {
263            return Objects.hash(super.hashCode(), lhs, rhs);
264        }
265
266        @Override
267        public boolean equals(Object obj) {
268            if (this == obj)
269                return true;
270            if (!super.equals(obj) || getClass() != obj.getClass())
271                return false;
272            AndSet other = (AndSet) obj;
273            if (lhs == null) {
274                if (other.lhs != null)
275                    return false;
276            } else if (!lhs.equals(other.lhs))
277                return false;
278            if (rhs == null) {
279                if (other.rhs != null)
280                    return false;
281            } else if (!rhs.equals(other.rhs))
282                return false;
283            return true;
284        }
285    }
286
287    private final ContextProvider context;
288    private final TemplateEntry template;
289
290    private static Match transform(Match m, int searchExpressionPosition) throws ParseError {
291        if (m instanceof Parent) {
292            Match child = transform(((Parent) m).getOperand(), searchExpressionPosition);
293            return new ParentSet(child);
294        } else if (m instanceof Child) {
295            Match parent = transform(((Child) m).getOperand(), searchExpressionPosition);
296            return new ChildSet(parent);
297        } else if (m instanceof And) {
298            Match lhs = transform(((And) m).getLhs(), searchExpressionPosition);
299            Match rhs = transform(((And) m).getRhs(), searchExpressionPosition);
300
301            if (lhs instanceof ContextProvider && rhs instanceof ContextProvider)
302                return new AndSet((ContextProvider) lhs, (ContextProvider) rhs);
303            else if (lhs instanceof ContextProvider) {
304                ContextProvider cp = (ContextProvider) lhs;
305                if (cp.condition == null) {
306                    cp.condition = rhs;
307                } else {
308                    cp.condition = new And(cp.condition, rhs);
309                }
310                return cp;
311            } else if (rhs instanceof ContextProvider) {
312                ContextProvider cp = (ContextProvider) rhs;
313                if (cp.condition == null) {
314                    cp.condition = lhs;
315                } else {
316                    cp.condition = new And(lhs, cp.condition);
317                }
318                return cp;
319            } else
320                return m;
321        } else if (m instanceof Or) {
322            Match lhs = transform(((Or) m).getLhs(), searchExpressionPosition);
323            Match rhs = transform(((Or) m).getRhs(), searchExpressionPosition);
324
325            if (lhs instanceof ContextProvider && rhs instanceof ContextProvider)
326                return new OrSet((ContextProvider) lhs, (ContextProvider) rhs);
327            else if (lhs instanceof ContextProvider)
328                throw new ParseError(
329                        tr("Error in search expression on position {0} - right side of or(|) expression must return set of primitives",
330                                searchExpressionPosition));
331            else if (rhs instanceof ContextProvider)
332                throw new ParseError(
333                        tr("Error in search expression on position {0} - left side of or(|) expression must return set of primitives",
334                                searchExpressionPosition));
335            else
336                return m;
337        } else if (m instanceof Not) {
338            Match match = transform(((Not) m).getMatch(), searchExpressionPosition);
339            if (match instanceof ContextProvider)
340                throw new ParseError(
341                        tr("Error in search expression on position {0} - not(-) cannot be used in this context",
342                                searchExpressionPosition));
343            else
344                return m;
345        } else
346            return m;
347    }
348
349    /**
350     * Constructs a new {@code ContextSwitchTemplate}.
351     * @param match match
352     * @param template template
353     * @param searchExpressionPosition search expression position
354     * @throws ParseError if a parse error occurs, or if the match transformation returns the same primitive
355     */
356    public ContextSwitchTemplate(Match match, TemplateEntry template, int searchExpressionPosition) throws ParseError {
357        Match m = transform(match, searchExpressionPosition);
358        if (!(m instanceof ContextProvider))
359            throw new ParseError(
360                    tr("Error in search expression on position {0} - expression must return different then current primitive",
361                            searchExpressionPosition));
362        else {
363            context = (ContextProvider) m;
364        }
365        this.template = template;
366    }
367
368    @Override
369    public void appendText(StringBuilder result, TemplateEngineDataProvider dataProvider) {
370        if (dataProvider instanceof OsmPrimitive) {
371            List<OsmPrimitive> primitives = context.getPrimitives((OsmPrimitive) dataProvider);
372            if (!Utils.isEmpty(primitives)) {
373                template.appendText(result, primitives.get(0));
374            }
375        }
376        template.appendText(result, EMPTY_PROVIDER);
377    }
378
379    @Override
380    public boolean isValid(TemplateEngineDataProvider dataProvider) {
381        if (dataProvider instanceof OsmPrimitive) {
382            List<OsmPrimitive> primitives = context.getPrimitives((OsmPrimitive) dataProvider);
383            if (!Utils.isEmpty(primitives)) {
384                return template.isValid(primitives.get(0));
385            }
386        }
387        return false;
388    }
389
390    @Override
391    public int hashCode() {
392        return Objects.hash(context, template);
393    }
394
395    @Override
396    public boolean equals(Object obj) {
397        if (this == obj)
398            return true;
399        if (obj == null || getClass() != obj.getClass())
400            return false;
401        ContextSwitchTemplate other = (ContextSwitchTemplate) obj;
402        if (context == null) {
403            if (other.context != null)
404                return false;
405        } else if (!context.equals(other.context))
406            return false;
407        if (template == null) {
408            if (other.template != null)
409                return false;
410        } else if (!template.equals(other.template))
411            return false;
412        return true;
413    }
414}