source: josm/trunk/src/org/openstreetmap/josm/actions/search/SearchCompiler.java@ 4694

Last change on this file since 4694 was 4694, checked in by stoecker, 12 years ago

typo

  • Property svn:eol-style set to native
File size: 33.7 KB
RevLine 
[298]1// License: GPL. Copyright 2007 by Immanuel Scholz and others
[319]2package org.openstreetmap.josm.actions.search;
3
[1213]4import static org.openstreetmap.josm.tools.I18n.marktr;
[888]5import static org.openstreetmap.josm.tools.I18n.tr;
6
[319]7import java.io.PushbackReader;
8import java.io.StringReader;
[3556]9import java.text.Normalizer;
[4372]10import java.util.Collection;
[4679]11import java.util.Date;
[1213]12import java.util.regex.Matcher;
13import java.util.regex.Pattern;
14import java.util.regex.PatternSyntaxException;
[319]15
[1603]16import org.openstreetmap.josm.Main;
[2993]17import org.openstreetmap.josm.actions.search.PushbackTokenizer.Range;
[2645]18import org.openstreetmap.josm.actions.search.PushbackTokenizer.Token;
[4375]19import org.openstreetmap.josm.data.Bounds;
[319]20import org.openstreetmap.josm.data.osm.Node;
21import org.openstreetmap.josm.data.osm.OsmPrimitive;
[2863]22import org.openstreetmap.josm.data.osm.OsmUtils;
[343]23import org.openstreetmap.josm.data.osm.Relation;
[1603]24import org.openstreetmap.josm.data.osm.RelationMember;
[582]25import org.openstreetmap.josm.data.osm.Way;
[1499]26import org.openstreetmap.josm.tools.DateUtils;
[4085]27import org.openstreetmap.josm.tools.Geometry;
[319]28
29/**
[2645]30 Implements a google-like search.
31 <br>
32 Grammar:
33<pre>
34expression =
35 fact | expression
36 fact expression
37 fact
38
39fact =
40 ( expression )
41 -fact
[2863]42 term?
[2645]43 term=term
44 term:term
45 term
46 </pre>
47
48 @author Imi
[319]49 */
50public class SearchCompiler {
51
[1169]52 private boolean caseSensitive = false;
[1213]53 private boolean regexSearch = false;
[2510]54 private static String rxErrorMsg = marktr("The regex \"{0}\" had a parse error at offset {1}, full error:\n\n{2}");
[3305]55 private static String rxErrorMsgNoPos = marktr("The regex \"{0}\" had a parse error, full error:\n\n{1}");
[1169]56 private PushbackTokenizer tokenizer;
[513]57
[1213]58 public SearchCompiler(boolean caseSensitive, boolean regexSearch, PushbackTokenizer tokenizer) {
[1169]59 this.caseSensitive = caseSensitive;
[1213]60 this.regexSearch = regexSearch;
[1169]61 this.tokenizer = tokenizer;
62 }
[319]63
[1169]64 abstract public static class Match {
[2510]65 abstract public boolean match(OsmPrimitive osm);
[4372]66
67 /**
68 * Tests whether one of the primitives matches.
69 */
70 protected boolean existsMatch(Collection<? extends OsmPrimitive> primitives) {
71 for (OsmPrimitive p : primitives) {
[4546]72 if (match(p))
[4372]73 return true;
74 }
75 return false;
76 }
77
78 /**
79 * Tests whether all primitives match.
80 */
81 protected boolean forallMatch(Collection<? extends OsmPrimitive> primitives) {
82 for (OsmPrimitive p : primitives) {
[4546]83 if (!match(p))
[4372]84 return false;
85 }
86 return true;
87 }
[1169]88 }
[319]89
[2791]90 public static class Always extends Match {
[2912]91 public static Always INSTANCE = new Always();
[1169]92 @Override public boolean match(OsmPrimitive osm) {
93 return true;
94 }
95 }
[319]96
[2791]97 public static class Never extends Match {
98 @Override
99 public boolean match(OsmPrimitive osm) {
100 return false;
101 }
102 }
103
[3355]104 public static class Not extends Match {
[1169]105 private final Match match;
106 public Not(Match match) {this.match = match;}
[2510]107 @Override public boolean match(OsmPrimitive osm) {
[1169]108 return !match.match(osm);
109 }
110 @Override public String toString() {return "!"+match;}
[4546]111 public Match getMatch() {
112 return match;
113 }
[1169]114 }
[319]115
[2863]116 private static class BooleanMatch extends Match {
117 private final String key;
118 private final boolean defaultValue;
119
120 public BooleanMatch(String key, boolean defaultValue) {
121 this.key = key;
122 this.defaultValue = defaultValue;
123 }
124 @Override
125 public boolean match(OsmPrimitive osm) {
126 Boolean ret = OsmUtils.getOsmBoolean(osm.get(key));
127 if (ret == null)
128 return defaultValue;
129 else
130 return ret;
131 }
132 }
133
[4546]134 public static class And extends Match {
135 private final Match lhs;
136 private final Match rhs;
[1169]137 public And(Match lhs, Match rhs) {this.lhs = lhs; this.rhs = rhs;}
[2510]138 @Override public boolean match(OsmPrimitive osm) {
[1169]139 return lhs.match(osm) && rhs.match(osm);
140 }
141 @Override public String toString() {return lhs+" && "+rhs;}
[4546]142 public Match getLhs() {
143 return lhs;
144 }
145 public Match getRhs() {
146 return rhs;
147 }
[1169]148 }
[319]149
[4546]150 public static class Or extends Match {
151 private final Match lhs;
152 private final Match rhs;
[1169]153 public Or(Match lhs, Match rhs) {this.lhs = lhs; this.rhs = rhs;}
[2510]154 @Override public boolean match(OsmPrimitive osm) {
[1169]155 return lhs.match(osm) || rhs.match(osm);
156 }
157 @Override public String toString() {return lhs+" || "+rhs;}
[4546]158 public Match getLhs() {
159 return lhs;
160 }
161 public Match getRhs() {
162 return rhs;
163 }
[1169]164 }
[319]165
[1169]166 private static class Id extends Match {
167 private long id;
[2973]168 public Id(long id) {
169 this.id = id;
170 }
[1169]171 @Override public boolean match(OsmPrimitive osm) {
[3140]172 return id == 0?osm.isNew():osm.getUniqueId() == id;
[1169]173 }
174 @Override public String toString() {return "id="+id;}
175 }
[319]176
[2604]177 private static class ChangesetId extends Match {
178 private long changesetid;
179 public ChangesetId(long changesetid) {this.changesetid = changesetid;}
180 @Override public boolean match(OsmPrimitive osm) {
181 return osm.getChangesetId() == changesetid;
182 }
183 @Override public String toString() {return "changeset="+changesetid;}
184 }
185
[2695]186 private static class Version extends Match {
187 private long version;
188 public Version(long version) {this.version = version;}
189 @Override public boolean match(OsmPrimitive osm) {
190 return osm.getVersion() == version;
191 }
192 @Override public String toString() {return "version="+version;}
193 }
194
[2510]195 private static class KeyValue extends Match {
196 private final String key;
197 private final Pattern keyPattern;
198 private final String value;
199 private final Pattern valuePattern;
200 private final boolean caseSensitive;
[1213]201
[2510]202 public KeyValue(String key, String value, boolean regexSearch, boolean caseSensitive) throws ParseError {
203 this.caseSensitive = caseSensitive;
[1607]204 if (regexSearch) {
[2510]205 int searchFlags = regexFlags(caseSensitive);
206
207 try {
208 this.keyPattern = Pattern.compile(key, searchFlags);
[3303]209 } catch (PatternSyntaxException e) {
210 throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()));
211 } catch (Exception e) {
[3305]212 throw new ParseError(tr(rxErrorMsgNoPos, key, e.getMessage()));
[3303]213 }
214 try {
[2510]215 this.valuePattern = Pattern.compile(value, searchFlags);
216 } catch (PatternSyntaxException e) {
217 throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()));
[3303]218 } catch (Exception e) {
[3305]219 throw new ParseError(tr(rxErrorMsgNoPos, value, e.getMessage()));
[2510]220 }
221 this.key = key;
222 this.value = value;
223
224 } else if (caseSensitive) {
225 this.key = key;
226 this.value = value;
227 this.keyPattern = null;
228 this.valuePattern = null;
229 } else {
230 this.key = key.toLowerCase();
231 this.value = value;
232 this.keyPattern = null;
233 this.valuePattern = null;
234 }
235 }
236
237 @Override public boolean match(OsmPrimitive osm) {
238
239 if (keyPattern != null) {
[1843]240 if (!osm.hasKeys())
[1213]241 return false;
242
243 /* The string search will just get a key like
244 * 'highway' and look that up as osm.get(key). But
245 * since we're doing a regex match we'll have to loop
246 * over all the keys to see if they match our regex,
247 * and only then try to match against the value
248 */
249
[2906]250 for (String k: osm.keySet()) {
251 String v = osm.get(k);
[1213]252
[2510]253 Matcher matcherKey = keyPattern.matcher(k);
[1213]254 boolean matchedKey = matcherKey.find();
255
256 if (matchedKey) {
[2510]257 Matcher matcherValue = valuePattern.matcher(v);
[1213]258 boolean matchedValue = matcherValue.find();
259
260 if (matchedValue)
261 return true;
262 }
263 }
264 } else {
[2636]265 String mv = null;
[1213]266
[1814]267 if (key.equals("timestamp")) {
[2636]268 mv = DateUtils.fromDate(osm.getTimestamp());
[1814]269 } else {
[2636]270 mv = osm.get(key);
[1814]271 }
[1213]272
[2636]273 if (mv == null)
[1213]274 return false;
275
[2636]276 String v1 = caseSensitive ? mv : mv.toLowerCase();
277 String v2 = caseSensitive ? value : value.toLowerCase();
[1213]278
[3556]279 v1 = Normalizer.normalize(v1, Normalizer.Form.NFC);
280 v2 = Normalizer.normalize(v2, Normalizer.Form.NFC);
[2636]281 return v1.indexOf(v2) != -1;
[1213]282 }
283
284 return false;
[1169]285 }
286 @Override public String toString() {return key+"="+value;}
287 }
[319]288
[2645]289 public static class ExactKeyValue extends Match {
[1641]290
291 private enum Mode {
292 ANY, ANY_KEY, ANY_VALUE, EXACT, NONE, MISSING_KEY,
293 ANY_KEY_REGEXP, ANY_VALUE_REGEXP, EXACT_REGEXP, MISSING_KEY_REGEXP;
294 }
295
296 private final String key;
297 private final String value;
298 private final Pattern keyPattern;
299 private final Pattern valuePattern;
300 private final Mode mode;
301
302 public ExactKeyValue(boolean regexp, String key, String value) throws ParseError {
[3556]303 if ("".equals(key))
[1641]304 throw new ParseError(tr("Key cannot be empty when tag operator is used. Sample use: key=value"));
305 this.key = key;
[2645]306 this.value = value == null?"":value;
[2993]307 if ("".equals(this.value) && "*".equals(key)) {
[1641]308 mode = Mode.NONE;
[2993]309 } else if ("".equals(this.value)) {
[1641]310 if (regexp) {
311 mode = Mode.MISSING_KEY_REGEXP;
312 } else {
313 mode = Mode.MISSING_KEY;
314 }
[2993]315 } else if ("*".equals(key) && "*".equals(this.value)) {
[1641]316 mode = Mode.ANY;
317 } else if ("*".equals(key)) {
318 if (regexp) {
319 mode = Mode.ANY_KEY_REGEXP;
320 } else {
321 mode = Mode.ANY_KEY;
322 }
[2993]323 } else if ("*".equals(this.value)) {
[1641]324 if (regexp) {
325 mode = Mode.ANY_VALUE_REGEXP;
326 } else {
327 mode = Mode.ANY_VALUE;
328 }
329 } else {
330 if (regexp) {
331 mode = Mode.EXACT_REGEXP;
332 } else {
333 mode = Mode.EXACT;
334 }
335 }
336
337 if (regexp && key.length() > 0 && !key.equals("*")) {
[3304]338 try {
339 keyPattern = Pattern.compile(key, regexFlags(false));
340 } catch (PatternSyntaxException e) {
341 throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()));
342 } catch (Exception e) {
[3305]343 throw new ParseError(tr(rxErrorMsgNoPos, key, e.getMessage()));
[3304]344 }
[1641]345 } else {
346 keyPattern = null;
347 }
[2993]348 if (regexp && this.value.length() > 0 && !this.value.equals("*")) {
[1786]349 try {
[3304]350 valuePattern = Pattern.compile(this.value, regexFlags(false));
[1786]351 } catch (PatternSyntaxException e) {
[3304]352 throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()));
353 } catch (Exception e) {
[3305]354 throw new ParseError(tr(rxErrorMsgNoPos, value, e.getMessage()));
[1786]355 }
[1641]356 } else {
357 valuePattern = null;
358 }
359 }
360
361 @Override
[2510]362 public boolean match(OsmPrimitive osm) {
[1641]363
[1843]364 if (!osm.hasKeys())
[1641]365 return mode == Mode.NONE;
366
367 switch (mode) {
[2407]368 case NONE:
369 return false;
370 case MISSING_KEY:
371 return osm.get(key) == null;
372 case ANY:
373 return true;
374 case ANY_VALUE:
375 return osm.get(key) != null;
376 case ANY_KEY:
377 for (String v:osm.getKeys().values()) {
378 if (v.equals(value))
379 return true;
380 }
381 return false;
382 case EXACT:
383 return value.equals(osm.get(key));
384 case ANY_KEY_REGEXP:
385 for (String v:osm.getKeys().values()) {
386 if (valuePattern.matcher(v).matches())
387 return true;
388 }
389 return false;
390 case ANY_VALUE_REGEXP:
391 case EXACT_REGEXP:
[2906]392 for (String key: osm.keySet()) {
393 if (keyPattern.matcher(key).matches()) {
[2407]394 if (mode == Mode.ANY_VALUE_REGEXP
[2906]395 || valuePattern.matcher(osm.get(key)).matches())
[1641]396 return true;
397 }
[2407]398 }
399 return false;
400 case MISSING_KEY_REGEXP:
401 for (String k:osm.keySet()) {
402 if (keyPattern.matcher(k).matches())
403 return false;
404 }
405 return true;
[1641]406 }
407 throw new AssertionError("Missed state");
408 }
409
410 @Override
411 public String toString() {
412 return key + '=' + value;
413 }
414
415 }
416
[2510]417 private static class Any extends Match {
418 private final String search;
419 private final Pattern searchRegex;
420 private final boolean caseSensitive;
[1213]421
[2510]422 public Any(String s, boolean regexSearch, boolean caseSensitive) throws ParseError {
[3556]423 s = Normalizer.normalize(s, Normalizer.Form.NFC);
[2510]424 this.caseSensitive = caseSensitive;
[1213]425 if (regexSearch) {
[1607]426 try {
[2510]427 this.searchRegex = Pattern.compile(s, regexFlags(caseSensitive));
[1607]428 } catch (PatternSyntaxException e) {
429 throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()));
[3303]430 } catch (Exception e) {
[3305]431 throw new ParseError(tr(rxErrorMsgNoPos, s, e.getMessage()));
[1213]432 }
[2510]433 this.search = s;
434 } else if (caseSensitive) {
435 this.search = s;
436 this.searchRegex = null;
[1213]437 } else {
[2510]438 this.search = s.toLowerCase();
439 this.searchRegex = null;
[1213]440 }
[2510]441 }
[1213]442
[2510]443 @Override public boolean match(OsmPrimitive osm) {
[3556]444 if (!osm.hasKeys() && osm.getUser() == null)
[2510]445 return search.equals("");
446
[2906]447 for (String key: osm.keySet()) {
448 String value = osm.get(key);
[2510]449 if (searchRegex != null) {
[1213]450
[3556]451 value = Normalizer.normalize(value, Normalizer.Form.NFC);
[1213]452
453 Matcher keyMatcher = searchRegex.matcher(key);
454 Matcher valMatcher = searchRegex.matcher(value);
455
456 boolean keyMatchFound = keyMatcher.find();
457 boolean valMatchFound = valMatcher.find();
458
459 if (keyMatchFound || valMatchFound)
460 return true;
461 } else {
[2962]462 if (!caseSensitive) {
[2906]463 key = key.toLowerCase();
464 value = value.toLowerCase();
465 }
[1213]466
[3556]467 value = Normalizer.normalize(value, Normalizer.Form.NFC);
[1213]468
469 if (key.indexOf(search) != -1 || value.indexOf(search) != -1)
470 return true;
471 }
[1169]472 }
473 return false;
474 }
[2510]475 @Override public String toString() {
476 return search;
477 }
[1169]478 }
[319]479
[1169]480 private static class ExactType extends Match {
[1625]481 private final Class<?> type;
482 public ExactType(String type) throws ParseError {
483 if ("node".equals(type)) {
484 this.type = Node.class;
485 } else if ("way".equals(type)) {
486 this.type = Way.class;
487 } else if ("relation".equals(type)) {
488 this.type = Relation.class;
[1814]489 } else
[1625]490 throw new ParseError(tr("Unknown primitive type: {0}. Allowed values are node, way or relation",
491 type));
492 }
[1169]493 @Override public boolean match(OsmPrimitive osm) {
[1625]494 return osm.getClass() == type;
[1169]495 }
496 @Override public String toString() {return "type="+type;}
497 }
[515]498
[1169]499 private static class UserMatch extends Match {
[2863]500 private String user;
[2070]501 public UserMatch(String user) {
[2567]502 if (user.equals("anonymous")) {
503 this.user = null;
[2070]504 } else {
[2863]505 this.user = user;
[2070]506 }
507 }
[2567]508
[1169]509 @Override public boolean match(OsmPrimitive osm) {
[2863]510 if (osm.getUser() == null)
511 return user == null;
512 else
513 return osm.getUser().hasName(user);
[1169]514 }
[2567]515
[2070]516 @Override public String toString() {
[2863]517 return "user=" + user == null ? "" : user;
[2070]518 }
[1169]519 }
[522]520
[3305]521 private static class RoleMatch extends Match {
522 private String role;
523 public RoleMatch(String role) {
524 if (role == null) {
525 this.role = "";
526 } else {
527 this.role = role;
528 }
529 }
530
531 @Override public boolean match(OsmPrimitive osm) {
532 for (OsmPrimitive ref: osm.getReferrers()) {
533 if (ref instanceof Relation && !ref.isIncomplete() && !ref.isDeleted()) {
534 for (RelationMember m : ((Relation) ref).getMembers()) {
535 if (m.getMember() == osm) {
536 String testRole = m.getRole();
537 if(role.equals(testRole == null ? "" : testRole))
538 return true;
539 }
540 }
541 }
542 }
543 return false;
544 }
545
546 @Override public String toString() {
547 return "role=" + role;
548 }
549 }
550
[4346]551 private abstract static class CountRange extends Match {
552
[4679]553 private long minCount;
554 private long maxCount;
[4346]555
[4679]556 public CountRange(long minCount, long maxCount) {
[4346]557 this.minCount = Math.min(minCount, maxCount);
558 this.maxCount = Math.max(minCount, maxCount);
559 }
560
[4679]561 protected abstract Long getCount(OsmPrimitive osm);
[4346]562
563 protected abstract String getCountString();
564
565 @Override
566 public boolean match(OsmPrimitive osm) {
[4679]567 Long count = getCount(osm);
[4546]568 if (count == null)
[4346]569 return false;
[4546]570 else
[4346]571 return (count >= minCount) && (count <= maxCount);
[1389]572 }
[4346]573
574 @Override
575 public String toString() {
576 return getCountString() + "=" + minCount + "-" + maxCount;
[1389]577 }
578 }
[319]579
[4346]580
581
582 private static class NodeCountRange extends CountRange {
583
[4679]584 public NodeCountRange(long minCount, long maxCount) {
[4346]585 super(minCount, maxCount);
586 }
587
588 @Override
[4679]589 protected Long getCount(OsmPrimitive osm) {
[4546]590 if (!(osm instanceof Way))
[4346]591 return null;
[4546]592 else
[4679]593 return (long) ((Way) osm).getNodesCount();
[2357]594 }
[4346]595
596 @Override
597 protected String getCountString() {
598 return "nodes";
[2357]599 }
600 }
601
[4346]602 private static class TagCountRange extends CountRange {
603
[4679]604 public TagCountRange(long minCount, long maxCount) {
[4346]605 super(minCount, maxCount);
606 }
607
608 @Override
[4679]609 protected Long getCount(OsmPrimitive osm) {
610 return (long) osm.getKeys().size();
[4346]611 }
612
613 @Override
614 protected String getCountString() {
615 return "tags";
616 }
617 }
618
[4679]619 private static class TimestampRange extends CountRange {
620
621 public TimestampRange(long minCount, long maxCount) {
622 super(minCount, maxCount);
623 }
624
625 @Override
626 protected Long getCount(OsmPrimitive osm) {
627 return osm.getTimestamp().getTime();
628 }
629
630 @Override
631 protected String getCountString() {
632 return "timestamp";
633 }
634
635 }
636
[4347]637 private static class New extends Match {
638 @Override public boolean match(OsmPrimitive osm) {
639 return osm.isNew();
640 }
641 @Override public String toString() {
642 return "new";
643 }
644 }
645
[1169]646 private static class Modified extends Match {
647 @Override public boolean match(OsmPrimitive osm) {
[3336]648 return osm.isModified() || osm.isNewOrUndeleted();
[1169]649 }
650 @Override public String toString() {return "modified";}
651 }
652
653 private static class Selected extends Match {
654 @Override public boolean match(OsmPrimitive osm) {
[2264]655 return Main.main.getCurrentDataSet().isSelected(osm);
[1169]656 }
657 @Override public String toString() {return "selected";}
658 }
659
660 private static class Incomplete extends Match {
661 @Override public boolean match(OsmPrimitive osm) {
[2578]662 return osm.isIncomplete();
[1169]663 }
664 @Override public String toString() {return "incomplete";}
665 }
[517]666
[1375]667 private static class Untagged extends Match {
668 @Override public boolean match(OsmPrimitive osm) {
[3317]669 return !osm.isTagged() && !osm.isIncomplete();
[1375]670 }
671 @Override public String toString() {return "untagged";}
672 }
[1607]673
[3818]674 private static class Closed extends Match {
675 @Override public boolean match(OsmPrimitive osm) {
676 return osm instanceof Way && ((Way) osm).isClosed();
677 }
678 @Override public String toString() {return "closed";}
679 }
680
[4546]681 public static class Parent extends Match {
682 private final Match child;
683 public Parent(Match m) {
684 if (m == null) {
685 // "parent" (null) should mean the same as "parent()"
686 // (Always). I.e. match everything
687 child = new Always();
688 } else {
689 child = m;
690 }
691 }
[2510]692 @Override public boolean match(OsmPrimitive osm) {
[1603]693 boolean isParent = false;
[1375]694
[1603]695 if (osm instanceof Way) {
[1862]696 for (Node n : ((Way)osm).getNodes()) {
[1603]697 isParent |= child.match(n);
[1814]698 }
[1603]699 } else if (osm instanceof Relation) {
[1925]700 for (RelationMember member : ((Relation)osm).getMembers()) {
[2645]701 isParent |= child.match(member.getMember());
[1603]702 }
703 }
704 return isParent;
705 }
706 @Override public String toString() {return "parent(" + child + ")";}
[4546]707 public Match getChild() {
708 return child;
709 }
[1603]710 }
711
[4546]712 public static class Child extends Match {
[2273]713 private final Match parent;
714
[2565]715 public Child(Match m) {
[1603]716 // "child" (null) should mean the same as "child()"
717 // (Always). I.e. match everything
[2273]718 if (m == null) {
[1603]719 parent = new Always();
[2273]720 } else {
721 parent = m;
[1814]722 }
[2273]723 }
[1603]724
[2510]725 @Override public boolean match(OsmPrimitive osm) {
[1603]726 boolean isChild = false;
[2565]727 for (OsmPrimitive p : osm.getReferrers()) {
[1603]728 isChild |= parent.match(p);
729 }
730 return isChild;
731 }
732 @Override public String toString() {return "child(" + parent + ")";}
[4546]733
734 public Match getParent() {
735 return parent;
736 }
[1603]737 }
[4546]738
[4085]739 /**
740 * Matches on the area of a closed way.
[4546]741 *
[4085]742 * @author Ole Jørgen Brønner
743 */
[4346]744 private static class Area extends CountRange {
[1603]745
[4679]746 public Area(long minCount, long maxCount) {
[4346]747 super(minCount, maxCount);
748 }
749
750 @Override
[4679]751 protected Long getCount(OsmPrimitive osm) {
[4546]752 if (!(osm instanceof Way && ((Way) osm).isClosed()))
[4346]753 return null;
754 Way way = (Way) osm;
[4679]755 return (long) Geometry.closedWayArea(way);
[4085]756 }
757
758 @Override
[4346]759 protected String getCountString() {
760 return "area";
[4085]761 }
762 }
763
[4372]764 /**
[4375]765 * Matches data within bounds.
[4372]766 */
[4375]767 private abstract static class InArea extends Match {
[4372]768
[4375]769 protected abstract Bounds getBounds();
770 protected final boolean all;
771 protected final Bounds bounds;
[4372]772
773 /**
774 * @param all if true, all way nodes or relation members have to be within source area;if false, one suffices.
775 */
[4375]776 public InArea(boolean all) {
[4372]777 this.all = all;
[4375]778 this.bounds = getBounds();
[4372]779 }
780
781 @Override
782 public boolean match(OsmPrimitive osm) {
[4546]783 if (!osm.isUsable())
[4372]784 return false;
[4546]785 else if (osm instanceof Node)
[4375]786 return bounds.contains(((Node) osm).getCoor());
[4546]787 else if (osm instanceof Way) {
[4372]788 Collection<Node> nodes = ((Way) osm).getNodes();
789 return all ? forallMatch(nodes) : existsMatch(nodes);
790 } else if (osm instanceof Relation) {
791 Collection<OsmPrimitive> primitives = ((Relation) osm).getMemberPrimitives();
792 return all ? forallMatch(primitives) : existsMatch(primitives);
[4546]793 } else
[4372]794 return false;
795 }
796 }
797
[4375]798 /**
799 * Matches data in source area ("downloaded area").
800 */
801 private static class InDataSourceArea extends InArea {
802
803 public InDataSourceArea(boolean all) {
804 super(all);
805 }
806
807 @Override
808 protected Bounds getBounds() {
809 return new Bounds(Main.main.getCurrentDataSet().getDataSourceArea().getBounds2D());
810 }
811 }
812
813 /**
814 * Matches data in current map view.
815 */
816 private static class InView extends InArea {
817
818 public InView(boolean all) {
819 super(all);
820 }
821
822 @Override
823 protected Bounds getBounds() {
824 return Main.map.mapView.getRealBounds();
825 }
826 }
827
[1169]828 public static class ParseError extends Exception {
829 public ParseError(String msg) {
830 super(msg);
831 }
[2993]832 public ParseError(Token expected, Token found) {
833 this(tr("Unexpected token. Expected {0}, found {1}", expected, found));
834 }
[1169]835 }
[319]836
[1213]837 public static Match compile(String searchStr, boolean caseSensitive, boolean regexSearch)
[4546]838 throws ParseError {
[1213]839 return new SearchCompiler(caseSensitive, regexSearch,
[1169]840 new PushbackTokenizer(
[1814]841 new PushbackReader(new StringReader(searchStr))))
842 .parse();
[1169]843 }
[319]844
[1169]845 public Match parse() throws ParseError {
[2645]846 Match m = parseExpression();
847 if (!tokenizer.readIfEqual(Token.EOF))
[1641]848 throw new ParseError(tr("Unexpected token: {0}", tokenizer.nextToken()));
[2661]849 if (m == null)
850 return new Always();
[1169]851 return m;
852 }
[513]853
[2645]854 private Match parseExpression() throws ParseError {
855 Match factor = parseFactor();
856 if (factor == null)
857 return null;
858 if (tokenizer.readIfEqual(Token.OR))
859 return new Or(factor, parseExpression(tr("Missing parameter for OR")));
860 else {
861 Match expression = parseExpression();
862 if (expression == null)
863 return factor;
864 else
865 return new And(factor, expression);
[1169]866 }
867 }
[513]868
[2645]869 private Match parseExpression(String errorMessage) throws ParseError {
870 Match expression = parseExpression();
871 if (expression == null)
872 throw new ParseError(errorMessage);
873 else
874 return expression;
[1169]875 }
[513]876
[2645]877 private Match parseFactor() throws ParseError {
878 if (tokenizer.readIfEqual(Token.LEFT_PARENT)) {
879 Match expression = parseExpression();
880 if (!tokenizer.readIfEqual(Token.RIGHT_PARENT))
[2993]881 throw new ParseError(Token.RIGHT_PARENT, tokenizer.nextToken());
[2645]882 return expression;
883 } else if (tokenizer.readIfEqual(Token.NOT))
884 return new Not(parseFactor(tr("Missing operator for NOT")));
885 else if (tokenizer.readIfEqual(Token.KEY)) {
886 String key = tokenizer.getText();
887 if (tokenizer.readIfEqual(Token.EQUALS))
[2993]888 return new ExactKeyValue(regexSearch, key, tokenizer.readTextOrNumber());
889 else if (tokenizer.readIfEqual(Token.COLON)) {
[2973]890 if ("id".equals(key))
891 return new Id(tokenizer.readNumber(tr("Primitive id expected")));
[2993]892 else if ("tags".equals(key)) {
[3046]893 Range range = tokenizer.readRange(tr("Range of numbers expected"));
[4679]894 return new TagCountRange(range.getStart(), range.getEnd());
[2995]895 } else if ("nodes".equals(key)) {
[3046]896 Range range = tokenizer.readRange(tr("Range of numbers expected"));
[4679]897 return new NodeCountRange(range.getStart(), range.getEnd());
[4089]898 } else if ("areasize".equals(key)) {
[4085]899 Range range = tokenizer.readRange(tr("Range of numbers expected"));
[4679]900 return new Area(range.getStart(), range.getEnd());
901 } else if ("timestamp".equals(key)) {
902 String rangeS = " " + tokenizer.readTextOrNumber() + " "; // add leading/trailing space in order to get expected split (e.g. "a--" => {"a", ""})
903 String[] rangeA = rangeS.split("/");
904 if (rangeA.length == 1) {
905 return new KeyValue(key, rangeS, regexSearch, caseSensitive);
906 } else if (rangeA.length == 2) {
907 String rangeA1 = rangeA[0].trim();
908 String rangeA2 = rangeA[1].trim();
909 long minDate = DateUtils.fromString(rangeA1.isEmpty() ? "1980" : rangeA1).getTime(); // if min timestap is empty: use lowest possible date
910 long maxDate = rangeA2.isEmpty() ? new Date().getTime() : DateUtils.fromString(rangeA2).getTime(); // if max timestamp is empty: use "now"
911 return new TimestampRange(minDate, maxDate);
912 } else {
[4694]913 /* I18n: Don't translate timestamp keyword */ throw new ParseError(tr("Expecting <i>min</i>/<i>max</i>'' after ''timestamp''"));
[4679]914 }
[2993]915 } else if ("changeset".equals(key))
916 return new ChangesetId(tokenizer.readNumber(tr("Changeset id expected")));
917 else if ("version".equals(key))
918 return new Version(tokenizer.readNumber(tr("Version expected")));
[2973]919 else
[2993]920 return parseKV(key, tokenizer.readTextOrNumber());
921 } else if (tokenizer.readIfEqual(Token.QUESTION_MARK))
[2863]922 return new BooleanMatch(key, false);
[4347]923 else if ("new".equals(key))
924 return new New();
[2645]925 else if ("modified".equals(key))
926 return new Modified();
927 else if ("incomplete".equals(key))
928 return new Incomplete();
929 else if ("untagged".equals(key))
930 return new Untagged();
931 else if ("selected".equals(key))
932 return new Selected();
[3818]933 else if ("closed".equals(key))
934 return new Closed();
[2645]935 else if ("child".equals(key))
936 return new Child(parseFactor());
937 else if ("parent".equals(key))
938 return new Parent(parseFactor());
[4377]939 else if ("indownloadedarea".equals(key))
[4372]940 return new InDataSourceArea(false);
[4377]941 else if ("allindownloadedarea".equals(key))
[4372]942 return new InDataSourceArea(true);
[4377]943 else if ("inview".equals(key))
[4375]944 return new InView(false);
[4377]945 else if ("allinview".equals(key))
[4375]946 return new InView(true);
[2645]947 else
948 return new Any(key, regexSearch, caseSensitive);
949 } else
950 return null;
[1169]951 }
[319]952
[2645]953 private Match parseFactor(String errorMessage) throws ParseError {
954 Match fact = parseFactor();
955 if (fact == null)
956 throw new ParseError(errorMessage);
957 else
958 return fact;
[1169]959 }
[319]960
[2645]961 private Match parseKV(String key, String value) throws ParseError {
962 if (value == null) {
963 value = "";
[1641]964 }
[1814]965 if (key.equals("type"))
[1169]966 return new ExactType(value);
[1814]967 else if (key.equals("user"))
[1169]968 return new UserMatch(value);
[3305]969 else if (key.equals("role"))
970 return new RoleMatch(value);
[2695]971 else
[2510]972 return new KeyValue(key, value, regexSearch, caseSensitive);
[1169]973 }
[1607]974
[2510]975 private static int regexFlags(boolean caseSensitive) {
[1607]976 int searchFlags = 0;
977
978 // Enables canonical Unicode equivalence so that e.g. the two
979 // forms of "\u00e9gal" and "e\u0301gal" will match.
980 //
981 // It makes sense to match no matter how the character
982 // happened to be constructed.
983 searchFlags |= Pattern.CANON_EQ;
984
985 // Make "." match any character including newline (/s in Perl)
986 searchFlags |= Pattern.DOTALL;
987
988 // CASE_INSENSITIVE by itself only matches US-ASCII case
989 // insensitively, but the OSM data is in Unicode. With
990 // UNICODE_CASE casefolding is made Unicode-aware.
[1814]991 if (!caseSensitive) {
[1607]992 searchFlags |= (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
[1814]993 }
[1607]994
995 return searchFlags;
996 }
[319]997}
Note: See TracBrowser for help on using the repository browser.