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