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}