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.actions.search.SearchCompiler.And;
012import org.openstreetmap.josm.actions.search.SearchCompiler.Child;
013import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
014import org.openstreetmap.josm.actions.search.SearchCompiler.Not;
015import org.openstreetmap.josm.actions.search.SearchCompiler.Or;
016import org.openstreetmap.josm.actions.search.SearchCompiler.Parent;
017import org.openstreetmap.josm.data.osm.Node;
018import org.openstreetmap.josm.data.osm.OsmPrimitive;
019import org.openstreetmap.josm.data.osm.Relation;
020import org.openstreetmap.josm.data.osm.RelationMember;
021import org.openstreetmap.josm.data.osm.Way;
022
023public class ContextSwitchTemplate implements TemplateEntry {
024
025    private static final TemplateEngineDataProvider EMTPY_PROVIDER = new TemplateEngineDataProvider() {
026        @Override
027        public Object getTemplateValue(String name, boolean special) {
028            return null;
029        }
030
031        @Override
032        public Collection<String> getTemplateKeys() {
033            return Collections.emptyList();
034        }
035
036        @Override
037        public boolean evaluateCondition(Match condition) {
038            return false;
039        }
040    };
041
042    private abstract class ContextProvider extends Match {
043        Match condition;
044        abstract List<OsmPrimitive> getPrimitives(OsmPrimitive root);
045    }
046
047    private class ParentSet extends ContextProvider {
048        private final Match childCondition;
049
050        ParentSet(Match child) {
051            this.childCondition = child;
052        }
053        @Override
054        public boolean match(OsmPrimitive osm) {
055            throw new UnsupportedOperationException();
056        }
057        @Override
058        List<OsmPrimitive> getPrimitives(OsmPrimitive root) {
059            List<OsmPrimitive> children;
060            if (childCondition instanceof ContextProvider) {
061                children = ((ContextProvider) childCondition).getPrimitives(root);
062            } else if (childCondition.match(root)) {
063                children = Collections.singletonList(root);
064            } else {
065                children = Collections.emptyList();
066            }
067
068            List<OsmPrimitive> result = new ArrayList<OsmPrimitive>();
069            for (OsmPrimitive child: children) {
070                for (OsmPrimitive parent: child.getReferrers(true)) {
071                    if (condition == null || condition.match(parent)) {
072                        result.add(parent);
073                    }
074                }
075            }
076            return result;
077        }
078    }
079
080    private class ChildSet extends ContextProvider {
081        private final Match parentCondition;
082
083        ChildSet(Match parentCondition) {
084            this.parentCondition = parentCondition;
085        }
086
087        @Override
088        public boolean match(OsmPrimitive osm) {
089            throw new UnsupportedOperationException();
090        }
091
092        @Override
093        List<OsmPrimitive> getPrimitives(OsmPrimitive root) {
094            List<OsmPrimitive> parents;
095            if (parentCondition instanceof ContextProvider) {
096                parents = ((ContextProvider) parentCondition).getPrimitives(root);
097            } else if (parentCondition.match(root)) {
098                parents = Collections.singletonList(root);
099            } else {
100                parents = Collections.emptyList();
101            }
102            List<OsmPrimitive> result = new ArrayList<OsmPrimitive>();
103            for (OsmPrimitive p: parents) {
104                if (p instanceof Way) {
105                    for (Node n: ((Way) p).getNodes()) {
106                        if (condition != null && condition.match(n)) {
107                            result.add(n);
108                        }
109                        result.add(n);
110                    }
111                } else if (p instanceof Relation) {
112                    for (RelationMember rm: ((Relation) p).getMembers()) {
113                        if (condition != null && condition.match(rm.getMember())) {
114                            result.add(rm.getMember());
115                        }
116                    }
117                }
118            }
119            return result;
120        }
121    }
122
123    private class OrSet extends ContextProvider {
124        private final ContextProvider lhs;
125        private final ContextProvider rhs;
126
127        OrSet(ContextProvider lhs, ContextProvider rhs) {
128            this.lhs = lhs;
129            this.rhs = rhs;
130        }
131
132        @Override
133        public boolean match(OsmPrimitive osm) {
134            throw new UnsupportedOperationException();
135        }
136
137        @Override
138        List<OsmPrimitive> getPrimitives(OsmPrimitive root) {
139            List<OsmPrimitive> result = new ArrayList<OsmPrimitive>();
140            for (OsmPrimitive o: lhs.getPrimitives(root)) {
141                if (condition == null || condition.match(o)) {
142                    result.add(o);
143                }
144            }
145            for (OsmPrimitive o: rhs.getPrimitives(root)) {
146                if (condition == null || condition.match(o) && !result.contains(o)) {
147                    result.add(o);
148                }
149            }
150            return result;
151        }
152    }
153
154    private class AndSet extends ContextProvider {
155        private final ContextProvider lhs;
156        private final ContextProvider rhs;
157
158        AndSet(ContextProvider lhs, ContextProvider rhs) {
159            this.lhs = lhs;
160            this.rhs = rhs;
161        }
162
163        @Override
164        public boolean match(OsmPrimitive osm) {
165            throw new UnsupportedOperationException();
166        }
167
168        @Override
169        List<OsmPrimitive> getPrimitives(OsmPrimitive root) {
170            List<OsmPrimitive> result = new ArrayList<OsmPrimitive>();
171            List<OsmPrimitive> lhsList = lhs.getPrimitives(root);
172            for (OsmPrimitive o: rhs.getPrimitives(root)) {
173                if (lhsList.contains(o) && (condition == null || condition.match(o))) {
174                    result.add(o);
175                }
176            }
177            return result;
178        }
179    }
180
181    private final ContextProvider context;
182    private final TemplateEntry template;
183
184    private Match transform(Match m, int searchExpressionPosition) throws ParseError {
185        if (m instanceof Parent) {
186            Match child = transform(((Parent) m).getOperand(), searchExpressionPosition);
187            return new ParentSet(child);
188        } else if (m instanceof Child) {
189            Match parent = transform(((Child) m).getOperand(), searchExpressionPosition);
190            return new ChildSet(parent);
191        } else if (m instanceof And) {
192            Match lhs = transform(((And) m).getLhs(), searchExpressionPosition);
193            Match rhs = transform(((And) m).getRhs(), searchExpressionPosition);
194
195            if (lhs instanceof ContextProvider && rhs instanceof ContextProvider)
196                return new AndSet((ContextProvider)lhs, (ContextProvider)rhs);
197            else if (lhs instanceof ContextProvider) {
198                ContextProvider cp = (ContextProvider) lhs;
199                if (cp.condition == null) {
200                    cp.condition = rhs;
201                } else {
202                    cp.condition = new And(cp.condition, rhs);
203                }
204                return cp;
205            } else if (rhs instanceof ContextProvider) {
206                ContextProvider cp = (ContextProvider) rhs;
207                if (cp.condition == null) {
208                    cp.condition = lhs;
209                } else {
210                    cp.condition = new And(lhs, cp.condition);
211                }
212                return cp;
213            } else
214                return m;
215        } else if (m instanceof Or) {
216            Match lhs = transform(((Or) m).getLhs(), searchExpressionPosition);
217            Match rhs = transform(((Or) m).getRhs(), searchExpressionPosition);
218
219            if (lhs instanceof ContextProvider && rhs instanceof ContextProvider)
220                return new OrSet((ContextProvider)lhs, (ContextProvider)rhs);
221            else if (lhs instanceof ContextProvider)
222                throw new ParseError(tr("Error in search expression on position {0} - right side of or(|) expression must return set of primitives", searchExpressionPosition));
223            else if (rhs instanceof ContextProvider)
224                throw new ParseError(tr("Error in search expression on position {0} - left side of or(|) expression must return set of primitives", searchExpressionPosition));
225            else
226                return m;
227        } else if (m instanceof Not) {
228            Match match = transform(((Not) m).getMatch(), searchExpressionPosition);
229            if (match instanceof ContextProvider)
230                throw new ParseError(tr("Error in search expression on position {0} - not(-) cannot be used in this context", searchExpressionPosition));
231            else
232                return m;
233        } else
234            return m;
235    }
236
237    public ContextSwitchTemplate(Match match, TemplateEntry template, int searchExpressionPosition) throws ParseError {
238        Match m = transform(match, searchExpressionPosition);
239        if (!(m instanceof ContextProvider))
240            throw new ParseError(tr("Error in search expression on position {0} - expression must return different then current primitive", searchExpressionPosition));
241        else {
242            context = (ContextProvider) m;
243        }
244        this.template = template;
245    }
246
247    @Override
248    public void appendText(StringBuilder result, TemplateEngineDataProvider dataProvider) {
249        List<OsmPrimitive> primitives = context.getPrimitives((OsmPrimitive) dataProvider);
250        if (primitives != null && !primitives.isEmpty()) {
251            template.appendText(result, primitives.get(0));
252        } else {
253            template.appendText(result, EMTPY_PROVIDER);
254        }
255    }
256
257    @Override
258    public boolean isValid(TemplateEngineDataProvider dataProvider) {
259        List<OsmPrimitive> primitives = context.getPrimitives((OsmPrimitive) dataProvider);
260        if (primitives != null && !primitives.isEmpty())
261            return template.isValid(primitives.get(0));
262        else
263            return false;
264    }
265
266}