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}