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
053    private static class ParentSet extends ContextProvider {
054        private final Match childCondition;
055
056        ParentSet(Match child) {
057            this.childCondition = child;
058        }
059
060        @Override
061        public boolean match(OsmPrimitive osm) {
062            throw new UnsupportedOperationException();
063        }
064
065        @Override
066        List<OsmPrimitive> getPrimitives(OsmPrimitive root) {
067            List<OsmPrimitive> children;
068            if (childCondition instanceof ContextProvider) {
069                children = ((ContextProvider) childCondition).getPrimitives(root);
070            } else if (childCondition.match(root)) {
071                children = Collections.singletonList(root);
072            } else {
073                children = Collections.emptyList();
074            }
075
076            List<OsmPrimitive> result = new ArrayList<>();
077            for (OsmPrimitive child: children) {
078                for (OsmPrimitive parent: child.getReferrers(true)) {
079                    if (condition == null || condition.match(parent)) {
080                        result.add(parent);
081                    }
082                }
083            }
084            return result;
085        }
086    }
087
088    private static class ChildSet extends ContextProvider {
089        private final Match parentCondition;
090
091        ChildSet(Match parentCondition) {
092            this.parentCondition = parentCondition;
093        }
094
095        @Override
096        public boolean match(OsmPrimitive osm) {
097            throw new UnsupportedOperationException();
098        }
099
100        @Override
101        List<OsmPrimitive> getPrimitives(OsmPrimitive root) {
102            List<OsmPrimitive> parents;
103            if (parentCondition instanceof ContextProvider) {
104                parents = ((ContextProvider) parentCondition).getPrimitives(root);
105            } else if (parentCondition.match(root)) {
106                parents = Collections.singletonList(root);
107            } else {
108                parents = Collections.emptyList();
109            }
110            List<OsmPrimitive> result = new ArrayList<>();
111            for (OsmPrimitive p: parents) {
112                if (p instanceof Way) {
113                    for (Node n: ((Way) p).getNodes()) {
114                        if (condition != null && condition.match(n)) {
115                            result.add(n);
116                        }
117                        result.add(n);
118                    }
119                } else if (p instanceof Relation) {
120                    for (RelationMember rm: ((Relation) p).getMembers()) {
121                        if (condition != null && condition.match(rm.getMember())) {
122                            result.add(rm.getMember());
123                        }
124                    }
125                }
126            }
127            return result;
128        }
129    }
130
131    private static class OrSet extends ContextProvider {
132        private final ContextProvider lhs;
133        private final ContextProvider rhs;
134
135        OrSet(ContextProvider lhs, ContextProvider rhs) {
136            this.lhs = lhs;
137            this.rhs = rhs;
138        }
139
140        @Override
141        public boolean match(OsmPrimitive osm) {
142            throw new UnsupportedOperationException();
143        }
144
145        @Override
146        List<OsmPrimitive> getPrimitives(OsmPrimitive root) {
147            List<OsmPrimitive> result = new ArrayList<>();
148            for (OsmPrimitive o: lhs.getPrimitives(root)) {
149                if (condition == null || condition.match(o)) {
150                    result.add(o);
151                }
152            }
153            for (OsmPrimitive o: rhs.getPrimitives(root)) {
154                if (condition == null || (condition.match(o) && !result.contains(o))) {
155                    result.add(o);
156                }
157            }
158            return result;
159        }
160    }
161
162    private static class AndSet extends ContextProvider {
163        private final ContextProvider lhs;
164        private final ContextProvider rhs;
165
166        AndSet(ContextProvider lhs, ContextProvider rhs) {
167            this.lhs = lhs;
168            this.rhs = rhs;
169        }
170
171        @Override
172        public boolean match(OsmPrimitive osm) {
173            throw new UnsupportedOperationException();
174        }
175
176        @Override
177        List<OsmPrimitive> getPrimitives(OsmPrimitive root) {
178            List<OsmPrimitive> result = new ArrayList<>();
179            List<OsmPrimitive> lhsList = lhs.getPrimitives(root);
180            for (OsmPrimitive o: rhs.getPrimitives(root)) {
181                if (lhsList.contains(o) && (condition == null || condition.match(o))) {
182                    result.add(o);
183                }
184            }
185            return result;
186        }
187    }
188
189    private final ContextProvider context;
190    private final TemplateEntry template;
191
192    private static Match transform(Match m, int searchExpressionPosition) throws ParseError {
193        if (m instanceof Parent) {
194            Match child = transform(((Parent) m).getOperand(), searchExpressionPosition);
195            return new ParentSet(child);
196        } else if (m instanceof Child) {
197            Match parent = transform(((Child) m).getOperand(), searchExpressionPosition);
198            return new ChildSet(parent);
199        } else if (m instanceof And) {
200            Match lhs = transform(((And) m).getLhs(), searchExpressionPosition);
201            Match rhs = transform(((And) m).getRhs(), searchExpressionPosition);
202
203            if (lhs instanceof ContextProvider && rhs instanceof ContextProvider)
204                return new AndSet((ContextProvider) lhs, (ContextProvider) rhs);
205            else if (lhs instanceof ContextProvider) {
206                ContextProvider cp = (ContextProvider) lhs;
207                if (cp.condition == null) {
208                    cp.condition = rhs;
209                } else {
210                    cp.condition = new And(cp.condition, rhs);
211                }
212                return cp;
213            } else if (rhs instanceof ContextProvider) {
214                ContextProvider cp = (ContextProvider) rhs;
215                if (cp.condition == null) {
216                    cp.condition = lhs;
217                } else {
218                    cp.condition = new And(lhs, cp.condition);
219                }
220                return cp;
221            } else
222                return m;
223        } else if (m instanceof Or) {
224            Match lhs = transform(((Or) m).getLhs(), searchExpressionPosition);
225            Match rhs = transform(((Or) m).getRhs(), searchExpressionPosition);
226
227            if (lhs instanceof ContextProvider && rhs instanceof ContextProvider)
228                return new OrSet((ContextProvider) lhs, (ContextProvider) rhs);
229            else if (lhs instanceof ContextProvider)
230                throw new ParseError(
231                        tr("Error in search expression on position {0} - right side of or(|) expression must return set of primitives",
232                                searchExpressionPosition));
233            else if (rhs instanceof ContextProvider)
234                throw new ParseError(
235                        tr("Error in search expression on position {0} - left side of or(|) expression must return set of primitives",
236                                searchExpressionPosition));
237            else
238                return m;
239        } else if (m instanceof Not) {
240            Match match = transform(((Not) m).getMatch(), searchExpressionPosition);
241            if (match instanceof ContextProvider)
242                throw new ParseError(
243                        tr("Error in search expression on position {0} - not(-) cannot be used in this context",
244                                searchExpressionPosition));
245            else
246                return m;
247        } else
248            return m;
249    }
250
251    /**
252     * Constructs a new {@code ContextSwitchTemplate}.
253     * @param match match
254     * @param template template
255     * @param searchExpressionPosition search expression position
256     * @throws ParseError if a parse error occurs, or if the match transformation returns the same primitive
257     */
258    public ContextSwitchTemplate(Match match, TemplateEntry template, int searchExpressionPosition) throws ParseError {
259        Match m = transform(match, searchExpressionPosition);
260        if (!(m instanceof ContextProvider))
261            throw new ParseError(
262                    tr("Error in search expression on position {0} - expression must return different then current primitive",
263                            searchExpressionPosition));
264        else {
265            context = (ContextProvider) m;
266        }
267        this.template = template;
268    }
269
270    @Override
271    public void appendText(StringBuilder result, TemplateEngineDataProvider dataProvider) {
272        if (dataProvider instanceof OsmPrimitive) {
273            List<OsmPrimitive> primitives = context.getPrimitives((OsmPrimitive) dataProvider);
274            if (primitives != null && !primitives.isEmpty()) {
275                template.appendText(result, primitives.get(0));
276            }
277        }
278        template.appendText(result, EMPTY_PROVIDER);
279    }
280
281    @Override
282    public boolean isValid(TemplateEngineDataProvider dataProvider) {
283        if (dataProvider instanceof OsmPrimitive) {
284            List<OsmPrimitive> primitives = context.getPrimitives((OsmPrimitive) dataProvider);
285            if (primitives != null && !primitives.isEmpty()) {
286                return template.isValid(primitives.get(0));
287            }
288        }
289        return false;
290    }
291}